From 8622a6da3b500c45d666506f841ce10592d1a0e6 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 10 Aug 2023 05:28:19 +0800 Subject: [PATCH 0001/2167] luautf8lib --- components/CMakeLists.txt | 2 +- components/lua/luastate.cpp | 5 ++++- components/lua/utf8.cpp | 21 +++++++++++++++++++++ components/lua/utf8.hpp | 9 +++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 components/lua/utf8.cpp create mode 100644 components/lua/utf8.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 32482ec331..dd40d41ab0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -34,7 +34,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (lua - luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage + luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 shapes/box ) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 2a5769e6dd..8a836e4a68 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -12,6 +12,7 @@ #include #include "scriptscontainer.hpp" +#include "utf8.hpp" namespace LuaUtil { @@ -51,7 +52,7 @@ namespace LuaUtil static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" }; - static const std::string safePackages[] = { "coroutine", "math", "string", "table" }; + static const std::string safePackages[] = { "coroutine", "math", "string", "table", "utf8" }; static constexpr int64_t countHookStep = 1000; @@ -181,6 +182,8 @@ namespace LuaUtil mSol["math"]["randomseed"](static_cast(std::time(nullptr))); mSol["math"]["randomseed"] = [] {}; + mSol["utf8"] = LuaUtf8::initUtf8Package(mSol); + mSol["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mSol["setEnvironment"] diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp new file mode 100644 index 0000000000..c8be068353 --- /dev/null +++ b/components/lua/utf8.cpp @@ -0,0 +1,21 @@ +#include "utf8.hpp" +#include "luastate.hpp" + +namespace +{ + static constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 + static constexpr uint32_t MAXUTF = 0x7FFFFFFFu; + static constexpr uint32_t MAXUNICODE = 0x10FFFFu; +} + +namespace LuaUtf8 +{ + sol::table initUtf8Package(sol::state_view& lua) + { + sol::table utf8(lua, sol::create); + + utf8["charpattern"] = UTF8PATT; + + return utf8; + } +} diff --git a/components/lua/utf8.hpp b/components/lua/utf8.hpp new file mode 100644 index 0000000000..cb8666ea33 --- /dev/null +++ b/components/lua/utf8.hpp @@ -0,0 +1,9 @@ +#ifndef COMPONENTS_LUA_UTF8_H +#define COMPONENTS_LUA_UTF8_H + +namespace LuaUtf8 +{ + sol::table initUtf8Package(sol::state_view&); +} + +#endif From d9c102e14d6aa604c0a442f7ddb3e1f77a05e792 Mon Sep 17 00:00:00 2001 From: Kindi Date: Sun, 13 Aug 2023 22:46:24 +0800 Subject: [PATCH 0002/2167] utf8.char --- components/lua/utf8.cpp | 31 ++++++++++++++++++++++++++++++- components/lua/utf8.hpp | 2 ++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index c8be068353..9b7fc97de8 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -1,11 +1,27 @@ +#include + #include "utf8.hpp" -#include "luastate.hpp" namespace { static constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 static constexpr uint32_t MAXUTF = 0x7FFFFFFFu; static constexpr uint32_t MAXUNICODE = 0x10FFFFu; + + inline static double getInteger(const sol::stack_proxy arg, const size_t& n, const std::string_view& name) + { + double integer; + if (!arg.is()) + throw std::runtime_error(std::format("bad argument #{} to '{}' (number expected, got {})", n, name, + sol::type_name(arg.lua_state(), arg.get_type()))); + + if (std::modf(arg, &integer) != 0) + throw std::runtime_error( + std::format("bad argument #{} to '{}' (number has no integer representation)", n, name)); + + return integer; + } + } namespace LuaUtf8 @@ -16,6 +32,19 @@ namespace LuaUtf8 utf8["charpattern"] = UTF8PATT; + utf8["char"] = [](const sol::variadic_args args) -> std::string { + std::string result{}; + std::wstring_convert> converter; + for (size_t i = 0; i < args.size(); ++i) + { + int64_t codepoint = getInteger(args[i], (i + 1), "char"); + if (codepoint < 0 || codepoint > MAXUTF) + throw std::runtime_error(std::format("bad argument #{} to 'char' (value out of range)", (i + 1))); + + result += converter.to_bytes(codepoint); + } + return result; + }; return utf8; } } diff --git a/components/lua/utf8.hpp b/components/lua/utf8.hpp index cb8666ea33..dd936b3b5e 100644 --- a/components/lua/utf8.hpp +++ b/components/lua/utf8.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_LUA_UTF8_H #define COMPONENTS_LUA_UTF8_H +#include + namespace LuaUtf8 { sol::table initUtf8Package(sol::state_view&); From 6d02c317208c6aeaed3b0eb61ea212aea0016fd5 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 17 Aug 2023 23:58:50 +0800 Subject: [PATCH 0003/2167] utf8.codes --- components/lua/utf8.cpp | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 9b7fc97de8..926e43e84b 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -22,6 +22,53 @@ namespace return integer; } + // returns: first - character pos in bytes, second - character codepoint + static std::pair poscodes(const std::string_view& s, std::vector& pos_byte) + { + const int64_t pos = pos_byte.back() - 1; + const unsigned char ch = static_cast(s[pos]); + int64_t codepoint = -1; + size_t byteSize = 0; + + if ((ch & 0b10000000) == 0) + { + codepoint = ch; + byteSize = 1; + } + else if ((ch & 0b11100000) == 0b11000000) + { + codepoint = ch & 0b00011111; + byteSize = 2; + } + else if ((ch & 0b11110000) == 0b11100000) + { + codepoint = ch & 0b00001111; + byteSize = 3; + } + else if ((ch & 0b11111000) == 0b11110000) + { + codepoint = ch & 0b00000111; + byteSize = 4; + } + + // construct codepoint for non-ascii + for (size_t i = 1; i < byteSize; ++i) + { + // if not a continuation byte + if ((pos + i) >= s.size() || (static_cast(s[pos + i]) & 0b11000000) != 0b10000000) + { + return std::make_pair(0, -1); + } + codepoint = (codepoint << 6) | (static_cast(s[pos + i]) & 0b00111111); + } + + std::pair res = std::make_pair(pos_byte.back(), codepoint); + + pos_byte.push_back(pos_byte.back() + byteSize); /* the next character (if exists) starts at this byte */ + + return res; + } + } namespace LuaUtf8 @@ -45,6 +92,20 @@ namespace LuaUtf8 } return result; }; + + utf8["codes"] = [pos_byte = std::vector{ 1 }](const std::string_view& s) { + return sol::as_function([s, pos_byte]() mutable -> sol::optional> { + if (pos_byte.back() <= static_cast(s.size())) + { + const auto pair = poscodes(s, pos_byte); + if (pair.second == -1) + throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); + + return pair; + } + return sol::nullopt; + }); + }; return utf8; } } From 92842cedf55995009106c726fe130b88b1cce735 Mon Sep 17 00:00:00 2001 From: Kindi Date: Sun, 27 Aug 2023 16:12:12 +0800 Subject: [PATCH 0004/2167] len,codepoint,offset --- components/lua/utf8.cpp | 119 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 926e43e84b..6a80505411 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -4,11 +4,16 @@ namespace { - static constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 - static constexpr uint32_t MAXUTF = 0x7FFFFFFFu; - static constexpr uint32_t MAXUNICODE = 0x10FFFFu; + constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 + constexpr uint32_t MAXUTF = 0x7FFFFFFFu; + constexpr uint32_t MAXUNICODE = 0x10FFFFu; - inline static double getInteger(const sol::stack_proxy arg, const size_t& n, const std::string_view& name) + inline bool isNilOrNone(const sol::stack_proxy arg) + { + return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); + } + + inline double getInteger(const sol::stack_proxy arg, const size_t& n, const std::string_view& name) { double integer; if (!arg.is()) @@ -22,8 +27,18 @@ namespace return integer; } + inline void posrelat(int64_t& pos, const size_t& len) + { + if (pos >= 0) + /* no change */; + else if (0u - pos > static_cast(len)) + pos = 0; + else + pos = len + pos + 1; + } + // returns: first - character pos in bytes, second - character codepoint - static std::pair poscodes(const std::string_view& s, std::vector& pos_byte) + std::pair poscodes(const std::string_view& s, std::vector& pos_byte) { const int64_t pos = pos_byte.back() - 1; const unsigned char ch = static_cast(s[pos]); @@ -106,6 +121,100 @@ namespace LuaUtf8 return sol::nullopt; }); }; + + utf8["len"] = [](const std::string_view& s, + const sol::variadic_args args) -> std::variant> { + size_t len = s.size(); + int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len"); + int64_t fv = isNilOrNone(args[1]) ? -1 : getInteger(args[1], 3, "len"); + + posrelat(iv, len); + posrelat(fv, len); + + if (iv <= 0) + throw std::runtime_error("bad argument #2 to 'len' (initial position out of bounds)"); + if (fv > static_cast(len)) + throw std::runtime_error("bad argument #3 to 'len' (final position out of bounds)"); + + if (len == 0) + return len; + + std::vector pos_byte = { iv }; + + while (pos_byte.back() <= fv) + { + if (poscodes(s, pos_byte).second == -1) + return std::pair(sol::lua_nil, pos_byte.back()); + } + return pos_byte.size() - 1; + }; + + utf8["codepoint"] + = [](const std::string_view& s, const sol::variadic_args args) -> sol::as_returns_t> { + size_t len = s.size(); + int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "codepoint"); + int64_t fv = isNilOrNone(args[1]) ? iv : getInteger(args[1], 3, "codepoint"); + + posrelat(iv, len); + posrelat(fv, len); + + if (iv <= 0) + throw std::runtime_error("bad argument #2 to 'codepoint' (initial position out of bounds)"); + if (fv > static_cast(len)) + throw std::runtime_error("bad argument #3 to 'codepoint' (final position out of bounds)"); + + if (iv > fv) + return sol::as_returns(std::vector{}); /* empty interval; return nothing */ + + std::vector pos_byte = { iv }; + std::vector codepoints; + + while (pos_byte.back() <= fv) + { + codepoints.push_back(poscodes(s, pos_byte).second); + if (codepoints.back() == -1) + throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); + } + + return sol::as_returns(std::move(codepoints)); + }; + + utf8["offset"] + = [](const std::string_view& s, const int64_t n, const sol::variadic_args args) -> sol::optional { + size_t len = s.size(); + int64_t iv = isNilOrNone(args[0]) ? ((n >= 0) ? 1 : s.size() + 1) : getInteger(args[0], 3, "offset"); + std::vector pos_byte = { 1 }; + + posrelat(iv, len); + + if (iv > static_cast(len) + 1) + throw std::runtime_error("bad argument #3 to 'offset' (position out of bounds)"); + + while (pos_byte.back() <= static_cast(len)) + poscodes(s, pos_byte); + + for (auto it = pos_byte.begin(); it != pos_byte.end(); ++it) + if (*it == iv) + { + if (n <= 0) + if ((it + n) >= pos_byte.begin()) + return *(it + n); + if (n > 0) + if ((it + n - 1) < pos_byte.end()) + return *(it + n - 1); + break; + } + else if (*it > iv) /* a continuation byte */ + { + if (n == 0) + return *(it - 1); /* special case */ + else + throw std::runtime_error("initial position is a continuation byte"); + } + + return sol::nullopt; + }; + return utf8; } } From 532230254b244fb7a0c2ce5abd95381dadd3e406 Mon Sep 17 00:00:00 2001 From: Kindi Date: Sun, 27 Aug 2023 16:48:00 +0800 Subject: [PATCH 0005/2167] add documentation --- components/lua/utf8.cpp | 3 +- files/lua_api/utf8.doclua | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 files/lua_api/utf8.doclua diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 6a80505411..69160fdd53 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -108,7 +108,8 @@ namespace LuaUtf8 return result; }; - utf8["codes"] = [pos_byte = std::vector{ 1 }](const std::string_view& s) { + utf8["codes"] = [](const std::string_view& s) { + std::vector pos_byte{ 1 }; return sol::as_function([s, pos_byte]() mutable -> sol::optional> { if (pos_byte.back() <= static_cast(s.size())) { diff --git a/files/lua_api/utf8.doclua b/files/lua_api/utf8.doclua new file mode 100644 index 0000000000..6c054f4401 --- /dev/null +++ b/files/lua_api/utf8.doclua @@ -0,0 +1,77 @@ +------------------------------------------------------------------------------- +-- UTF-8 Support. +-- This library provides basic support for UTF-8 encoding. +-- It provides all its functions inside the table utf8. +-- This library does not provide any support for Unicode other than the handling of the encoding. +-- Any operation that needs the meaning of a character, such as character classification, is outside its scope. +-- +-- Unless stated otherwise, all functions that expect a byte position as a parameter assume that +-- the given position is either the start of a byte sequence or one plus the length of the subject string. +-- As in the string library, negative indices count from the end of the string. +-- @module utf8 + +------------------------------------------------------------------------------- +-- Receives zero or more integers, converts each one to its +-- corresponding UTF-8 byte sequence, and returns a string with the concatenation +-- of all these sequences. +-- @function [parent=#utf8] char +-- @param ... zero or more integers. +-- @return #string + +------------------------------------------------------------------------------- +-- The pattern which matches exactly one UTF-8 byte sequence, assuming that +-- the subject is a valid UTF-8 string. +-- @function [parent=#utf8] charpattern +-- @return #string + +------------------------------------------------------------------------------- +-- Returns values so that the construction +-- +-- for p, c in utf8.codes(s) do body end +-- +-- will iterate over all characters in string s, with p being the position (in bytes) +-- and c the code point of each character. +-- It raises an error if it meets any invalid byte sequence. +-- @function [parent=#utf8] codes +-- @param #string s string to handle. + +------------------------------------------------------------------------------- +-- Returns the codepoints (as integers) from all characters in s that start +-- between byte position i and j (both included). The default for i is 1 and for j is i. +-- It raises an error if it meets any invalid byte sequence. +-- @function [parent=#utf8] codepoint +-- @param #string s string to handle +-- @param #number i the initial position (default value is 1) +-- @param #number j the final position (default value is i) +-- @return #number the codepoints of each character in s + +------------------------------------------------------------------------------- +-- Returns the number of UTF-8 characters in string s that start +-- between positions i and j (both inclusive). +-- The default for i is 1 and for j is -1. +-- If it finds any invalid byte sequence, +-- returns a false value plus the position of the first invalid byte. +-- @function [parent=#utf8] len +-- @param #string s string to handle +-- @param #number i the initial position (default value is 1) +-- @param #number j the final position (default value is -1) +-- @return #number the number of utf8 characters in s + +------------------------------------------------------------------------------- +-- Returns the position (in bytes) where the encoding of the n-th character of s +-- (counting from position i) starts. A negative n gets characters before position i. +-- The default for i is 1 when n is non-negative and #s + 1 otherwise, +-- so that utf8.offset(s, -n) gets the offset of the n-th character from the end of the string. +-- If the specified character is neither in the subject nor right after its end, the function returns nil. +-- +-- As a special case, when n is 0 the function returns the +-- start of the encoding of the character that contains the i-th byte of s. +-- +-- This function assumes that s is a valid UTF-8 string. +-- @function [parent=#utf8] offset +-- @param #string s string to handle +-- @param #number n the n-th character +-- @param #number i the initial position (default value is 1 if n is is non-negative and #s + 1 otherwise) +-- @return #number + +return nil From 55a9ab4f52848cb601ff6883dbded627ad46c295 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 27 Aug 2023 09:21:36 +0200 Subject: [PATCH 0006/2167] Add `obj.parentContainer` in Lua. Refactor ContainerStore::mPtr, ContainerStore::mActor. --- apps/openmw/mwclass/container.cpp | 10 +++++-- apps/openmw/mwclass/creature.cpp | 11 ++++--- apps/openmw/mwclass/npc.cpp | 24 +++++++++------- apps/openmw/mwlua/objectbindings.cpp | 7 +++++ apps/openmw/mwworld/cellstore.cpp | 6 +--- apps/openmw/mwworld/class.cpp | 4 --- apps/openmw/mwworld/containerstore.cpp | 40 ++++++++++++++------------ apps/openmw/mwworld/containerstore.hpp | 11 ++++--- apps/openmw/mwworld/inventorystore.cpp | 36 ++++++++++++----------- apps/openmw/mwworld/inventorystore.hpp | 3 -- apps/openmw/mwworld/ptr.hpp | 5 ++-- files/lua_api/openmw/core.lua | 1 + 12 files changed, 85 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 19132f30ee..e947951e33 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -21,6 +21,7 @@ #include "../mwworld/failedaction.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" #include "../mwgui/ustring.hpp" @@ -68,10 +69,12 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); MWWorld::LiveCellRef* ref = ptr.get(); // store ptr.getRefData().setCustomData(std::make_unique(*ref->mBase, ptr.getCell())); + getContainerStore(ptr).setPtr(ptr); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -223,9 +226,7 @@ namespace MWClass MWWorld::ContainerStore& Container::getContainerStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& data = ptr.getRefData().getCustomData()->asContainerCustomData(); - data.mStore.mPtr = ptr; - return data.mStore; + return ptr.getRefData().getCustomData()->asContainerCustomData().mStore; } ESM::RefId Container::getScript(const MWWorld::ConstPtr& ptr) const @@ -312,6 +313,9 @@ namespace MWClass const ESM::ContainerState& containerState = state.asContainerState(); ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory)); + + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + getContainerStore(ptr).setPtr(ptr); } void Container::writeAdditionalState(const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 672b198704..2ee412a190 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -39,6 +39,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/localscripts.hpp" #include "../mwworld/ptr.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -117,6 +118,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); auto tempData = std::make_unique(); CreatureCustomData* data = tempData.get(); MWMechanics::CreatureCustomDataResetter resetter{ ptr }; @@ -161,6 +163,7 @@ namespace MWClass data->mContainerStore = std::make_unique(); else data->mContainerStore = std::make_unique(); + data->mContainerStore->setPtr(ptr); data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); @@ -505,10 +508,7 @@ namespace MWClass MWWorld::ContainerStore& Creature::getContainerStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& store = *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; - if (hasInventoryStore(ptr)) - static_cast(store).setActor(ptr); - return store; + return *ptr.getRefData().getCustomData()->asCreatureCustomData().mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr& ptr) const @@ -807,6 +807,9 @@ namespace MWClass else data->mContainerStore = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mContainerStore->setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b9e03aa45c..6b79f3ab7e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -297,6 +297,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); bool recalculate = false; auto tempData = std::make_unique(); NpcCustomData* data = tempData.get(); @@ -397,9 +398,10 @@ namespace MWClass // inventory // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - getInventoryStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); - - getInventoryStore(ptr).autoEquip(); + MWWorld::InventoryStore& inventory = getInventoryStore(ptr); + inventory.setPtr(ptr); + inventory.fill(ref->mBase->mInventory, ptr.getCellRef().getRefId(), prng); + inventory.autoEquip(); } } @@ -956,18 +958,13 @@ namespace MWClass MWWorld::ContainerStore& Npc::getContainerStore(const MWWorld::Ptr& ptr) const { - ensureCustomData(ptr); - auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; - store.setActor(ptr); - return store; + return getInventoryStore(ptr); } MWWorld::InventoryStore& Npc::getInventoryStore(const MWWorld::Ptr& ptr) const { ensureCustomData(ptr); - auto& store = ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; - store.setActor(ptr); - return store; + return ptr.getRefData().getCustomData()->asNpcCustomData().mInventoryStore; } ESM::RefId Npc::getScript(const MWWorld::ConstPtr& ptr) const @@ -1362,8 +1359,13 @@ namespace MWClass if (npcState.mCreatureStats.mMissingACDT) ensureCustomData(ptr); else + { // Create a CustomData, but don't fill it from ESM records (not needed) - ptr.getRefData().setCustomData(std::make_unique()); + auto data = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mInventoryStore.setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); + } } } else diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 32bd0b95d4..a7b2252a31 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -190,6 +190,13 @@ namespace MWLua else return sol::nullopt; }); + objectT["parentContainer"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { + const MWWorld::Ptr& ptr = o.ptr(); + if (ptr.getContainerStore()) + return ObjectT(ptr.getContainerStore()->getPtr()); + else + return sol::nullopt; + }); objectT["position"] = sol::readonly_property( [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); objectT["scale"] diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d5868f5ee3..193071259a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -495,11 +495,7 @@ namespace MWWorld mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); requestMergedRefsUpdate(); - MWWorld::Ptr ptr(object.getBase(), cellToMoveTo); - const Class& cls = ptr.getClass(); - if (cls.hasInventoryStore(ptr)) - cls.getInventoryStore(ptr).setActor(ptr); - return ptr; + return MWWorld::Ptr(object.getBase(), cellToMoveTo); } struct MergeVisitor diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index b360e90471..de3c2b011d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -376,8 +376,6 @@ namespace MWWorld newPtr.getRefData().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - if (hasInventoryStore(newPtr)) - getInventoryStore(newPtr).setActor(newPtr); return newPtr; } @@ -386,8 +384,6 @@ namespace MWWorld Ptr newPtr = copyToCellImpl(ptr, cell); ptr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - if (hasInventoryStore(newPtr)) - getInventoryStore(newPtr).setActor(newPtr); return newPtr; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index f5e45415ae..d76dc36c15 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -350,7 +350,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( const ESM::RefId& script = item.getClass().getScript(item); if (!script.empty()) { - if (mActor == player) + const Ptr& contPtr = getPtr(); + if (contPtr == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed item.mCell = nullptr; @@ -359,10 +360,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( { // Set mCell to the cell of the container/actor, so that the scripts are removed properly when // the cell of the container/actor goes inactive - if (!mPtr.isEmpty()) - item.mCell = mPtr.getCell(); - else if (!mActor.isEmpty()) - item.mCell = mActor.getCell(); + if (!contPtr.isEmpty()) + item.mCell = contPtr.getCell(); } item.mContainerStore = this; @@ -371,12 +370,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( // Set OnPCAdd special variable, if it is declared // Make sure to do this *after* we have added the script to LocalScripts - if (mActor == player) + if (contPtr == player) item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } // we should not fire event for InventoryStore yet - it has some custom logic - if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) + if (mListener && typeid(*this) == typeid(ContainerStore)) mListener->itemAdded(item, count); return it; @@ -415,8 +414,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { // Don't stack with equipped items - if (!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)) - if (mActor.getClass().getInventoryStore(mActor).isEquipped(*iter)) + if (auto* inventoryStore = dynamic_cast(this)) + if (inventoryStore->isEquipped(*iter)) continue; if (stacks(*iter, ptr)) @@ -586,7 +585,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplac flagAsModified(); // we should not fire event for InventoryStore yet - it has some custom logic - if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) + if (mListener && typeid(*this) == typeid(ContainerStore)) mListener->itemRemoved(item, count - toRemove); // number of removed items @@ -694,13 +693,14 @@ bool MWWorld::ContainerStore::isResolved() const void MWWorld::ContainerStore::resolve() { - if (!mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; - fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); - addScripts(*this, mPtr.mCell); + fill(container.get()->mBase->mInventory, ESM::RefId(), prng); + addScripts(*this, container.mCell); } mModified = true; } @@ -715,13 +715,14 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() listener = std::make_shared(*this); mResolutionListener = listener; } - if (!mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; - fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); - addScripts(*this, mPtr.mCell); + fill(container.get()->mBase->mInventory, ESM::RefId(), prng); + addScripts(*this, container.mCell); } return { listener }; } @@ -731,12 +732,13 @@ void MWWorld::ContainerStore::unresolve() if (mModified) return; - if (mResolved && !mPtr.isEmpty()) + const Ptr& container = getPtr(); + if (mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); - fillNonRandom(mPtr.get()->mBase->mInventory, ESM::RefId(), mSeed); - addScripts(*this, mPtr.mCell); + fillNonRandom(container.get()->mBase->mInventory, ESM::RefId(), mSeed); + addScripts(*this, container.mCell); mResolved = false; } } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index bced5820fa..a8392d38b8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -125,11 +125,6 @@ namespace MWWorld bool mRechargingItemsUpToDate; - // Non-empty only if is InventoryStore. - // The actor whose inventory it is. - // TODO: Consider merging mActor and mPtr. - MWWorld::Ptr mActor; - private: MWWorld::CellRefList potions; MWWorld::CellRefList appas; @@ -150,7 +145,7 @@ namespace MWWorld bool mModified; bool mResolved; unsigned int mSeed; - MWWorld::Ptr mPtr; // Container that contains this store. Set in MWClass::Container::getContainerStore + MWWorld::SafePtr mPtr; // Container or actor that holds this store. std::weak_ptr mResolutionListener; ContainerStoreIterator addImp(const Ptr& ptr, int count, bool markModified = true); @@ -189,6 +184,10 @@ namespace MWWorld return res; } + // Container or actor that holds this store. + const Ptr& getPtr() const { return mPtr.ptrOrEmpty(); } + void setPtr(const Ptr& ptr) { mPtr = SafePtr(ptr); } + ConstContainerStoreIterator cbegin(int mask = Type_All) const; ConstContainerStoreIterator cend() const; ConstContainerStoreIterator begin(int mask = Type_All) const; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 99949a73d4..9b3470a835 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -133,8 +133,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add( = MWWorld::ContainerStore::add(itemPtr, count, allowAutoEquip, resolve); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves - if (allowAutoEquip && mActor != MWMechanics::getPlayer() && mActor.getClass().isNpc() - && !mActor.getClass().getNpcStats(mActor).isWerewolf()) + const Ptr& actor = getPtr(); + if (allowAutoEquip && actor != MWMechanics::getPlayer() && actor.getClass().isNpc() + && !actor.getClass().getNpcStats(actor).isWerewolf()) { auto type = itemPtr.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) @@ -220,12 +221,13 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot(int slot) cons void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) { - if (!mActor.getClass().isNpc()) + const Ptr& actor = getPtr(); + if (!actor.getClass().isNpc()) { // In original game creatures do not autoequip weapon, but we need it for weapon sheathing. // The only case when the difference is noticable - when this creature sells weapon. // So just disable weapon autoequipping for creatures which sells weapon. - int services = mActor.getClass().getServices(mActor); + int services = actor.getClass().getServices(actor); bool sellsWeapon = services & (ESM::NPC::Weapon | ESM::NPC::MagicItems); if (sellsWeapon) return; @@ -280,7 +282,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) for (int j = 0; j < static_cast(weaponSkillsLength); ++j) { - float skillValue = mActor.getClass().getSkill(mActor, weaponSkills[j]); + float skillValue = actor.getClass().getSkill(actor, weaponSkills[j]); if (skillValue > max && !weaponSkillVisited[j]) { max = skillValue; @@ -323,7 +325,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) } } - if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, mActor).first) + if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; @@ -378,7 +380,8 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) { // Only NPCs can wear armor for now. // For creatures we equip only shields. - if (!mActor.getClass().isNpc()) + const Ptr& actor = getPtr(); + if (!actor.getClass().isNpc()) { autoEquipShield(slots_); return; @@ -389,7 +392,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); - float unarmoredSkill = mActor.getClass().getSkill(mActor, ESM::Skill::Unarmored); + float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end(); @@ -397,7 +400,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) { Ptr test = *iter; - switch (test.getClass().canBeEquipped(test, mActor).first) + switch (test.getClass().canBeEquipped(test, actor).first) { case 0: continue; @@ -406,7 +409,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) } if (iter.getType() == ContainerStore::Type_Armor - && test.getClass().getEffectiveArmorRating(test, mActor) <= std::max(unarmoredRating, 0.f)) + && test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f)) { continue; } @@ -431,8 +434,8 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) if (old.get()->mBase->mData.mType == test.get()->mBase->mData.mType) { - if (old.getClass().getEffectiveArmorRating(old, mActor) - >= test.getClass().getEffectiveArmorRating(test, mActor)) + if (old.getClass().getEffectiveArmorRating(old, actor) + >= test.getClass().getEffectiveArmorRating(test, actor)) // old armor had better armor rating continue; } @@ -494,7 +497,7 @@ void MWWorld::InventoryStore::autoEquipShield(TSlots& slots_) { if (iter->get()->mBase->mData.mType != ESM::Armor::Shield) continue; - if (iter->getClass().canBeEquipped(*iter, mActor).first != 1) + if (iter->getClass().canBeEquipped(*iter, getPtr()).first != 1) continue; std::pair, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter); int slot = shieldSlots.first[0]; @@ -606,8 +609,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac // If an armor/clothing item is removed, try to find a replacement, // but not for the player nor werewolves, and not if the RemoveItem script command // was used (equipReplacement is false) - if (equipReplacement && wasEquipped && (mActor != MWMechanics::getPlayer()) && mActor.getClass().isNpc() - && !mActor.getClass().getNpcStats(mActor).isWerewolf()) + const Ptr& actor = getPtr(); + if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc() + && !actor.getClass().getNpcStats(actor).isWerewolf()) { auto type = item.getType(); if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId) @@ -643,7 +647,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, b { retval = restack(*it); - if (mActor == MWMechanics::getPlayer()) + if (getPtr() == MWMechanics::getPlayer()) { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared const ESM::RefId& script = it->getClass().getScript(*it); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index c0723d319c..6df5fa1e5a 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -94,9 +94,6 @@ namespace MWWorld InventoryStore& operator=(const InventoryStore& store); - const MWWorld::Ptr& getActor() const { return mActor; } - void setActor(const MWWorld::Ptr& actor) { mActor = actor; } - std::unique_ptr clone() override { auto res = std::make_unique(*this); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 1adaa3cbf9..85fef8d9fb 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -153,8 +153,9 @@ namespace MWWorld class SafePtr { public: - using Id = ESM::RefNum; + using Id = ESM::FormId; + SafePtr() = default; explicit SafePtr(Id id) : mId(id) { @@ -172,7 +173,7 @@ namespace MWWorld } private: - const Id mId; + Id mId; mutable Ptr mPtr; mutable size_t mLastUpdate = 0; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 7a43cd2c3f..f018e4dd21 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -159,6 +159,7 @@ -- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value. -- @field #number ownerFactionRank Rank required to be allowed to pick up the object. Global and self scripts can set the value. -- @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 #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). -- @field #string recordId Returns record ID of the object in lowercase. From 84987450ee488df4d5ab0b56d3cd0c17b0bdf47f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 27 Aug 2023 20:47:43 -0500 Subject: [PATCH 0007/2167] Add baseCount --- apps/openmw/mwlua/objectbindings.cpp | 2 ++ files/lua_api/openmw/core.lua | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 32bd0b95d4..7dcf0d5fe7 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -214,6 +214,8 @@ namespace MWLua const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); + objectT["baseCount"] + = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(false); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 7a43cd2c3f..8500993208 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -161,6 +161,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 #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). +-- @field #number baseCount Base Count (<1 means a restocking item). -- @field #string recordId Returns record ID of the object in lowercase. --- From 5fdaee093a6d36d0422f677304de0a4d6d63eda1 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 28 Aug 2023 08:30:54 -0500 Subject: [PATCH 0008/2167] add isRestocking --- apps/openmw/mwlua/objectbindings.cpp | 2 -- apps/openmw/mwlua/types/item.cpp | 2 ++ files/lua_api/openmw/core.lua | 1 - files/lua_api/openmw/types.lua | 7 +++++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 7dcf0d5fe7..32bd0b95d4 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -214,8 +214,6 @@ namespace MWLua const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); - objectT["baseCount"] - = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(false); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 2b77b6a7c8..01e968777d 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -12,5 +12,7 @@ namespace MWLua = [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); }; item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; + item["isRestocking"] + = [](const GObject& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; } } diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 8500993208..7a43cd2c3f 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -161,7 +161,6 @@ -- @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 #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). --- @field #number baseCount Base Count (<1 means a restocking item). -- @field #string recordId Returns record ID of the object in lowercase. --- diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 199b1a8e8b..a62ecbc846 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -632,6 +632,13 @@ -- @param openmw.core#GameObject item -- @return #number The charge remaining. -1 if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of -1. +--- +-- Checks if the item restocks. +-- Returns true if the object restocks, and false otherwise. +-- @function [parent=#Item] isRestocking +-- @param openmw.core#GameObject item +-- @return #boolean + --- -- Set this item's enchantment charge. -- @function [parent=#Item] setEnchantmentCharge From 95c736d54e1bfd4c746b3ac5674e9c4a9415b9f3 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 28 Aug 2023 08:50:25 -0500 Subject: [PATCH 0009/2167] Remove gobjefct --- apps/openmw/mwlua/types/item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 01e968777d..e15be1a2e4 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -13,6 +13,6 @@ namespace MWLua item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; item["isRestocking"] - = [](const GObject& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; + = [](const Object& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; } } From ecc89ed8263651874bb47116ce7f358a837b266f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 28 Aug 2023 21:20:12 -0500 Subject: [PATCH 0010/2167] Add two missing record types --- files/lua_api/openmw/world.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 53f1e44e26..17e4cc50f5 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -153,6 +153,8 @@ -- * @{openmw.types#ArmorRecord}, -- * @{openmw.types#BookRecord}, -- * @{openmw.types#MiscellaneousRecord}, +-- * @{openmw.types#ClothingRecord}, +-- * @{openmw.types#WeaponRecord}, -- * @{openmw.types#ActivatorRecord} -- @function [parent=#world] createRecord -- @param #any record A record to be registered in the database. Must be one of the supported types. From 45f25e3f1464e962b9098b59d2d4c257bf576d32 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 29 Aug 2023 11:19:11 -0500 Subject: [PATCH 0011/2167] Add in initial support --- apps/openmw/mwlua/types/activator.cpp | 18 ++++-- apps/openmw/mwlua/types/armor.cpp | 56 +++++++++++++------ apps/openmw/mwlua/types/book.cpp | 69 +++++++++++++++-------- apps/openmw/mwlua/types/clothing.cpp | 50 ++++++++++++----- apps/openmw/mwlua/types/misc.cpp | 28 +++++++--- apps/openmw/mwlua/types/potion.cpp | 41 +++++++++----- apps/openmw/mwlua/types/weapon.cpp | 80 ++++++++++++++++++--------- files/lua_api/openmw/types.lua | 14 ++--- 8 files changed, 239 insertions(+), 117 deletions(-) diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index 9fec22b221..a9edc1493f 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -22,11 +22,19 @@ namespace ESM::Activator tableToActivator(const sol::table& rec) { ESM::Activator activator; - activator.mName = rec["name"]; - activator.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - std::string_view scriptId = rec["mwscript"].get(); - activator.mScript = ESM::RefId::deserializeText(scriptId); - activator.mRecordFlags = 0; + if (rec["template"] != sol::nil) + activator = LuaUtil::cast(rec["template"]); + else + activator.blank(); + if (rec["name"] != sol::nil) + activator.mName = rec["name"]; + if (rec["model"] != sol::nil) + activator.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + activator.mScript = ESM::RefId::deserializeText(scriptId); + } return activator; } } diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index 9f28f56e2f..4808107ebf 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -22,25 +22,45 @@ namespace ESM::Armor tableToArmor(const sol::table& rec) { ESM::Armor armor; - armor.mName = rec["name"]; - armor.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - armor.mIcon = rec["icon"]; - std::string_view enchantId = rec["enchant"].get(); - armor.mEnchant = ESM::RefId::deserializeText(enchantId); - std::string_view scriptId = rec["mwscript"].get(); - armor.mScript = ESM::RefId::deserializeText(scriptId); - - armor.mData.mWeight = rec["weight"]; - armor.mData.mValue = rec["value"]; - int armorType = rec["type"].get(); - if (armorType >= 0 && armorType <= ESM::Armor::RBracer) - armor.mData.mType = armorType; + if (rec["template"] != sol::nil) + armor = LuaUtil::cast(rec["template"]); else - throw std::runtime_error("Invalid Armor Type provided: " + std::to_string(armorType)); - armor.mData.mHealth = rec["health"]; - armor.mData.mArmor = rec["baseArmor"]; - armor.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); - armor.mRecordFlags = 0; + armor.blank(); + if (rec["name"] != sol::nil) + armor.mName = rec["name"]; + if (rec["model"] != sol::nil) + armor.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + armor.mIcon = rec["icon"]; + if (rec["enchant"] != sol::nil) + { + std::string_view enchantId = rec["enchant"].get(); + armor.mEnchant = ESM::RefId::deserializeText(enchantId); + } + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + armor.mScript = ESM::RefId::deserializeText(scriptId); + } + + if (rec["weight"] != sol::nil) + armor.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + armor.mData.mValue = rec["value"]; + if (rec["type"] != sol::nil) + { + int armorType = rec["type"].get(); + if (armorType >= 0 && armorType <= ESM::Armor::RBracer) + armor.mData.mType = armorType; + else + throw std::runtime_error("Invalid Armor Type provided: " + std::to_string(armorType)); + } + if (rec["health"] != sol::nil) + armor.mData.mHealth = rec["health"]; + if (rec["baseArmor"] != sol::nil) + armor.mData.mArmor = rec["baseArmor"]; + if (rec["enchantCapacity"] != sol::nil) + armor.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); return armor; } diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 9501691a72..3436882803 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -27,30 +27,55 @@ namespace ESM::Book tableToBook(const sol::table& rec) { ESM::Book book; - book.mName = rec["name"]; - book.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - book.mIcon = rec["icon"]; - book.mText = rec["text"]; - std::string_view enchantId = rec["enchant"].get(); - book.mEnchant = ESM::RefId::deserializeText(enchantId); - - book.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); - std::string_view scriptId = rec["mwscript"].get(); - book.mScript = ESM::RefId::deserializeText(scriptId); - book.mData.mWeight = rec["weight"]; - book.mData.mValue = rec["value"]; - book.mData.mIsScroll = rec["isScroll"]; - book.mRecordFlags = 0; - - ESM::RefId skill = ESM::RefId::deserializeText(rec["skill"].get()); - - book.mData.mSkillId = -1; - if (!skill.empty()) + if (rec["template"] != sol::nil) + book = LuaUtil::cast(rec["template"]); + else { - book.mData.mSkillId = ESM::Skill::refIdToIndex(skill); - if (book.mData.mSkillId == -1) - throw std::runtime_error("Incorrect skill: " + skill.toDebugString()); + book.blank(); + book.mData.mSkillId = -1; } + if (rec["name"] != sol::nil) + book.mName = rec["name"]; + if (rec["model"] != sol::nil) + book.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + book.mIcon = rec["icon"]; + if (rec["text"] != sol::nil) + book.mText = rec["text"]; + if (rec["enchant"] != sol::nil) + { + std::string_view enchantId = rec["enchant"].get(); + book.mEnchant = ESM::RefId::deserializeText(enchantId); + } + + if (rec["enchantCapacity"] != sol::nil) + book.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + book.mScript = ESM::RefId::deserializeText(scriptId); + } + if (rec["weight"] != sol::nil) + book.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + book.mData.mValue = rec["value"]; + if (rec["isScroll"] != sol::nil) + book.mData.mIsScroll = rec["isScroll"]; + + if (rec["skill"] != sol::nil) + { + ESM::RefId skill = ESM::RefId::deserializeText(rec["skill"].get()); + + book.mData.mSkillId = -1; + if (!skill.empty()) + { + book.mData.mSkillId = ESM::Skill::refIdToIndex(skill); + if (book.mData.mSkillId == -1) + throw std::runtime_error("Incorrect skill: " + skill.toDebugString()); + } + } + + return book; } } diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 9bdd579286..d9d0e42579 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -22,22 +22,42 @@ namespace ESM::Clothing tableToClothing(const sol::table& rec) { ESM::Clothing clothing; - clothing.mName = rec["name"]; - clothing.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - clothing.mIcon = rec["icon"]; - std::string_view scriptId = rec["mwscript"].get(); - clothing.mScript = ESM::RefId::deserializeText(scriptId); - clothing.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); - std::string_view enchantId = rec["enchant"].get(); - clothing.mEnchant = ESM::RefId::deserializeText(enchantId); - clothing.mData.mWeight = rec["weight"]; - clothing.mData.mValue = rec["value"]; - clothing.mRecordFlags = 0; - int clothingType = rec["type"].get(); - if (clothingType >= 0 && clothingType <= ESM::Clothing::Amulet) - clothing.mData.mType = clothingType; + if (rec["template"] != sol::nil) + clothing = LuaUtil::cast(rec["template"]); else - throw std::runtime_error("Invalid Clothing Type provided: " + std::to_string(clothingType)); + clothing.blank(); + + if (rec["name"] != sol::nil) + clothing.mName = rec["name"]; + if (rec["model"] != sol::nil) + clothing.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + clothing.mIcon = rec["icon"]; + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + clothing.mScript = ESM::RefId::deserializeText(scriptId); + } + + if (rec["enchant"] != sol::nil) + { + std::string_view enchantId = rec["enchant"].get(); + clothing.mEnchant = ESM::RefId::deserializeText(enchantId); + } + if (rec["enchantCapacity"] != sol::nil) + clothing.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + if (rec["weight"] != sol::nil) + clothing.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + clothing.mData.mValue = rec["value"]; + if (rec["type"] != sol::nil) + { + int clothingType = rec["type"].get(); + if (clothingType >= 0 && clothingType <= ESM::Clothing::Amulet) + clothing.mData.mType = clothingType; + else + throw std::runtime_error("Invalid Clothing Type provided: " + std::to_string(clothingType)); + } return clothing; } } diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index 461444abff..d359534638 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -24,15 +24,25 @@ namespace ESM::Miscellaneous tableToMisc(const sol::table& rec) { ESM::Miscellaneous misc; - misc.mName = rec["name"]; - misc.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - misc.mIcon = rec["icon"]; - std::string_view scriptId = rec["mwscript"].get(); - misc.mScript = ESM::RefId::deserializeText(scriptId); - misc.mData.mWeight = rec["weight"]; - misc.mData.mValue = rec["value"]; - misc.mData.mFlags = 0; - misc.mRecordFlags = 0; + if (rec["template"] != sol::nil) + misc = LuaUtil::cast(rec["template"]); + else + misc.blank(); + if (rec["name"] != sol::nil) + misc.mName = rec["name"]; + if (rec["model"] != sol::nil) + misc.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + misc.mIcon = rec["icon"]; + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + misc.mScript = ESM::RefId::deserializeText(scriptId); + } + if (rec["weight"] != sol::nil) + misc.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + misc.mData.mValue = rec["value"]; return misc; } } diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index badf611bc7..022af56b02 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -23,20 +23,33 @@ namespace ESM::Potion tableToPotion(const sol::table& rec) { ESM::Potion potion; - potion.mName = rec["name"]; - potion.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - potion.mIcon = rec["icon"]; - std::string_view scriptId = rec["mwscript"].get(); - potion.mScript = ESM::RefId::deserializeText(scriptId); - potion.mData.mWeight = rec["weight"]; - potion.mData.mValue = rec["value"]; - potion.mData.mAutoCalc = 0; - potion.mRecordFlags = 0; - sol::table effectsTable = rec["effects"]; - size_t numEffects = effectsTable.size(); - potion.mEffects.mList.resize(numEffects); - for (size_t i = 0; i < numEffects; ++i) - potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + if (rec["template"] != sol::nil) + potion = LuaUtil::cast(rec["template"]); + else + potion.blank(); + if (rec["name"] != sol::nil) + potion.mName = rec["name"]; + if (rec["model"] != sol::nil) + potion.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + potion.mIcon = rec["icon"]; + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + potion.mScript = ESM::RefId::deserializeText(scriptId); + } + if (rec["weight"] != sol::nil) + potion.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + potion.mData.mValue = rec["value"]; + if (rec["effects"] != sol::nil) + { + sol::table effectsTable = rec["effects"]; + size_t numEffects = effectsTable.size(); + potion.mEffects.mList.resize(numEffects); + for (size_t i = 0; i < numEffects; ++i) + potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + } return potion; } } diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 1cbe9b1d26..e927fedadd 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -24,37 +24,63 @@ namespace ESM::Weapon tableToWeapon(const sol::table& rec) { ESM::Weapon weapon; - weapon.mName = rec["name"]; - weapon.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); - weapon.mIcon = rec["icon"]; - std::string_view enchantId = rec["enchant"].get(); - weapon.mEnchant = ESM::RefId::deserializeText(enchantId); - std::string_view scriptId = rec["mwscript"].get(); - weapon.mScript = ESM::RefId::deserializeText(scriptId); - weapon.mData.mFlags = 0; - weapon.mRecordFlags = 0; + if (rec["template"] != sol::nil) + weapon = LuaUtil::cast(rec["template"]); + else + weapon.blank(); + + if (rec["name"] != sol::nil) + weapon.mName = rec["name"]; + if (rec["model"] != sol::nil) + weapon.mModel = Misc::ResourceHelpers::meshPathForESM3(rec["model"].get()); + if (rec["icon"] != sol::nil) + weapon.mIcon = rec["icon"]; + if (rec["enchant"] != sol::nil) + { + std::string_view enchantId = rec["enchant"].get(); + weapon.mEnchant = ESM::RefId::deserializeText(enchantId); + } + if (rec["mwscript"] != sol::nil) + { + std::string_view scriptId = rec["mwscript"].get(); + weapon.mScript = ESM::RefId::deserializeText(scriptId); + } if (rec["isMagical"]) weapon.mData.mFlags |= ESM::Weapon::Magical; if (rec["isSilver"]) weapon.mData.mFlags |= ESM::Weapon::Silver; - int weaponType = rec["type"].get(); - if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown) - weapon.mData.mType = weaponType; - else - throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); - - weapon.mData.mWeight = rec["weight"]; - weapon.mData.mValue = rec["value"]; - weapon.mData.mHealth = rec["health"]; - weapon.mData.mSpeed = rec["speed"]; - weapon.mData.mReach = rec["reach"]; - weapon.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); - weapon.mData.mChop[0] = rec["chopMinDamage"]; - weapon.mData.mChop[1] = rec["chopMaxDamage"]; - weapon.mData.mSlash[0] = rec["slashMinDamage"]; - weapon.mData.mSlash[1] = rec["slashMaxDamage"]; - weapon.mData.mThrust[0] = rec["thrustMinDamage"]; - weapon.mData.mThrust[1] = rec["thrustMaxDamage"]; + if (rec["type"] != sol::nil) + { + int weaponType = rec["type"].get(); + if (weaponType >= 0 && weaponType <= ESM::Weapon::MarksmanThrown) + weapon.mData.mType = weaponType; + else + throw std::runtime_error("Invalid Weapon Type provided: " + std::to_string(weaponType)); + } + if (rec["weight"] != sol::nil) + weapon.mData.mWeight = rec["weight"]; + if (rec["value"] != sol::nil) + weapon.mData.mValue = rec["value"]; + if (rec["health"] != sol::nil) + weapon.mData.mHealth = rec["health"]; + if (rec["speed"] != sol::nil) + weapon.mData.mSpeed = rec["speed"]; + if (rec["reach"] != sol::nil) + weapon.mData.mReach = rec["reach"]; + if (rec["enchantCapacity"] != sol::nil) + weapon.mData.mEnchant = std::round(rec["enchantCapacity"].get() * 10); + if (rec["chopMinDamage"] != sol::nil) + weapon.mData.mChop[0] = rec["chopMinDamage"]; + if (rec["chopMaxDamage"] != sol::nil) + weapon.mData.mChop[1] = rec["chopMaxDamage"]; + if (rec["slashMinDamage"] != sol::nil) + weapon.mData.mSlash[0] = rec["slashMinDamage"]; + if (rec["slashMaxDamage"] != sol::nil) + weapon.mData.mSlash[1] = rec["slashMaxDamage"]; + if (rec["thrustMinDamage"] != sol::nil) + weapon.mData.mThrust[0] = rec["thrustMinDamage"]; + if (rec["thrustMaxDamage"] != sol::nil) + weapon.mData.mThrust[1] = rec["thrustMaxDamage"]; return weapon; } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index cc8a0365f1..382fb64a43 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -849,7 +849,7 @@ -- Creates a @{#ArmorRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Armor] createRecordDraft --- @param #ArmorRecord armor A Lua table with the fields of a ArmorRecord. +-- @param #ArmorRecord armor A Lua table with the fields of a ArmorRecord, with an additional field `template` that accepts a @{#ArmorRecord} as a base. -- @return #ArmorRecord A strongly typed Armor record. @@ -926,7 +926,7 @@ -- Creates a @{#BookRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Book] createRecordDraft --- @param #BookRecord book A Lua table with the fields of a BookRecord. +-- @param #BookRecord book A Lua table with the fields of a BookRecord, with an additional field `template` that accepts a @{#BookRecord} as a base. -- @return #BookRecord A strongly typed Book record. --- @{#Clothing} functions @@ -970,7 +970,7 @@ -- Creates a @{#ClothingRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Clothing] createRecordDraft --- @param #ClothingRecord clothing A Lua table with the fields of a ClothingRecord. +-- @param #ClothingRecord clothing A Lua table with the fields of a ClothingRecord, with an additional field `template` that accepts a @{#ClothingRecord} as a base. -- @return #ClothingRecord A strongly typed clothing record. --- @@ -1145,7 +1145,7 @@ -- Creates a @{#MiscellaneousRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Miscellaneous] createRecordDraft --- @param #MiscellaneousRecord miscellaneous A Lua table with the fields of a MiscellaneousRecord. +-- @param #MiscellaneousRecord miscellaneous A Lua table with the fields of a MiscellaneousRecord, with an additional field `template` that accepts a @{#MiscellaneousRecord} as a base. -- @return #MiscellaneousRecord A strongly typed Miscellaneous record. --- @@ -1190,7 +1190,7 @@ -- Creates a @{#PotionRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Potion] createRecordDraft --- @param #PotionRecord potion A Lua table with the fields of a PotionRecord. +-- @param #PotionRecord potion A Lua table with the fields of a PotionRecord, with an additional field `template` that accepts a @{#PotionRecord} as a base. -- @return #PotionRecord A strongly typed Potion record. --- @@ -1275,7 +1275,7 @@ -- Creates a @{#WeaponRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Weapon] createRecordDraft --- @param #WeaponRecord weapon A Lua table with the fields of a WeaponRecord. +-- @param #WeaponRecord weapon A Lua table with the fields of a WeaponRecord, with an additional field `template` that accepts a @{#WeaponRecord} as a base. -- @return #WeaponRecord A strongly typed Weapon record. --- @{#Apparatus} functions @@ -1450,7 +1450,7 @@ -- Creates a @{#ActivatorRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Activator] createRecordDraft --- @param #ActivatorRecord activator A Lua table with the fields of a ActivatorRecord. +-- @param #ActivatorRecord activator A Lua table with the fields of a ActivatorRecord, with an additional field `template` that accepts a @{#ActivatorRecord} as a base. -- @return #ActivatorRecord A strongly typed Activator record. From 0085fb923e89f4c41fbcd1e3d84ed6848435f506 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 29 Aug 2023 11:33:13 -0500 Subject: [PATCH 0012/2167] Formatting fixes --- apps/openmw/mwlua/types/book.cpp | 3 +-- apps/openmw/mwlua/types/clothing.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 3436882803..1323e09a7f 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -31,7 +31,7 @@ namespace book = LuaUtil::cast(rec["template"]); else { - book.blank(); + book.blank(); book.mData.mSkillId = -1; } if (rec["name"] != sol::nil) @@ -74,7 +74,6 @@ namespace throw std::runtime_error("Incorrect skill: " + skill.toDebugString()); } } - return book; } diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index d9d0e42579..7f4d6e7002 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -26,7 +26,7 @@ namespace clothing = LuaUtil::cast(rec["template"]); else clothing.blank(); - + if (rec["name"] != sol::nil) clothing.mName = rec["name"]; if (rec["model"] != sol::nil) From 3202900fd3c50fe55745d085ba65e170c6bb8f21 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 17:11:42 +0200 Subject: [PATCH 0013/2167] Make GenericResourceManager::setExpiryDelay final --- components/resource/resourcemanager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index fc5ecc7f03..79632799e2 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -59,7 +59,7 @@ namespace Resource void clearCache() override { mCache->clear(); } /// How long to keep objects in cache after no longer being referenced. - void setExpiryDelay(double expiryDelay) override { mExpiryDelay = expiryDelay; } + void setExpiryDelay(double expiryDelay) final { mExpiryDelay = expiryDelay; } double getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } From b6a3d3c906c37631c814f0789a0b6f0eae637678 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 17:12:20 +0200 Subject: [PATCH 0014/2167] Make BaseResourceManager abstract --- components/resource/resourcemanager.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 79632799e2..1b91d0b412 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -23,11 +23,11 @@ namespace Resource { public: virtual ~BaseResourceManager() = default; - virtual void updateCache(double referenceTime) {} - virtual void clearCache() {} - virtual void setExpiryDelay(double expiryDelay) {} - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} - virtual void releaseGLObjects(osg::State* state) {} + virtual void updateCache(double referenceTime) = 0; + virtual void clearCache() = 0; + virtual void setExpiryDelay(double expiryDelay) = 0; + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const = 0; + virtual void releaseGLObjects(osg::State* state) = 0; }; /// @brief Base class for managers that require a virtual file system and object cache. From 6d120f92e00b6846d0a6ddb5779017d33cd7f5d1 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 28 Aug 2023 22:53:46 +0200 Subject: [PATCH 0015/2167] Lookup for terrain template using std::map::lower_bound instead of linear search --- components/resource/objectcache.hpp | 12 ++++++- components/terrain/chunkmanager.cpp | 46 ++++++++++--------------- components/terrain/chunkmanager.hpp | 47 ++++++++++++++++++++++++-- components/terrain/terraindrawable.hpp | 2 +- 4 files changed, 73 insertions(+), 34 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 3847129ed3..2895aaa3d2 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -195,11 +195,21 @@ namespace Resource return _objectCache.size(); } + template + std::optional>> lowerBound(K&& key) + { + const std::lock_guard lock(_objectCacheMutex); + const auto it = _objectCache.lower_bound(std::forward(key)); + if (it == _objectCache.end()) + return std::nullopt; + return std::pair(it->first, it->second.first); + } + protected: virtual ~GenericObjectCache() {} typedef std::pair, double> ObjectTimeStampPair; - typedef std::map ObjectCacheMap; + typedef std::map> ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 0364a0a4fd..6273041f72 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -21,7 +21,7 @@ namespace Terrain ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer, ESM::RefId worldspace) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr) , QuadTreeWorld::ChunkManager(worldspace) , mStorage(storage) , mSceneManager(sceneMgr) @@ -39,38 +39,26 @@ namespace Terrain mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } - struct FindChunkTemplate - { - void operator()(ChunkId id, osg::Object* obj) - { - if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId)) - mFoundTemplate = obj; - } - ChunkId mId; - osg::ref_ptr mFoundTemplate; - }; - osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { // Override lod with the vertexLodMod adjusted value. // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. lod = static_cast(lodFlags >> (4 * 4)); - ChunkId id = std::make_tuple(center, lod, lodFlags); - osg::ref_ptr obj = mCache->getRefFromObjectCache(id); - if (obj) + + const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags }; + if (osg::ref_ptr obj = mCache->getRefFromObjectCache(key)) return static_cast(obj.get()); - else - { - FindChunkTemplate find; - find.mId = id; - mCache->call(find); - TerrainDrawable* templateGeometry - = find.mFoundTemplate ? static_cast(find.mFoundTemplate.get()) : nullptr; - osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); - mCache->addEntryToObjectCache(id, node.get()); - return node; - } + + const TerrainDrawable* templateGeometry = nullptr; + const TemplateKey templateKey{ .mCenter = center, .mLod = lod }; + const auto pair = mCache->lowerBound(templateKey); + if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod }) + templateGeometry = static_cast(pair->second.get()); + + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); + mCache->addEntryToObjectCache(key, node.get()); + return node; } void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const @@ -80,14 +68,14 @@ namespace Terrain void ChunkManager::clearCache() { - GenericResourceManager::clearCache(); + GenericResourceManager::clearCache(); mBufferCache.clearCache(); } void ChunkManager::releaseGLObjects(osg::State* state) { - GenericResourceManager::releaseGLObjects(state); + GenericResourceManager::releaseGLObjects(state); mBufferCache.releaseGLObjects(state); } @@ -202,7 +190,7 @@ namespace Terrain } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, - unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry) { osg::ref_ptr geometry(new TerrainDrawable); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 4238f01c8b..a55dd15cc1 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -28,10 +28,51 @@ namespace Terrain class CompositeMap; class TerrainDrawable; - typedef std::tuple ChunkId; // Center, Lod, Lod Flags + struct TemplateKey + { + osg::Vec2f mCenter; + unsigned char mLod; + }; + + inline auto tie(const TemplateKey& v) + { + return std::tie(v.mCenter, v.mLod); + } + + inline bool operator<(const TemplateKey& l, const TemplateKey& r) + { + return tie(l) < tie(r); + } + + inline bool operator==(const TemplateKey& l, const TemplateKey& r) + { + return tie(l) == tie(r); + } + + struct ChunkKey + { + osg::Vec2f mCenter; + unsigned char mLod; + unsigned mLodFlags; + }; + + inline auto tie(const ChunkKey& v) + { + return std::tie(v.mCenter, v.mLod, v.mLodFlags); + } + + inline bool operator<(const ChunkKey& l, const ChunkKey& r) + { + return tie(l) < tie(r); + } + + inline bool operator<(const ChunkKey& l, const TemplateKey& r) + { + return TemplateKey{ .mCenter = l.mCenter, .mLod = l.mLod } < r; + } /// @brief Handles loading and caching of terrain chunks - class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager + class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, @@ -55,7 +96,7 @@ namespace Terrain private: osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry); + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry); osg::ref_ptr createCompositeMapRTT(); diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index b94f47df97..3ce846ad73 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -60,7 +60,7 @@ namespace Terrain const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } - CompositeMap* getCompositeMap() { return mCompositeMap; } + CompositeMap* getCompositeMap() const { return mCompositeMap; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: From 52ab47771ce183495f81437a4fc4611ceb44a3d5 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 17:12:42 +0200 Subject: [PATCH 0016/2167] Initialize expiry delay for all GenericResourceManager instances --- apps/openmw/mwworld/scene.cpp | 3 --- components/resource/niffilemanager.cpp | 4 +++- components/resource/resourcemanager.hpp | 14 +++++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 78c8ccab1d..b5aff875bd 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -834,9 +834,6 @@ namespace MWWorld mPreloader = std::make_unique(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()); mPreloader->setWorkQueue(mRendering.getWorkQueue()); - - rendering.getResourceSystem()->setExpiryDelay(Settings::cells().mCacheExpiryDelay); - mPreloader->setExpiryDelay(Settings::cells().mPreloadCellExpiryDelay); mPreloader->setMinCacheSize(Settings::cells().mPreloadCellCacheMin); mPreloader->setMaxCacheSize(Settings::cells().mPreloadCellCacheMax); diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index ea95edf2ae..5e457cdfaa 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -32,7 +32,9 @@ namespace Resource }; NifFileManager::NifFileManager(const VFS::Manager* vfs) - : ResourceManager(vfs) + // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, + // so no point in using an expiry delay. + : ResourceManager(vfs, 0) { } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 1b91d0b412..09b99faf28 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include "objectcache.hpp" namespace VFS @@ -39,10 +41,11 @@ namespace Resource public: typedef GenericObjectCache CacheType; - GenericResourceManager(const VFS::Manager* vfs) + explicit GenericResourceManager( + const VFS::Manager* vfs, double expiryDelay = Settings::cells().mCacheExpiryDelay) : mVFS(vfs) , mCache(new CacheType) - , mExpiryDelay(0.0) + , mExpiryDelay(expiryDelay) { } @@ -77,10 +80,15 @@ namespace Resource class ResourceManager : public GenericResourceManager { public: - ResourceManager(const VFS::Manager* vfs) + explicit ResourceManager(const VFS::Manager* vfs) : GenericResourceManager(vfs) { } + + explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) + : GenericResourceManager(vfs, expiryDelay) + { + } }; } From d3dca99a76a251ea4a1436d014d04a467ece02e7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 16:03:47 +0200 Subject: [PATCH 0017/2167] Preload terrain in single pass Otherwise there is lodFlags mismatch because some of the neighbours are removed during preloading. This makes rendering culling create land chunk nodes again for the same position, lod because lodFlags are different. --- components/terrain/quadtreeworld.cpp | 39 +++++++--------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 8449f3df25..52edd96b9f 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -544,37 +544,16 @@ namespace Terrain vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - for (unsigned int pass = 0; pass < 3; ++pass) + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + + reporter.addTotal(vd->getNumEntries()); + + for (unsigned int i = 0, n = vd->getNumEntries(); i < n && !abort; ++i) { - unsigned int startEntry = vd->getNumEntries(); - - float distanceModifier = 0.f; - if (pass == 1) - distanceModifier = 1024; - else if (pass == 2) - distanceModifier = -1024; - DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize, distanceModifier); - mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - - if (pass == 0) - { - std::size_t progressTotal = 0; - for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); - - reporter.addTotal(progressTotal); - } - - for (unsigned int i = startEntry; i < vd->getNumEntries() && !abort; ++i) - { - ViewDataEntry& entry = vd->getEntry(i); - - loadRenderingNode(entry, vd, cellWorldSize, grid, true); - if (pass == 0) - reporter.addProgress(entry.mNode->getSize()); - vd->removeNodeFromIndex(entry.mNode); - entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass - } + ViewDataEntry& entry = vd->getEntry(i); + loadRenderingNode(entry, vd, cellWorldSize, grid, true); + reporter.addProgress(1); } } From 5f4bd498cfcf25ac280c65823d36c1bc05451343 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 16:02:46 +0200 Subject: [PATCH 0018/2167] Move cached value into container to be removed --- components/resource/objectcache.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 2895aaa3d2..b5fb443ae3 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -83,8 +83,8 @@ namespace Resource { if (oitr->second.second <= expiryTime) { - if (oitr->second.first != nullptr) - objectsToRemove.push_back(oitr->second.first); + if (oitr->second.mValue != nullptr) + objectsToRemove.push_back(std::move(oitr->second.first)); _objectCache.erase(oitr++); } else From 915a8df9424b45a2a46dbdaa515266d71a7a4953 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 27 Aug 2023 16:03:14 +0200 Subject: [PATCH 0019/2167] Use struct for GenericObjectCache items --- components/resource/objectcache.hpp | 36 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index b5fb443ae3..881729ffc4 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -62,9 +62,9 @@ namespace Resource { // If ref count is greater than 1, the object has an external reference. // If the timestamp is yet to be initialized, it needs to be updated too. - if ((itr->second.first != nullptr && itr->second.first->referenceCount() > 1) - || itr->second.second == 0.0) - itr->second.second = referenceTime; + if ((itr->second.mValue != nullptr && itr->second.mValue->referenceCount() > 1) + || itr->second.mLastUsage == 0.0) + itr->second.mLastUsage = referenceTime; } } @@ -81,10 +81,10 @@ namespace Resource typename ObjectCacheMap::iterator oitr = _objectCache.begin(); while (oitr != _objectCache.end()) { - if (oitr->second.second <= expiryTime) + if (oitr->second.mLastUsage <= expiryTime) { if (oitr->second.mValue != nullptr) - objectsToRemove.push_back(std::move(oitr->second.first)); + objectsToRemove.push_back(std::move(oitr->second.mValue)); _objectCache.erase(oitr++); } else @@ -106,7 +106,7 @@ namespace Resource void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) { std::lock_guard lock(_objectCacheMutex); - _objectCache[key] = ObjectTimeStampPair(object, timestamp); + _objectCache[key] = Item{ object, timestamp }; } /** Remove Object from cache.*/ @@ -124,7 +124,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr != _objectCache.end()) - return itr->second.first; + return itr->second.mValue; else return nullptr; } @@ -135,7 +135,7 @@ namespace Resource const auto it = _objectCache.find(key); if (it == _objectCache.end()) return std::nullopt; - return it->second.first; + return it->second.mValue; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ @@ -145,7 +145,7 @@ namespace Resource typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr != _objectCache.end()) { - itr->second.second = timeStamp; + itr->second.mLastUsage = timeStamp; return true; } else @@ -158,7 +158,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { - osg::Object* object = itr->second.first.get(); + osg::Object* object = itr->second.mValue.get(); object->releaseGLObjects(state); } } @@ -169,8 +169,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { - osg::Object* object = itr->second.first.get(); - if (object) + if (osg::Object* object = itr->second.mValue.get()) { osg::Node* node = dynamic_cast(object); if (node) @@ -185,7 +184,7 @@ namespace Resource { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->first, it->second.first.get()); + f(it->first, it->second.mValue.get()); } /** Get the number of objects in the cache. */ @@ -202,14 +201,19 @@ namespace Resource const auto it = _objectCache.lower_bound(std::forward(key)); if (it == _objectCache.end()) return std::nullopt; - return std::pair(it->first, it->second.first); + return std::pair(it->first, it->second.mValue); } protected: + struct Item + { + osg::ref_ptr mValue; + double mLastUsage; + }; + virtual ~GenericObjectCache() {} - typedef std::pair, double> ObjectTimeStampPair; - typedef std::map> ObjectCacheMap; + using ObjectCacheMap = std::map>; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; From af58b531da743a54f7a480444affab1eeb919dc4 Mon Sep 17 00:00:00 2001 From: Kindi Date: Tue, 29 Aug 2023 08:27:40 +0800 Subject: [PATCH 0020/2167] change function names and add documentation in overview.rst --- components/lua/utf8.cpp | 24 +++++++++---------- .../reference/lua-scripting/overview.rst | 6 ++++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 69160fdd53..eac3954230 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -27,10 +27,10 @@ namespace return integer; } - inline void posrelat(int64_t& pos, const size_t& len) + inline void relativePosition(int64_t& pos, const size_t& len) { if (pos >= 0) - /* no change */; + return; else if (0u - pos > static_cast(len)) pos = 0; else @@ -38,7 +38,7 @@ namespace } // returns: first - character pos in bytes, second - character codepoint - std::pair poscodes(const std::string_view& s, std::vector& pos_byte) + std::pair decodeNextUTF8Character(const std::string_view& s, std::vector& pos_byte) { const int64_t pos = pos_byte.back() - 1; const unsigned char ch = static_cast(s[pos]); @@ -113,7 +113,7 @@ namespace LuaUtf8 return sol::as_function([s, pos_byte]() mutable -> sol::optional> { if (pos_byte.back() <= static_cast(s.size())) { - const auto pair = poscodes(s, pos_byte); + const auto pair = decodeNextUTF8Character(s, pos_byte); if (pair.second == -1) throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); @@ -129,8 +129,8 @@ namespace LuaUtf8 int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len"); int64_t fv = isNilOrNone(args[1]) ? -1 : getInteger(args[1], 3, "len"); - posrelat(iv, len); - posrelat(fv, len); + relativePosition(iv, len); + relativePosition(fv, len); if (iv <= 0) throw std::runtime_error("bad argument #2 to 'len' (initial position out of bounds)"); @@ -144,7 +144,7 @@ namespace LuaUtf8 while (pos_byte.back() <= fv) { - if (poscodes(s, pos_byte).second == -1) + if (decodeNextUTF8Character(s, pos_byte).second == -1) return std::pair(sol::lua_nil, pos_byte.back()); } return pos_byte.size() - 1; @@ -156,8 +156,8 @@ namespace LuaUtf8 int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "codepoint"); int64_t fv = isNilOrNone(args[1]) ? iv : getInteger(args[1], 3, "codepoint"); - posrelat(iv, len); - posrelat(fv, len); + relativePosition(iv, len); + relativePosition(fv, len); if (iv <= 0) throw std::runtime_error("bad argument #2 to 'codepoint' (initial position out of bounds)"); @@ -172,7 +172,7 @@ namespace LuaUtf8 while (pos_byte.back() <= fv) { - codepoints.push_back(poscodes(s, pos_byte).second); + codepoints.push_back(decodeNextUTF8Character(s, pos_byte).second); if (codepoints.back() == -1) throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); } @@ -186,13 +186,13 @@ namespace LuaUtf8 int64_t iv = isNilOrNone(args[0]) ? ((n >= 0) ? 1 : s.size() + 1) : getInteger(args[0], 3, "offset"); std::vector pos_byte = { 1 }; - posrelat(iv, len); + relativePosition(iv, len); if (iv > static_cast(len) + 1) throw std::runtime_error("bad argument #3 to 'offset' (position out of bounds)"); while (pos_byte.back() <= static_cast(len)) - poscodes(s, pos_byte); + decodeNextUTF8Character(s, pos_byte); for (auto it = pos_byte.begin(); it != pos_byte.end(); ++it) if (*it == iv) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 283664b2c4..6e25f321f3 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -4,7 +4,7 @@ Overview of Lua scripting Language and sandboxing ======================= -OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2. +OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2 and Lua 5.3. There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT. .. note:: @@ -38,6 +38,10 @@ Supported Lua 5.2 features: - ``__pairs`` and ``__ipairs`` metamethods; - Function ``table.unpack`` (alias to Lua 5.1 ``unpack``). +Supported Lua 5.3 features: + +- All functions in the `UTF-8 Library `__ + Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way: 1. If `library_name` is one of the standard libraries, then return the library. From 8bbaa57a1491be205e92df596784d805764ebff1 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 29 Aug 2023 21:53:47 +0200 Subject: [PATCH 0021/2167] Add changelog record for #7557 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee31b6762..0448e19188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #7472: Crash when enchanting last projectiles Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect + Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6447: Add LOD support to Object Paging From 4b7bf6267150430302432cb02a72d3f98d8e301f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 29 Aug 2023 16:37:32 -0500 Subject: [PATCH 0022/2167] Fix weapon table, and docs --- apps/openmw/mwlua/types/weapon.cpp | 19 +++++++++++++++---- files/lua_api/openmw/types.lua | 26 +++++++++++++++++++------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index e927fedadd..993f1ecc95 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -45,10 +45,21 @@ namespace std::string_view scriptId = rec["mwscript"].get(); weapon.mScript = ESM::RefId::deserializeText(scriptId); } - if (rec["isMagical"]) - weapon.mData.mFlags |= ESM::Weapon::Magical; - if (rec["isSilver"]) - weapon.mData.mFlags |= ESM::Weapon::Silver; + if (auto isMagical = rec["isMagical"]; isMagical != sol::nil) + { + if (isMagical) + weapon.mData.mFlags |= ESM::Weapon::Magical; + else + weapon.mData.mFlags &= ~ESM::Weapon::Magical; + } + if (auto isSilver = rec["isSilver"]; isSilver != sol::nil) + { + if (isSilver) + weapon.mData.mFlags |= ESM::Weapon::Silver; + else + weapon.mData.mFlags &= ~ESM::Weapon::Silver; + } + if (rec["type"] != sol::nil) { int weaponType = rec["type"].get(); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 382fb64a43..ab913bc788 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -846,11 +846,17 @@ -- @field #number enchantCapacity --- --- Creates a @{#ArmorRecord} without adding it to the world database. +-- Creates a @{#ArmorRecord} without adding it to the world database, for the armor to appear correctly on the body, make sure to use a template as described below. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Armor] createRecordDraft -- @param #ArmorRecord armor A Lua table with the fields of a ArmorRecord, with an additional field `template` that accepts a @{#ArmorRecord} as a base. -- @return #ArmorRecord A strongly typed Armor record. +-- @usage local armorTemplate = types.Armor.record('orcish_cuirass') +-- local armorTable = {name = "Better Orcish Cuirass",template = armorTemplate,baseArmor = armorTemplate.baseArmor + 10} +-- --This is the new record we want to create, with a record provided as a template. +-- local recordDraft = types.Armor.createRecordDraft(armorTable)--Need to convert the table into the record draft +-- local newRecord = world.createRecord(recordDraft)--This creates the actual record +-- world.createObject(newRecord):moveInto(playerActor)--Create an instance of this object, and move it into the player's inventory --- @{#Book} functions @@ -926,7 +932,7 @@ -- Creates a @{#BookRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Book] createRecordDraft --- @param #BookRecord book A Lua table with the fields of a BookRecord, with an additional field `template` that accepts a @{#BookRecord} as a base. +-- @param #BookRecord book A Lua table with the fields of a BookRecord, with an optional field `template` that accepts a @{#BookRecord} as a base. -- @return #BookRecord A strongly typed Book record. --- @{#Clothing} functions @@ -967,11 +973,17 @@ -- @return #ClothingRecord --- --- Creates a @{#ClothingRecord} without adding it to the world database. +-- Creates a @{#ClothingRecord} without adding it to the world database, for the clothing to appear correctly on the body, make sure to use a template as described below. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Clothing] createRecordDraft -- @param #ClothingRecord clothing A Lua table with the fields of a ClothingRecord, with an additional field `template` that accepts a @{#ClothingRecord} as a base. -- @return #ClothingRecord A strongly typed clothing record. +-- @usage local clothingTemplate = types.Clothing.record('exquisite_robe_01') +-- local clothingTable = {name = "Better Exquisite Robe",template = clothingTemplate,enchantCapacity = clothingTemplate.enchantCapacity + 10} +-- --This is the new record we want to create, with a record provided as a template. +-- local recordDraft = types.Clothing.createRecordDraft(clothingTable)--Need to convert the table into the record draft +-- local newRecord = world.createRecord(recordDraft)--This creates the actual record +-- world.createObject(newRecord):moveInto(playerActor)--Create an instance of this object, and move it into the player's inventory --- -- @type ClothingRecord @@ -1145,7 +1157,7 @@ -- Creates a @{#MiscellaneousRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Miscellaneous] createRecordDraft --- @param #MiscellaneousRecord miscellaneous A Lua table with the fields of a MiscellaneousRecord, with an additional field `template` that accepts a @{#MiscellaneousRecord} as a base. +-- @param #MiscellaneousRecord miscellaneous A Lua table with the fields of a MiscellaneousRecord, with an optional field `template` that accepts a @{#MiscellaneousRecord} as a base. -- @return #MiscellaneousRecord A strongly typed Miscellaneous record. --- @@ -1190,7 +1202,7 @@ -- Creates a @{#PotionRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Potion] createRecordDraft --- @param #PotionRecord potion A Lua table with the fields of a PotionRecord, with an additional field `template` that accepts a @{#PotionRecord} as a base. +-- @param #PotionRecord potion A Lua table with the fields of a PotionRecord, with an optional field `template` that accepts a @{#PotionRecord} as a base. -- @return #PotionRecord A strongly typed Potion record. --- @@ -1275,7 +1287,7 @@ -- Creates a @{#WeaponRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Weapon] createRecordDraft --- @param #WeaponRecord weapon A Lua table with the fields of a WeaponRecord, with an additional field `template` that accepts a @{#WeaponRecord} as a base. +-- @param #WeaponRecord weapon A Lua table with the fields of a WeaponRecord, with an optional field `template` that accepts a @{#WeaponRecord} as a base. -- @return #WeaponRecord A strongly typed Weapon record. --- @{#Apparatus} functions @@ -1450,7 +1462,7 @@ -- Creates a @{#ActivatorRecord} without adding it to the world database. -- Use @{openmw_world#(world).createRecord} to add the record to the world. -- @function [parent=#Activator] createRecordDraft --- @param #ActivatorRecord activator A Lua table with the fields of a ActivatorRecord, with an additional field `template` that accepts a @{#ActivatorRecord} as a base. +-- @param #ActivatorRecord activator A Lua table with the fields of a ActivatorRecord, with an optional field `template` that accepts a @{#ActivatorRecord} as a base. -- @return #ActivatorRecord A strongly typed Activator record. From 4d717ade6c3798121e85ac16c59dd27683efeb12 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 30 Aug 2023 17:39:20 +0300 Subject: [PATCH 0023/2167] Allow BSA string tables to contain padding --- components/bsa/compressedbsafile.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 934197e6ca..b916de7919 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -144,8 +144,9 @@ namespace Bsa input.read(reinterpret_cast(&file.mOffset), 4); } } + if (mHeader.mFolderNamesLength != 0) - fail("Failed to read folder names: " + std::to_string(mHeader.mFolderNamesLength) + " bytes remaining"); + input.ignore(mHeader.mFolderNamesLength); if (input.bad()) fail("Failed to read compressed BSA file records: input error"); @@ -172,13 +173,13 @@ namespace Bsa file.mName.insert(file.mName.begin() + folder.mName.size(), '\\'); } } - - if (input.bad()) - fail("Failed to read compressed BSA filenames: input error"); } if (mHeader.mFileNamesLength != 0) - fail("Failed to read file names: " + std::to_string(mHeader.mFileNamesLength) + " bytes remaining"); + input.ignore(mHeader.mFileNamesLength); + + if (input.bad()) + fail("Failed to read compressed BSA filenames: input error"); for (auto& [folder, filelist] : folders) { From 4140f9da3ce31fc17b2893fa7e37303d22fa5896 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 30 Aug 2023 18:58:20 +0300 Subject: [PATCH 0024/2167] Reorganize the list of recognized NIF records --- components/nif/niffile.cpp | 389 ++++++++++++++++++++++--------------- 1 file changed, 237 insertions(+), 152 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 0edabdd7a7..f806c61bc1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -49,180 +49,265 @@ namespace Nif static std::map makeFactory() { return { + // 4.0.0.2 refers to Bethesda variant of NetImmerse 4.0.0.2 file format + // Gamebryo refers to files newer than 4.0.0.2 + // Bethesda refers to custom records Bethesda introduced post-4.0.0.2 + + // NODES + + // NiNode-like nodes, 4.0.0.2 { "NiNode", &construct }, - { "NiSwitchNode", &construct }, - { "NiLODNode", &construct }, - { "NiFltAnimationNode", &construct }, { "AvoidNode", &construct }, - { "NiCollisionSwitch", &construct }, - { "NiBSParticleNode", &construct }, - { "NiBSAnimationNode", &construct }, { "NiBillboardNode", &construct }, - { "NiTriShape", &construct }, - { "NiTriStrips", &construct }, - { "NiLines", &construct }, - { "NiParticles", &construct }, - { "NiRotatingParticles", &construct }, - { "NiAutoNormalParticles", &construct }, - { "NiCamera", &construct }, + { "NiBSAnimationNode", &construct }, + { "NiBSParticleNode", &construct }, + { "NiCollisionSwitch", &construct }, + { "NiSortAdjustNode", &construct }, { "RootCollisionNode", &construct }, - { "NiTexturingProperty", &construct }, - { "NiFogProperty", &construct }, - { "NiMaterialProperty", &construct }, - { "NiZBufferProperty", &construct }, - { "NiAlphaProperty", &construct }, - { "NiVertexColorProperty", &construct }, - { "NiShadeProperty", &construct }, - { "NiDitherProperty", &construct }, - { "NiWireframeProperty", &construct }, - { "NiSpecularProperty", &construct }, - { "NiStencilProperty", &construct }, - { "NiVisController", &construct }, - { "NiGeomMorpherController", &construct }, - { "NiKeyframeController", &construct }, - { "NiAlphaController", &construct }, - { "NiRollController", &construct }, - { "NiUVController", &construct }, - { "NiPathController", &construct }, - { "NiMaterialColorController", &construct }, - { "NiBSPArrayController", &construct }, - { "NiParticleSystemController", &construct }, - { "NiFlipController", &construct }, - { "NiTextureTransformController", - &construct }, - { "NiAmbientLight", &construct }, - { "NiDirectionalLight", &construct }, - { "NiPointLight", &construct }, - { "NiSpotLight", &construct }, - { "NiTextureEffect", &construct }, - { "NiExtraData", &construct }, - { "NiVertWeightsExtraData", &construct }, - { "NiTextKeyExtraData", &construct }, - { "NiStringExtraData", &construct }, - { "NiGravity", &construct }, - { "NiPlanarCollider", &construct }, - { "NiSphericalCollider", &construct }, - { "NiParticleGrowFade", &construct }, - { "NiParticleColorModifier", &construct }, - { "NiParticleRotation", &construct }, - { "NiFloatData", &construct }, - { "NiTriShapeData", &construct }, - { "NiTriStripsData", &construct }, - { "NiLinesData", &construct }, - { "NiVisData", &construct }, - { "NiColorData", &construct }, - { "NiPixelData", &construct }, - { "NiMorphData", &construct }, - { "NiKeyframeData", &construct }, - { "NiSkinData", &construct }, - { "NiUVData", &construct }, - { "NiPosData", &construct }, - { "NiParticlesData", &construct }, - { "NiRotatingParticlesData", &construct }, - { "NiAutoNormalParticlesData", &construct }, - { "NiSequenceStreamHelper", &construct }, - { "NiSourceTexture", &construct }, - { "NiSkinInstance", &construct }, - { "NiLookAtController", &construct }, - { "NiPalette", &construct }, - { "NiIntegerExtraData", &construct }, - { "NiIntegersExtraData", &construct }, - { "NiBinaryExtraData", &construct }, - { "NiBooleanExtraData", &construct }, - { "NiVectorExtraData", &construct }, - { "NiColorExtraData", &construct }, - { "NiFloatExtraData", &construct }, - { "NiFloatsExtraData", &construct }, - { "NiStringPalette", &construct }, - { "NiBoolData", &construct }, - { "NiSkinPartition", &construct }, - { "BSXFlags", &construct }, - { "BSBound", &construct }, - { "NiTransformData", &construct }, - { "BSFadeNode", &construct }, - { "BSLeafAnimNode", &construct }, - { "BSTreeNode", &construct }, - { "BSValueNode", &construct }, - { "BSOrderedNode", &construct }, - { "BSMultiBoundNode", &construct }, - { "BSRangeNode", &construct }, + + // NiNode-like nodes, Bethesda { "BSBlastNode", &construct }, { "BSDamageStage", &construct }, - { "bhkBlendController", &construct }, + { "BSFadeNode", &construct }, + { "BSLeafAnimNode", &construct }, + { "BSMultiBoundNode", &construct }, + { "BSOrderedNode", &construct }, + { "BSRangeNode", &construct }, + { "BSTreeNode", &construct }, + { "BSValueNode", &construct }, + + // Switch nodes, 4.0.0.2 + { "NiSwitchNode", &construct }, + { "NiFltAnimationNode", &construct }, + { "NiLODNode", &construct }, + + // NiSequence nodes, 4.0.0.2 + { "NiSequenceStreamHelper", &construct }, + + // NiSequence nodes, Gamebryo + { "NiSequence", &construct }, + { "NiControllerSequence", &construct }, + + // Other nodes, 4.0.0.2 + { "NiCamera", &construct }, + + // ACCUMULATORS + + // 4.0.0.2 + { "NiAlphaAccumulator", &construct }, + { "NiClusterAccumulator", &construct }, + + // CONTROLLERS + + // 4.0.0.2 + { "NiAlphaController", &construct }, + { "NiBSPArrayController", &construct }, + { "NiFlipController", &construct }, + { "NiGeomMorpherController", &construct }, + { "NiKeyframeController", &construct }, + { "NiLookAtController", &construct }, + { "NiMaterialColorController", &construct }, + { "NiParticleSystemController", &construct }, + { "NiPathController", &construct }, + { "NiRollController", &construct }, + { "NiUVController", &construct }, + { "NiVisController", &construct }, + + // Gamebryo + { "NiControllerManager", &construct }, + { "NiTransformController", &construct }, + { "NiTextureTransformController", + &construct }, + { "NiMultiTargetTransformController", + &construct }, + + // Bethesda { "BSMaterialEmittanceMultController", &construct }, { "BSRefractionFirePeriodController", &construct }, { "BSRefractionStrengthController", &construct }, - { "NiFloatInterpolator", &construct }, - { "NiBoolInterpolator", &construct }, - { "NiBoolTimelineInterpolator", &construct }, - { "NiPoint3Interpolator", &construct }, - { "NiTransformController", &construct }, - { "NiMultiTargetTransformController", - &construct }, - { "NiTransformInterpolator", &construct }, - { "NiColorInterpolator", &construct }, - { "BSShaderTextureSet", &construct }, - { "BSLODTriShape", &construct }, - { "BSShaderProperty", &construct }, - { "BSShaderPPLightingProperty", &construct }, - { "BSShaderNoLightingProperty", &construct }, - { "BSFurnitureMarker", &construct }, - { "BSFurnitureMarkerNode", &construct }, - { "NiCollisionObject", &construct }, - { "bhkCollisionObject", &construct }, - { "bhkSPCollisionObject", &construct }, - { "bhkPCollisionObject", &construct }, - { "BSDismemberSkinInstance", &construct }, - { "NiControllerManager", &construct }, - { "bhkMoppBvTreeShape", &construct }, - { "bhkNiTriStripsShape", &construct }, - { "bhkPackedNiTriStripsShape", &construct }, - { "hkPackedNiTriStripsData", &construct }, - { "bhkConvexVerticesShape", &construct }, - { "bhkConvexTransformShape", &construct }, - { "bhkTransformShape", &construct }, - { "bhkSimpleShapePhantom", &construct }, - { "bhkBoxShape", &construct }, - { "bhkCapsuleShape", &construct }, - { "bhkSphereShape", &construct }, - { "bhkListShape", &construct }, - { "bhkRigidBody", &construct }, - { "bhkRigidBodyT", &construct }, - { "bhkRagdollConstraint", &construct }, - { "bhkHingeConstraint", &construct }, - { "bhkLimitedHingeConstraint", &construct }, - { "BSLightingShaderProperty", &construct }, - { "BSEffectShaderProperty", &construct }, - { "NiSortAdjustNode", &construct }, - { "NiClusterAccumulator", &construct }, - { "NiAlphaAccumulator", &construct }, - { "NiSequence", &construct }, - { "NiControllerSequence", &construct }, - { "NiDefaultAVObjectPalette", &construct }, + { "BSEffectShaderPropertyColorController", + &construct }, + { "BSEffectShaderPropertyFloatController", + &construct }, + { "BSLightingShaderPropertyColorController", + &construct }, + { "BSLightingShaderPropertyFloatController", + &construct }, + { "bhkBlendController", &construct }, + + // Interpolators, Gamebryo { "NiBlendBoolInterpolator", &construct }, { "NiBlendFloatInterpolator", &construct }, { "NiBlendPoint3Interpolator", &construct }, { "NiBlendTransformInterpolator", &construct }, - { "bhkCompressedMeshShape", &construct }, - { "bhkCompressedMeshShapeData", &construct }, + { "NiBoolInterpolator", &construct }, + { "NiBoolTimelineInterpolator", &construct }, + { "NiColorInterpolator", &construct }, + { "NiFloatInterpolator", &construct }, + { "NiPoint3Interpolator", &construct }, + { "NiTransformInterpolator", &construct }, + + // DATA + + // 4.0.0.2 + { "NiColorData", &construct }, + { "NiFloatData", &construct }, + { "NiKeyframeData", &construct }, + { "NiMorphData", &construct }, + { "NiPalette", &construct }, + { "NiPixelData", &construct }, + { "NiPosData", &construct }, + { "NiSourceTexture", &construct }, + { "NiUVData", &construct }, + { "NiVisData", &construct }, + + // Gamebryo + { "NiBoolData", &construct }, + { "NiDefaultAVObjectPalette", &construct }, + { "NiTransformData", &construct }, + + // Bethesda + { "BSShaderTextureSet", &construct }, + + // DYNAMIC EFFECTS + + // 4.0.0.2 + { "NiAmbientLight", &construct }, + { "NiDirectionalLight", &construct }, + { "NiPointLight", &construct }, + { "NiSpotLight", &construct }, + { "NiTextureEffect", &construct }, + + // EXTRA DATA + + // 4.0.0.2 + { "NiExtraData", &construct }, + { "NiStringExtraData", &construct }, + { "NiTextKeyExtraData", &construct }, + { "NiVertWeightsExtraData", &construct }, + + // Gamebryo + { "NiBinaryExtraData", &construct }, + { "NiBooleanExtraData", &construct }, + { "NiColorExtraData", &construct }, + { "NiFloatExtraData", &construct }, + { "NiFloatsExtraData", &construct }, + { "NiIntegerExtraData", &construct }, + { "NiIntegersExtraData", &construct }, + { "NiVectorExtraData", &construct }, + { "NiStringPalette", &construct }, + + // Bethesda bounds + { "BSBound", &construct }, { "BSMultiBound", &construct }, { "BSMultiBoundOBB", &construct }, { "BSMultiBoundSphere", &construct }, + + // Bethesda markers + { "BSFurnitureMarker", &construct }, + { "BSFurnitureMarkerNode", &construct }, { "BSInvMarker", &construct }, - { "BSTriShape", &construct }, - { "BSEffectShaderPropertyFloatController", - &construct }, - { "BSLightingShaderPropertyFloatController", - &construct }, - { "BSEffectShaderPropertyColorController", - &construct }, - { "BSLightingShaderPropertyColorController", - &construct }, + + // Other Bethesda records { "BSBehaviorGraphExtraData", &construct }, + { "BSXFlags", &construct }, + + // GEOMETRY + + // 4.0.0.2 + { "NiAutoNormalParticles", &construct }, + { "NiAutoNormalParticlesData", &construct }, + { "NiLines", &construct }, + { "NiLinesData", &construct }, + { "NiParticles", &construct }, + { "NiParticlesData", &construct }, + { "NiRotatingParticles", &construct }, + { "NiRotatingParticlesData", &construct }, + { "NiSkinData", &construct }, + { "NiSkinInstance", &construct }, + { "NiSkinPartition", &construct }, + { "NiTriShape", &construct }, + { "NiTriShapeData", &construct }, + { "NiTriStrips", &construct }, + { "NiTriStripsData", &construct }, + + // Bethesda + { "BSDismemberSkinInstance", &construct }, + { "BSTriShape", &construct }, + { "BSLODTriShape", &construct }, + + // PARTICLES + + // Modifiers, 4.0.0.2 + { "NiGravity", &construct }, + { "NiParticleColorModifier", &construct }, + { "NiParticleGrowFade", &construct }, + { "NiParticleRotation", &construct }, + + // Colliders, 4.0.0.2 + { "NiPlanarCollider", &construct }, + { "NiSphericalCollider", &construct }, + + // PHYSICS + + // Collision objects, Gamebryo + { "NiCollisionObject", &construct }, + + // Collision objects, Bethesda + { "bhkCollisionObject", &construct }, + { "bhkPCollisionObject", &construct }, + { "bhkSPCollisionObject", &construct }, + + // Constraint records, Bethesda + { "bhkHingeConstraint", &construct }, + { "bhkLimitedHingeConstraint", &construct }, + { "bhkRagdollConstraint", &construct }, + + // Physics body records, Bethesda + { "bhkRigidBody", &construct }, + { "bhkRigidBodyT", &construct }, + + // Physics geometry records, Bethesda + { "bhkBoxShape", &construct }, + { "bhkCapsuleShape", &construct }, + { "bhkCompressedMeshShape", &construct }, + { "bhkCompressedMeshShapeData", &construct }, + { "bhkConvexTransformShape", &construct }, + { "bhkConvexVerticesShape", &construct }, + { "bhkListShape", &construct }, + { "bhkMoppBvTreeShape", &construct }, + { "bhkNiTriStripsShape", &construct }, + { "bhkPackedNiTriStripsShape", &construct }, + { "hkPackedNiTriStripsData", &construct }, + { "bhkSimpleShapePhantom", &construct }, + { "bhkSphereShape", &construct }, + { "bhkTransformShape", &construct }, + + // PROPERTIES + + // 4.0.0.2 + { "NiAlphaProperty", &construct }, + { "NiDitherProperty", &construct }, + { "NiFogProperty", &construct }, + { "NiMaterialProperty", &construct }, + { "NiShadeProperty", &construct }, + { "NiSpecularProperty", &construct }, + { "NiStencilProperty", &construct }, + { "NiTexturingProperty", &construct }, + { "NiVertexColorProperty", &construct }, + { "NiWireframeProperty", &construct }, + { "NiZBufferProperty", &construct }, + + // Shader properties, Bethesda + { "BSShaderProperty", &construct }, + { "BSShaderPPLightingProperty", &construct }, + { "BSShaderNoLightingProperty", &construct }, + { "BSLightingShaderProperty", &construct }, + { "BSEffectShaderProperty", &construct }, }; } From f8b2967dab08598b04ff188098ae68dc3c4770f3 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 30 Aug 2023 19:11:00 +0300 Subject: [PATCH 0025/2167] Alphabetize NIF record type list --- components/nif/record.hpp | 257 +++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 129 deletions(-) diff --git a/components/nif/record.hpp b/components/nif/record.hpp index dc4fab8e2e..3cb000e90e 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -35,149 +35,148 @@ namespace Nif enum RecordType { RC_MISSING = 0, - RC_NiNode, - RC_NiSwitchNode, - RC_NiLODNode, - RC_NiFltAnimationNode, - RC_NiBillboardNode, RC_AvoidNode, - RC_NiCollisionSwitch, - RC_NiTriShape, - RC_NiTriStrips, - RC_NiLines, - RC_NiParticles, - RC_NiBSParticleNode, - RC_NiCamera, - RC_NiTexturingProperty, - RC_NiFogProperty, - RC_NiMaterialProperty, - RC_NiZBufferProperty, - RC_NiAlphaProperty, - RC_NiVertexColorProperty, - RC_NiShadeProperty, - RC_NiDitherProperty, - RC_NiWireframeProperty, - RC_NiSpecularProperty, - RC_NiStencilProperty, - RC_NiVisController, - RC_NiGeomMorpherController, - RC_NiKeyframeController, - RC_NiAlphaController, - RC_NiRollController, - RC_NiUVController, - RC_NiPathController, - RC_NiMaterialColorController, - RC_NiBSPArrayController, - RC_NiParticleSystemController, - RC_NiFlipController, - RC_NiTextureTransformController, - RC_NiBSAnimationNode, - RC_NiLight, - RC_NiTextureEffect, - RC_NiExtraData, - RC_NiVertWeightsExtraData, - RC_NiTextKeyExtraData, - RC_NiStringExtraData, - RC_NiGravity, - RC_NiPlanarCollider, - RC_NiParticleGrowFade, - RC_NiParticleColorModifier, - RC_NiParticleRotation, - RC_NiFloatData, - RC_NiTriShapeData, - RC_NiTriStripsData, - RC_NiLinesData, - RC_NiVisData, - RC_NiColorData, - RC_NiPixelData, - RC_NiMorphData, - RC_NiKeyframeData, - RC_NiSkinData, - RC_NiUVData, - RC_NiPosData, - RC_NiRotatingParticlesData, - RC_NiParticlesData, - RC_NiSequenceStreamHelper, - RC_NiSourceTexture, - RC_NiSkinInstance, - RC_RootCollisionNode, - RC_NiSphericalCollider, - RC_NiLookAtController, - RC_NiPalette, - RC_NiIntegerExtraData, - RC_NiIntegersExtraData, - RC_NiBinaryExtraData, - RC_NiBooleanExtraData, - RC_NiVectorExtraData, - RC_NiColorExtraData, - RC_NiFloatExtraData, - RC_NiFloatsExtraData, - RC_NiStringPalette, - RC_NiBoolData, - RC_NiSkinPartition, - RC_BSXFlags, - RC_BSBound, RC_bhkBlendController, - RC_BSMaterialEmittanceMultController, - RC_BSRefractionFirePeriodController, - RC_BSRefractionStrengthController, - RC_NiFloatInterpolator, - RC_NiPoint3Interpolator, - RC_NiBoolInterpolator, - RC_NiBoolTimelineInterpolator, - RC_NiTransformInterpolator, - RC_NiColorInterpolator, - RC_BSShaderTextureSet, - RC_BSLODTriShape, - RC_BSShaderProperty, - RC_BSShaderPPLightingProperty, - RC_BSShaderNoLightingProperty, - RC_BSFurnitureMarker, - RC_NiCollisionObject, + RC_bhkBoxShape, + RC_bhkCapsuleShape, RC_bhkCollisionObject, - RC_BSDismemberSkinInstance, - RC_NiControllerManager, + RC_bhkCompressedMeshShape, + RC_bhkCompressedMeshShapeData, + RC_bhkConvexTransformShape, + RC_bhkConvexVerticesShape, + RC_bhkHingeConstraint, + RC_bhkLimitedHingeConstraint, + RC_bhkListShape, RC_bhkMoppBvTreeShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, - RC_hkPackedNiTriStripsData, - RC_bhkConvexVerticesShape, - RC_bhkConvexTransformShape, - RC_bhkSimpleShapePhantom, - RC_bhkBoxShape, - RC_bhkCapsuleShape, - RC_bhkSphereShape, - RC_bhkListShape, + RC_bhkRagdollConstraint, RC_bhkRigidBody, RC_bhkRigidBodyT, - RC_bhkRagdollConstraint, - RC_bhkHingeConstraint, - RC_bhkLimitedHingeConstraint, - RC_BSLightingShaderProperty, + RC_bhkSimpleShapePhantom, + RC_bhkSphereShape, + RC_BSBehaviorGraphExtraData, + RC_BSBound, + RC_BSDismemberSkinInstance, RC_BSEffectShaderProperty, - RC_NiClusterAccumulator, + RC_BSEffectShaderPropertyColorController, + RC_BSEffectShaderPropertyFloatController, + RC_BSFurnitureMarker, + RC_BSInvMarker, + RC_BSLightingShaderProperty, + RC_BSLightingShaderPropertyColorController, + RC_BSLightingShaderPropertyFloatController, + RC_BSLODTriShape, + RC_BSMaterialEmittanceMultController, + RC_BSMultiBound, + RC_BSMultiBoundOBB, + RC_BSMultiBoundSphere, + RC_BSRefractionFirePeriodController, + RC_BSRefractionStrengthController, + RC_BSShaderNoLightingProperty, + RC_BSShaderPPLightingProperty, + RC_BSShaderProperty, + RC_BSShaderTextureSet, + RC_BSTriShape, + RC_BSXFlags, + RC_hkPackedNiTriStripsData, RC_NiAlphaAccumulator, - RC_NiSortAdjustNode, - RC_NiMultiTargetTransformController, - RC_NiSequence, - RC_NiControllerSequence, - RC_NiDefaultAVObjectPalette, + RC_NiAlphaController, + RC_NiAlphaProperty, + RC_NiBillboardNode, + RC_NiBinaryExtraData, RC_NiBlendBoolInterpolator, RC_NiBlendFloatInterpolator, RC_NiBlendPoint3Interpolator, RC_NiBlendTransformInterpolator, - RC_bhkCompressedMeshShape, - RC_bhkCompressedMeshShapeData, - RC_BSMultiBound, - RC_BSMultiBoundOBB, - RC_BSMultiBoundSphere, - RC_BSInvMarker, - RC_BSTriShape, - RC_BSEffectShaderPropertyFloatController, - RC_BSEffectShaderPropertyColorController, - RC_BSLightingShaderPropertyFloatController, - RC_BSLightingShaderPropertyColorController, - RC_BSBehaviorGraphExtraData, + RC_NiBoolData, + RC_NiBooleanExtraData, + RC_NiBoolInterpolator, + RC_NiBoolTimelineInterpolator, + RC_NiBSAnimationNode, + RC_NiBSPArrayController, + RC_NiBSParticleNode, + RC_NiCamera, + RC_NiClusterAccumulator, + RC_NiCollisionObject, + RC_NiCollisionSwitch, + RC_NiColorData, + RC_NiColorExtraData, + RC_NiColorInterpolator, + RC_NiControllerManager, + RC_NiControllerSequence, + RC_NiDefaultAVObjectPalette, + RC_NiDitherProperty, + RC_NiExtraData, + RC_NiFlipController, + RC_NiFloatData, + RC_NiFloatExtraData, + RC_NiFloatInterpolator, + RC_NiFloatsExtraData, + RC_NiFltAnimationNode, + RC_NiFogProperty, + RC_NiGeomMorpherController, + RC_NiGravity, + RC_NiIntegerExtraData, + RC_NiIntegersExtraData, + RC_NiKeyframeController, + RC_NiKeyframeData, + RC_NiLight, + RC_NiLines, + RC_NiLinesData, + RC_NiLODNode, + RC_NiLookAtController, + RC_NiMaterialColorController, + RC_NiMaterialProperty, + RC_NiMorphData, + RC_NiMultiTargetTransformController, + RC_NiNode, + RC_NiPalette, + RC_NiParticleColorModifier, + RC_NiParticleGrowFade, + RC_NiParticleRotation, + RC_NiParticles, + RC_NiParticlesData, + RC_NiParticleSystemController, + RC_NiPathController, + RC_NiPixelData, + RC_NiPlanarCollider, + RC_NiPoint3Interpolator, + RC_NiPosData, + RC_NiRollController, + RC_NiSequence, + RC_NiSequenceStreamHelper, + RC_NiShadeProperty, + RC_NiSkinData, + RC_NiSkinInstance, + RC_NiSkinPartition, + RC_NiSortAdjustNode, + RC_NiSourceTexture, + RC_NiSpecularProperty, + RC_NiSphericalCollider, + RC_NiStencilProperty, + RC_NiStringExtraData, + RC_NiStringPalette, + RC_NiSwitchNode, + RC_NiTextKeyExtraData, + RC_NiTextureEffect, + RC_NiTextureTransformController, + RC_NiTexturingProperty, + RC_NiTransformInterpolator, + RC_NiTriShape, + RC_NiTriShapeData, + RC_NiTriStrips, + RC_NiTriStripsData, + RC_NiUVController, + RC_NiUVData, + RC_NiVectorExtraData, + RC_NiVertexColorProperty, + RC_NiVertWeightsExtraData, + RC_NiVisController, + RC_NiVisData, + RC_NiWireframeProperty, + RC_NiZBufferProperty, + RC_RootCollisionNode, }; /// Base class for all records From b2bb19ae4da3e63d560237881218fa88831d8ec1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 30 Aug 2023 21:54:31 +0300 Subject: [PATCH 0026/2167] Support Starfield BA2s --- components/bsa/ba2dx10file.cpp | 7 ++++++- components/bsa/ba2gnrlfile.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index af49c277ad..945e8dd931 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -112,11 +112,16 @@ namespace Bsa if (header[0] == 0x00415342) /*"BSA\x00"*/ fail("Unrecognized compressed BSA format"); mVersion = header[1]; - if (mVersion != 0x01 /*F04*/) + if (mVersion != 0x01 /*FO4*/ && mVersion != 0x02 /*Starfield*/) fail("Unrecognized compressed BSA version"); type = header[2]; fileCount = header[3]; + if (mVersion == 0x02) + { + uint64_t dummy; + input.read(reinterpret_cast(&dummy), 8); + } } if (type == ESM::fourCC("DX10")) diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index bdb0457034..f3961a3bc4 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -107,11 +107,16 @@ namespace Bsa if (header[0] == 0x00415342) /*"BSA\x00"*/ fail("Unrecognized compressed BSA format"); mVersion = header[1]; - if (mVersion != 0x01 /*FO4*/) + if (mVersion != 0x01 /*FO4*/ && mVersion != 0x02 /*Starfield*/) fail("Unrecognized compressed BSA version"); type = header[2]; fileCount = header[3]; + if (mVersion == 0x02) + { + uint64_t dummy; + input.read(reinterpret_cast(&dummy), 8); + } } if (type == ESM::fourCC("GNRL")) From ea8692a5341b9ae932c45dee5b492d1ee0d37d03 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Aug 2023 22:55:43 +0200 Subject: [PATCH 0027/2167] Add usehandlers.lua (same approach as activationhandlers.lua) --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwgui/inventorywindow.cpp | 3 +- apps/openmw/mwlua/engineevents.cpp | 9 ++ apps/openmw/mwlua/engineevents.hpp | 7 +- apps/openmw/mwlua/globalscripts.hpp | 14 ++- apps/openmw/mwlua/luabindings.cpp | 16 +++ apps/openmw/mwlua/luamanagerimp.hpp | 4 + docs/source/luadoc_data_paths.sh | 1 + docs/source/reference/lua-scripting/api.rst | 35 +------ .../source/reference/lua-scripting/events.rst | 13 +++ .../lua-scripting/interface_item_usage.rst | 6 ++ .../reference/lua-scripting/overview.rst | 31 +----- .../lua-scripting/tables/interfaces.rst | 34 +++++++ files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + files/data/scripts/omw/activationhandlers.lua | 2 +- files/data/scripts/omw/usehandlers.lua | 97 +++++++++++++++++++ files/lua_api/openmw/world.lua | 1 + 18 files changed, 209 insertions(+), 67 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_item_usage.rst create mode 100644 docs/source/reference/lua-scripting/tables/interfaces.rst create mode 100644 files/data/scripts/omw/usehandlers.lua diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 12a894d343..e120b1403e 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -51,6 +51,7 @@ namespace MWBase virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0; virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 9ae9ecf2b0..decbede856 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -19,6 +19,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -600,7 +601,7 @@ namespace MWGui } } else - useItem(ptr); + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer()); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 569eb22bcf..ac1ac687ed 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -65,6 +65,15 @@ namespace MWLua scripts->onActivated(LObject(actor)); } + void operator()(const OnUseItem& event) const + { + MWWorld::Ptr obj = getPtr(event.mObject); + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty() || obj.isEmpty()) + return; + mGlobalScripts.onUseItem(GObject(obj), GObject(actor)); + } + void operator()(const OnConsume& event) const { MWWorld::Ptr actor = getPtr(event.mActor); diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index ac854abd4a..3cbc366623 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -36,6 +36,11 @@ namespace MWLua ESM::RefNum mActor; ESM::RefNum mObject; }; + struct OnUseItem + { + ESM::RefNum mActor; + ESM::RefNum mObject; + }; struct OnConsume { ESM::RefNum mActor; @@ -45,7 +50,7 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - using Event = std::variant; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp index 0190000586..314a4118e6 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -19,8 +19,16 @@ namespace MWLua GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global") { - registerEngineHandlers({ &mObjectActiveHandlers, &mActorActiveHandlers, &mItemActiveHandlers, - &mNewGameHandlers, &mPlayerAddedHandlers, &mOnActivateHandlers, &mOnNewExteriorHandlers }); + registerEngineHandlers({ + &mObjectActiveHandlers, + &mActorActiveHandlers, + &mItemActiveHandlers, + &mNewGameHandlers, + &mPlayerAddedHandlers, + &mOnActivateHandlers, + &mOnUseItemHandlers, + &mOnNewExteriorHandlers, + }); } void newGameStarted() { callEngineHandlers(mNewGameHandlers); } @@ -32,6 +40,7 @@ namespace MWLua { callEngineHandlers(mOnActivateHandlers, obj, actor); } + void onUseItem(const GObject& obj, const GObject& actor) { callEngineHandlers(mOnUseItemHandlers, obj, actor); } void onNewExterior(const GCell& cell) { callEngineHandlers(mOnNewExteriorHandlers, cell); } private: @@ -41,6 +50,7 @@ namespace MWLua EngineHandlerList mNewGameHandlers{ "onNewGame" }; EngineHandlerList mPlayerAddedHandlers{ "onPlayerAdded" }; EngineHandlerList mOnActivateHandlers{ "onActivate" }; + EngineHandlerList mOnUseItemHandlers{ "_onUseItem" }; EngineHandlerList mOnNewExteriorHandlers{ "onNewExterior" }; }; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 65b6977982..2e771a7a34 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -18,6 +18,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/datetimemanager.hpp" @@ -315,6 +316,21 @@ namespace MWLua }, "_runStandardActivationAction"); }; + api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor) { + context.mLuaManager->addAction( + [object, actor] { + const MWWorld::Ptr& actorPtr = actor.ptr(); + const MWWorld::Ptr& objectPtr = object.ptr(); + if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->useItem(objectPtr, true); + else + { + std::unique_ptr action = objectPtr.getClass().use(objectPtr, true); + action->execute(actorPtr, true); + } + }, + "_runStandardUseAction"); + }; return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d00fda9dda..03efa3f066 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,6 +77,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object) }); + } void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index c4322dbc0a..7bcda5110c 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -7,5 +7,6 @@ paths=( scripts/omw/mwui/init.lua scripts/omw/settings/player.lua scripts/omw/ui.lua + scripts/omw/usehandlers.lua ) printf '%s\n' "${paths[@]}" \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 76092fec3f..ca6aec3dc3 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -32,6 +32,7 @@ Lua API reference interface_ai interface_camera interface_controls + interface_item_usage interface_mwui interface_settings interface_ui @@ -62,36 +63,6 @@ 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 - - * - Interface - - Can be used - - Description - * - :ref:`Activation ` - - by global scripts - - Allows to extend or override built-in activation mechanics. - * - :ref:`AI ` - - by local scripts - - Control basic AI of NPCs and creatures. - * - :ref:`Camera ` - - by player scripts - - | Allows to alter behavior of the built-in camera script - | without overriding the script completely. - * - :ref:`Controls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player controls. - * - :ref:`Settings ` - - by player and global scripts - - Save, display and track changes of setting values. - * - :ref:`MWUI ` - - by player scripts - - Morrowind-style UI templates. - * - :ref:`UI ` - - by player scripts - - | High-level UI modes interface. Allows to override parts - | of the interface. +.. include:: tables/interfaces.rst diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index da4b2e3812..4f61b0b427 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -4,6 +4,8 @@ Built-in events Actor events ------------ +**StartAIPackage, RemoveAIPackages** + Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``. The effect is equivalent to calling ``interfaces.AI.startPackage`` or ``interfaces.AI.removePackages`` in a local script on this actor. @@ -14,6 +16,17 @@ Examples: actor:sendEvent('StartAIPackage', {type='Combat', target=self.object}) actor:sendEvent('RemoveAIPackages', 'Pursue') +**UseItem** + +Any script can send global event ``UseItem`` with arguments ``object`` and ``actor``. +The actor will use (e.g. equip or consume) the object. The object should be in the actor's inventory. + +Example: + +.. code-block:: Lua + + core.sendGlobalEvent('UseItem', {object = potion, actor = player}) + UI events --------- diff --git a/docs/source/reference/lua-scripting/interface_item_usage.rst b/docs/source/reference/lua-scripting/interface_item_usage.rst new file mode 100644 index 0000000000..cdaa53353f --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_item_usage.rst @@ -0,0 +1,6 @@ +Interface ItemUsage +=================== + +.. raw:: html + :file: generated_html/scripts_omw_usehandlers.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index d0eb0a4cb2..64eb2ac2ef 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -455,36 +455,7 @@ The order in which the scripts are started is important. So if one mod should ov **Interfaces of built-in scripts** -.. list-table:: - :widths: 20 20 60 - - * - Interface - - Can be used - - Description - * - :ref:`Activation ` - - by global scripts - - Allows to extend or override built-in activation mechanics. - * - :ref:`AI ` - - by local scripts - - Control basic AI of NPCs and creatures. - * - :ref:`Camera ` - - by player scripts - - | Allows to alter behavior of the built-in camera script - | without overriding the script completely. - * - :ref:`Controls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player controls. - * - :ref:`Settings ` - - by player and global scripts - - Save, display and track changes of setting values. - * - :ref:`MWUI ` - - by player scripts - - Morrowind-style UI templates. - * - :ref:`UI ` - - by player scripts - - | High-level UI modes interface. Allows to override parts - | of the interface. +.. include:: tables/interfaces.rst Event system ============ diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst new file mode 100644 index 0000000000..e05eb642f0 --- /dev/null +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -0,0 +1,34 @@ +.. list-table:: + :widths: 20 20 60 + + * - Interface + - Can be used + - Description + * - :ref:`Activation ` + - by global scripts + - Allows to extend or override built-in activation mechanics. + * - :ref:`AI ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Controls ` + - by player scripts + - | Allows to alter behavior of the built-in script + | that handles player controls. + * - :ref:`ItemUsage ` + - by global scripts + - | Allows to extend or override built-in item usage + | mechanics. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. + * - :ref:`UI ` + - by player scripts + - | High-level UI modes interface. Allows to override parts + | of the interface. diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index f52be02451..afab9b4c79 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua scripts/omw/ui.lua + scripts/omw/usehandlers.lua shaders/adjustments.omwfx shaders/bloomlinear.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 8999a98f80..b86bedc509 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -8,6 +8,7 @@ PLAYER: scripts/omw/settings/player.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua GLOBAL: scripts/omw/cellhandlers.lua +GLOBAL: scripts/omw/usehandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index ea69777582..7d185cf9da 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -23,7 +23,6 @@ local handlersPerType = {} handlersPerType[types.ESM4Door] = { ESM4DoorActivation } local function onActivate(obj, actor) - types.Actor.activeEffects(actor):remove('invisibility') local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do @@ -40,6 +39,7 @@ local function onActivate(obj, actor) end end end + types.Actor.activeEffects(actor):remove('invisibility') world._runStandardActivationAction(obj, actor) end diff --git a/files/data/scripts/omw/usehandlers.lua b/files/data/scripts/omw/usehandlers.lua new file mode 100644 index 0000000000..7cc7e12f94 --- /dev/null +++ b/files/data/scripts/omw/usehandlers.lua @@ -0,0 +1,97 @@ +local types = require('openmw.types') +local world = require('openmw.world') + +local handlersPerObject = {} +local handlersPerType = {} + +local function useItem(obj, actor) + local handlers = handlersPerObject[obj.id] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](obj, actor) == false then + return -- skip other handlers + end + end + end + handlers = handlersPerType[obj.type] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](obj, actor) == false then + return -- skip other handlers + end + end + end + world._runStandardUseAction(obj, actor) +end + +return { + interfaceName = 'ItemUsage', + --- + -- Allows to extend or override built-in item usage mechanics. + -- Note: at the moment it can override item usage in inventory + -- (dragging an item on the character's model), but + -- + -- * can't intercept actions performed by mwscripts; + -- * can't intercept actions performed by the AI (i.e. drinking a potion in combat); + -- * can't intercept actions performed via quick keys menu. + -- @module ItemUsage + -- @usage local I = require('openmw.interfaces') + -- + -- -- Override Use action (global script). + -- -- Forbid equipping armor with weight > 5 + -- I.ItemUsage.addHandlerForType(types.Armor, function(armor, actor) + -- if types.Armor.record(armor).weight > 5 then + -- return false -- disable other handlers + -- end + -- end) + -- + -- -- Call Use action (any script). + -- core.sendGlobalEvent('UseItem', {object = armor, actor = player}) + interface = { + --- Interface version + -- @field [parent=#ItemUsage] #number version + version = 0, + + --- Add new use action handler for a specific object. + -- If `handler(object, actor)` returns false, other handlers for + -- the same object (including type handlers) will be skipped. + -- @function [parent=#ItemUsage] addHandlerForObject + -- @param openmw.core#GameObject obj The object. + -- @param #function handler The handler. + addHandlerForObject = function(obj, handler) + local handlers = handlersPerObject[obj.id] + if handlers == nil then + handlers = {} + handlersPerObject[obj.id] = handlers + end + handlers[#handlers + 1] = handler + end, + + --- Add new use action handler for a type of objects. + -- If `handler(object, actor)` returns false, other handlers for + -- the same object (including type handlers) will be skipped. + -- @function [parent=#ItemUsage] addHandlerForType + -- @param #any type A type from the `openmw.types` package. + -- @param #function handler The handler. + addHandlerForType = function(type, handler) + local handlers = handlersPerType[type] + if handlers == nil then + handlers = {} + handlersPerType[type] = handlers + end + handlers[#handlers + 1] = handler + end, + }, + engineHandlers = { _onUseItem = useItem }, + eventHandlers = { + UseItem = function(data) + if not data.object then + error('UseItem: missing argument "object"') + end + if not data.actor or not types.Actor.objectIsInstance(data.actor) then + error('UseItem: invalid argument "actor"') + end + useItem(data.object, data.actor) + end + } +} diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 17e4cc50f5..c55860ee26 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -149,6 +149,7 @@ -- Creates a custom record in the world database. -- Eventually meant to support all records, but the current -- set of supported types is limited to: +-- -- * @{openmw.types#PotionRecord}, -- * @{openmw.types#ArmorRecord}, -- * @{openmw.types#BookRecord}, From 1d05aa2e37e82e3704cfe0da4525f57db7201a2e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 18:03:41 +0300 Subject: [PATCH 0028/2167] Modernize particle modifiers --- components/nif/controlled.cpp | 70 +++++++++++++++++---------------- components/nif/controlled.hpp | 30 +++++++++----- components/nifosg/nifloader.cpp | 10 ++--- components/nifosg/particle.cpp | 17 +++----- components/nifosg/particle.hpp | 18 +++------ 5 files changed, 72 insertions(+), 73 deletions(-) diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index bacba07d5f..b2e8087ad1 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -55,56 +55,59 @@ namespace Nif void NiParticleModifier::read(NIFStream* nif) { - next.read(nif); - controller.read(nif); + mNext.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mController.read(nif); } void NiParticleModifier::post(Reader& nif) { - next.post(nif); - controller.post(nif); + mNext.post(nif); + mController.post(nif); } void NiParticleGrowFade::read(NIFStream* nif) { NiParticleModifier::read(nif); - growTime = nif->getFloat(); - fadeTime = nif->getFloat(); + nif->read(mGrowTime); + nif->read(mFadeTime); } void NiParticleColorModifier::read(NIFStream* nif) { NiParticleModifier::read(nif); - data.read(nif); + + mData.read(nif); } void NiParticleColorModifier::post(Reader& nif) { NiParticleModifier::post(nif); - data.post(nif); + + mData.post(nif); } void NiGravity::read(NIFStream* nif) { NiParticleModifier::read(nif); - mDecay = nif->getFloat(); - mForce = nif->getFloat(); - mType = nif->getUInt(); - mPosition = nif->getVector3(); - mDirection = nif->getVector3(); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mDecay); + nif->read(mForce); + mType = static_cast(nif->get()); + nif->read(mPosition); + nif->read(mDirection); } void NiParticleCollider::read(NIFStream* nif) { NiParticleModifier::read(nif); - mBounceFactor = nif->getFloat(); + nif->read(mBounceFactor); if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2)) { - // Unused in NifSkope. Need to figure out what these do. - /*bool mSpawnOnCollision = */ nif->getBoolean(); - /*bool mDieOnCollision = */ nif->getBoolean(); + nif->read(mSpawnOnCollision); + nif->read(mDieOnCollision); } } @@ -112,29 +115,28 @@ namespace Nif { NiParticleCollider::read(nif); - mExtents = nif->getVector2(); - mPosition = nif->getVector3(); - mXVector = nif->getVector3(); - mYVector = nif->getVector3(); - mPlaneNormal = nif->getVector3(); - mPlaneDistance = nif->getFloat(); - } - - void NiParticleRotation::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - /* bool mRandomInitialAxis = */ nif->getChar(); - /* osg::Vec3f mInitialAxis = */ nif->getVector3(); - /* float mRotationSpeed = */ nif->getFloat(); + nif->read(mExtents); + nif->read(mPosition); + nif->read(mXVector); + nif->read(mYVector); + nif->read(mPlaneNormal); + nif->read(mPlaneDistance); } void NiSphericalCollider::read(NIFStream* nif) { NiParticleCollider::read(nif); - mRadius = nif->getFloat(); - mCenter = nif->getVector3(); + nif->read(mRadius); + nif->read(mCenter); } + void NiParticleRotation::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mRandomInitialAxis); + nif->read(mInitialAxis); + nif->read(mRotationSpeed); + } } diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 09bab3cbfd..8ed7a54e07 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -85,8 +85,8 @@ namespace Nif struct NiParticleModifier : public Record { - NiParticleModifierPtr next; - ControllerPtr controller; + NiParticleModifierPtr mNext; + ControllerPtr mController; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -94,15 +94,15 @@ namespace Nif struct NiParticleGrowFade : public NiParticleModifier { - float growTime; - float fadeTime; + float mGrowTime; + float mFadeTime; void read(NIFStream* nif) override; }; struct NiParticleColorModifier : public NiParticleModifier { - NiColorDataPtr data; + NiColorDataPtr mData; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -110,12 +110,15 @@ namespace Nif struct NiGravity : public NiParticleModifier { + enum class ForceType : uint32_t + { + Wind = 0, // Fixed direction + Point = 1, // Fixed origin + }; + + float mDecay{ 0.f }; float mForce; - /* 0 - Wind (fixed direction) - * 1 - Point (fixed origin) - */ - int mType; - float mDecay; + ForceType mType; osg::Vec3f mPosition; osg::Vec3f mDirection; @@ -125,6 +128,9 @@ namespace Nif struct NiParticleCollider : public NiParticleModifier { float mBounceFactor; + bool mSpawnOnCollision{ false }; + bool mDieOnCollision{ false }; + void read(NIFStream* nif) override; }; @@ -150,6 +156,10 @@ namespace Nif struct NiParticleRotation : public NiParticleModifier { + uint8_t mRandomInitialAxis; + osg::Vec3f mInitialAxis; + float mRotationSpeed; + void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2e2d0293e6..b9fd2cd986 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1067,12 +1067,12 @@ namespace NifOsg attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); - for (; !affectors.empty(); affectors = affectors->next) + for (; !affectors.empty(); affectors = affectors->mNext) { if (affectors->recType == Nif::RC_NiParticleGrowFade) { const Nif::NiParticleGrowFade* gf = static_cast(affectors.getPtr()); - program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime)); + program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime)); } else if (affectors->recType == Nif::RC_NiGravity) { @@ -1083,9 +1083,9 @@ namespace NifOsg { const Nif::NiParticleColorModifier* cl = static_cast(affectors.getPtr()); - if (cl->data.empty()) + if (cl->mData.empty()) continue; - const Nif::NiColorData* clrdata = cl->data.getPtr(); + const Nif::NiColorData* clrdata = cl->mData.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } else if (affectors->recType == Nif::RC_NiParticleRotation) @@ -1095,7 +1095,7 @@ namespace NifOsg else Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; } - for (; !colliders.empty(); colliders = colliders->next) + for (; !colliders.empty(); colliders = colliders->mNext) { if (colliders->recType == Nif::RC_NiPlanarCollider) { diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 2700b7eb93..40f305a6a4 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -281,20 +281,13 @@ namespace NifOsg GravityAffector::GravityAffector(const Nif::NiGravity* gravity) : mForce(gravity->mForce) - , mType(static_cast(gravity->mType)) + , mType(gravity->mType) , mPosition(gravity->mPosition) , mDirection(gravity->mDirection) , mDecay(gravity->mDecay) { } - GravityAffector::GravityAffector() - : mForce(0) - , mType(Type_Wind) - , mDecay(0.f) - { - } - GravityAffector::GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop) : osgParticle::Operator(copy, copyop) { @@ -311,8 +304,8 @@ namespace NifOsg { bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); - if (mType == Type_Point - || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied + // We don't need the position for Wind gravity, except if decay is being applied + if (mType == Nif::NiGravity::ForceType::Point || mDecay != 0.f) mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; @@ -324,7 +317,7 @@ namespace NifOsg const float magic = 1.6f; switch (mType) { - case Type_Wind: + case Nif::NiGravity::ForceType::Wind: { float decayFactor = 1.f; if (mDecay != 0.f) @@ -338,7 +331,7 @@ namespace NifOsg break; } - case Type_Point: + case Nif::NiGravity::ForceType::Point: { osg::Vec3f diff = mCachedWorldPosition - particle->getPosition(); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 3424512208..fd7fa64c58 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -10,15 +10,14 @@ #include #include +#include // NiGravity::ForceType + #include #include "controller.hpp" // ValueInterpolator namespace Nif { - struct NiGravity; - struct NiPlanarCollider; - struct NiSphericalCollider; struct NiColorData; } @@ -180,7 +179,7 @@ namespace NifOsg { public: GravityAffector(const Nif::NiGravity* gravity); - GravityAffector(); + GravityAffector() = default; GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); GravityAffector& operator=(const GravityAffector&) = delete; @@ -191,16 +190,11 @@ namespace NifOsg void beginOperate(osgParticle::Program*) override; private: - float mForce; - enum ForceType - { - Type_Wind, - Type_Point - }; - ForceType mType; + float mForce{ 0.f }; + Nif::NiGravity::ForceType mType{ Nif::NiGravity::ForceType::Wind }; osg::Vec3f mPosition; osg::Vec3f mDirection; - float mDecay; + float mDecay{ 0.f }; osg::Vec3f mCachedWorldPosition; osg::Vec3f mCachedWorldDirection; }; From 4a88726abaa23d2d98348097c800fe01b3b40876 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 18:24:36 +0300 Subject: [PATCH 0029/2167] Move particle modifiers into a dedicated file --- components/CMakeLists.txt | 2 +- components/nif/controlled.cpp | 86 --------------------------- components/nif/controlled.hpp | 105 +-------------------------------- components/nif/controller.cpp | 2 +- components/nif/niffile.cpp | 1 + components/nif/particle.cpp | 95 +++++++++++++++++++++++++++++ components/nif/particle.hpp | 90 ++++++++++++++++++++++++++++ components/nifosg/particle.hpp | 2 +- 8 files changed, 190 insertions(+), 193 deletions(-) create mode 100644 components/nif/particle.cpp create mode 100644 components/nif/particle.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9a073b5ca1..c349d9a248 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -103,7 +103,7 @@ add_component_dir (sceneutil ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics + base controlled controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr ) add_component_dir (nifosg diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index b2e8087ad1..fd38a4909d 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -53,90 +53,4 @@ namespace Nif nif->getSizedStrings(textures, nif->getUInt()); } - void NiParticleModifier::read(NIFStream* nif) - { - mNext.read(nif); - if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) - mController.read(nif); - } - - void NiParticleModifier::post(Reader& nif) - { - mNext.post(nif); - mController.post(nif); - } - - void NiParticleGrowFade::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - nif->read(mGrowTime); - nif->read(mFadeTime); - } - - void NiParticleColorModifier::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - mData.read(nif); - } - - void NiParticleColorModifier::post(Reader& nif) - { - NiParticleModifier::post(nif); - - mData.post(nif); - } - - void NiGravity::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) - nif->read(mDecay); - nif->read(mForce); - mType = static_cast(nif->get()); - nif->read(mPosition); - nif->read(mDirection); - } - - void NiParticleCollider::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - nif->read(mBounceFactor); - if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2)) - { - nif->read(mSpawnOnCollision); - nif->read(mDieOnCollision); - } - } - - void NiPlanarCollider::read(NIFStream* nif) - { - NiParticleCollider::read(nif); - - nif->read(mExtents); - nif->read(mPosition); - nif->read(mXVector); - nif->read(mYVector); - nif->read(mPlaneNormal); - nif->read(mPlaneDistance); - } - - void NiSphericalCollider::read(NIFStream* nif) - { - NiParticleCollider::read(nif); - - nif->read(mRadius); - nif->read(mCenter); - } - - void NiParticleRotation::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - nif->read(mRandomInitialAxis); - nif->read(mInitialAxis); - nif->read(mRotationSpeed); - } } diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index 8ed7a54e07..1a5cc6db42 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -1,26 +1,3 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (controlled.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP #define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP @@ -83,85 +60,5 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticleModifier : public Record - { - NiParticleModifierPtr mNext; - ControllerPtr mController; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiParticleGrowFade : public NiParticleModifier - { - float mGrowTime; - float mFadeTime; - - void read(NIFStream* nif) override; - }; - - struct NiParticleColorModifier : public NiParticleModifier - { - NiColorDataPtr mData; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiGravity : public NiParticleModifier - { - enum class ForceType : uint32_t - { - Wind = 0, // Fixed direction - Point = 1, // Fixed origin - }; - - float mDecay{ 0.f }; - float mForce; - ForceType mType; - osg::Vec3f mPosition; - osg::Vec3f mDirection; - - void read(NIFStream* nif) override; - }; - - struct NiParticleCollider : public NiParticleModifier - { - float mBounceFactor; - bool mSpawnOnCollision{ false }; - bool mDieOnCollision{ false }; - - void read(NIFStream* nif) override; - }; - - // NiPinaColada - struct NiPlanarCollider : public NiParticleCollider - { - osg::Vec2f mExtents; - osg::Vec3f mPosition; - osg::Vec3f mXVector, mYVector; - osg::Vec3f mPlaneNormal; - float mPlaneDistance; - - void read(NIFStream* nif) override; - }; - - struct NiSphericalCollider : public NiParticleCollider - { - float mRadius; - osg::Vec3f mCenter; - - void read(NIFStream* nif) override; - }; - - struct NiParticleRotation : public NiParticleModifier - { - uint8_t mRandomInitialAxis; - osg::Vec3f mInitialAxis; - float mRotationSpeed; - - void read(NIFStream* nif) override; - }; - -} // Namespace +} #endif diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 9e93b31162..ba6fa025c7 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -3,7 +3,7 @@ #include "controlled.hpp" #include "data.hpp" #include "node.hpp" -#include "recordptr.hpp" +#include "particle.hpp" namespace Nif { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f806c61bc1..59e73196fb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,6 +17,7 @@ #include "exception.hpp" #include "extra.hpp" #include "node.hpp" +#include "particle.hpp" #include "physics.hpp" #include "property.hpp" diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp new file mode 100644 index 0000000000..ae391c59e4 --- /dev/null +++ b/components/nif/particle.cpp @@ -0,0 +1,95 @@ +#include "particle.hpp" + +#include "data.hpp" + +namespace Nif +{ + + void NiParticleModifier::read(NIFStream* nif) + { + mNext.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mController.read(nif); + } + + void NiParticleModifier::post(Reader& nif) + { + mNext.post(nif); + mController.post(nif); + } + + void NiParticleGrowFade::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + nif->read(mGrowTime); + nif->read(mFadeTime); + } + + void NiParticleColorModifier::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + mData.read(nif); + } + + void NiParticleColorModifier::post(Reader& nif) + { + NiParticleModifier::post(nif); + + mData.post(nif); + } + + void NiGravity::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mDecay); + nif->read(mForce); + mType = static_cast(nif->get()); + nif->read(mPosition); + nif->read(mDirection); + } + + void NiParticleCollider::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mBounceFactor); + if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2)) + { + nif->read(mSpawnOnCollision); + nif->read(mDieOnCollision); + } + } + + void NiPlanarCollider::read(NIFStream* nif) + { + NiParticleCollider::read(nif); + + nif->read(mExtents); + nif->read(mPosition); + nif->read(mXVector); + nif->read(mYVector); + nif->read(mPlaneNormal); + nif->read(mPlaneDistance); + } + + void NiSphericalCollider::read(NIFStream* nif) + { + NiParticleCollider::read(nif); + + nif->read(mRadius); + nif->read(mCenter); + } + + void NiParticleRotation::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mRandomInitialAxis); + nif->read(mInitialAxis); + nif->read(mRotationSpeed); + } + +} diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp new file mode 100644 index 0000000000..5590877294 --- /dev/null +++ b/components/nif/particle.hpp @@ -0,0 +1,90 @@ +#ifndef OPENMW_COMPONENTS_NIF_PARTICLE_HPP +#define OPENMW_COMPONENTS_NIF_PARTICLE_HPP + +#include "base.hpp" + +namespace Nif +{ + + struct NiParticleModifier : public Record + { + NiParticleModifierPtr mNext; + ControllerPtr mController; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiParticleGrowFade : public NiParticleModifier + { + float mGrowTime; + float mFadeTime; + + void read(NIFStream* nif) override; + }; + + struct NiParticleColorModifier : public NiParticleModifier + { + NiColorDataPtr mData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiGravity : public NiParticleModifier + { + enum class ForceType : uint32_t + { + Wind = 0, // Fixed direction + Point = 1, // Fixed origin + }; + + float mDecay{ 0.f }; + float mForce; + ForceType mType; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + + void read(NIFStream* nif) override; + }; + + struct NiParticleCollider : public NiParticleModifier + { + float mBounceFactor; + bool mSpawnOnCollision{ false }; + bool mDieOnCollision{ false }; + + void read(NIFStream* nif) override; + }; + + // NiPinaColada + struct NiPlanarCollider : public NiParticleCollider + { + osg::Vec2f mExtents; + osg::Vec3f mPosition; + osg::Vec3f mXVector, mYVector; + osg::Vec3f mPlaneNormal; + float mPlaneDistance; + + void read(NIFStream* nif) override; + }; + + struct NiSphericalCollider : public NiParticleCollider + { + float mRadius; + osg::Vec3f mCenter; + + void read(NIFStream* nif) override; + }; + + struct NiParticleRotation : public NiParticleModifier + { + uint8_t mRandomInitialAxis; + osg::Vec3f mInitialAxis; + float mRotationSpeed; + + void read(NIFStream* nif) override; + }; + +} +#endif diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index fd7fa64c58..a9b628f695 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -10,7 +10,7 @@ #include #include -#include // NiGravity::ForceType +#include // NiGravity::ForceType #include From fb8ead2bd4c53f0422021e9f06c88d6c4ae720f6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 18:59:33 +0300 Subject: [PATCH 0030/2167] Rename nif/controlled to nif/texture --- components/CMakeLists.txt | 2 +- components/nif/controller.cpp | 2 +- components/nif/effect.cpp | 2 +- components/nif/niffile.cpp | 2 +- components/nif/property.cpp | 2 +- components/nif/{controlled.cpp => texture.cpp} | 2 +- components/nif/{controlled.hpp => texture.hpp} | 10 +++++++--- components/nifosg/nifloader.cpp | 3 ++- components/nifosg/particle.cpp | 1 - 9 files changed, 15 insertions(+), 11 deletions(-) rename components/nif/{controlled.cpp => texture.cpp} (98%) rename components/nif/{controlled.hpp => texture.hpp} (89%) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c349d9a248..ffb286772b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -103,7 +103,7 @@ add_component_dir (sceneutil ) add_component_dir (nif - base controlled controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr + base controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr texture ) add_component_dir (nifosg diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index ba6fa025c7..1355ce21d3 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -1,9 +1,9 @@ #include "controller.hpp" -#include "controlled.hpp" #include "data.hpp" #include "node.hpp" #include "particle.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 3b90d50564..e5dd2282b6 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -1,7 +1,7 @@ #include "effect.hpp" -#include "controlled.hpp" #include "node.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 59e73196fb..e147ee3528 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -10,7 +10,6 @@ #include #include -#include "controlled.hpp" #include "controller.hpp" #include "data.hpp" #include "effect.hpp" @@ -20,6 +19,7 @@ #include "particle.hpp" #include "physics.hpp" #include "property.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 3c1c424de9..39a7cbff23 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -1,7 +1,7 @@ #include "property.hpp" -#include "controlled.hpp" #include "data.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/controlled.cpp b/components/nif/texture.cpp similarity index 98% rename from components/nif/controlled.cpp rename to components/nif/texture.cpp index fd38a4909d..0e86c04079 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/texture.cpp @@ -1,4 +1,4 @@ -#include "controlled.hpp" +#include "texture.hpp" #include "data.hpp" diff --git a/components/nif/controlled.hpp b/components/nif/texture.hpp similarity index 89% rename from components/nif/controlled.hpp rename to components/nif/texture.hpp index 1a5cc6db42..b74b44bec0 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/texture.hpp @@ -1,12 +1,16 @@ -#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP -#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP +#ifndef OPENMW_COMPONENTS_NIF_TEXTURE_HPP +#define OPENMW_COMPONENTS_NIF_TEXTURE_HPP #include "base.hpp" namespace Nif { - struct NiSourceTexture : public Named + struct NiTexture : public Named + { + }; + + struct NiSourceTexture : public NiTexture { // Is this an external (references a separate texture file) or // internal (data is inside the nif itself) texture? diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index b9fd2cd986..3a8e3017c9 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -42,13 +42,14 @@ #include #include -#include #include #include #include #include #include +#include #include +#include #include #include #include diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 40f305a6a4..8be0d4ba4f 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include From 2e847a12c4b630b2c4d324a963f98285ddc4b261 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 20:33:43 +0300 Subject: [PATCH 0031/2167] Modernize NiSourceTexture and BSShaderTextureSet --- components/nif/texture.cpp | 55 +++++++++------------- components/nif/texture.hpp | 83 +++++++++++++++++++-------------- components/nifosg/nifloader.cpp | 36 +++++++------- 3 files changed, 90 insertions(+), 84 deletions(-) diff --git a/components/nif/texture.cpp b/components/nif/texture.cpp index 0e86c04079..116ded6f7e 100644 --- a/components/nif/texture.cpp +++ b/components/nif/texture.cpp @@ -7,50 +7,41 @@ namespace Nif void NiSourceTexture::read(NIFStream* nif) { - Named::read(nif); + NiTexture::read(nif); - external = nif->getChar() != 0; - bool internal = false; - if (external) - filename = nif->getString(); - else - { - if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3)) - internal = nif->getChar(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - filename = nif->getString(); // Original file path of the internal texture - } - if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3)) - { - if (!external && internal) - data.read(nif); - } - else - { - data.read(nif); - } + nif->read(mExternal); + if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mFile); - pixel = nif->getUInt(); - mipmap = nif->getUInt(); - alpha = nif->getUInt(); + bool hasData = nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 4); + if (!hasData && !mExternal) + nif->read(hasData); - // Renderer hints, typically of no use for us - /* bool mIsStatic = */ nif->getChar(); + if (hasData) + mData.read(nif); + + mPrefs.mPixelLayout = static_cast(nif->get()); + mPrefs.mUseMipMaps = static_cast(nif->get()); + mPrefs.mAlphaFormat = static_cast(nif->get()); + + nif->read(mIsStatic); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103)) - /* bool mDirectRendering = */ nif->getBoolean(); - if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4)) - /* bool mPersistRenderData = */ nif->getBoolean(); + { + nif->read(mDirectRendering); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4)) + nif->read(mPersistRenderData); + } } void NiSourceTexture::post(Reader& nif) { - Named::post(nif); - data.post(nif); + NiTexture::post(nif); + mData.post(nif); } void BSShaderTextureSet::read(NIFStream* nif) { - nif->getSizedStrings(textures, nif->getUInt()); + nif->getSizedStrings(mTextures, nif->get()); } } diff --git a/components/nif/texture.hpp b/components/nif/texture.hpp index b74b44bec0..a326b47f14 100644 --- a/components/nif/texture.hpp +++ b/components/nif/texture.hpp @@ -12,35 +12,48 @@ namespace Nif struct NiSourceTexture : public NiTexture { - // Is this an external (references a separate texture file) or - // internal (data is inside the nif itself) texture? - bool external; + enum class PixelLayout : uint32_t + { + Palette = 0, + HighColor = 1, + TrueColor = 2, + Compressed = 3, + BumpMap = 4, + Default = 5, + }; - std::string filename; // In case of external textures - NiPixelDataPtr data; // In case of internal textures + enum class MipMapFormat : uint32_t + { + No = 0, + Yes = 1, + Default = 2, + }; - /* Pixel layout - 0 - Palettised - 1 - High color 16 - 2 - True color 32 - 3 - Compressed - 4 - Bumpmap - 5 - Default */ - unsigned int pixel; + enum class AlphaFormat : uint32_t + { + None = 0, + Binary = 1, + Smooth = 2, + Default = 3, + }; - /* Mipmap format - 0 - no - 1 - yes - 2 - default */ - unsigned int mipmap; + struct FormatPrefs + { + PixelLayout mPixelLayout; + MipMapFormat mUseMipMaps; + AlphaFormat mAlphaFormat; + }; - /* Alpha - 0 - none - 1 - binary - 2 - smooth - 3 - default (use material alpha, or multiply material with texture if present) - */ - unsigned int alpha; + char mExternal; // References external file + + std::string mFile; + NiPixelDataPtr mData; + + FormatPrefs mPrefs; + + char mIsStatic{ 1 }; + bool mDirectRendering{ true }; + bool mPersistRenderData{ false }; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -48,18 +61,18 @@ namespace Nif struct BSShaderTextureSet : public Record { - enum TextureType + enum class TextureType : uint32_t { - TextureType_Base = 0, - TextureType_Normal = 1, - TextureType_Glow = 2, - TextureType_Parallax = 3, - TextureType_Env = 4, - TextureType_EnvMask = 5, - TextureType_Subsurface = 6, - TextureType_BackLighting = 7 + Base = 0, + Normal = 1, + Glow = 2, + Parallax = 3, + Environment = 4, + EnvironmentMask = 5, + Subsurface = 6, + BackLighting = 7, }; - std::vector textures; + std::vector mTextures; void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3a8e3017c9..c8c89a0660 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -510,15 +510,15 @@ namespace NifOsg return nullptr; osg::ref_ptr image; - if (!st->external && !st->data.empty()) + if (st->mExternal) { - image = handleInternalTexture(st->data.getPtr()); - } - else - { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); image = imageManager->getImage(filename); } + else if (!st->mData.empty()) + { + image = handleInternalTexture(st->mData.getPtr()); + } return image; } @@ -2009,15 +2009,15 @@ namespace NifOsg const unsigned int uvSet = 0; - for (size_t i = 0; i < textureSet->textures.size(); ++i) + for (size_t i = 0; i < textureSet->mTextures.size(); ++i) { - if (textureSet->textures[i].empty()) + if (textureSet->mTextures[i].empty()) continue; - switch (i) + switch (static_cast(i)) { - case Nif::BSShaderTextureSet::TextureType_Base: - case Nif::BSShaderTextureSet::TextureType_Normal: - case Nif::BSShaderTextureSet::TextureType_Glow: + case Nif::BSShaderTextureSet::TextureType::Base: + case Nif::BSShaderTextureSet::TextureType::Normal: + case Nif::BSShaderTextureSet::TextureType::Glow: break; default: { @@ -2027,7 +2027,7 @@ namespace NifOsg } } std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS()); + = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) @@ -2036,17 +2036,19 @@ namespace NifOsg unsigned int texUnit = boundTextures.size(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); // BSShaderTextureSet presence means there's no need for FFP support for the affected node - switch (i) + switch (static_cast(i)) { - case Nif::BSShaderTextureSet::TextureType_Base: + case Nif::BSShaderTextureSet::TextureType::Base: texture2d->setName("diffuseMap"); break; - case Nif::BSShaderTextureSet::TextureType_Normal: + case Nif::BSShaderTextureSet::TextureType::Normal: texture2d->setName("normalMap"); break; - case Nif::BSShaderTextureSet::TextureType_Glow: + case Nif::BSShaderTextureSet::TextureType::Glow: texture2d->setName("emissiveMap"); break; + default: + break; } boundTextures.emplace_back(uvSet); } From f260fa006e38fb337e895cc61976f7dcb7147d20 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 27 Aug 2023 20:43:12 +0200 Subject: [PATCH 0032/2167] Show OpenMW version and Lua API revision in documentation. --- .gitignore | 1 + CHANGELOG.md | 1 + CMakeLists.txt | 2 +- apps/bulletobjecttool/main.cpp | 3 +- apps/launcher/maindialog.cpp | 11 +++--- apps/navmeshtool/main.cpp | 3 +- apps/opencs/CMakeLists.txt | 4 -- apps/opencs/view/doc/view.cpp | 2 +- apps/openmw/CMakeLists.txt | 4 -- apps/openmw/engine.cpp | 2 +- apps/openmw/main.cpp | 17 +-------- apps/openmw/mwlua/luabindings.cpp | 3 +- cmake/GitVersion.cmake | 2 +- cmake/OpenMWMacros.cmake | 7 ++++ components/CMakeLists.txt | 20 +++++----- components/version/version.cpp | 38 ------------------- components/version/version.cpp.in | 36 ++++++++++++++++++ components/version/version.hpp | 23 ++++------- docs/source/conf.py | 8 ++++ .../reference/lua-scripting/aipackages.rst | 2 + docs/source/reference/lua-scripting/api.rst | 2 + .../lua-scripting/engine_handlers.rst | 2 + .../source/reference/lua-scripting/events.rst | 2 + docs/source/reference/lua-scripting/index.rst | 6 ++- .../lua-scripting/interface_activation.rst | 2 + .../reference/lua-scripting/interface_ai.rst | 2 + .../lua-scripting/interface_camera.rst | 2 + .../lua-scripting/interface_controls.rst | 2 + .../lua-scripting/interface_mwui.rst | 2 + .../lua-scripting/interface_settings.rst | 2 + .../reference/lua-scripting/interface_ui.rst | 2 + .../reference/lua-scripting/iterables.rst | 2 + .../lua-scripting/openmw_ambient.rst | 2 + .../reference/lua-scripting/openmw_async.rst | 2 + .../lua-scripting/openmw_aux_calendar.rst | 2 + .../lua-scripting/openmw_aux_time.rst | 2 + .../reference/lua-scripting/openmw_aux_ui.rst | 2 + .../lua-scripting/openmw_aux_util.rst | 2 + .../reference/lua-scripting/openmw_camera.rst | 2 + .../reference/lua-scripting/openmw_core.rst | 2 + .../reference/lua-scripting/openmw_debug.rst | 2 + .../reference/lua-scripting/openmw_input.rst | 2 + .../reference/lua-scripting/openmw_nearby.rst | 2 + .../lua-scripting/openmw_postprocessing.rst | 2 + .../reference/lua-scripting/openmw_self.rst | 2 + .../lua-scripting/openmw_storage.rst | 2 + .../reference/lua-scripting/openmw_types.rst | 2 + .../reference/lua-scripting/openmw_ui.rst | 2 + .../reference/lua-scripting/openmw_util.rst | 2 + .../reference/lua-scripting/openmw_world.rst | 2 + .../reference/lua-scripting/overview.rst | 2 + .../lua-scripting/setting_renderers.rst | 2 + .../lua-scripting/user_interface.rst | 2 + .../reference/lua-scripting/version.rst | 2 + files/lua_api/openmw/core.lua | 2 +- files/version.in | 3 -- 56 files changed, 157 insertions(+), 109 deletions(-) delete mode 100644 components/version/version.cpp create mode 100644 components/version/version.cpp.in create mode 100644 docs/source/reference/lua-scripting/version.rst delete mode 100644 files/version.in diff --git a/.gitignore b/.gitignore index f25adf58e6..dfb56dc359 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ apps/wizard/ui_intropage.h apps/wizard/ui_languageselectionpage.h apps/wizard/ui_methodselectionpage.h components/ui_contentselector.h +components/version/version.cpp docs/mainpage.hpp docs/Doxyfile docs/DoxyfilePages diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee31b6762..21c1c55275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Bug #7553: Faction reaction loading is incorrect Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics + Feature #6149: Dehardcode Lua API_REVISION Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds diff --git a/CMakeLists.txt b/CMakeLists.txt index d9865e6569..f887cb181e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) +set(OPENMW_LUA_API_REVISION 45) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") @@ -1041,7 +1042,6 @@ elseif(NOT APPLE) # Install global configuration files INSTALL(FILES "${INSTALL_SOURCE}/defaults.bin" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") - INSTALL(FILES "${INSTALL_SOURCE}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") INSTALL(FILES "${INSTALL_SOURCE}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 2610061af6..d52d278f9e 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -146,8 +146,7 @@ namespace config.filterOutNonExistingPaths(dataDirs); const auto resDir = variables["resources"].as(); - const auto v = Version::getOpenmwVersion(resDir); - Log(Debug::Info) << v.describe(); + Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const auto fileCollections = Files::Collections(dataDirs); const auto archives = variables["fallback-archive"].as(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 0caf7a576a..023d6b729a 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -174,14 +174,13 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() void Launcher::MainDialog::setVersionLabel() { // Add version information to bottom of the window - Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData()); - - QString revision(QString::fromUtf8(v.mCommitHash.c_str())); - QString tag(QString::fromUtf8(v.mTagHash.c_str())); + QString revision(QString::fromUtf8(Version::getCommitHash().data(), Version::getCommitHash().size())); + QString tag(QString::fromUtf8(Version::getTagHash().data(), Version::getTagHash().size())); versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag)) - versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str()))); + if (!Version::getVersion().empty() && (revision.isEmpty() || revision == tag)) + versionLabel->setText( + tr("OpenMW %1 release").arg(QString::fromUtf8(Version::getVersion().data(), Version::getVersion().size()))); else versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10))); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index e5126d9209..86ff8c3ddb 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -165,8 +165,7 @@ namespace NavMeshTool config.filterOutNonExistingPaths(dataDirs); const auto resDir = variables["resources"].as(); - Version::Version v = Version::getOpenmwVersion(resDir); - Log(Debug::Info) << v.describe(); + Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const auto fileCollections = Files::Collections(dataDirs); const auto archives = variables["fallback-archive"].as(); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 20bd62d145..8586f490e8 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -225,10 +225,6 @@ if(APPLE AND BUILD_OPENCS) MACOSX_PACKAGE_LOCATION Resources/resources) set_source_files_properties(${OPENCS_OPENMW_CFG} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - - add_custom_command(TARGET openmw-cs - POST_BUILD - COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${OPENCS_BUNDLE_RESOURCES_DIR}/resources") endif() target_link_libraries(openmw-cs-lib diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 08db4efa5e..4f4e687d8f 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -774,7 +774,7 @@ void CSVDoc::View::tutorial() void CSVDoc::View::infoAbout() { // Get current OpenMW version - QString versionInfo = (Version::getOpenmwVersionDescription(mDocument->getResourceDir()) + + QString versionInfo = (Version::getOpenmwVersionDescription() + #if defined(__x86_64__) || defined(_M_X64) " (64-bit)") .c_str(); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3df00f1be0..ec9636fde1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -210,10 +210,6 @@ if(APPLE) configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) - add_custom_command(TARGET openmw - POST_BUILD - COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources") - find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0c77329f48..1d3232290d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -723,7 +723,7 @@ void OMW::Engine::prepareEngine() mWindowManager = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), mCfgMgr.getLogPath(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, - Version::getOpenmwVersionDescription(mResDir), shadersSupported, mCfgMgr); + Version::getOpenmwVersionDescription(), shadersSupported, mCfgMgr); mEnvironment.setWindowManager(*mWindowManager); mInputManager = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index e326e0aa2b..a78bd22d42 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -58,27 +58,14 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati if (variables.count("version")) { - cfgMgr.readConfiguration(variables, desc, true); - - Version::Version v - = Version::getOpenmwVersion(variables["resources"] - .as() - .u8string()); // This call to u8string is redundant, but required to build - // on MSVC 14.26 due to implementation bugs. - getRawStdout() << v.describe() << std::endl; + getRawStdout() << Version::getOpenmwVersionDescription() << std::endl; return false; } cfgMgr.readConfiguration(variables, desc); setupLogging(cfgMgr.getLogPath(), "OpenMW"); - - Version::Version v - = Version::getOpenmwVersion(variables["resources"] - .as() - .u8string()); // This call to u8string is redundant, but required to build on - // MSVC 14.26 due to implementation bugs. - Log(Debug::Info) << v.describe(); + Log(Debug::Info) << Version::getOpenmwVersionDescription(); Settings::Manager::load(cfgMgr); diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 65b6977982..2bace90614 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -128,7 +129,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 44; + api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index ff719d72a7..a4816a53b6 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -28,4 +28,4 @@ endif () include(${MACROSFILE}) -configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) +configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 0561fcefd7..2762b8ff72 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -62,6 +62,13 @@ macro (add_component_dir dir) list (APPEND cppfiles "${f}") endforeach (f) + if (u MATCHES ".*[ch]pp") + list (APPEND files "${dir}/${u}") + list (APPEND COMPONENT_FILES "${dir}/${u}") + endif() + if (u MATCHES ".*cpp") + list (APPEND cppfiles "${dir}/${u}") + endif() endforeach (u) if (OPENMW_UNITY_BUILD) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 9a073b5ca1..d519c1cd92 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,21 +6,22 @@ if(APPLE) endif(APPLE) # Version file -set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in") -set (VERSION_FILE_PATH_BASE "${OpenMW_BINARY_DIR}") -set (VERSION_FILE_PATH_RELATIVE resources/version) +set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/components/version/version.cpp.in") +set (VERSION_FILE "${OpenMW_SOURCE_DIR}/components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_target (git-version + add_custom_command ( + OUTPUT ${VERSION_FILE} + DEPENDS ${VERSION_IN_FILE} COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DVERSION_IN_FILE=${VERSION_IN_FILE} - -DVERSION_FILE_PATH_BASE=${VERSION_FILE_PATH_BASE} - -DVERSION_FILE_PATH_RELATIVE=${VERSION_FILE_PATH_RELATIVE} + -DVERSION_FILE=${VERSION_FILE} -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_LUA_API_REVISION=${OPENMW_LUA_API_REVISION} -DOPENMW_VERSION=${OPENMW_VERSION} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" @@ -28,7 +29,7 @@ if (GIT_CHECKOUT) -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake VERBATIM) else (GIT_CHECKOUT) - configure_resource_file(${VERSION_IN_FILE} ${VERSION_FILE_PATH_BASE} ${VERSION_FILE_PATH_RELATIVE}) + configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) endif (GIT_CHECKOUT) # source files @@ -311,6 +312,7 @@ add_component_dir (sdlutil add_component_dir (version version + version.cpp ) add_component_dir (fallback @@ -551,10 +553,6 @@ if (USE_QT) endif() endif() -if (GIT_CHECKOUT) - add_dependencies (components git-version) -endif (GIT_CHECKOUT) - if (OSG_STATIC AND CMAKE_SYSTEM_NAME MATCHES "Linux") find_package(X11 REQUIRED COMPONENTS Xinerama Xrandr) target_link_libraries(components ${CMAKE_DL_LIBS} X11::X11 X11::Xinerama X11::Xrandr) diff --git a/components/version/version.cpp b/components/version/version.cpp deleted file mode 100644 index 85eea712b3..0000000000 --- a/components/version/version.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "version.hpp" - -#include -#include - -namespace Version -{ - - Version getOpenmwVersion(const std::filesystem::path& resourcePath) - { - std::ifstream stream(resourcePath / "version"); - - Version v; - std::getline(stream, v.mVersion); - std::getline(stream, v.mCommitHash); - std::getline(stream, v.mTagHash); - return v; - } - - std::string Version::describe() const - { - std::string str = "OpenMW version " + mVersion; - std::string rev = mCommitHash; - if (!rev.empty()) - { - rev = rev.substr(0, 10); - str += "\nRevision: " + rev; - } - return str; - } - - std::string getOpenmwVersionDescription(const std::filesystem::path& resourcePath) - { - Version v = getOpenmwVersion(resourcePath); - return v.describe(); - } - -} diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in new file mode 100644 index 0000000000..f2748b9186 --- /dev/null +++ b/components/version/version.cpp.in @@ -0,0 +1,36 @@ +#include "version.hpp" + +namespace Version +{ + std::string_view getVersion() + { + return "@OPENMW_VERSION@"; + } + + std::string_view getCommitHash() + { + return "@OPENMW_VERSION_COMMITHASH@"; + } + + std::string_view getTagHash() + { + return "@OPENMW_VERSION_TAGHASH@"; + } + + int getLuaApiRevision() + { + return @OPENMW_LUA_API_REVISION@; + } + + std::string getOpenmwVersionDescription() + { + std::string str = "OpenMW version "; + str += getVersion(); + if (!getCommitHash().empty()) + { + str += "\nRevision: "; + str += getCommitHash().substr(0, 10); + } + return str; + } +} diff --git a/components/version/version.hpp b/components/version/version.hpp index f6427a4823..c4a62c9e1e 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -1,27 +1,18 @@ #ifndef VERSION_HPP #define VERSION_HPP -#include #include +#include namespace Version { + std::string_view getVersion(); + std::string_view getCommitHash(); + std::string_view getTagHash(); + int getLuaApiRevision(); - struct Version - { - std::string mVersion; - std::string mCommitHash; - std::string mTagHash; - - std::string describe() const; - }; - - /// Read OpenMW version from the version file located in resourcePath. - Version getOpenmwVersion(const std::filesystem::path& resourcePath); - - /// Helper function to getOpenmwVersion and describe() it - std::string getOpenmwVersionDescription(const std::filesystem::path& resourcePath); - + // Prepares string that contains version and commit hash. + std::string getOpenmwVersionDescription(); } #endif // VERSION_HPP diff --git a/docs/source/conf.py b/docs/source/conf.py index 207c3f7c13..096dec6ae0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,6 +66,7 @@ copyright = u'2023, OpenMW Team' # The full version, including alpha/beta/rc tags. release = version = "UNRELEASED" +luaApiRevision = "UNKNOWN" try: cmake_raw = open(project_root+'/CMakeLists.txt', 'r').read() @@ -76,11 +77,18 @@ try: release = version = '.'.join((majorVersionMatch.group(1), minorVersionMatch.group(1), releaseVersionMatch.group(1))) + luaApiRevisionMatch = re.search('set\(OPENMW_LUA_API_REVISION (\d+)\)', cmake_raw) + if luaApiRevisionMatch: + luaApiRevision = luaApiRevisionMatch.group(1) except Exception as ex: print("WARNING: Version will be set to '{0}' because: '{1}'.".format(release, str(ex))) import traceback; traceback.print_exc() +rst_prolog = f""" +.. |luaApiRevision| replace:: {luaApiRevision} +""" + # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = cpp diff --git a/docs/source/reference/lua-scripting/aipackages.rst b/docs/source/reference/lua-scripting/aipackages.rst index 39f973c65d..4ef3149582 100644 --- a/docs/source/reference/lua-scripting/aipackages.rst +++ b/docs/source/reference/lua-scripting/aipackages.rst @@ -1,6 +1,8 @@ Built-in AI packages ==================== +.. include:: version.rst + Starting an AI package ---------------------- diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 76092fec3f..77d4c9b14b 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -2,6 +2,8 @@ Lua API reference ################# +.. include:: version.rst + .. toctree:: :hidden: diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 8d5db51f84..1ffa1820f3 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -1,6 +1,8 @@ Engine handlers reference ========================= +.. include:: version.rst + Engine handler is a function defined by a script, that can be called by the engine. diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index da4b2e3812..513fd8c871 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -1,6 +1,8 @@ Built-in events =============== +.. include:: version.rst + Actor events ------------ diff --git a/docs/source/reference/lua-scripting/index.rst b/docs/source/reference/lua-scripting/index.rst index c6adfc7723..f3764c4401 100644 --- a/docs/source/reference/lua-scripting/index.rst +++ b/docs/source/reference/lua-scripting/index.rst @@ -2,8 +2,10 @@ OpenMW Lua scripting #################### -.. warning:: - OpenMW Lua scripting is in early stage of development. Also note that OpenMW Lua is not compatible with MWSE. +.. note:: + OpenMW Lua is not compatible with MWSE. + +.. include:: version.rst .. toctree:: :caption: Table of Contents diff --git a/docs/source/reference/lua-scripting/interface_activation.rst b/docs/source/reference/lua-scripting/interface_activation.rst index ccc51ca457..ad31cca3f6 100644 --- a/docs/source/reference/lua-scripting/interface_activation.rst +++ b/docs/source/reference/lua-scripting/interface_activation.rst @@ -1,6 +1,8 @@ Interface Activation ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_activationhandlers.html diff --git a/docs/source/reference/lua-scripting/interface_ai.rst b/docs/source/reference/lua-scripting/interface_ai.rst index ec79b50d5d..43197721e2 100644 --- a/docs/source/reference/lua-scripting/interface_ai.rst +++ b/docs/source/reference/lua-scripting/interface_ai.rst @@ -1,6 +1,8 @@ Interface AI ============ +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_ai.html diff --git a/docs/source/reference/lua-scripting/interface_camera.rst b/docs/source/reference/lua-scripting/interface_camera.rst index 6a0f19d47b..0a71680ee3 100644 --- a/docs/source/reference/lua-scripting/interface_camera.rst +++ b/docs/source/reference/lua-scripting/interface_camera.rst @@ -1,6 +1,8 @@ Interface Camera ================ +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_camera_camera.html diff --git a/docs/source/reference/lua-scripting/interface_controls.rst b/docs/source/reference/lua-scripting/interface_controls.rst index e458aca133..c4b1709f59 100644 --- a/docs/source/reference/lua-scripting/interface_controls.rst +++ b/docs/source/reference/lua-scripting/interface_controls.rst @@ -1,6 +1,8 @@ Interface Controls ================== +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_playercontrols.html diff --git a/docs/source/reference/lua-scripting/interface_mwui.rst b/docs/source/reference/lua-scripting/interface_mwui.rst index cd9b00cb87..51a86843b4 100644 --- a/docs/source/reference/lua-scripting/interface_mwui.rst +++ b/docs/source/reference/lua-scripting/interface_mwui.rst @@ -1,6 +1,8 @@ Interface MWUI ============== +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_mwui_init.html diff --git a/docs/source/reference/lua-scripting/interface_settings.rst b/docs/source/reference/lua-scripting/interface_settings.rst index cd1994ccfa..1f57f00d2a 100644 --- a/docs/source/reference/lua-scripting/interface_settings.rst +++ b/docs/source/reference/lua-scripting/interface_settings.rst @@ -1,6 +1,8 @@ Interface Settings ================== +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_settings_player.html diff --git a/docs/source/reference/lua-scripting/interface_ui.rst b/docs/source/reference/lua-scripting/interface_ui.rst index 8c3ac679e5..f406924cd5 100644 --- a/docs/source/reference/lua-scripting/interface_ui.rst +++ b/docs/source/reference/lua-scripting/interface_ui.rst @@ -1,6 +1,8 @@ Interface UI ============ +.. include:: version.rst + .. raw:: html :file: generated_html/scripts_omw_ui.html diff --git a/docs/source/reference/lua-scripting/iterables.rst b/docs/source/reference/lua-scripting/iterables.rst index 208b7f1c92..420ff593ef 100644 --- a/docs/source/reference/lua-scripting/iterables.rst +++ b/docs/source/reference/lua-scripting/iterables.rst @@ -1,6 +1,8 @@ Iterable types ============== +.. include:: version.rst + List Iterable ------------- diff --git a/docs/source/reference/lua-scripting/openmw_ambient.rst b/docs/source/reference/lua-scripting/openmw_ambient.rst index a68f5f4469..4a44636bd3 100644 --- a/docs/source/reference/lua-scripting/openmw_ambient.rst +++ b/docs/source/reference/lua-scripting/openmw_ambient.rst @@ -1,5 +1,7 @@ Package openmw.ambient ====================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_ambient.html diff --git a/docs/source/reference/lua-scripting/openmw_async.rst b/docs/source/reference/lua-scripting/openmw_async.rst index 9f24f63a4f..55e44526be 100644 --- a/docs/source/reference/lua-scripting/openmw_async.rst +++ b/docs/source/reference/lua-scripting/openmw_async.rst @@ -1,5 +1,7 @@ Package openmw.async ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_async.html diff --git a/docs/source/reference/lua-scripting/openmw_aux_calendar.rst b/docs/source/reference/lua-scripting/openmw_aux_calendar.rst index ea60b62852..728d3e89da 100644 --- a/docs/source/reference/lua-scripting/openmw_aux_calendar.rst +++ b/docs/source/reference/lua-scripting/openmw_aux_calendar.rst @@ -1,5 +1,7 @@ Package openmw_aux.calendar =========================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_aux_calendar.html diff --git a/docs/source/reference/lua-scripting/openmw_aux_time.rst b/docs/source/reference/lua-scripting/openmw_aux_time.rst index 120d888a01..919690c19f 100644 --- a/docs/source/reference/lua-scripting/openmw_aux_time.rst +++ b/docs/source/reference/lua-scripting/openmw_aux_time.rst @@ -1,5 +1,7 @@ Package openmw_aux.time ======================= +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_aux_time.html diff --git a/docs/source/reference/lua-scripting/openmw_aux_ui.rst b/docs/source/reference/lua-scripting/openmw_aux_ui.rst index 18c0926c03..2a45bb529e 100644 --- a/docs/source/reference/lua-scripting/openmw_aux_ui.rst +++ b/docs/source/reference/lua-scripting/openmw_aux_ui.rst @@ -1,5 +1,7 @@ Package openmw_aux.ui ======================= +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_aux_ui.html diff --git a/docs/source/reference/lua-scripting/openmw_aux_util.rst b/docs/source/reference/lua-scripting/openmw_aux_util.rst index 01b200bfc2..662498f693 100644 --- a/docs/source/reference/lua-scripting/openmw_aux_util.rst +++ b/docs/source/reference/lua-scripting/openmw_aux_util.rst @@ -1,5 +1,7 @@ Package openmw_aux.util ======================= +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_aux_util.html diff --git a/docs/source/reference/lua-scripting/openmw_camera.rst b/docs/source/reference/lua-scripting/openmw_camera.rst index 4090843920..3c9a74c037 100644 --- a/docs/source/reference/lua-scripting/openmw_camera.rst +++ b/docs/source/reference/lua-scripting/openmw_camera.rst @@ -1,5 +1,7 @@ Package openmw.camera ===================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_camera.html diff --git a/docs/source/reference/lua-scripting/openmw_core.rst b/docs/source/reference/lua-scripting/openmw_core.rst index bde93fb9c9..2dc3f762b3 100644 --- a/docs/source/reference/lua-scripting/openmw_core.rst +++ b/docs/source/reference/lua-scripting/openmw_core.rst @@ -1,5 +1,7 @@ Package openmw.core =================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_core.html diff --git a/docs/source/reference/lua-scripting/openmw_debug.rst b/docs/source/reference/lua-scripting/openmw_debug.rst index 5be48f6558..c139131853 100644 --- a/docs/source/reference/lua-scripting/openmw_debug.rst +++ b/docs/source/reference/lua-scripting/openmw_debug.rst @@ -1,5 +1,7 @@ Package openmw.debug ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_debug.html diff --git a/docs/source/reference/lua-scripting/openmw_input.rst b/docs/source/reference/lua-scripting/openmw_input.rst index 39136dc4c2..9e327d00e1 100644 --- a/docs/source/reference/lua-scripting/openmw_input.rst +++ b/docs/source/reference/lua-scripting/openmw_input.rst @@ -1,6 +1,8 @@ Package openmw.input ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_input.html diff --git a/docs/source/reference/lua-scripting/openmw_nearby.rst b/docs/source/reference/lua-scripting/openmw_nearby.rst index ca0a7d5d00..6812fad44e 100644 --- a/docs/source/reference/lua-scripting/openmw_nearby.rst +++ b/docs/source/reference/lua-scripting/openmw_nearby.rst @@ -1,5 +1,7 @@ Package openmw.nearby ===================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_nearby.html diff --git a/docs/source/reference/lua-scripting/openmw_postprocessing.rst b/docs/source/reference/lua-scripting/openmw_postprocessing.rst index ec2f025049..0acf32c38b 100644 --- a/docs/source/reference/lua-scripting/openmw_postprocessing.rst +++ b/docs/source/reference/lua-scripting/openmw_postprocessing.rst @@ -1,5 +1,7 @@ Package openmw.postprocessing ============================= +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_postprocessing.html diff --git a/docs/source/reference/lua-scripting/openmw_self.rst b/docs/source/reference/lua-scripting/openmw_self.rst index 71a2cb04ae..4c0b8dbec3 100644 --- a/docs/source/reference/lua-scripting/openmw_self.rst +++ b/docs/source/reference/lua-scripting/openmw_self.rst @@ -1,5 +1,7 @@ Package openmw.self =================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_self.html diff --git a/docs/source/reference/lua-scripting/openmw_storage.rst b/docs/source/reference/lua-scripting/openmw_storage.rst index 5abf664e1a..785d1b661c 100644 --- a/docs/source/reference/lua-scripting/openmw_storage.rst +++ b/docs/source/reference/lua-scripting/openmw_storage.rst @@ -1,5 +1,7 @@ Package openmw.storage ====================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_storage.html diff --git a/docs/source/reference/lua-scripting/openmw_types.rst b/docs/source/reference/lua-scripting/openmw_types.rst index 1819b1a6ce..3f408909db 100644 --- a/docs/source/reference/lua-scripting/openmw_types.rst +++ b/docs/source/reference/lua-scripting/openmw_types.rst @@ -1,5 +1,7 @@ Package openmw.types ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_types.html diff --git a/docs/source/reference/lua-scripting/openmw_ui.rst b/docs/source/reference/lua-scripting/openmw_ui.rst index 1b7b57c359..d523fe57fd 100644 --- a/docs/source/reference/lua-scripting/openmw_ui.rst +++ b/docs/source/reference/lua-scripting/openmw_ui.rst @@ -1,5 +1,7 @@ Package openmw.ui ================= +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_ui.html diff --git a/docs/source/reference/lua-scripting/openmw_util.rst b/docs/source/reference/lua-scripting/openmw_util.rst index f054d7b44d..d7d692a1f7 100644 --- a/docs/source/reference/lua-scripting/openmw_util.rst +++ b/docs/source/reference/lua-scripting/openmw_util.rst @@ -1,5 +1,7 @@ Package openmw.util =================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_util.html diff --git a/docs/source/reference/lua-scripting/openmw_world.rst b/docs/source/reference/lua-scripting/openmw_world.rst index b8818d4027..3ee7f8ac51 100644 --- a/docs/source/reference/lua-scripting/openmw_world.rst +++ b/docs/source/reference/lua-scripting/openmw_world.rst @@ -1,5 +1,7 @@ Package openmw.world ==================== +.. include:: version.rst + .. raw:: html :file: generated_html/openmw_world.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index d0eb0a4cb2..157a9e686d 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -1,6 +1,8 @@ Overview of Lua scripting ######################### +.. include:: version.rst + Language and sandboxing ======================= diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index a0ce9e99f9..966246d503 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -1,6 +1,8 @@ Built-in Setting Renderers ========================== +.. include:: version.rst + textLine -------- diff --git a/docs/source/reference/lua-scripting/user_interface.rst b/docs/source/reference/lua-scripting/user_interface.rst index d1d3162a4b..9251538657 100644 --- a/docs/source/reference/lua-scripting/user_interface.rst +++ b/docs/source/reference/lua-scripting/user_interface.rst @@ -1,6 +1,8 @@ User interface reference ======================== +.. include:: version.rst + Layouts ------- diff --git a/docs/source/reference/lua-scripting/version.rst b/docs/source/reference/lua-scripting/version.rst new file mode 100644 index 0000000000..ad96ee456b --- /dev/null +++ b/docs/source/reference/lua-scripting/version.rst @@ -0,0 +1,2 @@ +| `OpenMW version:` |version| +| `core.API_REVISION:` |luaApiRevision| `* `_ diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index f018e4dd21..abe151eb73 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -7,7 +7,7 @@ --- --- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. +-- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. See the actual value at the top of the page. -- @field [parent=#core] #number API_REVISION --- diff --git a/files/version.in b/files/version.in deleted file mode 100644 index f894d3b4a5..0000000000 --- a/files/version.in +++ /dev/null @@ -1,3 +0,0 @@ -@OPENMW_VERSION@ -@OPENMW_VERSION_COMMITHASH@ -@OPENMW_VERSION_TAGHASH@ From d39552962f36473cbaf031a563b45d9835336024 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 31 Aug 2023 20:55:28 +0200 Subject: [PATCH 0033/2167] Validate version of resources --- .gitignore | 1 - apps/opencs/CMakeLists.txt | 4 ++++ apps/openmw/CMakeLists.txt | 4 ++++ apps/openmw/engine.cpp | 3 +++ cmake/GitVersion.cmake | 7 ++++--- cmake/OpenMWMacros.cmake | 7 ------- components/CMakeLists.txt | 24 +++++++++++++++--------- components/version/version.cpp.in | 16 +++++++++++++++- components/version/version.hpp | 3 +++ files/version.in | 3 +++ 10 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 files/version.in diff --git a/.gitignore b/.gitignore index dfb56dc359..f25adf58e6 100644 --- a/.gitignore +++ b/.gitignore @@ -68,7 +68,6 @@ apps/wizard/ui_intropage.h apps/wizard/ui_languageselectionpage.h apps/wizard/ui_methodselectionpage.h components/ui_contentselector.h -components/version/version.cpp docs/mainpage.hpp docs/Doxyfile docs/DoxyfilePages diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 8586f490e8..20bd62d145 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -225,6 +225,10 @@ if(APPLE AND BUILD_OPENCS) MACOSX_PACKAGE_LOCATION Resources/resources) set_source_files_properties(${OPENCS_OPENMW_CFG} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + add_custom_command(TARGET openmw-cs + POST_BUILD + COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${OPENCS_BUNDLE_RESOURCES_DIR}/resources") endif() target_link_libraries(openmw-cs-lib diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ec9636fde1..3df00f1be0 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -210,6 +210,10 @@ if(APPLE) configure_file("${OpenMW_BINARY_DIR}/openmw.cfg" ${BUNDLE_RESOURCES_DIR} COPYONLY) configure_file("${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" ${BUNDLE_RESOURCES_DIR} COPYONLY) + add_custom_command(TARGET openmw + POST_BUILD + COMMAND cp "${OpenMW_BINARY_DIR}/resources/version" "${BUNDLE_RESOURCES_DIR}/resources") + find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) target_link_libraries(openmw ${COCOA_FRAMEWORK} ${IOKIT_FRAMEWORK}) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 1d3232290d..3e7f151fad 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -423,6 +423,9 @@ void OMW::Engine::addArchive(const std::string& archive) void OMW::Engine::setResourceDir(const std::filesystem::path& parResDir) { mResDir = parResDir; + if (!Version::checkResourcesVersion(mResDir)) + Log(Debug::Error) << "Resources dir " << mResDir + << " doesn't match OpenMW binary, the game may work incorrectly."; } // Set start cell name diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index a4816a53b6..a77b0d5b0a 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -1,6 +1,6 @@ execute_process ( COMMAND ${GIT_EXECUTABLE} rev-list --tags --max-count=1 - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + WORKING_DIRECTORY ${OpenMW_SOURCE_DIR} RESULT_VARIABLE EXITCODE1 OUTPUT_VARIABLE TAGHASH OUTPUT_STRIP_TRAILING_WHITESPACE @@ -8,7 +8,7 @@ execute_process ( execute_process ( COMMAND ${GIT_EXECUTABLE} rev-parse HEAD - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + WORKING_DIRECTORY ${OpenMW_SOURCE_DIR} RESULT_VARIABLE EXITCODE2 OUTPUT_VARIABLE COMMITHASH OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -28,4 +28,5 @@ endif () include(${MACROSFILE}) -configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) +configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) +configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 2762b8ff72..0561fcefd7 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -62,13 +62,6 @@ macro (add_component_dir dir) list (APPEND cppfiles "${f}") endforeach (f) - if (u MATCHES ".*[ch]pp") - list (APPEND files "${dir}/${u}") - list (APPEND COMPONENT_FILES "${dir}/${u}") - endif() - if (u MATCHES ".*cpp") - list (APPEND cppfiles "${dir}/${u}") - endif() endforeach (u) if (OPENMW_UNITY_BUILD) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index d519c1cd92..71e508e882 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -6,18 +6,22 @@ if(APPLE) endif(APPLE) # Version file -set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/components/version/version.cpp.in") -set (VERSION_FILE "${OpenMW_SOURCE_DIR}/components/version/version.cpp") +set (VERSION_RESOURCE_FILE_IN "${OpenMW_SOURCE_DIR}/files/version.in") +set (VERSION_RESOURCE_FILE_RELATIVE "resources/version") +set (VERSION_CPP_FILE "components/version/version.cpp") + if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) add_custom_command ( - OUTPUT ${VERSION_FILE} - DEPENDS ${VERSION_IN_FILE} + OUTPUT "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" + DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} - -DVERSION_IN_FILE=${VERSION_IN_FILE} - -DVERSION_FILE=${VERSION_FILE} + -DOpenMW_SOURCE_DIR=${OpenMW_SOURCE_DIR} + -DOpenMW_BINARY_DIR=${OpenMW_BINARY_DIR} + -DVERSION_RESOURCE_FILE_IN=${VERSION_RESOURCE_FILE_IN} + -DVERSION_RESOURCE_FILE_RELATIVE=${VERSION_RESOURCE_FILE_RELATIVE} + -DVERSION_CPP_FILE=${VERSION_CPP_FILE} -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} @@ -29,9 +33,12 @@ if (GIT_CHECKOUT) -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake VERBATIM) else (GIT_CHECKOUT) - configure_file(${VERSION_IN_FILE} ${VERSION_FILE}) + configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) + configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") endif (GIT_CHECKOUT) +list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") + # source files add_component_dir (lua @@ -312,7 +319,6 @@ add_component_dir (sdlutil add_component_dir (version version - version.cpp ) add_component_dir (fallback diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in index f2748b9186..d95e47cbd6 100644 --- a/components/version/version.cpp.in +++ b/components/version/version.cpp.in @@ -1,4 +1,7 @@ -#include "version.hpp" +#include + +#include +#include namespace Version { @@ -33,4 +36,15 @@ namespace Version } return str; } + + bool checkResourcesVersion(const std::filesystem::path& resourcePath) + { + std::ifstream stream(resourcePath / "version"); + std::string version, commitHash, tagHash; + std::getline(stream, version); + std::getline(stream, commitHash); + std::getline(stream, tagHash); + return getVersion() == version && getCommitHash() == commitHash && getTagHash() == tagHash; + } + } diff --git a/components/version/version.hpp b/components/version/version.hpp index c4a62c9e1e..64b8cd05e0 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -1,6 +1,7 @@ #ifndef VERSION_HPP #define VERSION_HPP +#include #include #include @@ -13,6 +14,8 @@ namespace Version // Prepares string that contains version and commit hash. std::string getOpenmwVersionDescription(); + + bool checkResourcesVersion(const std::filesystem::path& resourcePath); } #endif // VERSION_HPP diff --git a/files/version.in b/files/version.in new file mode 100644 index 0000000000..f894d3b4a5 --- /dev/null +++ b/files/version.in @@ -0,0 +1,3 @@ +@OPENMW_VERSION@ +@OPENMW_VERSION_COMMITHASH@ +@OPENMW_VERSION_TAGHASH@ From bb6e0088014ed44bcefcb77455547aaa516d46cd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 22:05:55 +0300 Subject: [PATCH 0034/2167] Modernize dynamic effects --- components/nif/effect.cpp | 96 +++++++++++++++------------------ components/nif/effect.hpp | 64 ++++++++++++---------- components/nifosg/nifloader.cpp | 25 +++++---- 3 files changed, 93 insertions(+), 92 deletions(-) diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index e5dd2282b6..36407f8dc2 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -9,55 +9,61 @@ namespace Nif void NiDynamicEffect::read(NIFStream* nif) { Node::read(nif); - if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106) - && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) - nif->getBoolean(); // Switch state - if (nif->getVersion() <= NIFFile::VER_MW - || (nif->getVersion() >= nif->generateVersion(10, 1, 0, 0) - && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)) - { - size_t numAffectedNodes = nif->get(); - nif->skip(numAffectedNodes * 4); - } + + if (nif->getVersion() > NIFFile::VER_MW && nif->getVersion() < nif->generateVersion(10, 1, 0, 0)) + return; + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + return; + + if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106)) + nif->read(mSwitchState); + size_t numAffectedNodes = nif->get(); + nif->skip(numAffectedNodes * 4); } void NiLight::read(NIFStream* nif) { NiDynamicEffect::read(nif); - dimmer = nif->getFloat(); - ambient = nif->getVector3(); - diffuse = nif->getVector3(); - specular = nif->getVector3(); + mDimmer = nif->getFloat(); + mAmbient = nif->getVector3(); + mDiffuse = nif->getVector3(); + mSpecular = nif->getVector3(); + } + + void NiPointLight::read(NIFStream* nif) + { + NiLight::read(nif); + + mConstantAttenuation = nif->getFloat(); + mLinearAttenuation = nif->getFloat(); + mQuadraticAttenuation = nif->getFloat(); + } + + void NiSpotLight::read(NIFStream* nif) + { + NiPointLight::read(nif); + + mCutoff = nif->getFloat(); + mExponent = nif->getFloat(); } void NiTextureEffect::read(NIFStream* nif) { NiDynamicEffect::read(nif); - // Model Projection Matrix - nif->skip(3 * 3 * sizeof(float)); - - // Model Projection Transform - nif->skip(3 * sizeof(float)); - - // Texture Filtering - nif->skip(4); - - // Max anisotropy samples + nif->read(mProjectionRotation); + nif->read(mProjectionPosition); + nif->read(mFilterMode); if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4)) - nif->skip(2); - - clamp = nif->getUInt(); - - textureType = (TextureType)nif->getUInt(); - - coordGenType = (CoordGenType)nif->getUInt(); - - texture.read(nif); - - nif->skip(1); // Use clipping plane - nif->skip(16); // Clipping plane dimensions vector + nif->read(mMaxAnisotropy); + nif->read(mClampMode); + mTextureType = static_cast(nif->get()); + mCoordGenType = static_cast(nif->get()); + mTexture.read(nif); + nif->read(mEnableClipPlane); + mClipPlane = osg::Plane(nif->get()); if (nif->getVersion() <= NIFStream::generateVersion(10, 2, 0, 0)) nif->skip(4); // PS2-specific shorts if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 12)) @@ -67,24 +73,8 @@ namespace Nif void NiTextureEffect::post(Reader& nif) { NiDynamicEffect::post(nif); - texture.post(nif); - } - void NiPointLight::read(NIFStream* nif) - { - NiLight::read(nif); - - constantAttenuation = nif->getFloat(); - linearAttenuation = nif->getFloat(); - quadraticAttenuation = nif->getFloat(); - } - - void NiSpotLight::read(NIFStream* nif) - { - NiPointLight::read(nif); - - cutoff = nif->getFloat(); - exponent = nif->getFloat(); + mTexture.post(nif); } } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index ea9e21f003..06f85cd5d5 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -29,67 +29,75 @@ namespace Nif { + // Abstract struct NiDynamicEffect : public Node { + bool mSwitchState{ true }; void read(NIFStream* nif) override; }; - // Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight. + // Abstract light source struct NiLight : NiDynamicEffect { - float dimmer; - osg::Vec3f ambient; - osg::Vec3f diffuse; - osg::Vec3f specular; + float mDimmer; + osg::Vec3f mAmbient; + osg::Vec3f mDiffuse; + osg::Vec3f mSpecular; void read(NIFStream* nif) override; }; struct NiPointLight : public NiLight { - float constantAttenuation; - float linearAttenuation; - float quadraticAttenuation; + float mConstantAttenuation; + float mLinearAttenuation; + float mQuadraticAttenuation; void read(NIFStream* nif) override; }; struct NiSpotLight : public NiPointLight { - float cutoff; - float exponent; + float mCutoff; + float mExponent; void read(NIFStream* nif) override; }; struct NiTextureEffect : NiDynamicEffect { - NiSourceTexturePtr texture; - unsigned int clamp; - - enum TextureType + enum class TextureType : uint32_t { - Projected_Light = 0, - Projected_Shadow = 1, - Environment_Map = 2, - Fog_Map = 3 + ProjectedLight = 0, + ProjectedShadow = 1, + EnvironmentMap = 2, + FogMap = 3, }; - TextureType textureType; - enum CoordGenType + enum class CoordGenType : uint32_t { - World_Parallel = 0, - World_Perspective, - Sphere_Map, - Specular_Cube_Map, - Diffuse_Cube_Map + WorldParallel = 0, + WorldPerspective = 1, + SphereMap = 2, + SpecularCubeMap = 3, + DiffuseCubeMap = 4, }; - CoordGenType coordGenType; + + Matrix3 mProjectionRotation; + osg::Vec3f mProjectionPosition; + uint32_t mFilterMode; + NiSourceTexturePtr mTexture; + uint16_t mMaxAnisotropy{ 0 }; + uint32_t mClampMode; + TextureType mTextureType; + CoordGenType mCoordGenType; + uint8_t mEnableClipPlane; + osg::Plane mClipPlane; void read(NIFStream* nif) override; void post(Reader& nif) override; - bool wrapT() const { return clamp & 1; } - bool wrapS() const { return (clamp >> 1) & 1; } + bool wrapT() const { return mClampMode & 1; } + bool wrapS() const { return mClampMode & 2; } }; } // Namespace diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c8c89a0660..88e470bae2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -537,38 +537,41 @@ namespace NifOsg } const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); - if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) + if (!textureEffect->mSwitchState) + return false; + + if (textureEffect->mTextureType != Nif::NiTextureEffect::TextureType::EnvironmentMap) { - Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " - << mFilename; + Log(Debug::Info) << "Unhandled NiTextureEffect type " + << static_cast(textureEffect->mTextureType) << " in " << mFilename; return false; } - if (textureEffect->texture.empty()) + if (textureEffect->mTexture.empty()) { Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename; return false; } osg::ref_ptr texGen(new osg::TexGen); - switch (textureEffect->coordGenType) + switch (textureEffect->mCoordGenType) { - case Nif::NiTextureEffect::World_Parallel: + case Nif::NiTextureEffect::CoordGenType::WorldParallel: texGen->setMode(osg::TexGen::OBJECT_LINEAR); break; - case Nif::NiTextureEffect::World_Perspective: + case Nif::NiTextureEffect::CoordGenType::WorldPerspective: texGen->setMode(osg::TexGen::EYE_LINEAR); break; - case Nif::NiTextureEffect::Sphere_Map: + case Nif::NiTextureEffect::CoordGenType::SphereMap: texGen->setMode(osg::TexGen::SPHERE_MAP); break; default: - Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType - << " in " << mFilename; + Log(Debug::Info) << "Unhandled NiTextureEffect CoordGenType " + << static_cast(textureEffect->mCoordGenType) << " in " << mFilename; return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->texture.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); osg::ref_ptr texture2d(new osg::Texture2D(image)); if (image) texture2d->setTextureSize(image->s(), image->t()); From ce1c78422b09d77374729f1412b3c2d84db875a0 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 31 Aug 2023 20:04:37 +0000 Subject: [PATCH 0035/2167] Do not bind movement to the Controller DPad, but allow it to be rebound --- apps/openmw/mwinput/bindingsmanager.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 29e66f7905..27355ec4a9 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -381,10 +381,6 @@ namespace MWInput defaultButtonBindings[A_Inventory] = SDL_CONTROLLER_BUTTON_B; defaultButtonBindings[A_GameMenu] = SDL_CONTROLLER_BUTTON_START; defaultButtonBindings[A_QuickSave] = SDL_CONTROLLER_BUTTON_GUIDE; - defaultButtonBindings[A_MoveForward] = SDL_CONTROLLER_BUTTON_DPAD_UP; - defaultButtonBindings[A_MoveLeft] = SDL_CONTROLLER_BUTTON_DPAD_LEFT; - defaultButtonBindings[A_MoveBackward] = SDL_CONTROLLER_BUTTON_DPAD_DOWN; - defaultButtonBindings[A_MoveRight] = SDL_CONTROLLER_BUTTON_DPAD_RIGHT; std::map defaultAxisBindings; defaultAxisBindings[A_MoveForwardBackward] = SDL_CONTROLLER_AXIS_LEFTY; @@ -594,11 +590,12 @@ namespace MWInput } const std::initializer_list& BindingsManager::getActionControllerSorting() { - static const std::initializer_list actions{ A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Sneak, A_Activate, A_Use, - A_ToggleWeapon, A_ToggleSpell, A_AutoMove, A_Jump, A_Inventory, A_Journal, A_Rest, A_QuickSave, A_QuickLoad, - A_ToggleHUD, A_Screenshot, A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, - A_QuickKey6, A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, A_CycleSpellLeft, A_CycleSpellRight, - A_CycleWeaponLeft, A_CycleWeaponRight }; + static const std::initializer_list actions{ A_MoveForward, A_MoveBackward, A_MoveLeft, A_MoveRight, + A_TogglePOV, A_ZoomIn, A_ZoomOut, A_Sneak, A_Activate, A_Use, A_ToggleWeapon, A_ToggleSpell, A_AutoMove, + A_Jump, A_Inventory, A_Journal, A_Rest, A_QuickSave, A_QuickLoad, A_ToggleHUD, A_Screenshot, + A_QuickKeysMenu, A_QuickKey1, A_QuickKey2, A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, A_QuickKey7, + A_QuickKey8, A_QuickKey9, A_QuickKey10, A_CycleSpellLeft, A_CycleSpellRight, A_CycleWeaponLeft, + A_CycleWeaponRight }; return actions; } From 087114e55b5c1c6db0cdc65a547ce502fb88545c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 31 Aug 2023 22:46:02 +0300 Subject: [PATCH 0036/2167] Print unsupported interpolator record names --- components/nifosg/nifloader.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 88e470bae2..5d703cf248 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -899,7 +899,7 @@ namespace NifOsg if (!key->mInterpolator.empty() && key->mInterpolator->recType != Nif::RC_NiTransformInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiKeyframeController " << key->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << key->mInterpolator->recName; continue; } osg::ref_ptr callback = new KeyframeController(key); @@ -926,7 +926,7 @@ namespace NifOsg && visctrl->mInterpolator->recType != Nif::RC_NiBoolInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiVisController " << visctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << visctrl->mInterpolator->recName; continue; } osg::ref_ptr callback(new VisController(visctrl, Loader::getHiddenNodeMask())); @@ -942,7 +942,7 @@ namespace NifOsg && rollctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiRollController " << rollctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << rollctrl->mInterpolator->recName; continue; } osg::ref_ptr callback = new RollController(rollctrl); @@ -977,8 +977,9 @@ namespace NifOsg if (!alphactrl->mInterpolator.empty() && alphactrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { - Log(Debug::Error) << "Unsupported interpolator type for NiAlphaController " - << alphactrl->recIndex << " in " << mFilename; + Log(Debug::Error) + << "Unsupported interpolator type for NiAlphaController " << alphactrl->recIndex << " in " + << mFilename << ": " << alphactrl->mInterpolator->recName; continue; } osg::ref_ptr osgctrl = new AlphaController(alphactrl, baseMaterial); @@ -998,8 +999,9 @@ namespace NifOsg if (!matctrl->mInterpolator.empty() && matctrl->mInterpolator->recType != Nif::RC_NiPoint3Interpolator) { - Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController " - << matctrl->recIndex << " in " << mFilename; + Log(Debug::Error) + << "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex + << " in " << mFilename << ": " << matctrl->mInterpolator->recName; continue; } osg::ref_ptr osgctrl = new MaterialColorController(matctrl, baseMaterial); @@ -1025,7 +1027,7 @@ namespace NifOsg && flipctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiFlipController " << flipctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << flipctrl->mInterpolator->recName; continue; } std::vector> textures; From 8a7e8a89ac6bad07b15c0a58877cb6459cfd8e95 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 1 Sep 2023 00:00:37 +0300 Subject: [PATCH 0037/2167] Modernize Bethesda Havok records --- components/nif/physics.cpp | 287 ++++++++++++++++++++----------------- components/nif/physics.hpp | 117 ++++++++++----- 2 files changed, 238 insertions(+), 166 deletions(-) diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index daa4f90c2a..95786cb247 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -12,15 +12,15 @@ namespace Nif void bhkWorldObjCInfoProperty::read(NIFStream* nif) { - mData = nif->getUInt(); - mSize = nif->getUInt(); - mCapacityAndFlags = nif->getUInt(); + nif->read(mData); + nif->read(mSize); + nif->read(mCapacityAndFlags); } void bhkWorldObjectCInfo::read(NIFStream* nif) { nif->skip(4); // Unused - mPhaseType = static_cast(nif->getChar()); + mPhaseType = static_cast(nif->get()); nif->skip(3); // Unused mProperty.read(nif); } @@ -29,47 +29,47 @@ namespace Nif { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown - mMaterial = nif->getUInt(); + nif->read(mMaterial); } void HavokFilter::read(NIFStream* nif) { - mLayer = nif->getChar(); - mFlags = nif->getChar(); - mGroup = nif->getUShort(); + nif->read(mLayer); + nif->read(mFlags); + nif->read(mGroup); } void hkSubPartData::read(NIFStream* nif) { mHavokFilter.read(nif); - mNumVertices = nif->getUInt(); + nif->read(mNumVertices); mHavokMaterial.read(nif); } - void hkpMoppCode::read(NIFStream* nif) - { - unsigned int size = nif->getUInt(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mOffset = nif->getVector4(); - if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->getChar(); // MOPP data build type - nif->readVector(mData, size); - } - void bhkEntityCInfo::read(NIFStream* nif) { - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); + } + + void hkpMoppCode::read(NIFStream* nif) + { + uint32_t dataSize; + nif->read(dataSize); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mOffset); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->read(mBuildType); + nif->readVector(mData, dataSize); } void TriangleData::read(NIFStream* nif) { - for (int i = 0; i < 3; i++) - mTriangle[i] = nif->getUShort(); - mWeldingInfo = nif->getUShort(); + nif->readArray(mTriangle); + nif->read(mWeldingInfo); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - mNormal = nif->getVector3(); + nif->read(mNormal); } void bhkMeshMaterial::read(NIFStream* nif) @@ -80,28 +80,27 @@ namespace Nif void bhkQsTransform::read(NIFStream* nif) { - mTranslation = nif->getVector4(); - mRotation = nif->getQuaternion(); + nif->read(mTranslation); + nif->read(mRotation); } void bhkCMSBigTri::read(NIFStream* nif) { - for (int i = 0; i < 3; i++) - mTriangle[i] = nif->getUShort(); - mMaterial = nif->getUInt(); - mWeldingInfo = nif->getUShort(); + nif->readArray(mTriangle); + nif->read(mMaterial); + nif->read(mWeldingInfo); } void bhkCMSChunk::read(NIFStream* nif) { - mTranslation = nif->getVector4(); - mMaterialIndex = nif->getUInt(); - mReference = nif->getUShort(); - mTransformIndex = nif->getUShort(); - nif->readVector(mVertices, nif->getUInt()); - nif->readVector(mIndices, nif->getUInt()); - nif->readVector(mStrips, nif->getUInt()); - nif->readVector(mWeldingInfos, nif->getUInt()); + nif->read(mTranslation); + nif->read(mMaterialIndex); + nif->read(mReference); + nif->read(mTransformIndex); + nif->readVector(mVertices, nif->get()); + nif->readVector(mIndices, nif->get()); + nif->readVector(mStrips, nif->get()); + nif->readVector(mWeldingInfos, nif->get()); } void bhkRigidBodyCInfo::read(NIFStream* nif) @@ -115,64 +114,67 @@ namespace Nif { if (nif->getBethVersion() >= 83) nif->skip(4); // Unused - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); } } if (nif->getBethVersion() < 83) nif->skip(4); // Unused - mTranslation = nif->getVector4(); - mRotation = nif->getQuaternion(); - mLinearVelocity = nif->getVector4(); - mAngularVelocity = nif->getVector4(); + nif->read(mTranslation); + nif->read(mRotation); + nif->read(mLinearVelocity); + nif->read(mAngularVelocity); + // A bit hacky, but this is the only instance where a 3x3 matrix has padding. for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) - mInertiaTensor[i][j] = nif->getFloat(); - mCenter = nif->getVector4(); - mMass = nif->getFloat(); - mLinearDamping = nif->getFloat(); - mAngularDamping = nif->getFloat(); + { + nif->read(mInertiaTensor.mValues[i], 3); + nif->skip(4); // Padding + } + nif->read(mCenter); + nif->read(mMass); + nif->read(mLinearDamping); + nif->read(mAngularDamping); if (nif->getBethVersion() >= 83) { if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) - mTimeFactor = nif->getFloat(); - mGravityFactor = nif->getFloat(); + nif->read(mTimeFactor); + nif->read(mGravityFactor); } - mFriction = nif->getFloat(); + nif->read(mFriction); if (nif->getBethVersion() >= 83) - mRollingFrictionMult = nif->getFloat(); - mRestitution = nif->getFloat(); + nif->read(mRollingFrictionMult); + nif->read(mRestitution); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { - mMaxLinearVelocity = nif->getFloat(); - mMaxAngularVelocity = nif->getFloat(); + nif->read(mMaxLinearVelocity); + nif->read(mMaxAngularVelocity); if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) - mPenetrationDepth = nif->getFloat(); + nif->read(mPenetrationDepth); } - mMotionType = static_cast(nif->getChar()); + mMotionType = static_cast(nif->get()); if (nif->getBethVersion() < 83) - mDeactivatorType = static_cast(nif->getChar()); + mDeactivatorType = static_cast(nif->get()); else - mEnableDeactivation = nif->getBoolean(); - mSolverDeactivation = static_cast(nif->getChar()); + nif->read(mEnableDeactivation); + mSolverDeactivation = static_cast(nif->get()); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) { nif->skip(1); - mPenetrationDepth = nif->getFloat(); - mTimeFactor = nif->getFloat(); + nif->read(mPenetrationDepth); + nif->read(mTimeFactor); nif->skip(4); - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); } - mQualityType = static_cast(nif->getChar()); + mQualityType = static_cast(nif->get()); if (nif->getBethVersion() >= 83) { - mAutoRemoveLevel = nif->getChar(); - mResponseModifierFlags = nif->getChar(); - mNumContactPointShapeKeys = nif->getChar(); - mForceCollidedOntoPPU = nif->getBoolean(); + nif->read(mAutoRemoveLevel); + nif->read(mResponseModifierFlags); + nif->read(mNumContactPointShapeKeys); + nif->read(mForceCollidedOntoPPU); } if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) nif->skip(3); // Unused @@ -182,7 +184,7 @@ namespace Nif void bhkConstraintCInfo::read(NIFStream* nif) { - nif->get(); // Number of entities, unused + nif->get(); // Number of entities, unused mEntityA.read(nif); mEntityB.read(nif); @@ -203,7 +205,7 @@ namespace Nif nif->read(mDamping); nif->read(mProportionalRecoveryVelocity); nif->read(mConstantRecoveryVelocity); - mEnabled = nif->getBoolean(); + nif->read(mEnabled); } void bhkVelocityConstraintMotor::read(NIFStream* nif) @@ -212,8 +214,8 @@ namespace Nif nif->read(mMaxForce); nif->read(mTau); nif->read(mTargetVelocity); - mUseVelocityTarget = nif->getBoolean(); - mEnabled = nif->getBoolean(); + nif->read(mUseVelocityTarget); + nif->read(mEnabled); } void bhkSpringDamperConstraintMotor::read(NIFStream* nif) @@ -222,7 +224,7 @@ namespace Nif nif->read(mMaxForce); nif->read(mSpringConstant); nif->read(mSpringDamping); - mEnabled = nif->getBoolean(); + nif->read(mEnabled); } void bhkConstraintMotorCInfo::read(NIFStream* nif) @@ -335,7 +337,8 @@ namespace Nif void bhkCollisionObject::read(NIFStream* nif) { NiCollisionObject::read(nif); - mFlags = nif->getUShort(); + + nif->read(mFlags); mBody.read(nif); } @@ -356,6 +359,7 @@ namespace Nif void bhkEntity::read(NIFStream* nif) { bhkWorldObject::read(nif); + mInfo.read(nif); } @@ -372,21 +376,26 @@ namespace Nif void bhkMoppBvTreeShape::read(NIFStream* nif) { bhkBvTreeShape::read(nif); + nif->skip(12); // Unused - mScale = nif->getFloat(); + nif->read(mScale); mMopp.read(nif); } void bhkNiTriStripsShape::read(NIFStream* nif) { mHavokMaterial.read(nif); - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(20); // Unused - mGrowBy = nif->getUInt(); + nif->read(mGrowBy); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mScale = nif->getVector4(); + nif->read(mScale); readRecordList(nif, mData); - nif->readVector(mFilters, nif->getUInt()); + uint32_t numFilters; + nif->read(numFilters); + mHavokFilters.resize(numFilters); + for (HavokFilter& filter : mHavokFilters) + filter.read(nif); } void bhkNiTriStripsShape::post(Reader& nif) @@ -398,15 +407,17 @@ namespace Nif { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { - mSubshapes.resize(nif->getUShort()); + uint16_t numSubshapes; + nif->read(numSubshapes); + mSubshapes.resize(numSubshapes); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } - mUserData = nif->getUInt(); + nif->read(mUserData); nif->skip(4); // Unused - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(4); // Unused - mScale = nif->getVector4(); + nif->read(mScale); nif->skip(20); // Duplicates of the two previous fields mData.read(nif); } @@ -418,22 +429,26 @@ namespace Nif void hkPackedNiTriStripsData::read(NIFStream* nif) { - unsigned int numTriangles = nif->getUInt(); + uint32_t numTriangles; + nif->read(numTriangles); mTriangles.resize(numTriangles); - for (unsigned int i = 0; i < numTriangles; i++) + for (uint32_t i = 0; i < numTriangles; i++) mTriangles[i].read(nif); - unsigned int numVertices = nif->getUInt(); + uint32_t numVertices; + nif->read(numVertices); bool compressed = false; if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - compressed = nif->getBoolean(); + nif->read(compressed); if (!compressed) nif->readVector(mVertices, numVertices); else nif->skip(6 * numVertices); // Half-precision vectors are not currently supported if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) { - mSubshapes.resize(nif->getUShort()); + uint16_t numSubshapes; + nif->read(numSubshapes); + mSubshapes.resize(numSubshapes); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } @@ -447,23 +462,25 @@ namespace Nif void bhkConvexShape::read(NIFStream* nif) { bhkSphereRepShape::read(nif); - mRadius = nif->getFloat(); + + nif->read(mRadius); } void bhkConvexVerticesShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + mVerticesProperty.read(nif); mNormalsProperty.read(nif); - nif->readVector(mVertices, nif->getUInt()); - nif->readVector(mNormals, nif->getUInt()); + nif->readVector(mVertices, nif->get()); + nif->readVector(mNormals, nif->get()); } void bhkConvexTransformShape::read(NIFStream* nif) { mShape.read(nif); mHavokMaterial.read(nif); - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(8); // Unused std::array mat; nif->readArray(mat); @@ -478,19 +495,21 @@ namespace Nif void bhkBoxShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + nif->skip(8); // Unused - mExtents = nif->getVector3(); + nif->read(mExtents); nif->skip(4); // Unused } void bhkCapsuleShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + nif->skip(8); // Unused - mPoint1 = nif->getVector3(); - mRadius1 = nif->getFloat(); - mPoint2 = nif->getVector3(); - mRadius2 = nif->getFloat(); + nif->read(mPoint1); + nif->read(mRadius1); + nif->read(mPoint2); + nif->read(mRadius2); } void bhkListShape::read(NIFStream* nif) @@ -499,7 +518,8 @@ namespace Nif mHavokMaterial.read(nif); mChildShapeProperty.read(nif); mChildFilterProperty.read(nif); - unsigned int numFilters = nif->getUInt(); + uint32_t numFilters; + nif->read(numFilters); mHavokFilters.resize(numFilters); for (HavokFilter& filter : mHavokFilters) filter.read(nif); @@ -508,12 +528,12 @@ namespace Nif void bhkCompressedMeshShape::read(NIFStream* nif) { mTarget.read(nif); - mUserData = nif->getUInt(); - mRadius = nif->getFloat(); - nif->getFloat(); // Unknown - mScale = nif->getVector4(); - nif->getFloat(); // Radius - nif->getVector4(); // Scale + nif->read(mUserData); + nif->read(mRadius); + nif->skip(4); // Unknown + nif->read(mScale); + nif->skip(4); // Radius + nif->skip(16); // Scale mData.read(nif); } @@ -525,60 +545,66 @@ namespace Nif void bhkCompressedMeshShapeData::read(NIFStream* nif) { - mBitsPerIndex = nif->getUInt(); - mBitsPerWIndex = nif->getUInt(); - mMaskWIndex = nif->getUInt(); - mMaskIndex = nif->getUInt(); - mError = nif->getFloat(); - mAabbMin = nif->getVector4(); - mAabbMax = nif->getVector4(); - mWeldingType = nif->getChar(); - mMaterialType = nif->getChar(); - nif->skip(nif->getUInt() * 4); // Unused - nif->skip(nif->getUInt() * 4); // Unused - nif->skip(nif->getUInt() * 4); // Unused + nif->read(mBitsPerIndex); + nif->read(mBitsPerWIndex); + nif->read(mMaskWIndex); + nif->read(mMaskIndex); + nif->read(mError); + nif->read(mAabbMin); + nif->read(mAabbMax); + nif->read(mWeldingType); + nif->read(mMaterialType); + nif->skip(nif->get() * 4); // Unused + nif->skip(nif->get() * 4); // Unused + nif->skip(nif->get() * 4); // Unused - size_t numMaterials = nif->getUInt(); + uint32_t numMaterials; + nif->read(numMaterials); mMaterials.resize(numMaterials); for (bhkMeshMaterial& material : mMaterials) material.read(nif); - nif->getUInt(); // Unused - size_t numTransforms = nif->getUInt(); + nif->skip(4); // Unused + uint32_t numTransforms; + nif->read(numTransforms); mChunkTransforms.resize(numTransforms); for (bhkQsTransform& transform : mChunkTransforms) transform.read(nif); - nif->readVector(mBigVerts, nif->getUInt()); + nif->readVector(mBigVerts, nif->get()); - size_t numBigTriangles = nif->getUInt(); + uint32_t numBigTriangles; + nif->read(numBigTriangles); mBigTris.resize(numBigTriangles); for (bhkCMSBigTri& tri : mBigTris) tri.read(nif); - size_t numChunks = nif->getUInt(); + uint32_t numChunks; + nif->read(numChunks); mChunks.resize(numChunks); for (bhkCMSChunk& chunk : mChunks) chunk.read(nif); - nif->getUInt(); // Unused + nif->skip(4); // Unused } void bhkRigidBody::read(NIFStream* nif) { bhkEntity::read(nif); + mInfo.read(nif); readRecordList(nif, mConstraints); if (nif->getBethVersion() < 76) - mBodyFlags = nif->getUInt(); + nif->read(mBodyFlags); else - mBodyFlags = nif->getUShort(); + mBodyFlags = nif->get(); } void bhkSimpleShapePhantom::read(NIFStream* nif) { bhkWorldObject::read(nif); + nif->skip(8); // Unused std::array mat; nif->readArray(mat); @@ -598,18 +624,21 @@ namespace Nif void bhkRagdollConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } void bhkHingeConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } void bhkLimitedHingeConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index cc1a3ba755..a7bfa1425d 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP #define OPENMW_COMPONENTS_NIF_PHYSICS_HPP +#include "niftypes.hpp" #include "record.hpp" #include "recordptr.hpp" @@ -23,9 +24,10 @@ namespace Nif struct bhkWorldObjCInfoProperty { - unsigned int mData; - unsigned int mSize; - unsigned int mCapacityAndFlags; + uint32_t mData; + uint32_t mSize; + uint32_t mCapacityAndFlags; + void read(NIFStream* nif); }; @@ -41,28 +43,32 @@ namespace Nif { BroadPhaseType mPhaseType; bhkWorldObjCInfoProperty mProperty; + void read(NIFStream* nif); }; struct HavokMaterial { - unsigned int mMaterial; + uint32_t mMaterial; + void read(NIFStream* nif); }; struct HavokFilter { - unsigned char mLayer; - unsigned char mFlags; - unsigned short mGroup; + uint8_t mLayer; + uint8_t mFlags; + uint16_t mGroup; + void read(NIFStream* nif); }; struct hkSubPartData { HavokMaterial mHavokMaterial; - unsigned int mNumVertices; + uint32_t mNumVertices; HavokFilter mHavokFilter; + void read(NIFStream* nif); }; @@ -77,22 +83,26 @@ namespace Nif struct bhkEntityCInfo { hkResponseType mResponseType; - unsigned short mProcessContactDelay; + uint16_t mProcessContactDelay; + void read(NIFStream* nif); }; struct hkpMoppCode { osg::Vec4f mOffset; - std::vector mData; + uint8_t mBuildType; + std::vector mData; + void read(NIFStream* nif); }; struct TriangleData { - unsigned short mTriangle[3]; - unsigned short mWeldingInfo; + std::array mTriangle; + uint16_t mWeldingInfo; osg::Vec3f mNormal; + void read(NIFStream* nif); }; @@ -100,6 +110,7 @@ namespace Nif { HavokMaterial mHavokMaterial; HavokFilter mHavokFilter; + void read(NIFStream* nif); }; @@ -107,27 +118,30 @@ namespace Nif { osg::Vec4f mTranslation; osg::Quat mRotation; + void read(NIFStream* nif); }; struct bhkCMSBigTri { - unsigned short mTriangle[3]; - unsigned int mMaterial; - unsigned short mWeldingInfo; + std::array mTriangle; + uint32_t mMaterial; + uint16_t mWeldingInfo; + void read(NIFStream* nif); }; struct bhkCMSChunk { osg::Vec4f mTranslation; - unsigned int mMaterialIndex; - unsigned short mReference; - unsigned short mTransformIndex; - std::vector mVertices; - std::vector mIndices; - std::vector mStrips; - std::vector mWeldingInfos; + uint32_t mMaterialIndex; + uint16_t mReference; + uint16_t mTransformIndex; + std::vector mVertices; + std::vector mIndices; + std::vector mStrips; + std::vector mWeldingInfos; + void read(NIFStream* nif); }; @@ -180,12 +194,12 @@ namespace Nif { HavokFilter mHavokFilter; hkResponseType mResponseType; - unsigned short mProcessContactDelay; + uint16_t mProcessContactDelay; osg::Vec4f mTranslation; osg::Quat mRotation; osg::Vec4f mLinearVelocity; osg::Vec4f mAngularVelocity; - float mInertiaTensor[3][4]; + Matrix3 mInertiaTensor; osg::Vec4f mCenter; float mMass; float mLinearDamping; @@ -203,10 +217,11 @@ namespace Nif bool mEnableDeactivation{ true }; hkSolverDeactivation mSolverDeactivation; hkQualityType mQualityType; - unsigned char mAutoRemoveLevel; - unsigned char mResponseModifierFlags; - unsigned char mNumContactPointShapeKeys; + uint8_t mAutoRemoveLevel; + uint8_t mResponseModifierFlags; + uint8_t mNumContactPointShapeKeys; bool mForceCollidedOntoPPU; + void read(NIFStream* nif); }; @@ -222,6 +237,7 @@ namespace Nif bhkEntityPtr mEntityA; bhkEntityPtr mEntityB; ConstraintPriority mPriority; + void read(NIFStream* nif); void post(Reader& nif); }; @@ -242,6 +258,7 @@ namespace Nif float mProportionalRecoveryVelocity; float mConstantRecoveryVelocity; bool mEnabled; + void read(NIFStream* nif); }; @@ -252,6 +269,7 @@ namespace Nif float mTargetVelocity; bool mUseVelocityTarget; bool mEnabled; + void read(NIFStream* nif); }; @@ -261,6 +279,7 @@ namespace Nif float mSpringConstant; float mSpringDamping; bool mEnabled; + void read(NIFStream* nif); }; @@ -270,6 +289,7 @@ namespace Nif bhkPositionConstraintMotor mPositionMotor; bhkVelocityConstraintMotor mVelocityMotor; bhkSpringDamperConstraintMotor mSpringDamperMotor; + void read(NIFStream* nif); }; @@ -289,6 +309,7 @@ namespace Nif float mTwistMinAngle, mTwistMaxAngle; float mMaxFriction; bhkConstraintMotorCInfo mMotor; + void read(NIFStream* nif); }; @@ -301,8 +322,10 @@ namespace Nif osg::Vec4f mPerpAxis1; osg::Vec4f mPerpAxis2; }; + HingeData mDataA; HingeData mDataB; + void read(NIFStream* nif); }; @@ -315,11 +338,13 @@ namespace Nif osg::Vec4f mPerpAxis1; osg::Vec4f mPerpAxis2; }; + HingeData mDataA; HingeData mDataB; float mMinAngle, mMaxAngle; float mMaxFriction; bhkConstraintMotorCInfo mMotor; + void read(NIFStream* nif); }; @@ -358,7 +383,7 @@ namespace Nif // Bethesda Havok-specific collision object struct bhkCollisionObject : public NiCollisionObject { - unsigned short mFlags; + uint16_t mFlags; bhkWorldObjectPtr mBody; void read(NIFStream* nif) override; @@ -375,6 +400,7 @@ namespace Nif bhkShapePtr mShape; HavokFilter mHavokFilter; bhkWorldObjectCInfo mWorldObjectInfo; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -383,6 +409,7 @@ namespace Nif struct bhkEntity : public bhkWorldObject { bhkEntityCInfo mInfo; + void read(NIFStream* nif) override; }; @@ -391,6 +418,7 @@ namespace Nif struct bhkBvTreeShape : public bhkShape { bhkShapePtr mShape; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -400,6 +428,7 @@ namespace Nif { float mScale; hkpMoppCode mMopp; + void read(NIFStream* nif) override; }; @@ -408,10 +437,11 @@ namespace Nif { HavokMaterial mHavokMaterial; float mRadius; - unsigned int mGrowBy; + uint32_t mGrowBy; osg::Vec4f mScale{ 1.f, 1.f, 1.f, 0.f }; NiTriStripsDataList mData; - std::vector mFilters; + std::vector mHavokFilters; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -420,7 +450,7 @@ namespace Nif struct bhkPackedNiTriStripsShape : public bhkShapeCollection { std::vector mSubshapes; - unsigned int mUserData; + uint32_t mUserData; float mRadius; osg::Vec4f mScale; hkPackedNiTriStripsDataPtr mData; @@ -435,6 +465,7 @@ namespace Nif std::vector mTriangles; std::vector mVertices; std::vector mSubshapes; + void read(NIFStream* nif) override; }; @@ -442,6 +473,7 @@ namespace Nif struct bhkSphereRepShape : public bhkShape { HavokMaterial mHavokMaterial; + void read(NIFStream* nif) override; }; @@ -449,6 +481,7 @@ namespace Nif struct bhkConvexShape : public bhkSphereRepShape { float mRadius; + void read(NIFStream* nif) override; }; @@ -459,6 +492,7 @@ namespace Nif bhkWorldObjCInfoProperty mNormalsProperty; std::vector mVertices; std::vector mNormals; + void read(NIFStream* nif) override; }; @@ -468,6 +502,7 @@ namespace Nif HavokMaterial mHavokMaterial; float mRadius; osg::Matrixf mTransform; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -476,6 +511,7 @@ namespace Nif struct bhkBoxShape : public bhkConvexShape { osg::Vec3f mExtents; + void read(NIFStream* nif) override; }; @@ -499,28 +535,30 @@ namespace Nif bhkWorldObjCInfoProperty mChildShapeProperty; bhkWorldObjCInfoProperty mChildFilterProperty; std::vector mHavokFilters; + void read(NIFStream* nif) override; }; struct bhkCompressedMeshShape : public bhkShape { NodePtr mTarget; - unsigned int mUserData; + uint32_t mUserData; float mRadius; osg::Vec4f mScale; bhkCompressedMeshShapeDataPtr mData; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; struct bhkCompressedMeshShapeData : public bhkRefObject { - unsigned int mBitsPerIndex, mBitsPerWIndex; - unsigned int mMaskWIndex, mMaskIndex; + uint32_t mBitsPerIndex, mBitsPerWIndex; + uint32_t mMaskWIndex, mMaskIndex; float mError; osg::Vec4f mAabbMin, mAabbMax; - char mWeldingType; - char mMaterialType; + uint8_t mWeldingType; + uint8_t mMaterialType; std::vector mMaterials; std::vector mChunkTransforms; std::vector mBigVerts; @@ -534,7 +572,7 @@ namespace Nif { bhkRigidBodyCInfo mInfo; bhkSerializableList mConstraints; - unsigned int mBodyFlags; + uint32_t mBodyFlags; void read(NIFStream* nif) override; }; @@ -542,6 +580,7 @@ namespace Nif struct bhkSimpleShapePhantom : public bhkWorldObject { osg::Matrixf mTransform; + void read(NIFStream* nif) override; }; @@ -549,6 +588,7 @@ namespace Nif struct bhkConstraint : public bhkSerializable { bhkConstraintCInfo mInfo; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -556,18 +596,21 @@ namespace Nif struct bhkRagdollConstraint : public bhkConstraint { bhkRagdollConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; struct bhkHingeConstraint : public bhkConstraint { bhkHingeConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; struct bhkLimitedHingeConstraint : public bhkConstraint { bhkLimitedHingeConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; From 2edf3399e2dc2d5124462590b84fe1dbbcd2e405 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 1 Sep 2023 00:58:23 +0300 Subject: [PATCH 0038/2167] Modernize extra data records --- apps/openmw_test_suite/nif/node.hpp | 2 +- .../nifloader/testbulletnifloader.cpp | 18 +-- components/nif/base.cpp | 6 +- components/nif/base.hpp | 8 +- components/nif/extra.cpp | 101 ++++--------- components/nif/extra.hpp | 135 ++++++------------ components/nifbullet/bulletnifloader.cpp | 15 +- components/nifosg/nifloader.cpp | 26 ++-- 8 files changed, 110 insertions(+), 201 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 7e413d03cd..2d82289592 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -13,7 +13,7 @@ namespace Nif::Testing inline void init(Extra& value) { - value.next = ExtraPtr(nullptr); + value.mNext = ExtraPtr(nullptr); } inline void init(Named& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 25df366d23..49030a8902 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -996,7 +996,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { - mNiStringExtraData.string = "NCC__"; + mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1024,8 +1024,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { - mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.string = "NCC__"; + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1052,7 +1052,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { - mNiStringExtraData.string = "NC___"; + mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1079,8 +1079,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { - mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.string = "NC___"; + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1141,7 +1141,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) { - mNiStringExtraData.string = "MRK"; + mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1160,7 +1160,7 @@ namespace TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.data = 32; // BSX flag "editor marker" + mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" mNiIntegerExtraData.recType = Nif::RC_BSXFlags; mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.parents.push_back(&mNiNode); @@ -1181,7 +1181,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { - mNiStringExtraData.string = "MRK"; + mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode2); diff --git a/components/nif/base.cpp b/components/nif/base.cpp index ed440cd96d..af98cfa16d 100644 --- a/components/nif/base.cpp +++ b/components/nif/base.cpp @@ -5,11 +5,11 @@ namespace Nif void Extra::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - name = nif->getString(); + nif->read(mName); else if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) { - next.read(nif); - recordSize = nif->getUInt(); + mNext.read(nif); + nif->read(mRecordSize); } } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 2cdbdec77f..69094ffc51 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -13,12 +13,12 @@ namespace Nif // An extra data record. All the extra data connected to an object form a linked list. struct Extra : public Record { - std::string name; - ExtraPtr next; // Next extra data record in the list - unsigned int recordSize{ 0u }; + std::string mName; + ExtraPtr mNext; // Next extra data record in the list + uint32_t mRecordSize{ 0u }; void read(NIFStream* nif) override; - void post(Reader& nif) override { next.post(nif); } + void post(Reader& nif) override { mNext.post(nif); } }; struct Controller : public Record diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 4384289f9e..e289ae626e 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -6,25 +6,21 @@ namespace Nif void NiExtraData::read(NIFStream* nif) { Extra::read(nif); - nif->readVector(data, recordSize); - } - void NiStringExtraData::read(NIFStream* nif) - { - Extra::read(nif); - string = nif->getString(); + nif->readVector(mData, mRecordSize); } void NiTextKeyExtraData::read(NIFStream* nif) { Extra::read(nif); - int keynum = nif->getInt(); - list.resize(keynum); - for (int i = 0; i < keynum; i++) + uint32_t numKeys; + nif->read(numKeys); + mList.resize(numKeys); + for (TextKey& key : mList) { - list[i].time = nif->getFloat(); - list[i].text = nif->getString(); + nif->read(key.mTime); + nif->read(key.mText); } } @@ -32,81 +28,39 @@ namespace Nif { Extra::read(nif); - nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess - } - - void NiIntegerExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - data = nif->getUInt(); - } - - void NiIntegersExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - nif->readVector(data, nif->getUInt()); - } - - void NiBinaryExtraData::read(NIFStream* nif) - { - Extra::read(nif); - nif->readVector(data, nif->getUInt()); - } - - void NiBooleanExtraData::read(NIFStream* nif) - { - Extra::read(nif); - data = nif->getBoolean(); - } - - void NiVectorExtraData::read(NIFStream* nif) - { - Extra::read(nif); - data = nif->getVector4(); - } - - void NiFloatExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - data = nif->getFloat(); - } - - void NiFloatsExtraData::read(NIFStream* nif) - { - Extra::read(nif); - nif->readVector(data, nif->getUInt()); + nif->skip(nif->get() * sizeof(float)); // vertex weights I guess } void BSBound::read(NIFStream* nif) { Extra::read(nif); - center = nif->getVector3(); - halfExtents = nif->getVector3(); + + nif->read(mCenter); + nif->read(mExtents); } void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream* nif) { - mOffset = nif->getVector3(); - mOrientation = nif->getUShort(); - mPositionRef = nif->getChar(); + nif->read(mOffset); + nif->read(mOrientation); + nif->read(mPositionRef); nif->skip(1); // Position ref 2 } void BSFurnitureMarker::FurniturePosition::read(NIFStream* nif) { - mOffset = nif->getVector3(); - mHeading = nif->getFloat(); - mType = nif->getUShort(); - mEntryPoint = nif->getUShort(); + nif->read(mOffset); + nif->read(mHeading); + nif->read(mType); + nif->read(mEntryPoint); } void BSFurnitureMarker::read(NIFStream* nif) { Extra::read(nif); - unsigned int num = nif->getUInt(); + + uint32_t num; + nif->read(num); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { mLegacyMarkers.resize(num); @@ -124,19 +78,20 @@ namespace Nif void BSInvMarker::read(NIFStream* nif) { Extra::read(nif); - float rotX = nif->getUShort() / 1000.0; - float rotY = nif->getUShort() / 1000.0; - float rotZ = nif->getUShort() / 1000.0; - mScale = nif->getFloat(); + float rotX = nif->get() / 1000.f; + float rotY = nif->get() / 1000.f; + float rotZ = nif->get() / 1000.f; mRotation = osg::Quat(rotX, osg::X_AXIS, rotY, osg::Y_AXIS, rotZ, osg::Z_AXIS); + nif->read(mScale); } void BSBehaviorGraphExtraData::read(NIFStream* nif) { Extra::read(nif); - mFile = nif->getString(); - mControlsBaseSkeleton = nif->getBoolean(); + + nif->read(mFile); + nif->read(mControlsBaseSkeleton); } } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index b59fb2f76a..dfe4539138 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -1,26 +1,3 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (extra.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP #define OPENMW_COMPONENTS_NIF_EXTRA_HPP @@ -29,9 +6,46 @@ namespace Nif { + template + struct TypedExtra : public Extra + { + T mData; + + void read(NIFStream* nif) override + { + Extra::read(nif); + + nif->read(mData); + } + }; + + template + struct TypedVectorExtra : public Extra + { + std::vector mData; + + void read(NIFStream* nif) override + { + Extra::read(nif); + + nif->readVector(mData, nif->get()); + } + }; + + using NiBooleanExtraData = TypedExtra; + using NiFloatExtraData = TypedExtra; + using NiIntegerExtraData = TypedExtra; + using NiStringExtraData = TypedExtra; + using NiVectorExtraData = TypedExtra; + + using NiBinaryExtraData = TypedVectorExtra; + using NiFloatsExtraData = TypedVectorExtra; + using NiIntegersExtraData = TypedVectorExtra; + + // Distinct from NiBinaryExtraData, uses mRecordSize as its size struct NiExtraData : public Extra { - std::vector data; + std::vector mData; void read(NIFStream* nif) override; }; @@ -45,78 +59,17 @@ namespace Nif { struct TextKey { - float time; - std::string text; + float mTime; + std::string mText; }; - std::vector list; - - void read(NIFStream* nif) override; - }; - - struct NiStringExtraData : public Extra - { - /* Known meanings: - "MRK" - marker, only visible in the editor, not rendered in-game - "NCC" - no collision except with the camera - Anything else starting with "NC" - no collision - */ - std::string string; - - void read(NIFStream* nif) override; - }; - - struct NiIntegerExtraData : public Extra - { - unsigned int data; - - void read(NIFStream* nif) override; - }; - - struct NiIntegersExtraData : public Extra - { - std::vector data; - - void read(NIFStream* nif) override; - }; - - struct NiBinaryExtraData : public Extra - { - std::vector data; - - void read(NIFStream* nif) override; - }; - - struct NiBooleanExtraData : public Extra - { - bool data; - - void read(NIFStream* nif) override; - }; - - struct NiVectorExtraData : public Extra - { - osg::Vec4f data; - - void read(NIFStream* nif) override; - }; - - struct NiFloatExtraData : public Extra - { - float data; - - void read(NIFStream* nif) override; - }; - - struct NiFloatsExtraData : public Extra - { - std::vector data; + std::vector mList; void read(NIFStream* nif) override; }; struct BSBound : public Extra { - osg::Vec3f center, halfExtents; + osg::Vec3f mCenter, mExtents; void read(NIFStream* nif) override; }; @@ -149,7 +102,7 @@ namespace Nif struct BSInvMarker : public Extra { osg::Quat mRotation; - float mScale = 1.0f; + float mScale; void read(NIFStream* nif) override; }; @@ -162,5 +115,5 @@ namespace Nif void read(NIFStream* nif) override; }; -} // Namespace +} #endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 251795eb21..0c85949a53 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -305,7 +305,7 @@ namespace NifBullet // Check for extra data std::vector extraCollection; - for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->mNext) extraCollection.emplace_back(e); for (const auto& extraNode : node.extralist) if (!extraNode.empty()) @@ -316,29 +316,30 @@ namespace NifBullet { // String markers may contain important information // affecting the entire subtree of this node - Nif::NiStringExtraData* sd = (Nif::NiStringExtraData*)e.getPtr(); + auto sd = static_cast(e.getPtr()); - if (Misc::StringUtils::ciStartsWith(sd->string, "NC")) + if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) { // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be // uppercase - if (sd->string.length() > 2 && sd->string[2] == 'C') + if (sd->mData.length() > 2 && sd->mData[2] == 'C') // Collide only with camera. visualCollisionType = Resource::VisualCollisionType::Camera; else // No collision. visualCollisionType = Resource::VisualCollisionType::Default; } - else if (sd->string == "MRK" && args.mAutogenerated) + // Don't autogenerate collision if MRK is set. + // FIXME: verify if this covers the entire subtree + else if (sd->mData == "MRK" && args.mAutogenerated) { - // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } } else if (e->recType == Nif::RC_BSXFlags) { auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->data & 32) // Editor marker flag + if (bsxFlags->mData & 32) // Editor marker flag args.mHasMarkers = true; } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5d703cf248..dde4f261e2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -175,16 +175,16 @@ namespace void extractTextKeys(const Nif::NiTextKeyExtraData* tk, SceneUtil::TextKeyMap& textkeys) { - for (size_t i = 0; i < tk->list.size(); i++) + for (const Nif::NiTextKeyExtraData::TextKey& key : tk->mList) { std::vector results; - Misc::StringUtils::split(tk->list[i].text, results, "\r\n"); + Misc::StringUtils::split(key.mText, results, "\r\n"); for (std::string& result : results) { Misc::StringUtils::trim(result); Misc::StringUtils::lowerCaseInPlace(result); if (!result.empty()) - textkeys.emplace(tk->list[i].time, std::move(result)); + textkeys.emplace(key.mTime, std::move(result)); } } } @@ -286,9 +286,9 @@ namespace NifOsg extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); - extra = extra->next; + extra = extra->mNext; Nif::ControllerPtr ctrl = seq->controller; - for (; !extra.empty() && !ctrl.empty(); (extra = extra->next), (ctrl = ctrl->next)) + for (; !extra.empty() && !ctrl.empty(); (extra = extra->mNext), (ctrl = ctrl->next)) { if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) { @@ -316,8 +316,8 @@ namespace NifOsg osg::ref_ptr callback = new NifOsg::KeyframeController(key); setupController(key, callback, /*animflags*/ 0); - if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) - Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " + if (!target.mKeyframeControllers.emplace(strdata->mData, callback).second) + Log(Debug::Verbose) << "Controller " << strdata->mData << " present more than once in " << nif.getFilename() << ", ignoring later version"; } } @@ -648,7 +648,7 @@ namespace NifOsg std::vector extraCollection; - for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->mNext) extraCollection.emplace_back(e); for (const auto& extraNode : nifNode->extralist) @@ -670,25 +670,25 @@ namespace NifOsg // String markers may contain important information // affecting the entire subtree of this obj - if (sd->string == "MRK" && !Loader::getShowMarkers()) + if (sd->mData == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. args.mHasMarkers = true; } - else if (sd->string == "BONE") + else if (sd->mData == "BONE") { node->getOrCreateUserDataContainer()->addDescription("CustomBone"); } - else if (sd->string.rfind(extraDataIdentifer, 0) == 0) + else if (sd->mData.rfind(extraDataIdentifer, 0) == 0) { node->setUserValue( - Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length())); + Misc::OsgUserValues::sExtraData, sd->mData.substr(extraDataIdentifer.length())); } } else if (e->recType == Nif::RC_BSXFlags) { auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->data & 32) // Editor marker flag + if (bsxFlags->mData & 32) // Editor marker flag args.mHasMarkers = true; } } From 86c28921aef30d62764127bfab7499660dc9f05b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 1 Sep 2023 08:06:34 +0000 Subject: [PATCH 0039/2167] Add function to get current disposition, add baseDisposition to NPC record --- apps/openmw/mwlua/types/npc.cpp | 12 ++++++++++++ files/lua_api/openmw/types.lua | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 06bcab243b..d1a91cabc9 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,8 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.serializeText(); }); record["hair"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); }); + record["baseDisposition"] + = sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; }); record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); @@ -54,5 +57,14 @@ namespace MWLua else throw std::runtime_error("NPC or Player expected"); }; + + npc["getDisposition"] = [](const Object& o, const Object& player) -> int { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player!"); + const MWWorld::Class& cls = o.ptr().getClass(); + if (!cls.isNpc()) + throw std::runtime_error("NPC expected"); + return MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(o.ptr()); + }; } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ab913bc788..b1746bb2e8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -714,6 +714,13 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Returns the current disposition of the provided NPC. This is their derived disposition, after modifiers such as personality and faction relations are taken into account. +-- @function [parent=#NPC] getDisposition +-- @param openmw.core#GameObject object +-- @param openmw.core#GameObject player The player that you want to check the disposition for. +-- @return #number + --- -- Whether the NPC or player is in the werewolf form at the moment. -- @function [parent=#NPC] isWerewolf @@ -736,6 +743,7 @@ -- @field #string hair Path to the hair body part model -- @field #string head Path to the head body part model -- @field #number baseGold The base barter gold of the NPC +-- @field #number baseDisposition NPC's starting disposition -- @field #bool isMale The gender setting of the NPC From dd61caa96d282004d12d189d6ad83d62003eeb27 Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 1 Sep 2023 19:26:18 +0800 Subject: [PATCH 0040/2167] using misc::stringutils::format and simplify relativeposition function --- components/lua/utf8.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index eac3954230..125b33fbe1 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -1,4 +1,5 @@ #include +#include #include "utf8.hpp" @@ -17,24 +18,20 @@ namespace { double integer; if (!arg.is()) - throw std::runtime_error(std::format("bad argument #{} to '{}' (number expected, got {})", n, name, - sol::type_name(arg.lua_state(), arg.get_type()))); + throw std::runtime_error(Misc::StringUtils::format("bad argument #%i to '%s' (number expected, got %s)", n, + name, sol::type_name(arg.lua_state(), arg.get_type()))); if (std::modf(arg, &integer) != 0) throw std::runtime_error( - std::format("bad argument #{} to '{}' (number has no integer representation)", n, name)); + Misc::StringUtils::format("bad argument #{} to '{}' (number has no integer representation)", n, name)); return integer; } inline void relativePosition(int64_t& pos, const size_t& len) { - if (pos >= 0) - return; - else if (0u - pos > static_cast(len)) - pos = 0; - else - pos = len + pos + 1; + if (pos < 0) + pos = std::max(0, pos + len + 1); } // returns: first - character pos in bytes, second - character codepoint @@ -101,7 +98,8 @@ namespace LuaUtf8 { int64_t codepoint = getInteger(args[i], (i + 1), "char"); if (codepoint < 0 || codepoint > MAXUTF) - throw std::runtime_error(std::format("bad argument #{} to 'char' (value out of range)", (i + 1))); + throw std::runtime_error( + Misc::StringUtils::format("bad argument #{} to 'char' (value out of range)", (i + 1))); result += converter.to_bytes(codepoint); } From db287b2bc64da2a57bd09c4146837c31ebe248b9 Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 1 Sep 2023 19:33:25 +0800 Subject: [PATCH 0041/2167] dont use pass by const reference for small types in func arguments --- components/lua/utf8.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 125b33fbe1..d3927212b4 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -7,14 +7,14 @@ namespace { constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 constexpr uint32_t MAXUTF = 0x7FFFFFFFu; - constexpr uint32_t MAXUNICODE = 0x10FFFFu; + //constexpr uint32_t MAXUNICODE = 0x10FFFFu; inline bool isNilOrNone(const sol::stack_proxy arg) { return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); } - inline double getInteger(const sol::stack_proxy arg, const size_t& n, const std::string_view& name) + inline double getInteger(const sol::stack_proxy arg, const size_t n, const std::string_view name) { double integer; if (!arg.is()) @@ -28,14 +28,14 @@ namespace return integer; } - inline void relativePosition(int64_t& pos, const size_t& len) + inline void relativePosition(int64_t& pos, const size_t len) { if (pos < 0) pos = std::max(0, pos + len + 1); } // returns: first - character pos in bytes, second - character codepoint - std::pair decodeNextUTF8Character(const std::string_view& s, std::vector& pos_byte) + std::pair decodeNextUTF8Character(const std::string_view s, std::vector& pos_byte) { const int64_t pos = pos_byte.back() - 1; const unsigned char ch = static_cast(s[pos]); @@ -123,7 +123,7 @@ namespace LuaUtf8 utf8["len"] = [](const std::string_view& s, const sol::variadic_args args) -> std::variant> { - size_t len = s.size(); + const size_t len = s.size(); int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len"); int64_t fv = isNilOrNone(args[1]) ? -1 : getInteger(args[1], 3, "len"); From 7da8f388f597a5f243ca2f07fde18d4ca27702a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 31 Aug 2023 10:30:37 +0400 Subject: [PATCH 0042/2167] More Coverity fixes --- apps/openmw/mwclass/npc.cpp | 7 +++++-- apps/openmw/mwmechanics/magiceffects.cpp | 4 ++-- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 7 +++++-- apps/openmw/mwmechanics/npcstats.cpp | 7 ++++++- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 13 ++++++++++--- apps/openmw/mwworld/store.cpp | 5 +++-- 8 files changed, 33 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6b79f3ab7e..97debd3298 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -93,8 +94,10 @@ namespace int level = creatureStats.getLevel(); for (const ESM::Attribute& attribute : attributes) { - const ESM::Race::MaleFemale& value - = race->mData.mAttributeValues[static_cast(ESM::Attribute::refIdToIndex(attribute.mId))]; + auto index = ESM::Attribute::refIdToIndex(attribute.mId); + assert(index >= 0); + + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0d626c9e11..477e8b36a6 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -48,9 +48,9 @@ namespace MWMechanics std::string EffectKey::toString() const { const auto& store = MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* magicEffect = store->get().search(mId); + const ESM::MagicEffect* magicEffect = store->get().find(mId); return getMagicEffectString( - *magicEffect, store->get().search(mArg), store->get().search(mArg)); + *magicEffect, store->get().find(mArg), store->get().find(mArg)); } bool operator<(const EffectKey& left, const EffectKey& right) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ff720345bc..36ff9db7ac 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,5 +1,7 @@ #include "mechanicsmanagerimp.hpp" +#include + #include #include @@ -150,9 +152,10 @@ namespace MWMechanics for (const ESM::Attribute& attribute : esmStore.get()) { - const ESM::Race::MaleFemale& value - = race->mData.mAttributeValues[static_cast(ESM::Attribute::refIdToIndex(attribute.mId))]; + auto index = ESM::Attribute::refIdToIndex(attribute.mId); + assert(index >= 0); + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 3427e460c3..1ee463e622 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -488,7 +488,12 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const state.mSkillIncrease.fill(0); for (const auto& [key, value] : mSkillIncreases) - state.mSkillIncrease[static_cast(ESM::Attribute::refIdToIndex(key))] = value; + { + // TODO extend format + auto index = ESM::Attribute::refIdToIndex(key); + assert(index >= 0); + state.mSkillIncrease[static_cast(index)] = value; + } for (size_t i = 0; i < state.mSpecIncreases.size(); ++i) state.mSpecIncreases[i] = mSpecIncreases[i]; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ff6752358e..61de2ab90d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -556,7 +556,7 @@ namespace MWWorld return false; } - CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) + CellStore::CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) : mStore(esmStore) , mReaders(readers) , mCellVariant(std::move(cell)) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 496e98bf1a..23bd071ff1 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -140,7 +140,7 @@ namespace MWWorld } /// @param readerList The readers to use for loading of the cell on-demand. - CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); + CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); CellStore(const CellStore&) = delete; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d76dc36c15..e643f947aa 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -159,9 +159,16 @@ MWWorld::ContainerStore::ContainerStore() MWWorld::ContainerStore::~ContainerStore() { - MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); - for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) - worldModel->deregisterPtr(*iter); + try + { + MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) + worldModel->deregisterPtr(*iter); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Failed to deregister container store: " << e.what(); + } } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a4b8548e52..6e5b56d9ff 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1017,11 +1017,12 @@ namespace MWWorld void Store::setUp() { auto addSetting = [&](const std::string& key, ESM::Variant value) { + auto id = ESM::RefId::stringRefId(key); ESM::GameSetting setting; setting.blank(); - setting.mId = ESM::RefId::stringRefId(key); + setting.mId = id; setting.mValue = std::move(value); - auto [iter, inserted] = mStatic.insert_or_assign(setting.mId, std::move(setting)); + auto [iter, inserted] = mStatic.insert_or_assign(id, std::move(setting)); if (inserted) mShared.push_back(&iter->second); }; From 7113db8b9733f63d671381e791be5c9c4c8e5f9c Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 1 Sep 2023 20:33:42 +0000 Subject: [PATCH 0043/2167] Clear selected enchanted item and/or spell with actor.clearSelectedCastable() --- apps/openmw/mwlua/magicbindings.cpp | 35 +++++++++++++++++++++++++---- files/lua_api/openmw/types.lua | 5 +++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index d1dab50574..4d4f111ab1 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -24,6 +24,7 @@ #include "../mwmechanics/spellutil.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwworld/worldmodel.hpp" #include "localscripts.hpp" @@ -615,17 +616,43 @@ namespace MWLua context.mLuaManager->addAction([obj = Object(ptr), spellId]() { const MWWorld::Ptr& ptr = obj.ptr(); auto& stats = ptr.getClass().getCreatureStats(ptr); + + if (spellId.empty()) + { + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + else + stats.getSpells().setSelectedSpell(ESM::RefId()); + return; + } if (!stats.getSpells().hasSpell(spellId)) throw std::runtime_error("Actor doesn't know spell " + spellId.toDebugString()); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) { - int chance = 0; - if (!spellId.empty()) - chance = MWMechanics::getSpellSuccessChance(spellId, ptr); + int chance = MWMechanics::getSpellSuccessChance(spellId, ptr); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, chance); } else - ptr.getClass().getCreatureStats(ptr).getSpells().setSelectedSpell(spellId); + stats.getSpells().setSelectedSpell(spellId); + }); + }; + + actor["clearSelectedCastable"] = [context](const SelfObject& o) { + if (!o.ptr().getClass().isActor()) + throw std::runtime_error("Actor expected"); + context.mLuaManager->addAction([obj = Object(o.ptr())]() { + const MWWorld::Ptr& ptr = obj.ptr(); + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getClass().hasInventoryStore(ptr)) + { + MWWorld::InventoryStore& inventory = ptr.getClass().getInventoryStore(ptr); + inventory.setSelectedEnchantItem(inventory.end()); + } + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + else + stats.getSpells().setSelectedSpell(ESM::RefId()); }); }; diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ab913bc788..5137f61e8f 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -161,6 +161,11 @@ -- @param openmw.core#GameObject actor -- @param openmw.core#Spell spell Spell (can be nil) +--- +-- Clears the actor's selected castable(spell or enchanted item) +-- @function [parent=#Actor] clearSelectedCastable +-- @param openmw.core#GameObject actor + --- -- Get currently selected enchanted item -- @function [parent=#Actor] getSelectedEnchantedItem From 9f80d68795ef93ad549b3a358807a12a89815aff Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 1 Sep 2023 16:39:33 -0500 Subject: [PATCH 0044/2167] add servicesOffered to npc and creature records --- apps/openmw/mwlua/types/creature.cpp | 47 ++++++++++++++++++++++++++ apps/openmw/mwlua/types/npc.cpp | 49 ++++++++++++++++++++++++++++ files/lua_api/openmw/types.lua | 2 ++ 3 files changed, 98 insertions(+) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 08c0dd021c..484de9d969 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -1,6 +1,7 @@ #include "types.hpp" #include +#include #include #include #include @@ -48,5 +49,51 @@ namespace MWLua record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); + record["servicesOffered"] = sol::readonly_property([](const ESM::Creature& rec) { + std::vector providedServices; + + int mServices = rec.mAiData.mServices; + if (mServices & ESM::NPC::Spells) + providedServices.push_back("Spells"); + if (mServices & ESM::NPC::Spellmaking) + providedServices.push_back("Spellmaking"); + if (mServices & ESM::NPC::Enchanting) + providedServices.push_back("Enchanting"); + if (mServices & ESM::NPC::Repair) + providedServices.push_back("Repair"); + if (mServices & ESM::NPC::AllItems) + providedServices.push_back("Barter"); + + if (mServices & ESM::NPC::Weapon) + providedServices.push_back("Weapon"); + if (mServices & ESM::NPC::Armor) + providedServices.push_back("Armor"); + if (mServices & ESM::NPC::Clothing) + providedServices.push_back("Clothing"); + if (mServices & ESM::NPC::Books) + providedServices.push_back("Books"); + if (mServices & ESM::NPC::Ingredients) + providedServices.push_back("Ingredients"); + if (mServices & ESM::NPC::Picks) + providedServices.push_back("Picks"); + if (mServices & ESM::NPC::Probes) + providedServices.push_back("Probes"); + if (mServices & ESM::NPC::Lights) + providedServices.push_back("Lights"); + if (mServices & ESM::NPC::Apparatus) + providedServices.push_back("Apparatus"); + if (mServices & ESM::NPC::RepairItem) + providedServices.push_back("RepairItem"); + if (mServices & ESM::NPC::Misc) + providedServices.push_back("Misc"); + if (mServices & ESM::NPC::Potions) + providedServices.push_back("Potions"); + if (mServices & ESM::NPC::MagicItems) + providedServices.push_back("MagicItems"); + if (rec.getTransport().size() > 0) + providedServices.push_back("Travel"); + + return providedServices; + }); } } diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index d1a91cabc9..6d7b62b222 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -3,6 +3,7 @@ #include #include +#include "apps/openmw/mwworld/worldmodel.hpp" #include #include #include @@ -57,6 +58,54 @@ namespace MWLua else throw std::runtime_error("NPC or Player expected"); }; + record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) { + std::vector providedServices; + + int mServices = rec.mAiData.mServices; + if (mServices & ESM::NPC::Spells) + providedServices.push_back("Spells"); + if (mServices & ESM::NPC::Spellmaking) + providedServices.push_back("Spellmaking"); + if (mServices & ESM::NPC::Enchanting) + providedServices.push_back("Enchanting"); + if (mServices & ESM::NPC::Training) + providedServices.push_back("Training"); + if (mServices & ESM::NPC::Repair) + providedServices.push_back("Repair"); + if (mServices & ESM::NPC::AllItems) + providedServices.push_back("Barter"); + + if (mServices & ESM::NPC::Weapon) + providedServices.push_back("Weapon"); + if (mServices & ESM::NPC::Armor) + providedServices.push_back("Armor"); + if (mServices & ESM::NPC::Clothing) + providedServices.push_back("Clothing"); + if (mServices & ESM::NPC::Books) + providedServices.push_back("Books"); + if (mServices & ESM::NPC::Ingredients) + providedServices.push_back("Ingredients"); + if (mServices & ESM::NPC::Picks) + providedServices.push_back("Picks"); + if (mServices & ESM::NPC::Probes) + providedServices.push_back("Probes"); + if (mServices & ESM::NPC::Lights) + providedServices.push_back("Lights"); + if (mServices & ESM::NPC::Apparatus) + providedServices.push_back("Apparatus"); + if (mServices & ESM::NPC::RepairItem) + providedServices.push_back("RepairItem"); + if (mServices & ESM::NPC::Misc) + providedServices.push_back("Misc"); + if (mServices & ESM::NPC::Potions) + providedServices.push_back("Potions"); + if (mServices & ESM::NPC::MagicItems) + providedServices.push_back("MagicItems"); + if (rec.getTransport().size() > 0) + providedServices.push_back("Travel"); + + return providedServices; + }); npc["getDisposition"] = [](const Object& o, const Object& player) -> int { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ea5368efde..b4b21dcab9 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -701,6 +701,7 @@ -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature +-- @field #list<#string> servicesOffered The services of the creature, in a table. Possible entries are: Spells, Spellmaking, Enchanting, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. --- @{#NPC} functions @@ -750,6 +751,7 @@ -- @field #number baseGold The base barter gold of the NPC -- @field #number baseDisposition NPC's starting disposition -- @field #bool isMale The gender setting of the NPC +-- @field #list<#string> servicesOffered The services of the creature, in a table. Possible entries are: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. -------------------------------------------------------------------------------- From aba63c0145ddb363a62326948c696e7699dcf9e2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 1 Sep 2023 16:44:58 -0500 Subject: [PATCH 0045/2167] Shorten, move to the correct place --- apps/openmw/mwlua/types/creature.cpp | 55 +++++++-------------- apps/openmw/mwlua/types/npc.cpp | 71 +++++++++------------------- 2 files changed, 39 insertions(+), 87 deletions(-) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 484de9d969..8e8e875745 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -51,48 +51,25 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); record["servicesOffered"] = sol::readonly_property([](const ESM::Creature& rec) { std::vector providedServices; + std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, + { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, + { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, + { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, + { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, + { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, + { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, + { ESM::NPC::MagicItems, "MagicItems" } }; int mServices = rec.mAiData.mServices; - if (mServices & ESM::NPC::Spells) - providedServices.push_back("Spells"); - if (mServices & ESM::NPC::Spellmaking) - providedServices.push_back("Spellmaking"); - if (mServices & ESM::NPC::Enchanting) - providedServices.push_back("Enchanting"); - if (mServices & ESM::NPC::Repair) - providedServices.push_back("Repair"); - if (mServices & ESM::NPC::AllItems) - providedServices.push_back("Barter"); - - if (mServices & ESM::NPC::Weapon) - providedServices.push_back("Weapon"); - if (mServices & ESM::NPC::Armor) - providedServices.push_back("Armor"); - if (mServices & ESM::NPC::Clothing) - providedServices.push_back("Clothing"); - if (mServices & ESM::NPC::Books) - providedServices.push_back("Books"); - if (mServices & ESM::NPC::Ingredients) - providedServices.push_back("Ingredients"); - if (mServices & ESM::NPC::Picks) - providedServices.push_back("Picks"); - if (mServices & ESM::NPC::Probes) - providedServices.push_back("Probes"); - if (mServices & ESM::NPC::Lights) - providedServices.push_back("Lights"); - if (mServices & ESM::NPC::Apparatus) - providedServices.push_back("Apparatus"); - if (mServices & ESM::NPC::RepairItem) - providedServices.push_back("RepairItem"); - if (mServices & ESM::NPC::Misc) - providedServices.push_back("Misc"); - if (mServices & ESM::NPC::Potions) - providedServices.push_back("Potions"); - if (mServices & ESM::NPC::MagicItems) - providedServices.push_back("MagicItems"); - if (rec.getTransport().size() > 0) + for (const auto& entry : serviceNames) + { + if (mServices & entry.first) + { + providedServices.push_back(entry.second); + } + } + if (!rec.getTransport().empty()) providedServices.push_back("Travel"); - return providedServices; }); } diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 6d7b62b222..5b24005e16 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -50,6 +50,29 @@ namespace MWLua record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); + record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) { + std::vector providedServices; + std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, + { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, + { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, + { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, + { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, + { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, + { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, + { ESM::NPC::MagicItems, "MagicItems" } }; + + int mServices = rec.mAiData.mServices; + for (const auto& entry : serviceNames) + { + if (mServices & entry.first) + { + providedServices.push_back(entry.second); + } + } + if (!rec.getTransport().empty()) + providedServices.push_back("Travel"); + return providedServices; + }); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); @@ -58,54 +81,6 @@ namespace MWLua else throw std::runtime_error("NPC or Player expected"); }; - record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) { - std::vector providedServices; - - int mServices = rec.mAiData.mServices; - if (mServices & ESM::NPC::Spells) - providedServices.push_back("Spells"); - if (mServices & ESM::NPC::Spellmaking) - providedServices.push_back("Spellmaking"); - if (mServices & ESM::NPC::Enchanting) - providedServices.push_back("Enchanting"); - if (mServices & ESM::NPC::Training) - providedServices.push_back("Training"); - if (mServices & ESM::NPC::Repair) - providedServices.push_back("Repair"); - if (mServices & ESM::NPC::AllItems) - providedServices.push_back("Barter"); - - if (mServices & ESM::NPC::Weapon) - providedServices.push_back("Weapon"); - if (mServices & ESM::NPC::Armor) - providedServices.push_back("Armor"); - if (mServices & ESM::NPC::Clothing) - providedServices.push_back("Clothing"); - if (mServices & ESM::NPC::Books) - providedServices.push_back("Books"); - if (mServices & ESM::NPC::Ingredients) - providedServices.push_back("Ingredients"); - if (mServices & ESM::NPC::Picks) - providedServices.push_back("Picks"); - if (mServices & ESM::NPC::Probes) - providedServices.push_back("Probes"); - if (mServices & ESM::NPC::Lights) - providedServices.push_back("Lights"); - if (mServices & ESM::NPC::Apparatus) - providedServices.push_back("Apparatus"); - if (mServices & ESM::NPC::RepairItem) - providedServices.push_back("RepairItem"); - if (mServices & ESM::NPC::Misc) - providedServices.push_back("Misc"); - if (mServices & ESM::NPC::Potions) - providedServices.push_back("Potions"); - if (mServices & ESM::NPC::MagicItems) - providedServices.push_back("MagicItems"); - if (rec.getTransport().size() > 0) - providedServices.push_back("Travel"); - - return providedServices; - }); npc["getDisposition"] = [](const Object& o, const Object& player) -> int { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) From 85d47dd715b54b87f6844291e1f17a8a71e7505a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 1 Sep 2023 17:53:28 -0500 Subject: [PATCH 0046/2167] Add return value --- apps/openmw/mwlua/types/creature.cpp | 2 +- apps/openmw/mwlua/types/npc.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 8e8e875745..e6ed260a30 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -49,7 +49,7 @@ namespace MWLua record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); - record["servicesOffered"] = sol::readonly_property([](const ESM::Creature& rec) { + record["servicesOffered"] = sol::readonly_property([](const ESM::Creature& rec) -> std::vector { std::vector providedServices; std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 5b24005e16..167658aefa 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -50,7 +50,7 @@ namespace MWLua record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); - record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) { + record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) -> std::vector { std::vector providedServices; std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, From 8798217b51909df88e0103e4fd6305005314270a Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 1 Sep 2023 20:03:19 +0800 Subject: [PATCH 0047/2167] remove const keyword from all string_view --- components/lua/utf8.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index d3927212b4..75e7343c55 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -7,14 +7,14 @@ namespace { constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 constexpr uint32_t MAXUTF = 0x7FFFFFFFu; - //constexpr uint32_t MAXUNICODE = 0x10FFFFu; + // constexpr uint32_t MAXUNICODE = 0x10FFFFu; inline bool isNilOrNone(const sol::stack_proxy arg) { return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); } - inline double getInteger(const sol::stack_proxy arg, const size_t n, const std::string_view name) + inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) { double integer; if (!arg.is()) @@ -35,7 +35,7 @@ namespace } // returns: first - character pos in bytes, second - character codepoint - std::pair decodeNextUTF8Character(const std::string_view s, std::vector& pos_byte) + std::pair decodeNextUTF8Character(std::string_view s, std::vector& pos_byte) { const int64_t pos = pos_byte.back() - 1; const unsigned char ch = static_cast(s[pos]); @@ -106,7 +106,7 @@ namespace LuaUtf8 return result; }; - utf8["codes"] = [](const std::string_view& s) { + utf8["codes"] = [](std::string_view s) { std::vector pos_byte{ 1 }; return sol::as_function([s, pos_byte]() mutable -> sol::optional> { if (pos_byte.back() <= static_cast(s.size())) @@ -121,7 +121,7 @@ namespace LuaUtf8 }); }; - utf8["len"] = [](const std::string_view& s, + utf8["len"] = [](std::string_view s, const sol::variadic_args args) -> std::variant> { const size_t len = s.size(); int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len"); @@ -149,7 +149,7 @@ namespace LuaUtf8 }; utf8["codepoint"] - = [](const std::string_view& s, const sol::variadic_args args) -> sol::as_returns_t> { + = [](std::string_view s, const sol::variadic_args args) -> sol::as_returns_t> { size_t len = s.size(); int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "codepoint"); int64_t fv = isNilOrNone(args[1]) ? iv : getInteger(args[1], 3, "codepoint"); @@ -179,7 +179,7 @@ namespace LuaUtf8 }; utf8["offset"] - = [](const std::string_view& s, const int64_t n, const sol::variadic_args args) -> sol::optional { + = [](std::string_view s, const int64_t n, const sol::variadic_args args) -> sol::optional { size_t len = s.size(); int64_t iv = isNilOrNone(args[0]) ? ((n >= 0) ? 1 : s.size() + 1) : getInteger(args[0], 3, "offset"); std::vector pos_byte = { 1 }; From c04a0ca3a583e4c64c546c075de46d4c75516c99 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 22 Aug 2023 18:04:14 +0400 Subject: [PATCH 0048/2167] Implement Lua API for VFS --- CHANGELOG.md | 1 + CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 2 + apps/openmw/mwlua/vfsbindings.cpp | 354 ++++++++++++++++++ apps/openmw/mwlua/vfsbindings.hpp | 13 + components/vfs/manager.cpp | 13 + components/vfs/manager.hpp | 40 ++ docs/source/reference/lua-scripting/api.rst | 1 + .../reference/lua-scripting/openmw_vfs.rst | 7 + .../lua-scripting/tables/packages.rst | 2 + files/lua_api/CMakeLists.txt | 7 +- files/lua_api/openmw/vfs.lua | 159 ++++++++ 13 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 apps/openmw/mwlua/vfsbindings.cpp create mode 100644 apps/openmw/mwlua/vfsbindings.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_vfs.rst create mode 100644 files/lua_api/openmw/vfs.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c1c55275..81223fd270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds Feature #6726: Lua API for creating new objects + Feature #6864: Lua file access API Feature #6922: Improve launcher appearance Feature #6933: Support high-resolution cursor textures Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData diff --git a/CMakeLists.txt b/CMakeLists.txt index f887cb181e..adfb7ca7f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 45) +set(OPENMW_LUA_API_REVISION 46) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3df00f1be0..a8c233ff56 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings + camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal worker magicbindings ) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 2bace90614..d183d86e36 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -43,6 +43,7 @@ #include "soundbindings.hpp" #include "types/types.hpp" #include "uibindings.hpp" +#include "vfsbindings.hpp" namespace MWLua { @@ -331,6 +332,7 @@ namespace MWLua { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, + { "openmw.vfs", initVFSPackage(context) }, }; } diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp new file mode 100644 index 0000000000..d91f3f669c --- /dev/null +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -0,0 +1,354 @@ +#include "vfsbindings.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "context.hpp" +#include "luamanagerimp.hpp" + +namespace MWLua +{ + namespace + { + // Too many arguments may cause stack corruption and crash. + constexpr std::size_t sMaximumReadArguments = 20; + + // Print a message if we read a large chunk of file to string. + constexpr std::size_t sFileSizeWarningThreshold = 1024 * 1024; + + struct FileHandle + { + public: + FileHandle(Files::IStreamPtr stream, std::string_view fileName) + { + mFilePtr = std::move(stream); + mFileName = fileName; + } + + Files::IStreamPtr mFilePtr; + std::string mFileName; + }; + + std::ios_base::seekdir getSeekDir(FileHandle& self, std::string_view whence) + { + if (whence == "cur") + return std::ios_base::cur; + if (whence == "set") + return std::ios_base::beg; + if (whence == "end") + return std::ios_base::end; + + throw std::runtime_error( + "Error when handling '" + self.mFileName + "': invalid seek direction: '" + std::string(whence) + "'."); + } + + size_t getBytesLeftInStream(Files::IStreamPtr& file) + { + auto oldPos = file->tellg(); + file->seekg(0, std::ios_base::end); + auto newPos = file->tellg(); + file->seekg(oldPos, std::ios_base::beg); + + return newPos - oldPos; + } + + void printLargeDataMessage(FileHandle& file, size_t size) + { + if (!file.mFilePtr || !Settings::lua().mLuaDebug || size < sFileSizeWarningThreshold) + return; + + Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'."; + } + + sol::object readFile(LuaUtil::LuaState* lua, FileHandle& file) + { + std::ostringstream os; + if (file.mFilePtr && file.mFilePtr->peek() != EOF) + os << file.mFilePtr->rdbuf(); + + auto result = os.str(); + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), std::move(result)); + } + + sol::object readLineFromFile(LuaUtil::LuaState* lua, FileHandle& file) + { + std::string result; + if (file.mFilePtr && std::getline(*file.mFilePtr, result)) + { + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), result); + } + + return sol::nil; + } + + sol::object readNumberFromFile(LuaUtil::LuaState* lua, Files::IStreamPtr& file) + { + double number = 0; + if (file && *file >> number) + return sol::make_object(lua->sol(), number); + + return sol::nil; + } + + sol::object readCharactersFromFile(LuaUtil::LuaState* lua, FileHandle& file, size_t count) + { + if (count <= 0 && file.mFilePtr->peek() != EOF) + return sol::make_object(lua->sol(), std::string()); + + auto bytesLeft = getBytesLeftInStream(file.mFilePtr); + if (bytesLeft <= 0) + return sol::nil; + + if (count > bytesLeft) + count = bytesLeft; + + std::string result(count, '\0'); + if (file.mFilePtr->read(&result[0], count)) + { + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), result); + } + + return sol::nil; + } + + void validateFile(const FileHandle& self) + { + if (self.mFilePtr) + return; + + throw std::runtime_error("Error when handling '" + self.mFileName + "': attempt to use a closed file."); + } + + sol::variadic_results seek( + LuaUtil::LuaState* lua, FileHandle& self, std::ios_base::seekdir dir, std::streamoff off) + { + sol::variadic_results values; + try + { + self.mFilePtr->seekg(off, dir); + if (self.mFilePtr->fail() || self.mFilePtr->bad()) + { + auto msg = "Failed to seek in file '" + self.mFileName + "'"; + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + else + values.push_back(sol::make_object(lua->sol(), self.mFilePtr->tellg())); + } + catch (std::exception& e) + { + auto msg = "Failed to seek in file '" + self.mFileName + "': " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + } + } + + sol::table initVFSPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + sol::usertype vfsIterator + = context.mLua->sol().new_usertype("VFSIterator"); + vfsIterator[sol::meta_function::to_string] = [](const VFS::Manager::StatefulIterator& vfsIterator) { + return "VFSIterator{'" + vfsIterator.getPath() + "'}"; + }; + vfsIterator["path"] = sol::readonly_property( + [](const VFS::Manager::StatefulIterator& vfsIterator) { return vfsIterator.getPath(); }); + + auto createIter = [](VFS::Manager::StatefulIterator& vfsIterator) { + return sol::as_function([vfsIterator, i = 1]() mutable { + if (auto v = vfsIterator.next()) + return std::tuple, sol::optional>(i++, *v); + else + return std::tuple, sol::optional>(sol::nullopt, sol::nullopt); + }); + }; + vfsIterator["__pairs"] = createIter; + vfsIterator["__ipairs"] = createIter; + + sol::usertype handle = context.mLua->sol().new_usertype("FileHandle"); + handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; }); + handle[sol::meta_function::to_string] = [](const FileHandle& self) { + return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}"; + }; + handle["seek"] = sol::overload( + [lua = context.mLua](FileHandle& self, std::string_view whence, sol::optional offset) { + validateFile(self); + + auto off = static_cast(offset.value_or(0)); + auto dir = getSeekDir(self, whence); + + return seek(lua, self, dir, off); + }, + [lua = context.mLua](FileHandle& self, sol::optional offset) { + validateFile(self); + + auto off = static_cast(offset.value_or(0)); + + return seek(lua, self, std::ios_base::cur, off); + }); + handle["lines"] = [lua = context.mLua](FileHandle& self) { + return sol::as_function([&lua, &self]() mutable { + validateFile(self); + return readLineFromFile(lua, self); + }); + }; + + api["lines"] = [lua = context.mLua, vfs](std::string_view fileName) { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + return sol::as_function( + [lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable { + validateFile(file); + auto result = readLineFromFile(lua, file); + if (result == sol::nil) + file.mFilePtr.reset(); + + return result; + }); + }; + + handle["close"] = [lua = context.mLua](FileHandle& self) { + sol::variadic_results values; + try + { + self.mFilePtr.reset(); + if (self.mFilePtr) + { + auto msg = "Can not close file '" + self.mFileName + "': file handle is still opened."; + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + else + values.push_back(sol::make_object(lua->sol(), true)); + } + catch (std::exception& e) + { + auto msg = "Can not close file '" + self.mFileName + "': " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + handle["read"] = [lua = context.mLua](FileHandle& self, const sol::variadic_args args) { + validateFile(self); + + if (args.size() > sMaximumReadArguments) + throw std::runtime_error( + "Error when handling '" + self.mFileName + "': too many arguments for 'read'."); + + sol::variadic_results values; + // If there are no arguments, read a string + if (args.size() == 0) + { + values.push_back(readLineFromFile(lua, self)); + return values; + } + + bool success = true; + size_t i = 0; + for (i = 0; i < args.size() && success; i++) + { + if (args[i].is()) + { + auto format = args[i].as(); + + if (format == "*a" || format == "*all") + { + values.push_back(readFile(lua, self)); + continue; + } + + if (format == "*n" || format == "*number") + { + auto result = readNumberFromFile(lua, self.mFilePtr); + values.push_back(result); + if (result == sol::nil) + success = false; + continue; + } + + if (format == "*l" || format == "*line") + { + auto result = readLineFromFile(lua, self); + values.push_back(result); + if (result == sol::nil) + success = false; + continue; + } + + throw std::runtime_error("Error when handling '" + self.mFileName + "': bad argument #" + + std::to_string(i + 1) + " to 'read' (invalid format)"); + } + else if (args[i].is()) + { + int number = args[i].as(); + auto result = readCharactersFromFile(lua, self, number); + values.push_back(result); + if (result == sol::nil) + success = false; + } + } + + // We should return nil if we just reached the end of stream + if (!success && self.mFilePtr->eof()) + return values; + + if (!success && (self.mFilePtr->fail() || self.mFilePtr->bad())) + { + auto msg = "Error when handling '" + self.mFileName + "': can not read data for argument #" + + std::to_string(i); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + api["open"] = [lua = context.mLua, vfs](std::string_view fileName) { + sol::variadic_results values; + try + { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + auto handle = FileHandle(vfs->getNormalized(normalizedName), normalizedName); + values.push_back(sol::make_object(lua->sol(), std::move(handle))); + } + catch (std::exception& e) + { + auto msg = "Can not open file: " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + api["type"] = sol::overload( + [](const FileHandle& handle) -> std::string { + if (handle.mFilePtr) + return "file"; + + return "closed file"; + }, + [](const sol::object&) -> sol::object { return sol::nil; }); + + api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; + api["getIterator"] + = [vfs](std::string_view path) -> VFS::Manager::StatefulIterator { return vfs->getStatefulIterator(path); }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/vfsbindings.hpp b/apps/openmw/mwlua/vfsbindings.hpp new file mode 100644 index 0000000000..b251db6fd4 --- /dev/null +++ b/apps/openmw/mwlua/vfsbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_VFSBINDINGS_H +#define MWLUA_VFSBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initVFSPackage(const Context&); +} + +#endif // MWLUA_VFSBINDINGS_H diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index bfc001e4f2..0484e06c54 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -89,4 +89,17 @@ namespace VFS ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } + + Manager::StatefulIterator Manager::getStatefulIterator(std::string_view path) const + { + if (path.empty()) + return { mIndex.begin(), mIndex.end(), std::string() }; + std::string normalized = Path::normalizeFilename(path); + const auto it = mIndex.lower_bound(normalized); + if (it == mIndex.end() || !startsWith(it->first, normalized)) + return { it, it, normalized }; + std::string upperBound = normalized; + ++upperBound.back(); + return { it, mIndex.lower_bound(upperBound), normalized }; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index db38e4b240..bfb44c3fc2 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -48,11 +49,18 @@ namespace VFS const std::string& operator*() const { return mIt->first; } const std::string* operator->() const { return &mIt->first; } bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } + bool operator==(const RecursiveDirectoryIterator& other) const { return mIt == other.mIt; } RecursiveDirectoryIterator& operator++() { ++mIt; return *this; } + RecursiveDirectoryIterator operator++(int) + { + RecursiveDirectoryIterator old = *this; + mIt++; + return old; + } private: std::map::const_iterator mIt; @@ -61,6 +69,31 @@ namespace VFS using RecursiveDirectoryRange = IteratorPair; public: + class StatefulIterator : RecursiveDirectoryRange + { + public: + StatefulIterator(RecursiveDirectoryIterator first, RecursiveDirectoryIterator last, const std::string& path) + : RecursiveDirectoryRange(first, last) + , mCurrent(first) + , mPath(path) + { + } + + const std::string& getPath() const { return mPath; } + + std::optional next() + { + if (mCurrent == end()) + return std::nullopt; + + return *mCurrent++; + } + + private: + RecursiveDirectoryIterator mCurrent; + std::string mPath; + }; + // Empty the file index and unregister archives. void reset(); @@ -93,6 +126,13 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; + /// Recursively iterate over the elements of the given path + /// In practice it return all files of the VFS starting with the given path + /// Stores iterator to current element. + /// @note the path is normalized + /// @note May be called from any thread once the index has been built. + StatefulIterator getStatefulIterator(std::string_view path) const; + /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 77d4c9b14b..9000ef1188 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -17,6 +17,7 @@ Lua API reference openmw_core openmw_types openmw_async + openmw_vfs openmw_world openmw_self openmw_nearby diff --git a/docs/source/reference/lua-scripting/openmw_vfs.rst b/docs/source/reference/lua-scripting/openmw_vfs.rst new file mode 100644 index 0000000000..407459e7e0 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_vfs.rst @@ -0,0 +1,7 @@ +Package openmw.vfs +================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_vfs.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index e746274e6d..67709bbf7b 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -15,6 +15,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers and callbacks. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.self ` | by local scripts | | Full access to the object the script is attached to. | diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 87f5bfe91b..96409e803e 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -12,14 +12,15 @@ set(LUA_API_FILES openmw/ambient.lua openmw/async.lua openmw/core.lua + openmw/debug.lua openmw/nearby.lua + openmw/postprocessing.lua openmw/self.lua + openmw/types.lua openmw/ui.lua openmw/util.lua + openmw/vfs.lua openmw/world.lua - openmw/types.lua - openmw/postprocessing.lua - openmw/debug.lua ) foreach (f ${LUA_API_FILES}) diff --git a/files/lua_api/openmw/vfs.lua b/files/lua_api/openmw/vfs.lua new file mode 100644 index 0000000000..f432273810 --- /dev/null +++ b/files/lua_api/openmw/vfs.lua @@ -0,0 +1,159 @@ +--- +-- `openmw.vfs` provides read-only access to data directories via VFS. +-- Interface is very similar to "io" library. +-- @module vfs +-- @usage local vfs = require('openmw.vfs') + + + +--- +-- @type VFSIterator +-- @field #string path VFS prefix path + +--- +-- @type FileHandle +-- @field #string fileName VFS path to related file + +--- +-- Close a file handle +-- @function [parent=#FileHandle] close +-- @param self +-- @return #boolean true if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. + +--- +-- Get an iterator function to fetch the next line from given file. +-- Throws an exception if file is closed. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#FileHandle] lines +-- @param self +-- @return #function Iterator function to get next line +-- @usage f = vfs.open("Test\\test.txt"); +-- for line in f:lines() do +-- print(line); +-- end + +--- +-- Set new position in file. +-- Throws an exception if file is closed or seek base is incorrect. +-- @function [parent=#FileHandle] seek +-- @param self +-- @param #string whence Seek base (optional, "cur" by default). Can be: +-- +-- * "set" - seek from beginning of file; +-- * "cur" - seek from current position; +-- * "end" - seek from end of file (offset needs to be <= 0); +-- @param #number offset Offset from given base (optional, 0 by default) +-- @return #number new position in file if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. +-- @usage -- set pointer to beginning of file +-- f = vfs.open("Test\\test.txt"); +-- f:seek("set"); +-- @usage -- print current position in file +-- f = vfs.open("Test\\test.txt"); +-- print(f:seek()); +-- @usage -- print file size +-- f = vfs.open("Test\\test.txt"); +-- print(f:seek("end")); + +--- +-- Read data from file to strings. +-- Throws an exception if file is closed, if there is too many arguments or if an invalid format encountered. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#FileHandle] read +-- @param self +-- @param ... Read formats (up to 20 arguments, default value is one "*l"). Can be: +-- +-- * "\*a" (or "*all") - reads the whole file, starting at the current position as #string. On end of file, it returns the empty string. +-- * "\*l" (or "*line") - reads the next line (skipping the end of line), returning nil on end of file (nil and error message if error occured); +-- * "\*n" (or "*number") - read a floating point value as #number (nil and error message if error occured); +-- * number - reads a #string with up to this number of characters, returning nil on end of file (nil and error message if error occured). If number is 0 and end of file is not reached, it reads nothing and returns an empty string; +-- @return #string One #string for every format if a call succeeds without errors. One #string for every successfully handled format, nil for first failed format. +-- @usage -- read three numbers from file +-- f = vfs.open("Test\\test.txt"); +-- local n1, n2, n3 = f:read("*number", "*number", "*number"); +-- @usage -- read 10 bytes from file +-- f = vfs.open("Test\\test.txt"); +-- local n4 = f:read(10); +-- @usage -- read until end of file +-- f = vfs.open("Test\\test.txt"); +-- local n5 = f:read("*all"); +-- @usage -- read a line from file +-- f = vfs.open("Test\\test.txt"); +-- local n6 = f:read(); +-- @usage -- try to read three numbers from file with "1" content +-- f = vfs.open("one.txt"); +-- print(f:read("*number", "*number", "*number")); +-- -- prints(1, nil) + +--- +-- Check if file exists in VFS +-- @function [parent=#vfs] fileExists +-- @param #string fileName Path to file in VFS +-- @return #boolean (true - exists, false - does not exist) +-- @usage local exists = vfs.fileExists("Test\\test.txt"); + +--- +-- Open a file +-- @function [parent=#vfs] open +-- @param #string fileName Path to file in VFS +-- @return #FileHandle Opened file handle if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. +-- @usage f, msg = vfs.open("Test\\test.txt"); +-- -- print file name or error message +-- if (f == nil) +-- print(msg); +-- else +-- print(f.fileName); +-- end + +--- +-- Get an iterator function to fetch the next line from file with given path. +-- Throws an exception if file is closed or file with given path does not exist. +-- Closes file automatically when it fails to read any more bytes. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#vfs] lines +-- @param #string fileName Path to file in VFS +-- @return #function Iterator function to get next line +-- @usage for line in vfs.lines("Test\\test.txt") do +-- print(line); +-- end + +--- +-- Get iterator to fetch file names with given path prefix from VFS +-- @function [parent=#vfs] getIterator +-- @param #string path Path prefix +-- @return #VFSIterator Opened iterator +-- @usage local dir = vfs.getIterator("Music\\Explore"); +-- for _, fileName in pairs(dir) do +-- print(fileName); +-- end + +--- +-- Detect a file handle type +-- @function [parent=#vfs] type +-- @param #any handle Object to check +-- @return #string File handle type. Can be: +-- +-- * "file" - an argument is a valid opened @{openmw.vfs#FileHandle}; +-- * "closed file" - an argument is a valid closed @{openmw.vfs#FileHandle}; +-- * nil - an argument is not a @{openmw.vfs#FileHandle}; +-- @usage f = vfs.open("Test\\test.txt"); +-- print(vfs.type(f)); + +return nil From 65109b3822f74af4ef22e6783f89c434d62bee8f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 2 Sep 2023 17:32:22 +0400 Subject: [PATCH 0049/2167] Simplify VFS index iteration --- apps/openmw/mwlua/vfsbindings.cpp | 34 ++++++++++---------------- components/vfs/manager.cpp | 13 ---------- components/vfs/manager.hpp | 40 ------------------------------- files/lua_api/openmw/vfs.lua | 18 +++++++------- 4 files changed, 22 insertions(+), 83 deletions(-) diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index d91f3f669c..ad32520649 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -160,25 +160,6 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - sol::usertype vfsIterator - = context.mLua->sol().new_usertype("VFSIterator"); - vfsIterator[sol::meta_function::to_string] = [](const VFS::Manager::StatefulIterator& vfsIterator) { - return "VFSIterator{'" + vfsIterator.getPath() + "'}"; - }; - vfsIterator["path"] = sol::readonly_property( - [](const VFS::Manager::StatefulIterator& vfsIterator) { return vfsIterator.getPath(); }); - - auto createIter = [](VFS::Manager::StatefulIterator& vfsIterator) { - return sol::as_function([vfsIterator, i = 1]() mutable { - if (auto v = vfsIterator.next()) - return std::tuple, sol::optional>(i++, *v); - else - return std::tuple, sol::optional>(sol::nullopt, sol::nullopt); - }); - }; - vfsIterator["__pairs"] = createIter; - vfsIterator["__ipairs"] = createIter; - sol::usertype handle = context.mLua->sol().new_usertype("FileHandle"); handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; }); handle[sol::meta_function::to_string] = [](const FileHandle& self) { @@ -346,8 +327,19 @@ namespace MWLua [](const sol::object&) -> sol::object { return sol::nil; }); api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; - api["getIterator"] - = [vfs](std::string_view path) -> VFS::Manager::StatefulIterator { return vfs->getStatefulIterator(path); }; + api["pathsWithPrefix"] = [vfs](std::string_view prefix) { + auto iterator = vfs->getRecursiveDirectoryIterator(prefix); + return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional { + if (current != iterator.end()) + { + const std::string& result = *current; + ++current; + return result; + } + + return sol::nullopt; + }); + }; return LuaUtil::makeReadOnly(api); } diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 0484e06c54..bfc001e4f2 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -89,17 +89,4 @@ namespace VFS ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } - - Manager::StatefulIterator Manager::getStatefulIterator(std::string_view path) const - { - if (path.empty()) - return { mIndex.begin(), mIndex.end(), std::string() }; - std::string normalized = Path::normalizeFilename(path); - const auto it = mIndex.lower_bound(normalized); - if (it == mIndex.end() || !startsWith(it->first, normalized)) - return { it, it, normalized }; - std::string upperBound = normalized; - ++upperBound.back(); - return { it, mIndex.lower_bound(upperBound), normalized }; - } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index bfb44c3fc2..db38e4b240 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -49,18 +48,11 @@ namespace VFS const std::string& operator*() const { return mIt->first; } const std::string* operator->() const { return &mIt->first; } bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } - bool operator==(const RecursiveDirectoryIterator& other) const { return mIt == other.mIt; } RecursiveDirectoryIterator& operator++() { ++mIt; return *this; } - RecursiveDirectoryIterator operator++(int) - { - RecursiveDirectoryIterator old = *this; - mIt++; - return old; - } private: std::map::const_iterator mIt; @@ -69,31 +61,6 @@ namespace VFS using RecursiveDirectoryRange = IteratorPair; public: - class StatefulIterator : RecursiveDirectoryRange - { - public: - StatefulIterator(RecursiveDirectoryIterator first, RecursiveDirectoryIterator last, const std::string& path) - : RecursiveDirectoryRange(first, last) - , mCurrent(first) - , mPath(path) - { - } - - const std::string& getPath() const { return mPath; } - - std::optional next() - { - if (mCurrent == end()) - return std::nullopt; - - return *mCurrent++; - } - - private: - RecursiveDirectoryIterator mCurrent; - std::string mPath; - }; - // Empty the file index and unregister archives. void reset(); @@ -126,13 +93,6 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; - /// Recursively iterate over the elements of the given path - /// In practice it return all files of the VFS starting with the given path - /// Stores iterator to current element. - /// @note the path is normalized - /// @note May be called from any thread once the index has been built. - StatefulIterator getStatefulIterator(std::string_view path) const; - /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/files/lua_api/openmw/vfs.lua b/files/lua_api/openmw/vfs.lua index f432273810..ba381a1249 100644 --- a/files/lua_api/openmw/vfs.lua +++ b/files/lua_api/openmw/vfs.lua @@ -6,10 +6,6 @@ ---- --- @type VFSIterator --- @field #string path VFS prefix path - --- -- @type FileHandle -- @field #string fileName VFS path to related file @@ -135,14 +131,18 @@ -- end --- --- Get iterator to fetch file names with given path prefix from VFS --- @function [parent=#vfs] getIterator +-- Get iterator function to fetch file names with given path prefix from VFS +-- @function [parent=#vfs] pathsWithPrefix -- @param #string path Path prefix --- @return #VFSIterator Opened iterator --- @usage local dir = vfs.getIterator("Music\\Explore"); --- for _, fileName in pairs(dir) do +-- @return #function Function to get next file name +-- @usage -- get all files with given prefix from VFS index +-- for fileName in vfs.pathsWithPrefix("Music\\Explore") do -- print(fileName); -- end +-- @usage -- get some first files +-- local getNextFile = vfs.pathsWithPrefix("Music\\Explore"); +-- local firstFile = getNextFile(); +-- local secondFile = getNextFile(); --- -- Detect a file handle type From 16cc1ad59f7cc18f97abb5f5d1cfa98ee531af37 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 2 Sep 2023 02:44:31 +0300 Subject: [PATCH 0050/2167] Editor: Disable Training service for creatures --- apps/opencs/model/world/refidadapterimp.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 149b5d19ca..ba9c6d65a7 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -516,10 +516,18 @@ QVariant CSMWorld::CreatureRefIdAdapter::getData(const RefIdColumn* column, cons if (column == mColumns.mBloodType) return record.get().mBloodType; - std::map::const_iterator iter = mColumns.mFlags.find(column); + { + std::map::const_iterator iter = mColumns.mFlags.find(column); - if (iter != mColumns.mFlags.end()) - return (record.get().mFlags & iter->second) != 0; + if (iter != mColumns.mFlags.end()) + return (record.get().mFlags & iter->second) != 0; + } + + { + std::map::const_iterator iter = mColumns.mServices.find(column); + if (iter != mColumns.mServices.end() && iter->second == ESM::NPC::Training) + return QVariant(); + } return ActorRefIdAdapter::getData(column, data, index); } From 44303ed8ca3c934603224b31c773bcbef118a019 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 14:10:50 -0500 Subject: [PATCH 0051/2167] Move services to new HPP --- apps/openmw/mwlua/types/actor.hpp | 54 ++++++++++++++++++++++++++++ apps/openmw/mwlua/types/creature.cpp | 25 ++----------- apps/openmw/mwlua/types/npc.cpp | 29 ++------------- 3 files changed, 59 insertions(+), 49 deletions(-) create mode 100644 apps/openmw/mwlua/types/actor.hpp diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp new file mode 100644 index 0000000000..9e009395da --- /dev/null +++ b/apps/openmw/mwlua/types/actor.hpp @@ -0,0 +1,54 @@ +#ifndef MWLUA_ACTOR_H +#define MWLUA_ACTOR_H + +#include + +#include +#include + +#include +#include + +#include "../context.hpp" +#include "../object.hpp" +namespace MWLua +{ + + template + void addActorServicesBindings(sol::usertype& record, const Context& context) + { + record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::vector { + std::vector providedServices; + std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, + { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, + { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, + { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, + { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, + { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, + { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, + { ESM::NPC::MagicItems, "MagicItems" } }; + + int services = rec.mAiData.mServices; + if constexpr (std::is_same_v) + { + if (rec.mFlags & ESM::NPC::Autocalc) + services = MWBase::Environment::get() + .getWorld() + ->getStore() + .get() + .find(rec.mClass) + ->mData.mServices; + } + for (const auto& [flag, name] : serviceNames) + { + if (services & flag) + providedServices.push_back(name); + } + + if (!rec.getTransport().empty()) + providedServices.push_back("Travel"); + return providedServices; + }); + } +} +#endif // MWLUA_TYPES_H \ No newline at end of file diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index e6ed260a30..fb87e1d375 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -1,4 +1,5 @@ #include "types.hpp" +#include "actor.hpp" #include #include @@ -49,28 +50,6 @@ namespace MWLua record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); - record["servicesOffered"] = sol::readonly_property([](const ESM::Creature& rec) -> std::vector { - std::vector providedServices; - std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, - { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, - { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, - { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, - { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, - { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, - { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, - { ESM::NPC::MagicItems, "MagicItems" } }; - - int mServices = rec.mAiData.mServices; - for (const auto& entry : serviceNames) - { - if (mServices & entry.first) - { - providedServices.push_back(entry.second); - } - } - if (!rec.getTransport().empty()) - providedServices.push_back("Travel"); - return providedServices; - }); + addActorServicesBindings(record, context); } } diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 167658aefa..7e8958e6c5 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -1,5 +1,5 @@ #include "types.hpp" - +#include "actor.hpp" #include #include @@ -49,31 +49,8 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); - - record["servicesOffered"] = sol::readonly_property([](const ESM::NPC& rec) -> std::vector { - std::vector providedServices; - std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, - { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, - { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, - { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, - { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, - { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, - { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, - { ESM::NPC::MagicItems, "MagicItems" } }; - - int mServices = rec.mAiData.mServices; - for (const auto& entry : serviceNames) - { - if (mServices & entry.first) - { - providedServices.push_back(entry.second); - } - } - if (!rec.getTransport().empty()) - providedServices.push_back("Travel"); - return providedServices; - }); - // This function is game-specific, in future we should replace it with something more universal. + addActorServicesBindings(record, context); + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); if (cls.isNpc()) From 9c4ffa82565d887871095bfd28ca52cf8e1d3891 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 14:23:57 -0500 Subject: [PATCH 0052/2167] Map by service name --- apps/openmw/mwlua/types/actor.hpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 9e009395da..3b90a6bfb0 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -17,9 +17,9 @@ namespace MWLua template void addActorServicesBindings(sol::usertype& record, const Context& context) { - record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::vector { - std::vector providedServices; - std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, + record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::map { + std::map providedServices; + const static std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, @@ -41,12 +41,9 @@ namespace MWLua } for (const auto& [flag, name] : serviceNames) { - if (services & flag) - providedServices.push_back(name); + providedServices[name] = (services & flag) != 0; } - - if (!rec.getTransport().empty()) - providedServices.push_back("Travel"); + providedServices["Travel"] = !rec.getTransport().empty(); return providedServices; }); } From e50532691bd5cda789389948d6ccea32af66c445 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 14:55:04 -0500 Subject: [PATCH 0053/2167] Fix define endif --- apps/openmw/mwlua/types/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 3b90a6bfb0..4e87a20473 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -48,4 +48,4 @@ namespace MWLua }); } } -#endif // MWLUA_TYPES_H \ No newline at end of file +#endif // MWLUA_ACTOR_H \ No newline at end of file From bbe7702dbc20cb114b6ae84107d32762c812b98c Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:13:46 -0500 Subject: [PATCH 0054/2167] Use string_view --- apps/openmw/mwlua/types/actor.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 4e87a20473..c322fe3760 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -17,16 +17,17 @@ namespace MWLua template void addActorServicesBindings(sol::usertype& record, const Context& context) { - record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::map { - std::map providedServices; - const static std::map serviceNames = { { ESM::NPC::Spells, "Spells" }, + record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::map { + std::map providedServices; + constexpr std::array, 19> serviceNames = { { { ESM::NPC::Spells, + "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, { ESM::NPC::Training, "Training" }, { ESM::NPC::Repair, "Repair" }, { ESM::NPC::AllItems, "Barter" }, { ESM::NPC::Weapon, "Weapon" }, { ESM::NPC::Armor, "Armor" }, { ESM::NPC::Clothing, "Clothing" }, { ESM::NPC::Books, "Books" }, { ESM::NPC::Ingredients, "Ingredients" }, { ESM::NPC::Picks, "Picks" }, { ESM::NPC::Probes, "Probes" }, { ESM::NPC::Lights, "Lights" }, { ESM::NPC::Apparatus, "Apparatus" }, { ESM::NPC::RepairItem, "RepairItem" }, { ESM::NPC::Misc, "Misc" }, { ESM::NPC::Potions, "Potions" }, - { ESM::NPC::MagicItems, "MagicItems" } }; + { ESM::NPC::MagicItems, "MagicItems" } } }; int services = rec.mAiData.mServices; if constexpr (std::is_same_v) From 40925fa912a66ab8460133403b7c4b58bd8ee861 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:15:30 -0500 Subject: [PATCH 0055/2167] Add line --- apps/openmw/mwlua/types/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index c322fe3760..f5b78fe5c0 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -49,4 +49,4 @@ namespace MWLua }); } } -#endif // MWLUA_ACTOR_H \ No newline at end of file +#endif // MWLUA_ACTOR_H From e139135fdc76e7eada4d3d00acfe667048b65b19 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:16:41 -0500 Subject: [PATCH 0056/2167] Revert unneeded changes --- apps/openmw/mwlua/types/npc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 7e8958e6c5..761afcc881 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -1,9 +1,9 @@ #include "types.hpp" #include "actor.hpp" + #include #include -#include "apps/openmw/mwworld/worldmodel.hpp" #include #include #include @@ -50,7 +50,7 @@ namespace MWLua record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - // This function is game-specific, in future we should replace it with something more universal. + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); if (cls.isNpc()) From 070c600a8379863e53d01cad2512cecb846ee208 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:16:59 -0500 Subject: [PATCH 0057/2167] Formatting --- apps/openmw/mwlua/types/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 761afcc881..d7072569c6 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -1,5 +1,5 @@ -#include "types.hpp" #include "actor.hpp" +#include "types.hpp" #include #include From 6c6885c39488af34429a0d7e776b34cd28f3431a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:17:14 -0500 Subject: [PATCH 0058/2167] Formatting --- apps/openmw/mwlua/types/creature.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index fb87e1d375..0ded34cac5 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -1,5 +1,5 @@ -#include "types.hpp" #include "actor.hpp" +#include "types.hpp" #include #include From ee5983f64a7f59dd2988fcf44e0f1edc8f936f11 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:21:03 -0500 Subject: [PATCH 0059/2167] Docs fixes --- apps/openmw/mwlua/types/npc.cpp | 1 + files/lua_api/openmw/types.lua | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index d7072569c6..c9bde4d946 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -50,6 +50,7 @@ namespace MWLua record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b4b21dcab9..557bbabd86 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -701,7 +701,7 @@ -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature --- @field #list<#string> servicesOffered The services of the creature, in a table. Possible entries are: Spells, Spellmaking, Enchanting, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. +-- @field #list<#string> servicesOffered The services of the creature, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. --- @{#NPC} functions @@ -751,7 +751,7 @@ -- @field #number baseGold The base barter gold of the NPC -- @field #number baseDisposition NPC's starting disposition -- @field #bool isMale The gender setting of the NPC --- @field #list<#string> servicesOffered The services of the creature, in a table. Possible entries are: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. +-- @field #list<#string> servicesOffered The services of the NPC, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. -------------------------------------------------------------------------------- From 1db236b5fd3e979db68fecad2e67d3b3698479b8 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:30:02 -0500 Subject: [PATCH 0060/2167] Fix docs, dependancies --- apps/openmw/mwlua/types/actor.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index f5b78fe5c0..32a18931c0 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -6,6 +6,9 @@ #include #include +#include "apps/openmw/mwworld/esmstore.hpp" +#include +#include #include #include From ecc69b54793af3338ad8d463c9739379e6d5362f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 2 Sep 2023 15:33:06 -0500 Subject: [PATCH 0061/2167] Fix line --- apps/openmw/mwlua/types/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index c9bde4d946..e612f228e0 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -50,7 +50,7 @@ namespace MWLua record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); From b4c8b8308fc5a5bddae48f2643c20c4a21033d19 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Sep 2023 02:43:55 +0200 Subject: [PATCH 0062/2167] Lock actiovation when the game is paused; Lock movement controls when UI is opened. --- files/data/scripts/omw/activationhandlers.lua | 3 +++ files/data/scripts/omw/playercontrols.lua | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index 7d185cf9da..2f63f59e92 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -23,6 +23,9 @@ local handlersPerType = {} handlersPerType[types.ESM4Door] = { ESM4DoorActivation } local function onActivate(obj, actor) + if world.isWorldPaused() then + return + end local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 56a0b962e6..bea7e5392f 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -107,7 +107,8 @@ local function processAttacking() end local function onFrame(dt) - local controlsAllowed = input.getControlSwitch(input.CONTROL_SWITCH.Controls) and not core.isWorldPaused() + local controlsAllowed = input.getControlSwitch(input.CONTROL_SWITCH.Controls) + and not core.isWorldPaused() and not I.UI.getMode() if not movementControlsOverridden then if controlsAllowed then processMovement() @@ -165,7 +166,7 @@ local function onInputAction(action) end end - if core.isWorldPaused() then + if core.isWorldPaused() or I.UI.getMode() then return end From 23a7661d0b58c976593f549d5d048e5291aead54 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Sep 2023 02:45:18 +0200 Subject: [PATCH 0063/2167] Control UI pause from Lua --- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 10 +++++--- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwworld/datetimemanager.cpp | 4 +++- .../source/reference/lua-scripting/events.rst | 19 +++++++++++++++ files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + files/data/scripts/omw/ui.lua | 23 ++++++++++++++++++- files/data/scripts/omw/worldeventhandlers.lua | 10 ++++++++ 9 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 files/data/scripts/omw/worldeventhandlers.lua diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 727aee4abc..df6893c1ba 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -134,8 +134,8 @@ namespace MWBase virtual bool isGuiMode() const = 0; virtual bool isConsoleMode() const = 0; - virtual bool isPostProcessorHudVisible() const = 0; + virtual bool isInteractiveMessageBoxActive() const = 0; virtual void toggleVisible(MWGui::GuiWindow wnd) = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b9c52136c1..9bcfb3e158 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1539,8 +1539,7 @@ namespace MWGui bool WindowManager::isGuiMode() const { - return !mGuiModes.empty() || isConsoleMode() || (mPostProcessorHud && mPostProcessorHud->isVisible()) - || (mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); + return !mGuiModes.empty() || isConsoleMode() || isPostProcessorHudVisible() || isInteractiveMessageBoxActive(); } bool WindowManager::isConsoleMode() const @@ -1550,7 +1549,12 @@ namespace MWGui bool WindowManager::isPostProcessorHudVisible() const { - return mPostProcessorHud->isVisible(); + return mPostProcessorHud && mPostProcessorHud->isVisible(); + } + + bool WindowManager::isInteractiveMessageBoxActive() const + { + return mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox(); } MWGui::GuiMode WindowManager::getMode() const diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9d79e0a0d7..fae3bb1ec9 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -160,8 +160,8 @@ namespace MWGui bool isGuiMode() const override; bool isConsoleMode() const override; - bool isPostProcessorHudVisible() const override; + bool isInteractiveMessageBoxActive() const override; void toggleVisible(GuiWindow wnd) override; diff --git a/apps/openmw/mwworld/datetimemanager.cpp b/apps/openmw/mwworld/datetimemanager.cpp index 7559242bef..78565cef60 100644 --- a/apps/openmw/mwworld/datetimemanager.cpp +++ b/apps/openmw/mwworld/datetimemanager.cpp @@ -263,6 +263,8 @@ namespace MWWorld void DateTimeManager::updateIsPaused() { - mPaused = !mPausedTags.empty() || MWBase::Environment::get().getWindowManager()->isGuiMode(); + auto wm = MWBase::Environment::get().getWindowManager(); + mPaused = !mPausedTags.empty() || wm->isConsoleMode() || wm->isPostProcessorHudVisible() + || wm->isInteractiveMessageBoxActive(); } } diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 22cf4710d4..3e689078d0 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -42,3 +42,22 @@ and ``arg`` (for example in the mode ``Book`` the argument is the book the playe print('UiModeChanged from', data.oldMode , 'to', data.newMode, '('..tostring(data.arg)..')') end } + +World events +------------ + +Global events that just call the corresponding function in `openmw.world`. + +.. code-block:: Lua + + -- world.pause(tag) + core.sendGlobalEvent('Pause', tag) + + -- world.unpause(tag) + core.sendGlobalEvent('Unpause', tag) + + -- world.setGameTimeScale(scale) + core.sendGlobalEvent('SetGameTimeScale', scale) + + -- world.setSimulationTimeScale(scale) + core.sendGlobalEvent('SetSimulationTimeScale', scale) diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index afab9b4c79..dbf86cc44d 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -91,6 +91,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/init.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua + scripts/omw/worldeventhandlers.lua shaders/adjustments.omwfx shaders/bloomlinear.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index b86bedc509..ec08c5299d 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -9,6 +9,7 @@ PLAYER: scripts/omw/settings/player.lua GLOBAL: scripts/omw/activationhandlers.lua GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua +GLOBAL: scripts/omw/worldeventhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 0c15e53bc3..8199aec8f0 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -1,6 +1,7 @@ local ui = require('openmw.ui') local util = require('openmw.util') local self = require('openmw.self') +local core = require('openmw.core') local ambient = require('openmw.ambient') local MODE = ui._getAllUiModes() @@ -10,6 +11,11 @@ local replacedWindows = {} local hiddenWindows = {} local modeStack = {} +local modePause = {} +for _, mode in pairs(MODE) do + modePause[mode] = true +end + local function registerWindow(window, showFn, hideFn) if not WINDOW[window] then error('At the moment it is only possible to override existing windows. Window "'.. @@ -114,6 +120,15 @@ local function onUiModeChanged(arg) end end end + local shouldPause = false + for _, m in pairs(modeStack) do + shouldPause = shouldPause or modePause[m] + end + if shouldPause then + core.sendGlobalEvent('Pause', 'ui') + else + core.sendGlobalEvent('Unpause', 'ui') + end self:sendEvent('UiModeChanged', {oldMode = oldMode, newMode = mode, arg = arg}) oldMode = mode end @@ -145,7 +160,7 @@ return { interface = { --- Interface version -- @field [parent=#UI] #number version - version = 0, + version = 1, --- All available UI modes. -- Use `view(I.UI.MODE)` in `luap` console mode to see the list. @@ -204,6 +219,12 @@ return { -- @param #string mode Mode to drop removeMode = removeMode, + --- Set whether the mode should pause the game. + -- @function [parent=#UI] setPauseOnMode + -- @param #string mode Mode to configure + -- @param #boolean shouldPause + setPauseOnMode = function(mode, shouldPause) modePause[mode] = shouldPause end + -- TODO -- registerHudElement = function(name, showFn, hideFn) end, -- showHud = function(bool) end, diff --git a/files/data/scripts/omw/worldeventhandlers.lua b/files/data/scripts/omw/worldeventhandlers.lua new file mode 100644 index 0000000000..4bf6044178 --- /dev/null +++ b/files/data/scripts/omw/worldeventhandlers.lua @@ -0,0 +1,10 @@ +local world = require('openmw.world') + +return { + eventHandlers = { + Pause = function(tag) world.pause(tag) end, + Unpause = function(tag) world.unpause(tag) end, + SetGameTimeScale = function(scale) world.setGameTimeScale(scale) end, + SetSimulationTimeScale = function(scale) world.setSimulationTimeScale(scale) end, + }, +} From 65cd518b44bb39e724b2392cf7819da5a01fdbe8 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Sep 2023 02:46:23 +0200 Subject: [PATCH 0064/2167] Update camera logic related to paused game / opened UI --- apps/openmw/mwrender/camera.cpp | 6 ++---- files/data/scripts/omw/camera/camera.lua | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index fbe5c6b4c7..86d5699d75 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -138,14 +138,12 @@ namespace MWRender if (mProcessViewChange) processViewChange(); - if (paused) - return; - // only show the crosshair in game mode MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager(); wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair); - updateFocalPointOffset(duration); + if (!paused) + updateFocalPointOffset(duration); updatePosition(); } diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 6fcb46bb2f..a3d2ae7b3f 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -5,6 +5,7 @@ local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') local async = require('openmw.async') +local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor @@ -189,7 +190,7 @@ local function updateIdleTimer(dt) end local function onFrame(dt) - if core.isWorldPaused() then return end + if core.isWorldPaused() or I.UI.getMode() then return end updateIdleTimer(dt) local mode = camera.getMode() if (mode == MODE.FirstPerson or mode == MODE.ThirdPerson) and not camera.getQueuedMode() then @@ -273,7 +274,7 @@ return { onUpdate = onUpdate, onFrame = onFrame, onInputAction = function(action) - if core.isWorldPaused() then return end + if core.isWorldPaused() or I.UI.getMode() then return end if action == input.ACTION.ZoomIn then zoom(10) elseif action == input.ACTION.ZoomOut then From 66bf7be373a2d705c9819966e9cad1b2f7da39af Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 3 Sep 2023 08:24:51 +0400 Subject: [PATCH 0065/2167] Preload new packages to console --- files/data/scripts/omw/console/global.lua | 1 + files/data/scripts/omw/console/local.lua | 1 + files/data/scripts/omw/console/player.lua | 2 ++ 3 files changed, 4 insertions(+) diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index e3b4375b1e..bba0cbc7b3 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -22,6 +22,7 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), async = require('openmw.async'), world = require('openmw.world'), aux_util = require('openmw_aux.util'), diff --git a/files/data/scripts/omw/console/local.lua b/files/data/scripts/omw/console/local.lua index adcad3d6cb..6962b9e798 100644 --- a/files/data/scripts/omw/console/local.lua +++ b/files/data/scripts/omw/console/local.lua @@ -24,6 +24,7 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index 464482e7ad..c614d2d962 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -71,6 +71,8 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), + ambient = require('openmw.ambient'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), From 7eb456a169bcb1900ba542def3e7fda8f4ffce44 Mon Sep 17 00:00:00 2001 From: Kindi Date: Sun, 3 Sep 2023 19:20:59 +0800 Subject: [PATCH 0066/2167] refactoring for readability --- components/lua/utf8.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 75e7343c55..83228afa65 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -28,6 +28,9 @@ namespace return integer; } + // If the input 'pos' is negative, it is treated as counting from the end of the string, + // where -1 represents the last character position, -2 represents the second-to-last position, + // and so on. If 'pos' is non-negative, it is used as-is. inline void relativePosition(int64_t& pos, const size_t len) { if (pos < 0) @@ -181,7 +184,18 @@ namespace LuaUtf8 utf8["offset"] = [](std::string_view s, const int64_t n, const sol::variadic_args args) -> sol::optional { size_t len = s.size(); - int64_t iv = isNilOrNone(args[0]) ? ((n >= 0) ? 1 : s.size() + 1) : getInteger(args[0], 3, "offset"); + int64_t iv; + + if (isNilOrNone(args[0])) + { + if (n >= 0) + iv = 1; + else + iv = s.size() + 1; + } + else + iv = getInteger(args[0], 3, "offset"); + std::vector pos_byte = { 1 }; relativePosition(iv, len); @@ -193,14 +207,13 @@ namespace LuaUtf8 decodeNextUTF8Character(s, pos_byte); for (auto it = pos_byte.begin(); it != pos_byte.end(); ++it) + { if (*it == iv) { - if (n <= 0) - if ((it + n) >= pos_byte.begin()) - return *(it + n); - if (n > 0) - if ((it + n - 1) < pos_byte.end()) - return *(it + n - 1); + if (n <= 0 && it + n >= pos_byte.begin()) + return *(it + n); + if (n > 0 && it + n - 1 < pos_byte.end()) + return *(it + n - 1); break; } else if (*it > iv) /* a continuation byte */ @@ -210,6 +223,7 @@ namespace LuaUtf8 else throw std::runtime_error("initial position is a continuation byte"); } + } return sol::nullopt; }; From 208cd14cfe989c72d9a9e8a0fbf4311721e8bb20 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 3 Sep 2023 11:26:10 -0500 Subject: [PATCH 0067/2167] Use lua table, getESMStore --- apps/openmw/mwlua/types/actor.hpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 32a18931c0..409559475f 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -20,8 +20,9 @@ namespace MWLua template void addActorServicesBindings(sol::usertype& record, const Context& context) { - record["servicesOffered"] = sol::readonly_property([](const T& rec) -> std::map { - std::map providedServices; + record["servicesOffered"] = sol::readonly_property([context](const T& rec) -> sol::table { + sol::state_view& lua = context.mLua->sol(); + sol::table providedServices(lua, sol::create); constexpr std::array, 19> serviceNames = { { { ESM::NPC::Spells, "Spells" }, { ESM::NPC::Spellmaking, "Spellmaking" }, { ESM::NPC::Enchanting, "Enchanting" }, @@ -36,12 +37,8 @@ namespace MWLua if constexpr (std::is_same_v) { if (rec.mFlags & ESM::NPC::Autocalc) - services = MWBase::Environment::get() - .getWorld() - ->getStore() - .get() - .find(rec.mClass) - ->mData.mServices; + services + = MWBase::Environment::get().getESMStore()->get().find(rec.mClass)->mData.mServices; } for (const auto& [flag, name] : serviceNames) { From fae9ced5f952f6743a0f438e2e096b5f05563829 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 4 Sep 2023 10:20:58 +0400 Subject: [PATCH 0068/2167] Do not copy a static string --- components/fx/stateupdater.cpp | 2 ++ components/fx/stateupdater.hpp | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/fx/stateupdater.cpp b/components/fx/stateupdater.cpp index 42f290eadf..9e86f25b9c 100644 --- a/components/fx/stateupdater.cpp +++ b/components/fx/stateupdater.cpp @@ -7,6 +7,8 @@ namespace fx { + std::string StateUpdater::sDefinition = UniformData::getDefinition("_omw_data"); + StateUpdater::StateUpdater(bool useUBO) : mUseUBO(useUBO) { diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp index c4bf71642c..f1a4cd89f3 100644 --- a/components/fx/stateupdater.hpp +++ b/components/fx/stateupdater.hpp @@ -100,11 +100,7 @@ namespace fx mPointLightBuffer = std::move(buffer); } - static std::string getStructDefinition() - { - static std::string definition = UniformData::getDefinition("_omw_data"); - return definition; - } + static const std::string& getStructDefinition() { return sDefinition; } void setDefaults(osg::StateSet* stateset) override; @@ -275,6 +271,8 @@ namespace fx UniformData mData; bool mUseUBO; + static std::string sDefinition; + std::shared_ptr mPointLightBuffer; }; } From 5d51f3ea268ddd76750de25efbfe3b741f37ef15 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 4 Sep 2023 12:16:47 +0400 Subject: [PATCH 0069/2167] Init structs to make sure that we do not store a random data --- apps/openmw/mwworld/worldmodel.cpp | 7 ++----- components/esm4/reader.cpp | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldmodel.cpp b/apps/openmw/mwworld/worldmodel.cpp index a488bf0913..b9187f59c4 100644 --- a/apps/openmw/mwworld/worldmodel.cpp +++ b/apps/openmw/mwworld/worldmodel.cpp @@ -53,23 +53,20 @@ namespace MWWorld const ESM::Cell* createEsmCell(ESM::ExteriorCellLocation location, ESMStore& store) { - ESM::Cell record; + ESM::Cell record = {}; record.mData.mFlags = ESM::Cell::HasWater; record.mData.mX = location.mX; record.mData.mY = location.mY; - record.mWater = 0; - record.mMapColor = 0; record.updateId(); return store.insert(record); } const ESM4::Cell* createEsm4Cell(ESM::ExteriorCellLocation location, ESMStore& store) { - ESM4::Cell record; + ESM4::Cell record = {}; record.mParent = location.mWorldspace; record.mX = location.mX; record.mY = location.mY; - record.mCellFlags = 0; return store.insert(record); } diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index 4a78fd21ea..f81a971e15 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -169,8 +169,8 @@ namespace ESM4 , currCellGrid(FormId{ 0, 0 }) , cellGridValid(false) { - subRecordHeader.typeId = 0; - subRecordHeader.dataSize = 0; + recordHeader = {}; + subRecordHeader = {}; } Reader::Reader(Files::IStreamPtr&& esmStream, const std::filesystem::path& filename, VFS::Manager const* vfs, From a849a053bea76f5e08a7a6e43fadaf51829527fd Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 4 Sep 2023 16:46:23 +0400 Subject: [PATCH 0070/2167] Do not copy vector --- components/lua/utf8.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 83228afa65..45e05b45d3 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -110,18 +110,19 @@ namespace LuaUtf8 }; utf8["codes"] = [](std::string_view s) { - std::vector pos_byte{ 1 }; - return sol::as_function([s, pos_byte]() mutable -> sol::optional> { - if (pos_byte.back() <= static_cast(s.size())) - { - const auto pair = decodeNextUTF8Character(s, pos_byte); - if (pair.second == -1) - throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); + return sol::as_function( + [s, pos_byte = std::vector{ 1 }]() mutable -> sol::optional> { + if (pos_byte.back() <= static_cast(s.size())) + { + const auto pair = decodeNextUTF8Character(s, pos_byte); + if (pair.second == -1) + throw std::runtime_error( + "Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); - return pair; - } - return sol::nullopt; - }); + return pair; + } + return sol::nullopt; + }); }; utf8["len"] = [](std::string_view s, From 7df43f28eda5d4d03d97428713684b1af7614dbc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 4 Sep 2023 10:47:47 -0500 Subject: [PATCH 0071/2167] Remove unused include --- apps/openmw/mwlua/types/creature.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 0ded34cac5..332a9b9b14 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -2,7 +2,6 @@ #include "types.hpp" #include -#include #include #include #include From 6cec92223e48e8d689695556205ef1772a3ae9fd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 5 Sep 2023 16:38:34 +0200 Subject: [PATCH 0072/2167] Replace find with search --- apps/openmw/mwmechanics/magiceffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 477e8b36a6..bba6e7361d 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -50,7 +50,7 @@ namespace MWMechanics const auto& store = MWBase::Environment::get().getESMStore(); const ESM::MagicEffect* magicEffect = store->get().find(mId); return getMagicEffectString( - *magicEffect, store->get().find(mArg), store->get().find(mArg)); + *magicEffect, store->get().search(mArg), store->get().search(mArg)); } bool operator<(const EffectKey& left, const EffectKey& right) From 0836680e36ede07d733eb02f8c9c2027d6555e01 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 31 Aug 2023 22:03:00 +0200 Subject: [PATCH 0073/2167] Ability to unbind controls in settings menu --- apps/openmw/mwinput/bindingsmanager.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 27355ec4a9..3f505896f4 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -77,7 +77,12 @@ namespace MWInput // Disallow binding escape key if (key == SDL_SCANCODE_ESCAPE) { - // Stop binding if esc pressed + // Unbind if esc pressed + if (mDetectingKeyboard) + clearAllKeyBindings(mInputBinder, control); + else + clearAllControllerBindings(mInputBinder, control); + control->setInitialValue(0.0f); mInputBinder->cancelDetectingBindingState(); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); return; @@ -154,7 +159,14 @@ namespace MWInput return; clearAllControllerBindings(mInputBinder, control); control->setInitialValue(0.0f); - ICS::DetectingBindingListener::joystickButtonBindingDetected(ICS, deviceID, control, button, direction); + if (button == SDL_CONTROLLER_BUTTON_START) + { + // Disallow rebinding SDL_CONTROLLER_BUTTON_START - it is used to open main and without it is not + // even possible to exit the game (or change the binding back). + mInputBinder->cancelDetectingBindingState(); + } + else + ICS::DetectingBindingListener::joystickButtonBindingDetected(ICS, deviceID, control, button, direction); MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } @@ -178,8 +190,11 @@ namespace MWInput mListener = std::make_unique(mInputBinder.get(), this); mInputBinder->setDetectingBindingListener(mListener.get()); - loadKeyDefaults(); - loadControllerDefaults(); + if (!userFileExists) + { + loadKeyDefaults(); + loadControllerDefaults(); + } for (int i = 0; i < A_Last; ++i) { From 5b07a78f2c355a6c800359f29bdff6b76a33af2a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 15:21:42 +0300 Subject: [PATCH 0074/2167] Modernize skin instance records --- apps/openmw_test_suite/nif/node.hpp | 4 +- components/nif/data.cpp | 81 ++++++++++++++++------------- components/nif/data.hpp | 16 ++++-- components/nifosg/nifloader.cpp | 14 ++--- 4 files changed, 65 insertions(+), 50 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 2d82289592..19276059c8 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -53,8 +53,8 @@ namespace Nif::Testing inline void init(NiSkinInstance& value) { - value.data = NiSkinDataPtr(nullptr); - value.root = NodePtr(nullptr); + value.mData = NiSkinDataPtr(nullptr); + value.mRoot = NodePtr(nullptr); } inline void init(Controller& value) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 1574462892..50501c4104 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -7,43 +7,6 @@ namespace Nif { - void NiSkinInstance::read(NIFStream* nif) - { - data.read(nif); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 101)) - partitions.read(nif); - root.read(nif); - readRecordList(nif, bones); - } - - void NiSkinInstance::post(Reader& nif) - { - data.post(nif); - partitions.post(nif); - root.post(nif); - postRecordList(nif, bones); - - if (data.empty() || root.empty()) - throw Nif::Exception("NiSkinInstance missing root or data", nif.getFilename()); - - if (bones.size() != data->bones.size()) - throw Nif::Exception("Mismatch in NiSkinData bone count", nif.getFilename()); - - for (auto& bone : bones) - { - if (bone.empty()) - throw Nif::Exception("Oops: Missing bone! Don't know how to handle this.", nif.getFilename()); - bone->setBone(); - } - } - - void BSDismemberSkinInstance::read(NIFStream* nif) - { - NiSkinInstance::read(nif); - unsigned int numPartitions = nif->getUInt(); - nif->skip(4 * numPartitions); // Body part information - } - void NiGeometryData::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 114)) @@ -121,6 +84,7 @@ namespace Nif void NiTriBasedGeomData::read(NIFStream* nif) { NiGeometryData::read(nif); + mNumTriangles = nif->getUShort(); } @@ -174,6 +138,7 @@ namespace Nif void NiLinesData::read(NIFStream* nif) { NiGeometryData::read(nif); + size_t num = vertices.size(); std::vector flags; nif->readVector(flags, num); @@ -326,6 +291,48 @@ namespace Nif } } + void NiSkinInstance::read(NIFStream* nif) + { + mData.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 101)) + mPartitions.read(nif); + mRoot.read(nif); + readRecordList(nif, mBones); + } + + void NiSkinInstance::post(Reader& nif) + { + mData.post(nif); + mPartitions.post(nif); + mRoot.post(nif); + postRecordList(nif, mBones); + + if (mData.empty() || mRoot.empty()) + throw Nif::Exception("NiSkinInstance missing root or data", nif.getFilename()); + + if (mBones.size() != mData->bones.size()) + throw Nif::Exception("Mismatch in NiSkinData bone count", nif.getFilename()); + + for (auto& bone : mBones) + { + if (bone.empty()) + throw Nif::Exception("Oops: Missing bone! Don't know how to handle this.", nif.getFilename()); + bone->setBone(); + } + } + + void BSDismemberSkinInstance::read(NIFStream* nif) + { + NiSkinInstance::read(nif); + + mParts.resize(nif->get()); + for (BodyPart& part : mParts) + { + nif->read(part.mFlags); + nif->read(part.mType); + } + } + void NiSkinData::read(NIFStream* nif) { trafo.rotation = nif->getMatrix3(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 6f0ca25237..48d2ff0310 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -172,10 +172,10 @@ namespace Nif struct NiSkinInstance : public Record { - NiSkinDataPtr data; - NiSkinPartitionPtr partitions; - NodePtr root; - NodeList bones; + NiSkinDataPtr mData; + NiSkinPartitionPtr mPartitions; + NodePtr mRoot; + NodeList mBones; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -183,6 +183,14 @@ namespace Nif struct BSDismemberSkinInstance : public NiSkinInstance { + struct BodyPart + { + uint16_t mFlags; + uint16_t mType; + }; + + std::vector mParts; + void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index dde4f261e2..cf2f05a378 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1391,14 +1391,14 @@ namespace NifOsg const Nif::NiSkinInstance* skin = niGeometry->skin.getPtr(); const Nif::NiSkinData* data = nullptr; const Nif::NiSkinPartition* partitions = nullptr; - if (!skin->data.empty()) + if (!skin->mData.empty()) { - data = skin->data.getPtr(); + data = skin->mData.getPtr(); if (!data->partitions.empty()) partitions = data->partitions.getPtr(); } - if (!partitions && !skin->partitions.empty()) - partitions = skin->partitions.getPtr(); + if (!partitions && !skin->mPartitions.empty()) + partitions = skin->mPartitions.getPtr(); hasPartitions = partitions != nullptr; if (hasPartitions) @@ -1549,9 +1549,9 @@ namespace NifOsg osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); const Nif::NiSkinInstance* skin = static_cast(nifNode)->skin.getPtr(); - const Nif::NiSkinData* data = skin->data.getPtr(); - const Nif::NodeList& bones = skin->bones; - for (std::size_t i = 0, n = bones.size(); i < n; ++i) + const Nif::NiSkinData* data = skin->mData.getPtr(); + const Nif::NodeList& bones = skin->mBones; + for (std::size_t i = 0; i < bones.size(); ++i) { std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); From 384a398b6250632e4988411f74335a4902978b5d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 16:29:42 +0300 Subject: [PATCH 0075/2167] Modernize NiMorphData, palette and BSBound records --- components/nif/data.cpp | 51 ++++++++++++++++++++------------- components/nif/data.hpp | 7 +++-- components/nifosg/nifloader.cpp | 2 +- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 50501c4104..59ac33c1ff 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -485,16 +485,17 @@ namespace Nif void NiMorphData::read(NIFStream* nif) { - int morphCount = nif->getInt(); - int vertCount = nif->getInt(); - nif->getChar(); // Relative targets, always 1 + uint32_t numMorphs, numVerts; + nif->read(numMorphs); + nif->read(numVerts); + nif->read(mRelativeTargets); - mMorphs.resize(morphCount); - for (int i = 0; i < morphCount; i++) + mMorphs.resize(numMorphs); + for (MorphData& morph : mMorphs) { - mMorphs[i].mKeyFrames = std::make_shared(); - mMorphs[i].mKeyFrames->read(nif, /*morph*/ true); - nif->readVector(mMorphs[i].mVertices, vertCount); + morph.mKeyFrames = std::make_shared(); + morph.mKeyFrames->read(nif, /*morph*/ true); + nif->readVector(morph.mVertices, numVerts); } } @@ -521,18 +522,28 @@ namespace Nif void NiPalette::read(NIFStream* nif) { - unsigned int alphaMask = !nif->getChar() ? 0xFF000000 : 0; + bool useAlpha = nif->get() != 0; + uint32_t alphaMask = useAlpha ? 0 : 0xFF000000; + + uint32_t numEntries; + nif->read(numEntries); + // Fill the entire palette with black even if there isn't enough entries. - colors.resize(256); - unsigned int numEntries = nif->getUInt(); - for (unsigned int i = 0; i < numEntries; i++) - colors[i] = nif->getUInt() | alphaMask; + mColors.resize(256); + if (numEntries > 256) + mColors.resize(numEntries); + + for (uint32_t i = 0; i < numEntries; i++) + { + nif->read(mColors[i]); + mColors[i] |= alphaMask; + } } void NiStringPalette::read(NIFStream* nif) { - palette = nif->getStringPalette(); - if (nif->getUInt() != palette.size()) + mPalette = nif->getStringPalette(); + if (nif->get() != mPalette.size()) Log(Debug::Warning) << "NIFFile Warning: Failed size check in NiStringPalette. File: " << nif->getFile().getFilename(); } @@ -555,15 +566,15 @@ namespace Nif void BSMultiBoundOBB::read(NIFStream* nif) { - mCenter = nif->getVector3(); - mSize = nif->getVector3(); - mRotation = nif->getMatrix3(); + nif->read(mCenter); + nif->read(mSize); + nif->read(mRotation); } void BSMultiBoundSphere::read(NIFStream* nif) { - mCenter = nif->getVector3(); - mRadius = nif->getFloat(); + nif->read(mCenter); + nif->read(mRadius); } } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 48d2ff0310..7f53f7333b 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -253,6 +253,8 @@ namespace Nif FloatKeyMapPtr mKeyFrames; std::vector mVertices; }; + + uint8_t mRelativeTargets; std::vector mMorphs; void read(NIFStream* nif) override; @@ -291,14 +293,15 @@ namespace Nif struct NiPalette : public Record { // 32-bit RGBA colors that correspond to 8-bit indices - std::vector colors; + std::vector mColors; void read(NIFStream* nif) override; }; struct NiStringPalette : public Record { - std::string palette; + std::string mPalette; + void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index cf2f05a378..3d04aee28b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1782,7 +1782,7 @@ namespace NifOsg pixelformat = pixelData->fmt == Nif::NiPixelData::NIPXFMT_PAL8 ? GL_RGB : GL_RGBA; // We're going to convert the indices that pixel data contains // into real colors using the palette. - const auto& palette = pixelData->palette->colors; + const auto& palette = pixelData->palette->mColors; const int numChannels = pixelformat == GL_RGBA ? 4 : 3; unsigned char* data = new unsigned char[pixels.size() * numChannels]; unsigned char* pixel = data; From fda6b0b4f8deeb25c5b1469d4d0a92c23f2b0d76 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 16:52:10 +0300 Subject: [PATCH 0076/2167] Modernize NiUVData, NiLinesData, NiTriBasedGeomData --- components/nif/data.cpp | 16 ++++++++-------- components/nif/data.hpp | 7 ++++--- components/nifbullet/bulletnifloader.cpp | 2 +- components/nifosg/nifloader.cpp | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 59ac33c1ff..910c67cdd7 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -85,7 +85,7 @@ namespace Nif { NiGeometryData::read(nif); - mNumTriangles = nif->getUShort(); + nif->read(mNumTriangles); } void NiTriShapeData::read(NIFStream* nif) @@ -150,15 +150,15 @@ namespace Nif { if (flags[i] & 1) { - lines.emplace_back(i); - lines.emplace_back(i + 1); + mLines.emplace_back(i); + mLines.emplace_back(i + 1); } } // If there are just two vertices, they can be connected twice. Probably isn't critical. if (flags[num - 1] & 1) { - lines.emplace_back(num - 1); - lines.emplace_back(0); + mLines.emplace_back(num - 1); + mLines.emplace_back(0); } } @@ -207,10 +207,10 @@ namespace Nif void NiUVData::read(NIFStream* nif) { - for (int i = 0; i < 4; i++) + for (FloatKeyMapPtr& keys : mKeyList) { - mKeyList[i] = std::make_shared(); - mKeyList[i]->read(nif); + keys = std::make_shared(); + keys->read(nif); } } diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 7f53f7333b..e853a30c33 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -47,7 +47,7 @@ namespace Nif // Abstract struct NiTriBasedGeomData : public NiGeometryData { - size_t mNumTriangles; + uint16_t mNumTriangles; void read(NIFStream* nif) override; }; @@ -71,7 +71,8 @@ namespace Nif struct NiLinesData : public NiGeometryData { // Lines, series of indices that correspond to connected vertices. - std::vector lines; + // NB: assumes <=65536 number of vertices + std::vector mLines; void read(NIFStream* nif) override; }; @@ -103,7 +104,7 @@ namespace Nif struct NiUVData : public Record { - FloatKeyMapPtr mKeyList[4]; + std::array mKeyList; void read(NIFStream* nif) override; }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 0c85949a53..4ef1a35903 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -40,7 +40,7 @@ namespace for (const osg::Vec3f& vertex : vertices) mesh.findOrAddVertex(Misc::Convert::toBullet(vertex), false); - mesh.preallocateIndices(static_cast(data.mNumTriangles * 3)); + mesh.preallocateIndices(static_cast(data.mNumTriangles) * 3); } void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3d04aee28b..868cdfa137 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1458,7 +1458,7 @@ namespace NifOsg if (niGeometryData->recType != Nif::RC_NiLinesData) return; auto data = static_cast(niGeometryData); - const auto& line = data->lines; + const auto& line = data->mLines; if (line.empty()) return; geometry->addPrimitiveSet( From eba0ab444bf5c8a78d93264f118524e9fb995707 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 17:41:52 +0300 Subject: [PATCH 0077/2167] Modernize NiTriStripsData --- .../nifloader/testbulletnifloader.cpp | 12 ++++++------ components/nif/data.cpp | 19 +++++++++---------- components/nif/data.hpp | 2 +- components/nifbullet/bulletnifloader.cpp | 4 ++-- components/nifosg/nifloader.cpp | 2 +- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 49030a8902..3560c1cd61 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -338,7 +338,7 @@ namespace mNiTriStripsData.vertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0) }; mNiTriStripsData.mNumTriangles = 2; - mNiTriStripsData.strips = { { 0, 1, 2, 3 } }; + mNiTriStripsData.mStrips = { { 0, 1, 2, 3 } }; mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); } }; @@ -1260,7 +1260,7 @@ namespace TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_empty_strips) { - mNiTriStripsData.strips.clear(); + mNiTriStripsData.mStrips.clear(); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriStrips); @@ -1275,7 +1275,7 @@ namespace TEST_F(TestBulletNifLoader, for_static_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { - mNiTriStripsData.strips.front() = { 0, 1 }; + mNiTriStripsData.mStrips.front() = { 0, 1 }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriStrips); @@ -1293,7 +1293,7 @@ namespace mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); mNiNode.recType = Nif::RC_AvoidNode; - mNiTriStripsData.strips.front() = { 0, 1 }; + mNiTriStripsData.mStrips.front() = { 0, 1 }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriStrips); @@ -1308,7 +1308,7 @@ namespace TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { - mNiTriStripsData.strips.front() = { 0, 1 }; + mNiTriStripsData.mStrips.front() = { 0, 1 }; mNiTriStrips.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriStrips) })); @@ -1325,7 +1325,7 @@ namespace TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) { - mNiTriStripsData.strips.front() = { 0, 1 }; + mNiTriStripsData.mStrips.front() = { 0, 1 }; mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 910c67cdd7..453fa64564 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -1,10 +1,11 @@ #include "data.hpp" + +#include + #include "exception.hpp" #include "nifkey.hpp" #include "node.hpp" -#include - namespace Nif { void NiGeometryData::read(NIFStream* nif) @@ -117,22 +118,20 @@ namespace Nif { NiTriBasedGeomData::read(nif); - // Number of triangle strips - int numStrips = nif->getUShort(); - - std::vector lengths; + uint16_t numStrips; + nif->read(numStrips); + std::vector lengths; nif->readVector(lengths, numStrips); - // "Has Strips" flag. Exceptionally useful. bool hasStrips = true; if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) - hasStrips = nif->getBoolean(); + nif->read(hasStrips); if (!hasStrips || !numStrips) return; - strips.resize(numStrips); + mStrips.resize(numStrips); for (int i = 0; i < numStrips; i++) - nif->readVector(strips[i], lengths[i]); + nif->readVector(mStrips[i], lengths[i]); } void NiLinesData::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index e853a30c33..8cde0a705f 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -63,7 +63,7 @@ namespace Nif struct NiTriStripsData : public NiTriBasedGeomData { // Triangle strips, series of vertex indices. - std::vector> strips; + std::vector> mStrips; void read(NIFStream* nif) override; }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 4ef1a35903..199a6f749d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -54,7 +54,7 @@ namespace void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data) { prepareTriangleMesh(mesh, data); - for (const std::vector& strip : data.strips) + for (const std::vector& strip : data.mStrips) { if (strip.size() < 3) continue; @@ -99,7 +99,7 @@ namespace return {}; auto data = static_cast(geometry.data.getPtr()); - if (data->strips.empty()) + if (data->mStrips.empty()) return {}; return function(static_cast(*data)); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 868cdfa137..f34fc4591f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1442,7 +1442,7 @@ namespace NifOsg return; auto data = static_cast(niGeometryData); bool hasGeometry = false; - for (const std::vector& strip : data->strips) + for (const std::vector& strip : data->mStrips) { if (strip.size() < 3) continue; From 523e7e8228946aff170cafdf6b0f84e769392318 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 18:43:08 +0300 Subject: [PATCH 0078/2167] Modernize NiRotatingParticlesData and NiVisData --- components/nif/data.cpp | 21 +++++++++++++++------ components/nif/data.hpp | 8 ++------ components/nifosg/controller.cpp | 14 ++++++-------- components/nifosg/controller.hpp | 17 ++++++++--------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 453fa64564..6086a07aa9 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -194,7 +194,12 @@ namespace Nif { NiParticlesData::read(nif); - if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->getBoolean()) + if (nif->getVersion() > NIFStream::generateVersion(4, 2, 2, 0)) + return; + + bool hasRotations; + nif->read(hasRotations); + if (hasRotations) nif->readVector(rotations, vertices.size()); } @@ -281,12 +286,16 @@ namespace Nif void NiVisData::read(NIFStream* nif) { - int count = nif->getInt(); - mVis.resize(count); - for (size_t i = 0; i < mVis.size(); i++) + mKeys = std::make_shared>(); + uint32_t numKeys; + nif->read(numKeys); + for (size_t i = 0; i < numKeys; i++) { - mVis[i].time = nif->getFloat(); - mVis[i].isSet = (nif->getChar() != 0); + float time; + char value; + nif->read(time); + nif->read(value); + (*mKeys)[time] = (value != 0); } } diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 8cde0a705f..d9f118f0fe 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -161,12 +161,8 @@ namespace Nif struct NiVisData : public Record { - struct VisData - { - float time; - bool isSet; - }; - std::vector mVis; + // TODO: investigate possible use of ByteKeyMap + std::shared_ptr> mKeys; void read(NIFStream* nif) override; }; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index ce5485ac53..20a3ee92e4 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -319,7 +319,7 @@ namespace NifOsg = ByteInterpolator(static_cast(ctrl->mInterpolator.getPtr())); } else if (!ctrl->mData.empty()) - mData = ctrl->mData->mVis; + mData = ctrl->mData->mKeys; } VisController::VisController() {} @@ -338,15 +338,13 @@ namespace NifOsg if (!mInterpolator.empty()) return mInterpolator.interpKey(time); - if (mData.size() == 0) + if (mData->empty()) return true; - for (size_t i = 1; i < mData.size(); i++) - { - if (mData[i].time > time) - return mData[i - 1].isSet; - } - return mData.back().isSet; + auto iter = mData->upper_bound(time); + if (iter != mData->begin()) + --iter; + return iter->second; } void VisController::operator()(osg::Node* node, osg::NodeVisitor* nv) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 7666fdeb3f..90e87366e1 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -1,19 +1,18 @@ #ifndef COMPONENTS_NIFOSG_CONTROLLER_H #define COMPONENTS_NIFOSG_CONTROLLER_H -#include -#include -#include - -#include -#include -#include - #include #include #include +#include +#include +#include +#include +#include +#include + namespace osg { class Material; @@ -283,7 +282,7 @@ namespace NifOsg class VisController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: - std::vector mData; + std::shared_ptr> mData; ByteInterpolator mInterpolator; unsigned int mMask{ 0u }; From 5e8f9e7dd971599f107f4e557f789e24587a1ac1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Sep 2023 20:49:09 +0300 Subject: [PATCH 0079/2167] Modernize NiPixelData --- components/nif/data.cpp | 91 ++++++++++++++------------- components/nif/data.hpp | 107 ++++++++++++++++++++++++-------- components/nifosg/nifloader.cpp | 88 ++++++++++++++------------ 3 files changed, 176 insertions(+), 110 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 6086a07aa9..ed17de9464 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -224,58 +224,61 @@ namespace Nif mKeyList->read(nif); } + void NiPixelFormat::read(NIFStream* nif) + { + mFormat = static_cast(nif->get()); + if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) + { + nif->readArray(mColorMasks); + nif->read(mBitsPerPixel); + nif->readArray(mCompareBits); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mPixelTiling); + } + else + { + mBitsPerPixel = nif->get(); + nif->read(mRendererHint); + nif->read(mExtraData); + nif->read(mFlags); + nif->read(mPixelTiling); + if (nif->getVersion() >= NIFStream::generateVersion(20, 3, 0, 4)) + nif->read(mUseSrgb); + for (int i = 0; i < 4; i++) + mChannels[i].read(nif); + } + } + + void NiPixelFormat::ChannelData::read(NIFStream* nif) + { + mType = static_cast(nif->get()); + mConvention = static_cast(nif->get()); + nif->read(mBitsPerChannel); + nif->read(mSigned); + } + void NiPixelData::read(NIFStream* nif) { - fmt = (Format)nif->getUInt(); - - if (nif->getVersion() < NIFStream::generateVersion(10, 4, 0, 2)) + mPixelFormat.read(nif); + mPalette.read(nif); + mMipmaps.resize(nif->get()); + nif->read(mBytesPerPixel); + for (Mipmap& mip : mMipmaps) { - for (unsigned int i = 0; i < 4; ++i) - colorMask[i] = nif->getUInt(); - bpp = nif->getUInt(); - nif->skip(8); // "Old Fast Compare". Whatever that means. - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - pixelTiling = nif->getUInt(); + nif->read(mip.mWidth); + nif->read(mip.mHeight); + nif->read(mip.mOffset); } - else // TODO: see if anything from here needs to be implemented - { - bpp = nif->getChar(); - nif->skip(4); // Renderer hint - nif->skip(4); // Extra data - nif->skip(4); // Flags - pixelTiling = nif->getUInt(); - if (nif->getVersion() >= NIFStream::generateVersion(20, 3, 0, 4)) - sRGB = nif->getBoolean(); - nif->skip(4 * 10); // Channel data - } - - palette.read(nif); - - numberOfMipmaps = nif->getUInt(); - - // Bytes per pixel, should be bpp / 8 - /* int bytes = */ nif->getUInt(); - - for (unsigned int i = 0; i < numberOfMipmaps; i++) - { - // Image size and offset in the following data field - Mipmap m; - m.width = nif->getUInt(); - m.height = nif->getUInt(); - m.dataOffset = nif->getUInt(); - mipmaps.push_back(m); - } - - // Read the data - unsigned int numPixels = nif->getUInt(); - bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10, 4, 0, 2); - unsigned int numFaces = hasFaces ? nif->getUInt() : 1; - nif->readVector(data, numPixels * numFaces); + uint32_t numPixels; + nif->read(numPixels); + if (nif->getVersion() >= NIFStream::generateVersion(10, 4, 0, 2)) + nif->read(mNumFaces); + nif->readVector(mData, numPixels * mNumFaces); } void NiPixelData::post(Reader& nif) { - palette.post(nif); + mPalette.post(nif); } void NiColorData::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index d9f118f0fe..0b62071a98 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -116,37 +116,94 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPixelFormat + { + enum class Format : uint32_t + { + RGB = 0, + RGBA = 1, + Palette = 2, + PaletteAlpha = 3, + BGR = 4, + BGRA = 5, + DXT1 = 6, + DXT3 = 7, + DXT5 = 8, + }; + + struct ChannelData + { + enum class Type : uint32_t + { + Red = 0, + Green = 1, + Blue = 2, + Alpha = 3, + Compressed = 4, + OffsetU = 5, + OffsetV = 6, + OffsetW = 7, + OffsetQ = 8, + Luma = 9, + Height = 10, + VectorX = 11, + VectorY = 12, + VectorZ = 13, + Padding = 14, + Intensity = 15, + Index = 16, + Depth = 17, + Stencil = 18, + Empty = 19, + }; + + enum class Convention : uint32_t + { + NormInt = 0, + Half = 1, + Float = 2, + Index = 3, + Compressed = 4, + Unknown = 5, + Int = 6, + }; + + Type mType; + Convention mConvention; + uint8_t mBitsPerChannel; + bool mSigned; + + void read(NIFStream* nif); + }; + + Format mFormat{ Format::RGB }; + std::array mColorMasks; + uint32_t mBitsPerPixel{ 0 }; + uint32_t mPixelTiling{ 0 }; + std::array mCompareBits; + uint32_t mRendererHint{ 0 }; + uint32_t mExtraData{ 0 }; + uint8_t mFlags{ 0 }; + bool mUseSrgb{ false }; + std::array mChannels; + + void read(NIFStream* nif); + }; + struct NiPixelData : public Record { - enum Format - { - NIPXFMT_RGB8, - NIPXFMT_RGBA8, - NIPXFMT_PAL8, - NIPXFMT_PALA8, - NIPXFMT_BGR8, - NIPXFMT_BGRA8, - NIPXFMT_DXT1, - NIPXFMT_DXT3, - NIPXFMT_DXT5 - }; - Format fmt{ NIPXFMT_RGB8 }; - - unsigned int colorMask[4]{ 0 }; - unsigned int bpp{ 0 }, pixelTiling{ 0 }; - bool sRGB{ false }; - - NiPalettePtr palette; - unsigned int numberOfMipmaps{ 0 }; - struct Mipmap { - int width, height; - int dataOffset; + uint32_t mWidth, mHeight; + uint32_t mOffset; }; - std::vector mipmaps; - std::vector data; + NiPixelFormat mPixelFormat; + NiPalettePtr mPalette; + uint32_t mBytesPerPixel; + std::vector mMipmaps; + uint32_t mNumFaces{ 1 }; + std::vector mData; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f34fc4591f..d9e3dc1439 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1680,71 +1680,76 @@ namespace NifOsg osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) { - osg::ref_ptr image(new osg::Image); + if (pixelData->mMipmaps.empty()) + return nullptr; - // Pixel row alignment, defining it to be consistent with OSG DDS plugin - int packing = 1; + // Not fatal, but warn the user + if (pixelData->mNumFaces != 1) + Log(Debug::Info) << "Unsupported multifaceted internal texture in " << mFilename; + + using Nif::NiPixelFormat; + NiPixelFormat niPixelFormat = pixelData->mPixelFormat; GLenum pixelformat = 0; - switch (pixelData->fmt) + // Pixel row alignment. Defining it to be consistent with OSG DDS plugin + int packing = 1; + switch (niPixelFormat.mFormat) { - case Nif::NiPixelData::NIPXFMT_RGB8: + case NiPixelFormat::Format::RGB: pixelformat = GL_RGB; break; - case Nif::NiPixelData::NIPXFMT_RGBA8: + case NiPixelFormat::Format::RGBA: pixelformat = GL_RGBA; break; - case Nif::NiPixelData::NIPXFMT_PAL8: - case Nif::NiPixelData::NIPXFMT_PALA8: + case NiPixelFormat::Format::Palette: + case NiPixelFormat::Format::PaletteAlpha: pixelformat = GL_RED; // Each color is defined by a byte. break; - case Nif::NiPixelData::NIPXFMT_BGR8: + case NiPixelFormat::Format::BGR: pixelformat = GL_BGR; break; - case Nif::NiPixelData::NIPXFMT_BGRA8: + case NiPixelFormat::Format::BGRA: pixelformat = GL_BGRA; break; - case Nif::NiPixelData::NIPXFMT_DXT1: + case NiPixelFormat::Format::DXT1: pixelformat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; packing = 2; break; - case Nif::NiPixelData::NIPXFMT_DXT3: + case NiPixelFormat::Format::DXT3: pixelformat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; packing = 4; break; - case Nif::NiPixelData::NIPXFMT_DXT5: + case NiPixelFormat::Format::DXT5: pixelformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; packing = 4; break; default: - Log(Debug::Info) << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename; + Log(Debug::Info) << "Unhandled internal pixel format " + << static_cast(niPixelFormat.mFormat) << " in " << mFilename; return nullptr; } - if (pixelData->mipmaps.empty()) - return nullptr; - int width = 0; int height = 0; - std::vector mipmapVector; - for (unsigned int i = 0; i < pixelData->mipmaps.size(); ++i) + std::vector mipmapOffsets; + for (unsigned int i = 0; i < pixelData->mMipmaps.size(); ++i) { - const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; + const Nif::NiPixelData::Mipmap& mip = pixelData->mMipmaps[i]; size_t mipSize = osg::Image::computeImageSizeInBytes( - mip.width, mip.height, 1, pixelformat, GL_UNSIGNED_BYTE, packing); - if (mipSize + mip.dataOffset > pixelData->data.size()) + mip.mWidth, mip.mHeight, 1, pixelformat, GL_UNSIGNED_BYTE, packing); + if (mipSize + mip.mOffset > pixelData->mData.size()) { Log(Debug::Info) << "Internal texture's mipmap data out of bounds, ignoring texture"; return nullptr; } if (i != 0) - mipmapVector.push_back(mip.dataOffset); + mipmapOffsets.push_back(mip.mOffset); else { - width = mip.width; - height = mip.height; + width = mip.mWidth; + height = mip.mHeight; } } @@ -1754,16 +1759,17 @@ namespace NifOsg return nullptr; } - const std::vector& pixels = pixelData->data; - switch (pixelData->fmt) + osg::ref_ptr image(new osg::Image); + const std::vector& pixels = pixelData->mData; + switch (niPixelFormat.mFormat) { - case Nif::NiPixelData::NIPXFMT_RGB8: - case Nif::NiPixelData::NIPXFMT_RGBA8: - case Nif::NiPixelData::NIPXFMT_BGR8: - case Nif::NiPixelData::NIPXFMT_BGRA8: - case Nif::NiPixelData::NIPXFMT_DXT1: - case Nif::NiPixelData::NIPXFMT_DXT3: - case Nif::NiPixelData::NIPXFMT_DXT5: + case NiPixelFormat::Format::RGB: + case NiPixelFormat::Format::RGBA: + case NiPixelFormat::Format::BGR: + case NiPixelFormat::Format::BGRA: + case NiPixelFormat::Format::DXT1: + case NiPixelFormat::Format::DXT3: + case NiPixelFormat::Format::DXT5: { unsigned char* data = new unsigned char[pixels.size()]; memcpy(data, pixels.data(), pixels.size()); @@ -1771,18 +1777,18 @@ namespace NifOsg osg::Image::USE_NEW_DELETE, packing); break; } - case Nif::NiPixelData::NIPXFMT_PAL8: - case Nif::NiPixelData::NIPXFMT_PALA8: + case NiPixelFormat::Format::Palette: + case NiPixelFormat::Format::PaletteAlpha: { - if (pixelData->palette.empty() || pixelData->bpp != 8) + if (pixelData->mPalette.empty() || niPixelFormat.mBitsPerPixel != 8) { Log(Debug::Info) << "Palettized texture in " << mFilename << " is invalid, ignoring"; return nullptr; } - pixelformat = pixelData->fmt == Nif::NiPixelData::NIPXFMT_PAL8 ? GL_RGB : GL_RGBA; + pixelformat = niPixelFormat.mFormat == NiPixelFormat::Format::PaletteAlpha ? GL_RGBA : GL_RGB; // We're going to convert the indices that pixel data contains // into real colors using the palette. - const auto& palette = pixelData->palette->mColors; + const auto& palette = pixelData->mPalette->mColors; const int numChannels = pixelformat == GL_RGBA ? 4 : 3; unsigned char* data = new unsigned char[pixels.size() * numChannels]; unsigned char* pixel = data; @@ -1791,7 +1797,7 @@ namespace NifOsg memcpy(pixel, &palette[index], sizeof(unsigned char) * numChannels); pixel += numChannels; } - for (unsigned int& offset : mipmapVector) + for (unsigned int& offset : mipmapOffsets) offset *= numChannels; image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE, packing); @@ -1801,7 +1807,7 @@ namespace NifOsg return nullptr; } - image->setMipmapLevels(mipmapVector); + image->setMipmapLevels(mipmapOffsets); image->flipVertical(); return image; From 56ee2588a59a8047dfad48ca037ded16438581a8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 4 Sep 2023 23:51:38 +0300 Subject: [PATCH 0080/2167] Fix NiSourceTexture loading for internal textures --- components/nif/texture.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/nif/texture.cpp b/components/nif/texture.cpp index 116ded6f7e..3525c6f043 100644 --- a/components/nif/texture.cpp +++ b/components/nif/texture.cpp @@ -10,12 +10,12 @@ namespace Nif NiTexture::read(nif); nif->read(mExternal); - if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(mFile); - bool hasData = nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 4); if (!hasData && !mExternal) - nif->read(hasData); + hasData = nif->get() != 0; + + if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mFile); if (hasData) mData.read(nif); From cfd37dbcc63c3cc0365ea0c1cc4ce207e09655e5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 00:08:28 +0300 Subject: [PATCH 0081/2167] Modernize NiTriShapeData --- .../nifloader/testbulletnifloader.cpp | 6 ++--- components/nif/data.cpp | 22 ++++++------------- components/nif/data.hpp | 3 ++- components/nifbullet/bulletnifloader.cpp | 4 ++-- components/nifosg/nifloader.cpp | 2 +- 5 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3560c1cd61..022ba5dbbb 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -325,13 +325,13 @@ namespace mNiTriShapeData.recType = Nif::RC_NiTriShapeData; mNiTriShapeData.vertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0) }; mNiTriShapeData.mNumTriangles = 1; - mNiTriShapeData.triangles = { 0, 1, 2 }; + mNiTriShapeData.mTriangles = { 0, 1, 2 }; mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.vertices = { osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1) }; mNiTriShapeData2.mNumTriangles = 1; - mNiTriShapeData2.triangles = { 0, 1, 2 }; + mNiTriShapeData2.mTriangles = { 0, 1, 2 }; mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); mNiTriStripsData.recType = Nif::RC_NiTriStripsData; @@ -978,7 +978,7 @@ namespace for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { auto data = static_cast(mNiTriShape.data.getPtr()); - data->triangles.clear(); + data->mTriangles.clear(); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index ed17de9464..aed18619e1 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -93,25 +93,17 @@ namespace Nif { NiTriBasedGeomData::read(nif); - // We have three times as many vertices as triangles, so this - // is always equal to mNumTriangles * 3. - int cnt = nif->getInt(); + uint32_t numIndices; + nif->read(numIndices); bool hasTriangles = true; if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) - hasTriangles = nif->getBoolean(); + nif->read(hasTriangles); if (hasTriangles) - nif->readVector(triangles, cnt); + nif->readVector(mTriangles, numIndices); - // Read the match list, which lists the vertices that are equal to - // vertices. We don't actually need need this for anything, so - // just skip it. - unsigned short verts = nif->getUShort(); - for (unsigned short i = 0; i < verts; i++) - { - // Number of vertices matching vertex 'i' - int num = nif->getUShort(); - nif->skip(num * sizeof(short)); - } + mMatchGroups.resize(nif->get()); + for (auto& group : mMatchGroups) + nif->readVector(group, nif->get()); } void NiTriStripsData::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 0b62071a98..eaf8120e81 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -55,7 +55,8 @@ namespace Nif struct NiTriShapeData : public NiTriBasedGeomData { // Triangles, three vertex indices per triangle - std::vector triangles; + std::vector mTriangles; + std::vector> mMatchGroups; void read(NIFStream* nif) override; }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 199a6f749d..51f8cae6c3 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -46,7 +46,7 @@ namespace void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data) { prepareTriangleMesh(mesh, data); - const std::vector& triangles = data.triangles; + const std::vector& triangles = data.mTriangles; for (std::size_t i = 0; i < triangles.size(); i += 3) mesh.addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); } @@ -87,7 +87,7 @@ namespace return {}; auto data = static_cast(geometry.data.getPtr()); - if (data->triangles.empty()) + if (data->mTriangles.empty()) return {}; return function(static_cast(*data)); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index d9e3dc1439..87d6d19ab0 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1430,7 +1430,7 @@ namespace NifOsg if (niGeometryData->recType != Nif::RC_NiTriShapeData) return; auto data = static_cast(niGeometryData); - const std::vector& triangles = data->triangles; + const std::vector& triangles = data->mTriangles; if (triangles.empty()) return; geometry->addPrimitiveSet( From 50a5d53b41d892520e9d8b8c35a88b1b598cc78d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 00:11:41 +0300 Subject: [PATCH 0082/2167] Modernize NiKeyframeData --- components/nif/data.cpp | 2 +- components/nif/data.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index aed18619e1..185684e4a3 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -509,7 +509,7 @@ namespace Nif if (mRotations->mInterpolationType == InterpolationType_XYZ) { if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 0)) - mAxisOrder = static_cast(nif->getInt()); + mAxisOrder = static_cast(nif->get()); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index eaf8120e81..3f548e361c 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -327,7 +327,7 @@ namespace Nif Vector3KeyMapPtr mTranslations; FloatKeyMapPtr mScales; - enum class AxisOrder + enum class AxisOrder : uint32_t { Order_XYZ = 0, Order_XZY = 1, From 89774716fb451d77887ec1e36d034d239b76f3a5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 00:56:20 +0300 Subject: [PATCH 0083/2167] Modernize NiGeometryData --- .../nifloader/testbulletnifloader.cpp | 6 +- components/nif/data.cpp | 94 ++++++++++--------- components/nif/data.hpp | 26 ++++- components/nifbullet/bulletnifloader.cpp | 4 +- components/nifosg/nifloader.cpp | 16 ++-- 5 files changed, 84 insertions(+), 62 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 022ba5dbbb..99f91aacb9 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -323,19 +323,19 @@ namespace init(mController); mNiTriShapeData.recType = Nif::RC_NiTriShapeData; - mNiTriShapeData.vertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0) }; + mNiTriShapeData.mVertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0) }; mNiTriShapeData.mNumTriangles = 1; mNiTriShapeData.mTriangles = { 0, 1, 2 }; mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; - mNiTriShapeData2.vertices = { osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1) }; + mNiTriShapeData2.mVertices = { osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1) }; mNiTriShapeData2.mNumTriangles = 1; mNiTriShapeData2.mTriangles = { 0, 1, 2 }; mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); mNiTriStripsData.recType = Nif::RC_NiTriStripsData; - mNiTriStripsData.vertices + mNiTriStripsData.mVertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0) }; mNiTriStripsData.mNumTriangles = 2; mNiTriStripsData.mStrips = { { 0, 1, 2, 3 } }; diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 185684e4a3..7522543a58 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -11,72 +11,79 @@ namespace Nif void NiGeometryData::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 114)) - nif->getInt(); // Group ID. (Almost?) always 0. + nif->read(mGroupId); - int verts = nif->getUShort(); + // Note: has special meaning for NiPSysData + nif->read(mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->skip(2); // Keep flags and compress flags + { + nif->read(mKeepFlags); + nif->read(mCompressFlags); + } - if (nif->getBoolean()) - nif->readVector(vertices, verts); + bool hasVertices; + nif->read(hasVertices); + if (hasVertices) + nif->readVector(mVertices, mNumVertices); - unsigned int dataFlags = 0; if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - dataFlags = nif->getUShort(); + nif->read(mDataFlags); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->getUInt(); // Material CRC + nif->read(mMaterialHash); - if (nif->getBoolean()) + bool hasNormals; + nif->read(hasNormals); + if (hasNormals) { - nif->readVector(normals, verts); - if (dataFlags & 0x1000) + nif->readVector(mNormals, mNumVertices); + if (mDataFlags & DataFlag_HasTangents) { - nif->readVector(tangents, verts); - nif->readVector(bitangents, verts); + nif->readVector(mTangents, mNumVertices); + nif->readVector(mBitangents, mNumVertices); } } - center = nif->getVector3(); - radius = nif->getFloat(); + nif->read(mCenter); + nif->read(mRadius); - if (nif->getBoolean()) - nif->readVector(colors, verts); + bool hasColors; + nif->read(hasColors); + if (hasColors) + nif->readVector(mColors, mNumVertices); - unsigned int numUVs = dataFlags; if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) - numUVs = nif->getUShort(); + nif->read(mDataFlags); - // In Morrowind this field only corresponds to the number of UV sets. - // In later games only the first 6 bits are used as a count and the rest are flags. + // In 4.0.0.2 the flags field corresponds to the number of UV sets. + // In later revisions the part that corresponds to the number is narrower. + uint16_t numUVs = mDataFlags; if (nif->getVersion() > NIFFile::NIFVersion::VER_MW) { - numUVs &= 0x3f; + numUVs &= DataFlag_NumUVsMask; if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) - numUVs &= 0x1; + numUVs &= DataFlag_HasUV; } bool hasUVs = true; if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - hasUVs = nif->getBoolean(); + nif->read(hasUVs); if (hasUVs) { - uvlist.resize(numUVs); - for (unsigned int i = 0; i < numUVs; i++) + mUVList.resize(numUVs); + for (std::vector& list : mUVList) { - nif->readVector(uvlist[i], verts); + nif->readVector(list, mNumVertices); // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin - for (unsigned int uv = 0; uv < uvlist[i].size(); ++uv) - { - uvlist[i][uv] = osg::Vec2f(uvlist[i][uv].x(), 1.f - uvlist[i][uv].y()); - } + for (osg::Vec2f& uv : list) + uv.y() = 1.f - uv.y(); } } if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - nif->getUShort(); // Consistency flags + nif->read(mConsistencyType); if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) nif->skip(4); // Additional data @@ -130,14 +137,13 @@ namespace Nif { NiGeometryData::read(nif); - size_t num = vertices.size(); std::vector flags; - nif->readVector(flags, num); + nif->readVector(flags, mNumVertices); // Can't construct a line from a single vertex. - if (num < 2) + if (mNumVertices < 2) return; // Convert connectivity flags into usable geometry. The last element needs special handling. - for (size_t i = 0; i < num - 1; ++i) + for (uint16_t i = 0; i < mNumVertices - 1; ++i) { if (flags[i] & 1) { @@ -146,9 +152,9 @@ namespace Nif } } // If there are just two vertices, they can be connected twice. Probably isn't critical. - if (flags[num - 1] & 1) + if (flags[mNumVertices - 1] & 1) { - mLines.emplace_back(num - 1); + mLines.emplace_back(mNumVertices - 1); mLines.emplace_back(0); } } @@ -164,21 +170,21 @@ namespace Nif if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 0)) std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat()); else if (nif->getBoolean()) - nif->readVector(particleRadii, vertices.size()); + nif->readVector(particleRadii, mNumVertices); activeCount = nif->getUShort(); // Particle sizes if (nif->getBoolean()) - nif->readVector(sizes, vertices.size()); + nif->readVector(sizes, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0) && nif->getBoolean()) - nif->readVector(rotations, vertices.size()); + nif->readVector(rotations, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) { if (nif->getBoolean()) - nif->readVector(rotationAngles, vertices.size()); + nif->readVector(rotationAngles, mNumVertices); if (nif->getBoolean()) - nif->readVector(rotationAxes, vertices.size()); + nif->readVector(rotationAxes, mNumVertices); } } @@ -192,7 +198,7 @@ namespace Nif bool hasRotations; nif->read(hasRotations); if (hasRotations) - nif->readVector(rotations, vertices.size()); + nif->readVector(rotations, mNumVertices); } void NiPosData::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 3f548e361c..6791265b45 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -35,11 +35,27 @@ namespace Nif // Common ancestor for several data classes struct NiGeometryData : public Record { - std::vector vertices, normals, tangents, bitangents; - std::vector colors; - std::vector> uvlist; - osg::Vec3f center; - float radius; + // Interpretation of Flags field differs depending on the version + enum DataFlags + { + DataFlag_HasUV = 0x0001, + DataFlag_NumUVsMask = 0x003F, + DataFlag_HasTangents = 0x1000, + }; + + int32_t mGroupId{ 0 }; + uint16_t mNumVertices; + uint8_t mKeepFlags{ 0 }; + uint8_t mCompressFlags{ 0 }; + std::vector mVertices; + uint16_t mDataFlags{ 0 }; + uint32_t mMaterialHash; + std::vector mNormals, mTangents, mBitangents; + osg::Vec3f mCenter; + float mRadius; + std::vector mColors; + std::vector> mUVList; + uint16_t mConsistencyType; void read(NIFStream* nif) override; }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 51f8cae6c3..be27b04603 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -35,7 +35,7 @@ namespace void prepareTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriBasedGeomData& data) { // FIXME: copying vertices/indices individually is unreasonable - const std::vector& vertices = data.vertices; + const std::vector& vertices = data.mVertices; mesh.preallocateVertices(static_cast(vertices.size())); for (const osg::Vec3f& vertex : vertices) mesh.findOrAddVertex(Misc::Convert::toBullet(vertex), false); @@ -381,7 +381,7 @@ namespace NifBullet if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.name, "EditorMarker")) return; - if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) + if (niGeometry.data.empty() || niGeometry.data->mVertices.empty()) return; if (!niGeometry.skin.empty()) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 87d6d19ab0..e56583e493 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1146,7 +1146,7 @@ namespace NifOsg if (particle.lifespan <= 0) continue; - if (particle.vertex >= particledata->vertices.size()) + if (particle.vertex >= particledata->mVertices.size()) continue; ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); @@ -1158,7 +1158,7 @@ namespace NifOsg // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up // post-load in the SceneManager. created->setVelocity(particle.velocity); - const osg::Vec3f& position = particledata->vertices[particle.vertex]; + const osg::Vec3f& position = particledata->mVertices[particle.vertex]; created->setPosition(position); created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); @@ -1173,7 +1173,7 @@ namespace NifOsg } // radius may be used to force a larger bounding box - box.expandBy(osg::BoundingSphere(osg::Vec3(0, 0, 0), particledata->radius)); + box.expandBy(osg::BoundingSphere(osg::Vec3(0, 0, 0), particledata->mRadius)); partsys->setInitialBound(box); } @@ -1346,9 +1346,9 @@ namespace NifOsg void handleNiGeometryData(osg::Geometry* geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) { - const auto& vertices = data->vertices; - const auto& normals = data->normals; - const auto& colors = data->colors; + const auto& vertices = data->mVertices; + const auto& normals = data->mNormals; + const auto& colors = data->mColors; if (!vertices.empty()) geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); if (!normals.empty()) @@ -1357,7 +1357,7 @@ namespace NifOsg if (!colors.empty()) geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); - const auto& uvlist = data->uvlist; + const auto& uvlist = data->mUVList; int textureStage = 0; for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it, ++textureStage) @@ -1473,7 +1473,7 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); + applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->mColors.empty(), animflags); } void handleGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, From 6ac271d5c065447bcf717421b0cae6fd600b8473 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 01:18:37 +0300 Subject: [PATCH 0084/2167] Modernize NiParticlesData and NiSkinData --- components/nif/data.cpp | 98 ++++++++++++++++++--------------- components/nif/data.hpp | 32 +++++------ components/nifosg/nifloader.cpp | 23 +++----- 3 files changed, 77 insertions(+), 76 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 7522543a58..16f5822bf0 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -165,26 +165,33 @@ namespace Nif // Should always match the number of vertices if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - numParticles = nif->getUShort(); + nif->read(mNumParticles); - if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 0)) - std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat()); - else if (nif->getBoolean()) - nif->readVector(particleRadii, mNumVertices); - activeCount = nif->getUShort(); - - // Particle sizes - if (nif->getBoolean()) - nif->readVector(sizes, mNumVertices); - - if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0) && nif->getBoolean()) - nif->readVector(rotations, mNumVertices); - if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) + bool numRadii = 1; + if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) { - if (nif->getBoolean()) - nif->readVector(rotationAngles, mNumVertices); - if (nif->getBoolean()) - nif->readVector(rotationAxes, mNumVertices); + numRadii = mNumVertices; + if (!nif->get() || (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0)) + numRadii = 0; + } + nif->readVector(mRadii, numRadii); + + nif->read(mActiveCount); + + if (nif->get()) + nif->readVector(mSizes, mNumVertices); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) + { + if (nif->get()) + nif->readVector(mRotations, mNumVertices); + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) + { + if (nif->get()) + nif->readVector(mRotationAngles, mNumVertices); + if (nif->get()) + nif->readVector(mRotationAxes, mNumVertices); + } } } @@ -198,7 +205,7 @@ namespace Nif bool hasRotations; nif->read(hasRotations); if (hasRotations) - nif->readVector(rotations, mNumVertices); + nif->readVector(mRotations, mNumVertices); } void NiPosData::read(NIFStream* nif) @@ -319,7 +326,7 @@ namespace Nif if (mData.empty() || mRoot.empty()) throw Nif::Exception("NiSkinInstance missing root or data", nif.getFilename()); - if (mBones.size() != mData->bones.size()) + if (mBones.size() != mData->mBones.size()) throw Nif::Exception("Mismatch in NiSkinData bone count", nif.getFilename()); for (auto& bone : mBones) @@ -344,45 +351,48 @@ namespace Nif void NiSkinData::read(NIFStream* nif) { - trafo.rotation = nif->getMatrix3(); - trafo.pos = nif->getVector3(); - trafo.scale = nif->getFloat(); - - int boneNum = nif->getInt(); - if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW - && nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 0)) - partitions.read(nif); + nif->read(mTransform.rotation); + nif->read(mTransform.pos); + nif->read(mTransform.scale); + uint32_t numBones; + nif->read(numBones); bool hasVertexWeights = true; - if (nif->getVersion() > NIFStream::generateVersion(4, 2, 1, 0)) - hasVertexWeights = nif->getBoolean(); - - bones.resize(boneNum); - for (BoneInfo& bi : bones) + if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) { - bi.trafo.rotation = nif->getMatrix3(); - bi.trafo.pos = nif->getVector3(); - bi.trafo.scale = nif->getFloat(); - bi.boundSphereCenter = nif->getVector3(); - bi.boundSphereRadius = nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 0)) + mPartitions.read(nif); - size_t numVertices = nif->getUShort(); + if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 1, 0)) + nif->read(hasVertexWeights); + } + + mBones.resize(numBones); + for (BoneInfo& bi : mBones) + { + nif->read(bi.mTransform.rotation); + nif->read(bi.mTransform.pos); + nif->read(bi.mTransform.scale); + bi.mBoundSphere = osg::BoundingSpheref(nif->get(), nif->get()); + + uint16_t numVertices; + nif->read(numVertices); if (!hasVertexWeights) continue; - bi.weights.resize(numVertices); - for (size_t j = 0; j < bi.weights.size(); j++) + bi.mWeights.resize(numVertices); + for (auto& [vertex, weight] : bi.mWeights) { - bi.weights[j].vertex = nif->getUShort(); - bi.weights[j].weight = nif->getFloat(); + nif->read(vertex); + nif->read(weight); } } } void NiSkinData::post(Reader& nif) { - partitions.post(nif); + mPartitions.post(nif); } void NiSkinPartition::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 6791265b45..c55dd7ffd8 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -96,13 +96,14 @@ namespace Nif struct NiParticlesData : public NiGeometryData { - int numParticles{ 0 }; + uint16_t mNumParticles{ 0 }; + uint16_t mActiveCount; - int activeCount{ 0 }; - - std::vector particleRadii, sizes, rotationAngles; - std::vector rotations; - std::vector rotationAxes; + std::vector mRadii; + std::vector mSizes; + std::vector mRotations; + std::vector mRotationAngles; + std::vector mRotationAxes; void read(NIFStream* nif) override; }; @@ -267,23 +268,18 @@ namespace Nif struct NiSkinData : public Record { - struct VertWeight - { - unsigned short vertex; - float weight; - }; + using VertWeight = std::pair; struct BoneInfo { - Transformation trafo; - osg::Vec3f boundSphereCenter; - float boundSphereRadius; - std::vector weights; + Transformation mTransform; + osg::BoundingSpheref mBoundSphere; + std::vector mWeights; }; - Transformation trafo; - std::vector bones; - NiSkinPartitionPtr partitions; + Transformation mTransform; + std::vector mBones; + NiSkinPartitionPtr mPartitions; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e56583e493..c748c1a6ae 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1133,14 +1133,14 @@ namespace NifOsg } auto particledata = static_cast(particleNode->data.getPtr()); - partsys->setQuota(particledata->numParticles); + partsys->setQuota(particledata->mNumParticles); osg::BoundingBox box; int i = 0; for (const auto& particle : partctrl->particles) { - if (i++ >= particledata->activeCount) + if (i++ >= particledata->mActiveCount) break; if (particle.lifespan <= 0) @@ -1165,8 +1165,8 @@ namespace NifOsg created->setAlphaRange(osgParticle::rangef(1.f, 1.f)); float size = partctrl->size; - if (particle.vertex < particledata->sizes.size()) - size *= particledata->sizes[particle.vertex]; + if (particle.vertex < particledata->mSizes.size()) + size *= particledata->mSizes[particle.vertex]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); @@ -1394,8 +1394,8 @@ namespace NifOsg if (!skin->mData.empty()) { data = skin->mData.getPtr(); - if (!data->partitions.empty()) - partitions = data->partitions.getPtr(); + if (!data->mPartitions.empty()) + partitions = data->mPartitions.getPtr(); } if (!partitions && !skin->mPartitions.empty()) partitions = skin->mPartitions.getPtr(); @@ -1556,14 +1556,9 @@ namespace NifOsg std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); SceneUtil::RigGeometry::BoneInfluence influence; - const std::vector& weights = data->bones[i].weights; - for (size_t j = 0; j < weights.size(); j++) - { - influence.mWeights.emplace_back(weights[j].vertex, weights[j].weight); - } - influence.mInvBindMatrix = data->bones[i].trafo.toMatrix(); - influence.mBoundSphere - = osg::BoundingSpheref(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius); + influence.mWeights = data->mBones[i].mWeights; + influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + influence.mBoundSphere = data->mBones[i].mBoundSphere; map->mData.emplace_back(boneName, influence); } From 0154bb78dfb300832984509696b7652343b3cff5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 23:19:35 +0300 Subject: [PATCH 0085/2167] Modernize NiSkinPartition --- components/nif/data.cpp | 92 +++++++++++++-------------------- components/nif/data.hpp | 20 +++---- components/nifosg/nifloader.cpp | 8 +-- 3 files changed, 51 insertions(+), 69 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 16f5822bf0..25aa0fce44 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -397,8 +397,7 @@ namespace Nif void NiSkinPartition::read(NIFStream* nif) { - nif->read(mPartitionNum); - mPartitions.resize(mPartitionNum); + mPartitions.resize(nif->get()); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) { @@ -417,89 +416,72 @@ namespace Nif void NiSkinPartition::Partition::read(NIFStream* nif) { - size_t numVertices = nif->getUShort(); - size_t numTriangles = nif->getUShort(); - size_t numBones = nif->getUShort(); - size_t numStrips = nif->getUShort(); - size_t bonesPerVertex = nif->getUShort(); - nif->readVector(bones, numBones); + uint16_t numVertices, numTriangles, numBones, numStrips, bonesPerVertex; + nif->read(numVertices); + nif->read(numTriangles); + nif->read(numBones); + nif->read(numStrips); + nif->read(bonesPerVertex); + nif->readVector(mBones, numBones); bool hasVertexMap = true; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - hasVertexMap = nif->getBoolean(); + nif->read(hasVertexMap); if (hasVertexMap) - nif->readVector(vertexMap, numVertices); + nif->readVector(mVertexMap, numVertices); bool hasVertexWeights = true; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - hasVertexWeights = nif->getBoolean(); + nif->read(hasVertexWeights); if (hasVertexWeights) - nif->readVector(weights, numVertices * bonesPerVertex); + nif->readVector(mWeights, numVertices * bonesPerVertex); std::vector stripLengths; nif->readVector(stripLengths, numStrips); bool hasFaces = true; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - hasFaces = nif->getBoolean(); + nif->read(hasFaces); if (hasFaces) { if (numStrips) { - strips.resize(numStrips); + mStrips.resize(numStrips); for (size_t i = 0; i < numStrips; i++) - nif->readVector(strips[i], stripLengths[i]); + nif->readVector(mStrips[i], stripLengths[i]); } else - nif->readVector(triangles, numTriangles * 3); + nif->readVector(mTriangles, numTriangles * 3); } - bool hasBoneIndices = nif->getChar() != 0; - if (hasBoneIndices) - nif->readVector(boneIndices, numVertices * bonesPerVertex); + if (nif->get() != 0) + nif->readVector(mBoneIndices, numVertices * bonesPerVertex); if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { - nif->getChar(); // LOD level - nif->getBoolean(); // Global VB + nif->read(mLODLevel); + nif->read(mGlobalVB); } if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) { mVertexDesc.read(nif); - nif->readVector(trueTriangles, numTriangles * 3); + nif->readVector(mTrueTriangles, numTriangles * 3); + } + else if (!mVertexMap.empty()) + { + if (!mStrips.empty()) + { + mTrueStrips = mStrips; + for (auto& strip : mTrueStrips) + for (auto& index : strip) + index = mVertexMap[index]; + } + else if (!mTriangles.empty()) + { + mTrueTriangles = mTriangles; + for (unsigned short& index : mTrueTriangles) + index = mVertexMap[index]; + } } - } - - std::vector NiSkinPartition::Partition::getTrueTriangles() const - { - if (!trueTriangles.empty()) - return trueTriangles; - - std::vector remappedTriangles; - if (vertexMap.empty() || triangles.empty()) - return remappedTriangles; - - remappedTriangles = triangles; - - for (unsigned short& index : remappedTriangles) - index = vertexMap[index]; - return remappedTriangles; - } - - std::vector> NiSkinPartition::Partition::getTrueStrips() const - { - if (!trueTriangles.empty()) - return {}; - - std::vector> remappedStrips; - if (vertexMap.empty() || strips.empty()) - return remappedStrips; - - remappedStrips = strips; - for (auto& strip : remappedStrips) - for (auto& index : strip) - index = vertexMap[index]; - - return remappedStrips; } void NiMorphData::read(NIFStream* nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index c55dd7ffd8..025a2d524e 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -289,20 +289,20 @@ namespace Nif { struct Partition { - std::vector bones; - std::vector vertexMap; - std::vector weights; - std::vector> strips; - std::vector triangles; - std::vector trueTriangles; - std::vector boneIndices; + std::vector mBones; + std::vector mVertexMap; + std::vector mWeights; + std::vector> mStrips; + std::vector mTriangles; + std::vector mBoneIndices; BSVertexDesc mVertexDesc; + std::vector mTrueTriangles; + std::vector> mTrueStrips; + uint8_t mLODLevel; + bool mGlobalVB; void read(NIFStream* nif); - std::vector getTrueTriangles() const; - std::vector> getTrueStrips() const; }; - unsigned int mPartitionNum; std::vector mPartitions; unsigned int mDataSize; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c748c1a6ae..acc1c2d054 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1403,18 +1403,18 @@ namespace NifOsg hasPartitions = partitions != nullptr; if (hasPartitions) { - std::vector trueTriangles; for (const Nif::NiSkinPartition::Partition& partition : partitions->mPartitions) { - trueTriangles = partition.getTrueTriangles(); + const std::vector& trueTriangles = partition.mTrueTriangles; if (!trueTriangles.empty()) { geometry->addPrimitiveSet(new osg::DrawElementsUShort( osg::PrimitiveSet::TRIANGLES, trueTriangles.size(), trueTriangles.data())); } - const std::vector> trueStrips = partition.getTrueStrips(); - for (const auto& strip : trueStrips) + for (const auto& strip : partition.mTrueStrips) { + if (strip.size() < 3) + continue; geometry->addPrimitiveSet(new osg::DrawElementsUShort( osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), strip.data())); } From fd3b24e2e6272adc1de2da00841f804d17919e58 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 23:30:37 +0300 Subject: [PATCH 0086/2167] Make better use of osg::BoundingSpheref in the NIF loader --- components/nif/data.cpp | 8 +++----- components/nif/data.hpp | 6 ++---- components/nif/nifstream.cpp | 13 +++++++++++++ components/nif/nifstream.hpp | 5 +++++ components/nif/node.cpp | 10 ++-------- components/nif/node.hpp | 11 ++--------- components/nifosg/nifloader.cpp | 2 +- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 25aa0fce44..5137f4309e 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -46,8 +46,7 @@ namespace Nif } } - nif->read(mCenter); - nif->read(mRadius); + nif->read(mBoundingSphere); bool hasColors; nif->read(hasColors); @@ -373,7 +372,7 @@ namespace Nif nif->read(bi.mTransform.rotation); nif->read(bi.mTransform.pos); nif->read(bi.mTransform.scale); - bi.mBoundSphere = osg::BoundingSpheref(nif->get(), nif->get()); + nif->read(bi.mBoundSphere); uint16_t numVertices; nif->read(numVertices); @@ -574,8 +573,7 @@ namespace Nif void BSMultiBoundSphere::read(NIFStream* nif) { - nif->read(mCenter); - nif->read(mRadius); + nif->read(mSphere); } } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 025a2d524e..847d568ba0 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -51,8 +51,7 @@ namespace Nif uint16_t mDataFlags{ 0 }; uint32_t mMaterialHash; std::vector mNormals, mTangents, mBitangents; - osg::Vec3f mCenter; - float mRadius; + osg::BoundingSpheref mBoundingSphere; std::vector mColors; std::vector> mUVList; uint16_t mConsistencyType; @@ -402,8 +401,7 @@ namespace Nif struct BSMultiBoundSphere : public BSMultiBoundData { - osg::Vec3f mCenter; - float mRadius; + osg::BoundingSpheref mSphere; void read(NIFStream* nif) override; }; diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index db7528b2d0..c666a5edab 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -122,6 +122,13 @@ namespace Nif quat.z() = data[3]; } + template <> + void NIFStream::read(osg::BoundingSpheref& sphere) + { + read(sphere.center()); + read(sphere.radius()); + } + template <> void NIFStream::read(Transformation& t) { @@ -178,6 +185,12 @@ namespace Nif readRange(*this, dest, size); } + template <> + void NIFStream::read(osg::BoundingSpheref* dest, size_t size) + { + readRange(*this, dest, size); + } + template <> void NIFStream::read(Transformation* dest, size_t size) { diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 58b160fcca..217d924059 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -174,6 +175,8 @@ namespace Nif template <> void NIFStream::read(osg::Quat& quat); template <> + void NIFStream::read(osg::BoundingSpheref& sphere); + template <> void NIFStream::read(Transformation& t); template <> void NIFStream::read(bool& data); @@ -191,6 +194,8 @@ namespace Nif template <> void NIFStream::read(osg::Quat* dest, size_t size); template <> + void NIFStream::read(osg::BoundingSpheref* dest, size_t size); + template <> void NIFStream::read(Transformation* dest, size_t size); template <> void NIFStream::read(bool* dest, size_t size); diff --git a/components/nif/node.cpp b/components/nif/node.cpp index a2ae77830a..6028ac253f 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -19,7 +19,7 @@ namespace Nif break; case SPHERE_BV: { - sphere.read(nif); + nif->read(sphere); break; } case BOX_BV: @@ -336,7 +336,7 @@ namespace Nif void BSTriShape::read(NIFStream* nif) { Node::read(nif); - mBoundingSphere.read(nif); + nif->read(mBoundingSphere); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) { @@ -412,12 +412,6 @@ namespace Nif mFlags = (data & 0xFFF00000000000) >> 0x2C; } - void NiBoundingVolume::NiSphereBV::read(NIFStream* nif) - { - nif->read(center); - nif->read(radius); - } - void BSVertexData::read(NIFStream* nif, uint16_t flags) { uint16_t vertexFlag = flags & BSVertexDesc::VertexAttribute::Vertex; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index dacaa1e823..2d8021eb49 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -26,13 +26,6 @@ namespace Nif HALFSPACE_BV = 5 }; - struct NiSphereBV - { - osg::Vec3f center; - float radius{ 0.f }; - void read(NIFStream* nif); - }; - struct NiBoxBV { osg::Vec3f center; @@ -59,7 +52,7 @@ namespace Nif }; unsigned int type; - NiSphereBV sphere; + osg::BoundingSpheref sphere; NiBoxBV box; NiCapsuleBV capsule; NiLozengeBV lozenge; @@ -355,7 +348,7 @@ namespace Nif struct BSTriShape : Node { - NiBoundingVolume::NiSphereBV mBoundingSphere; + osg::BoundingSpheref mBoundingSphere; std::array mBoundMinMax; NiSkinInstancePtr mSkin; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index acc1c2d054..d1563df63c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1173,7 +1173,7 @@ namespace NifOsg } // radius may be used to force a larger bounding box - box.expandBy(osg::BoundingSphere(osg::Vec3(0, 0, 0), particledata->mRadius)); + box.expandBy(osg::BoundingSphere(osg::Vec3(0, 0, 0), particledata->mBoundingSphere.radius())); partsys->setInitialBound(box); } From efe1c66536c7e2dffe2647dded49e9556f0b0876 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 5 Sep 2023 23:49:09 +0300 Subject: [PATCH 0087/2167] Further cleanup in NiGeometryData, update NiParticlesData to 20.2.0.7 --- components/nif/data.cpp | 85 ++++++++++++++++++++++------------------- components/nif/data.hpp | 7 ++++ 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 5137f4309e..693d05b82c 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -13,7 +13,7 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 114)) nif->read(mGroupId); - // Note: has special meaning for NiPSysData + // Note: has special meaning for NiPSysData (unimplemented) nif->read(mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) @@ -22,21 +22,19 @@ namespace Nif nif->read(mCompressFlags); } - bool hasVertices; - nif->read(hasVertices); - if (hasVertices) + if (nif->get()) nif->readVector(mVertices, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) + { nif->read(mDataFlags); - if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS - && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->read(mMaterialHash); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS + && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->read(mMaterialHash); + } - bool hasNormals; - nif->read(hasNormals); - if (hasNormals) + if (nif->get()) { nif->readVector(mNormals, mNumVertices); if (mDataFlags & DataFlag_HasTangents) @@ -48,9 +46,7 @@ namespace Nif nif->read(mBoundingSphere); - bool hasColors; - nif->read(hasColors); - if (hasColors) + if (nif->get()) nif->readVector(mColors, mNumVertices); if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) @@ -65,27 +61,24 @@ namespace Nif if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) numUVs &= DataFlag_HasUV; } + else if (!nif->get()) + numUVs = 0; - bool hasUVs = true; - if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - nif->read(hasUVs); - if (hasUVs) + mUVList.resize(numUVs); + for (std::vector& list : mUVList) { - mUVList.resize(numUVs); - for (std::vector& list : mUVList) - { - nif->readVector(list, mNumVertices); - // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin - for (osg::Vec2f& uv : list) - uv.y() = 1.f - uv.y(); - } + nif->readVector(list, mNumVertices); + // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin + for (osg::Vec2f& uv : list) + uv.y() = 1.f - uv.y(); } if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) + { nif->read(mConsistencyType); - - if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) - nif->skip(4); // Additional data + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) + nif->skip(4); // Additional data + } } void NiTriBasedGeomData::read(NIFStream* nif) @@ -165,31 +158,45 @@ namespace Nif // Should always match the number of vertices if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) nif->read(mNumParticles); + else if (nif->getVersion() != NIFFile::NIFVersion::VER_BGS || nif->getBethVersion() == 0) + mNumParticles = mNumVertices; bool numRadii = 1; if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) - { - numRadii = mNumVertices; - if (!nif->get() || (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0)) - numRadii = 0; - } + numRadii = nif->get() ? mNumParticles : 0; nif->readVector(mRadii, numRadii); - nif->read(mActiveCount); - if (nif->get()) - nif->readVector(mSizes, mNumVertices); + nif->readVector(mSizes, mNumParticles); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) { if (nif->get()) - nif->readVector(mRotations, mNumVertices); + nif->readVector(mRotations, mNumParticles); if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) { if (nif->get()) - nif->readVector(mRotationAngles, mNumVertices); + nif->readVector(mRotationAngles, mNumParticles); if (nif->get()) - nif->readVector(mRotationAxes, mNumVertices); + nif->readVector(mRotationAxes, mNumParticles); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) + { + nif->read(mHasTextureIndices); + uint32_t numSubtextureOffsets; + if (nif->getBethVersion() <= 34) + numSubtextureOffsets = nif->get(); + else + nif->read(numSubtextureOffsets); + nif->readVector(mSubtextureOffsets, numSubtextureOffsets); + if (nif->getBethVersion() > 34) + { + nif->read(mAspectRatio); + nif->read(mAspectFlags); + nif->read(mAspectRatio2); + nif->read(mAspectSpeed); + nif->read(mAspectSpeed2); + } + } } } } diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 847d568ba0..42f52ba5f1 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -104,6 +104,13 @@ namespace Nif std::vector mRotationAngles; std::vector mRotationAxes; + bool mHasTextureIndices{ false }; + std::vector mSubtextureOffsets; + float mAspectRatio{ 1.f }; + uint16_t mAspectFlags{ 0 }; + float mAspectRatio2; + float mAspectSpeed, mAspectSpeed2; + void read(NIFStream* nif) override; }; From 5b8f574be3b49d0278bdb0d67010abe0e7a96f7b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 6 Sep 2023 00:32:04 +0300 Subject: [PATCH 0088/2167] Update keygroup/morph loading --- components/nif/nifkey.hpp | 53 +++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index 8f0e54de0d..bbd06cb048 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -49,7 +49,9 @@ namespace Nif using ValueType = T; using KeyType = KeyT; - unsigned int mInterpolationType = InterpolationType_Unknown; + std::string mFrameName; + float mLegacyWeight; + uint32_t mInterpolationType = InterpolationType_Unknown; MapType mKeys; // Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) @@ -57,21 +59,25 @@ namespace Nif { assert(nif); - if (morph && nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106)) - nif->getString(); // Frame name - - if (morph && nif->getVersion() > NIFStream::generateVersion(10, 1, 0, 0)) + if (morph) { - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104) - && nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 2) && nif->getBethVersion() < 10) - nif->getFloat(); // Legacy weight - return; + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106)) + nif->read(mFrameName); + + if (nif->getVersion() > NIFStream::generateVersion(10, 1, 0, 0)) + { + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104) + && nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 2) && nif->getBethVersion() < 10) + nif->read(mLegacyWeight); + return; + } } - size_t count = nif->getUInt(); + uint32_t count; + nif->read(count); if (count != 0 || morph) - mInterpolationType = nif->getUInt(); + nif->read(mInterpolationType); KeyType key = {}; @@ -79,7 +85,8 @@ namespace Nif { for (size_t i = 0; i < count; i++) { - float time = nif->getFloat(); + float time; + nif->read(time); readValue(*nif, key); mKeys[time] = key; } @@ -88,7 +95,8 @@ namespace Nif { for (size_t i = 0; i < count; i++) { - float time = nif->getFloat(); + float time; + nif->read(time); readQuadratic(*nif, key); mKeys[time] = key; } @@ -97,7 +105,8 @@ namespace Nif { for (size_t i = 0; i < count; i++) { - float time = nif->getFloat(); + float time; + nif->read(time); readTBC(*nif, key); mKeys[time] = key; } @@ -134,16 +143,16 @@ namespace Nif static void readTBC(NIFStream& nif, KeyT& key) { readValue(nif, key); - /*key.mTension = */ nif.getFloat(); - /*key.mBias = */ nif.getFloat(); - /*key.mContinuity = */ nif.getFloat(); + /*key.mTension = */ nif.get(); + /*key.mBias = */ nif.get(); + /*key.mContinuity = */ nif.get(); } }; - using FloatKeyMap = KeyMapT; - using Vector3KeyMap = KeyMapT; - using Vector4KeyMap = KeyMapT; - using QuaternionKeyMap = KeyMapT; - using ByteKeyMap = KeyMapT; + using FloatKeyMap = KeyMapT>; + using Vector3KeyMap = KeyMapT>; + using Vector4KeyMap = KeyMapT>; + using QuaternionKeyMap = KeyMapT>; + using ByteKeyMap = KeyMapT>; using FloatKeyMapPtr = std::shared_ptr; using Vector3KeyMapPtr = std::shared_ptr; From bf64ad6470743d659bfa61bf2fbafa8acfd19d70 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 6 Sep 2023 01:12:29 +0300 Subject: [PATCH 0089/2167] Further cleanup --- components/nif/data.cpp | 59 ++++++++++++----------------------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 693d05b82c..c62fe1a4f0 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -94,12 +94,9 @@ namespace Nif uint32_t numIndices; nif->read(numIndices); - bool hasTriangles = true; - if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) - nif->read(hasTriangles); - if (hasTriangles) - nif->readVector(mTriangles, numIndices); - + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD && !nif->get()) + numIndices = 0; + nif->readVector(mTriangles, numIndices); mMatchGroups.resize(nif->get()); for (auto& group : mMatchGroups) nif->readVector(group, nif->get()); @@ -113,13 +110,8 @@ namespace Nif nif->read(numStrips); std::vector lengths; nif->readVector(lengths, numStrips); - - bool hasStrips = true; - if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) - nif->read(hasStrips); - if (!hasStrips || !numStrips) - return; - + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD && !nif->get()) + numStrips = 0; mStrips.resize(numStrips); for (int i = 0; i < numStrips; i++) nif->readVector(mStrips[i], lengths[i]); @@ -205,13 +197,8 @@ namespace Nif { NiParticlesData::read(nif); - if (nif->getVersion() > NIFStream::generateVersion(4, 2, 2, 0)) - return; - - bool hasRotations; - nif->read(hasRotations); - if (hasRotations) - nif->readVector(mRotations, mNumVertices); + if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->get()) + nif->readVector(mRotations, mNumParticles); } void NiPosData::read(NIFStream* nif) @@ -429,26 +416,13 @@ namespace Nif nif->read(numStrips); nif->read(bonesPerVertex); nif->readVector(mBones, numBones); - - bool hasVertexMap = true; - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(hasVertexMap); - if (hasVertexMap) + if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) nif->readVector(mVertexMap, numVertices); - - bool hasVertexWeights = true; - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(hasVertexWeights); - if (hasVertexWeights) + if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) nif->readVector(mWeights, numVertices * bonesPerVertex); - std::vector stripLengths; nif->readVector(stripLengths, numStrips); - - bool hasFaces = true; - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(hasFaces); - if (hasFaces) + if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) { if (numStrips) { @@ -465,14 +439,15 @@ namespace Nif { nif->read(mLODLevel); nif->read(mGlobalVB); + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) + { + mVertexDesc.read(nif); + nif->readVector(mTrueTriangles, numTriangles * 3); + } } - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) - { - mVertexDesc.read(nif); - nif->readVector(mTrueTriangles, numTriangles * 3); - } - else if (!mVertexMap.empty()) + // Not technically a part of the loading process + if (mTrueTriangles.empty() && !mVertexMap.empty()) { if (!mStrips.empty()) { From b7c69d109f75b68fd765edd57fc76c501a039c98 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 6 Sep 2023 20:45:43 +0300 Subject: [PATCH 0090/2167] Use Fallout 3 stream version constant in NiParticlesData --- components/nif/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index c62fe1a4f0..fddb2057e9 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -175,12 +175,12 @@ namespace Nif { nif->read(mHasTextureIndices); uint32_t numSubtextureOffsets; - if (nif->getBethVersion() <= 34) + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) numSubtextureOffsets = nif->get(); else nif->read(numSubtextureOffsets); nif->readVector(mSubtextureOffsets, numSubtextureOffsets); - if (nif->getBethVersion() > 34) + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { nif->read(mAspectRatio); nif->read(mAspectFlags); From 145ce8f924a060eb1be2a1a4c2c22f69c8896e0e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 6 Sep 2023 20:54:04 +0300 Subject: [PATCH 0091/2167] Deduplicate some version checks, avoid resizing twice --- components/nif/data.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index fddb2057e9..08abcc1621 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -416,13 +416,14 @@ namespace Nif nif->read(numStrips); nif->read(bonesPerVertex); nif->readVector(mBones, numBones); - if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) + bool hasPresenceFlags = nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0); + if (!hasPresenceFlags || nif->get()) nif->readVector(mVertexMap, numVertices); - if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) + if (!hasPresenceFlags || nif->get()) nif->readVector(mWeights, numVertices * bonesPerVertex); std::vector stripLengths; nif->readVector(stripLengths, numStrips); - if (nif->getVersion() < NIFStream::generateVersion(10, 1, 0, 0) || nif->get()) + if (!hasPresenceFlags || nif->get()) { if (numStrips) { @@ -509,12 +510,8 @@ namespace Nif uint32_t numEntries; nif->read(numEntries); - // Fill the entire palette with black even if there isn't enough entries. - mColors.resize(256); - if (numEntries > 256) - mColors.resize(numEntries); - + mColors.resize(numEntries > 256 ? numEntries : 256); for (uint32_t i = 0; i < numEntries; i++) { nif->read(mColors[i]); From 42f2ece8b357df28e50185c5638bc72a469fed06 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 7 Sep 2023 02:16:22 +0200 Subject: [PATCH 0092/2167] Fix bug: UI modes that are added not through Lua don't show windows that were hidden by Lua in other mode. --- apps/openmw/mwlua/luamanagerimp.cpp | 6 +++++- apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/playerscripts.hpp | 6 +++--- files/data/scripts/omw/ui.lua | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 7262d945c5..ab0aa6c890 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -243,6 +243,7 @@ namespace MWLua void LuaManager::applyDelayedActions() { + mApplyingDelayedActions = true; for (DelayedAction& action : mActionQueue) action.apply(); mActionQueue.clear(); @@ -250,6 +251,7 @@ namespace MWLua if (mTeleportPlayerAction) mTeleportPlayerAction->apply(); mTeleportPlayerAction.reset(); + mApplyingDelayedActions = false; } void LuaManager::clear() @@ -318,7 +320,7 @@ namespace MWLua return; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) - playerScripts->uiModeChanged(arg); + playerScripts->uiModeChanged(arg, mApplyingDelayedActions); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) @@ -558,6 +560,8 @@ namespace MWLua void LuaManager::addAction(std::function action, std::string_view name) { + if (mApplyingDelayedActions) + throw std::runtime_error("DelayedAction is not allowed to create another DelayedAction"); mActionQueue.emplace_back(&mLua, std::move(action), name); } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 03efa3f066..c14c9ace5b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -153,6 +153,7 @@ namespace MWLua bool mInitialized = false; bool mGlobalScriptsStarted = false; bool mProcessingInputEvents = false; + bool mApplyingDelayedActions = false; bool mNewGameStarted = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index de48064734..e8cad4968c 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -66,12 +66,12 @@ namespace MWLua } // `arg` is either forwarded from MWGui::pushGuiMode or empty - void uiModeChanged(const MWWorld::Ptr& arg) + void uiModeChanged(const MWWorld::Ptr& arg, bool byLuaAction) { if (arg.isEmpty()) - callEngineHandlers(mUiModeChanged); + callEngineHandlers(mUiModeChanged, byLuaAction); else - callEngineHandlers(mUiModeChanged, LObject(arg)); + callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg)); } private: diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 8199aec8f0..5837b01f36 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -99,7 +99,7 @@ local function removeMode(mode) end local oldMode = nil -local function onUiModeChanged(arg) +local function onUiModeChanged(changedByLua, arg) local newStack = ui._getUiModeStack() for i = 1, math.max(#modeStack, #newStack) do modeStack[i] = newStack[i] @@ -112,6 +112,9 @@ local function onUiModeChanged(arg) end local mode = newStack[#newStack] if mode then + if not changedByLua then + updateHidden(mode) + end for _, w in pairs(ui._getAllowedWindows(mode)) do local state = replacedWindows[w] if state and not hiddenWindows[w] then From cd0e612cb703b6d8665c85da53dc091355334203 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 7 Sep 2023 21:30:08 +0200 Subject: [PATCH 0093/2167] Expose skill and attribute records to Lua and deprecate their enums --- apps/openmw/mwlua/luabindings.cpp | 2 + apps/openmw/mwlua/stats.cpp | 80 +++++++++++++++++++++++++++++++ apps/openmw/mwlua/stats.hpp | 1 + files/lua_api/openmw/core.lua | 58 +++++++++++++++++++++- files/lua_api/openmw/types.lua | 2 +- 5 files changed, 140 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index d844670eca..9aac877e4b 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -42,6 +42,7 @@ #include "objectbindings.hpp" #include "postprocessingbindings.hpp" #include "soundbindings.hpp" +#include "stats.hpp" #include "types/types.hpp" #include "uibindings.hpp" #include "vfsbindings.hpp" @@ -151,6 +152,7 @@ namespace MWLua }; addTimeBindings(api, context, false); api["magic"] = initCoreMagicBindings(context); + api["stats"] = initCoreStatsBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index c202e9dc33..5c1e536dd6 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include "context.hpp" #include "localscripts.hpp" @@ -20,6 +22,7 @@ #include "../mwworld/esmstore.hpp" #include "objectvariant.hpp" +#include "types/types.hpp" namespace { @@ -336,6 +339,18 @@ namespace sol struct is_automagical : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -393,4 +408,69 @@ namespace MWLua for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get()) skills[ESM::RefId(skill.mId).serializeText()] = addIndexedAccessor(skill.mId); } + + sol::table initCoreStatsBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table statsApi(lua, sol::create); + auto* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + sol::table attributes(lua, sol::create); + addRecordFunctionBinding(attributes, context); + statsApi["Attribute"] = LuaUtil::makeReadOnly(attributes); + statsApi["Attribute"][sol::metatable_key][sol::meta_function::to_string] = ESM::Attribute::getRecordType; + + auto attributeT = context.mLua->sol().new_usertype("Attribute"); + attributeT[sol::meta_function::to_string] + = [](const ESM::Attribute& rec) { return "ESM3_Attribute[" + rec.mId.toDebugString() + "]"; }; + attributeT["id"] = sol::readonly_property( + [](const ESM::Attribute& rec) -> std::string { return ESM::RefId{ rec.mId }.serializeText(); }); + attributeT["name"] + = sol::readonly_property([](const ESM::Attribute& rec) -> std::string_view { return rec.mName; }); + attributeT["description"] + = sol::readonly_property([](const ESM::Attribute& rec) -> std::string_view { return rec.mDescription; }); + attributeT["icon"] = sol::readonly_property([vfs](const ESM::Attribute& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + + sol::table skills(lua, sol::create); + addRecordFunctionBinding(skills, context); + statsApi["Skill"] = LuaUtil::makeReadOnly(skills); + statsApi["Skill"][sol::metatable_key][sol::meta_function::to_string] = ESM::Skill::getRecordType; + + auto skillT = context.mLua->sol().new_usertype("Skill"); + skillT[sol::meta_function::to_string] + = [](const ESM::Skill& rec) { return "ESM3_Skill[" + rec.mId.toDebugString() + "]"; }; + skillT["id"] = sol::readonly_property( + [](const ESM::Skill& rec) -> std::string { return ESM::RefId{ rec.mId }.serializeText(); }); + skillT["name"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mName; }); + skillT["description"] + = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); + skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { + return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); + }); + skillT["school"] = sol::readonly_property([](const ESM::Skill& rec) -> const ESM::MagicSchool* { + if (!rec.mSchool) + return nullptr; + return &*rec.mSchool; + }); + + auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); + schoolT[sol::meta_function::to_string] + = [](const ESM::MagicSchool& rec) { return "ESM3_MagicSchool[" + rec.mName + "]"; }; + schoolT["name"] + = sol::readonly_property([](const ESM::MagicSchool& rec) -> std::string_view { return rec.mName; }); + schoolT["areaSound"] = sol::readonly_property( + [](const ESM::MagicSchool& rec) -> std::string { return rec.mAreaSound.serializeText(); }); + schoolT["boltSound"] = sol::readonly_property( + [](const ESM::MagicSchool& rec) -> std::string { return rec.mBoltSound.serializeText(); }); + schoolT["castSound"] = sol::readonly_property( + [](const ESM::MagicSchool& rec) -> std::string { return rec.mCastSound.serializeText(); }); + schoolT["failureSound"] = sol::readonly_property( + [](const ESM::MagicSchool& rec) -> std::string { return rec.mFailureSound.serializeText(); }); + schoolT["hitSound"] = sol::readonly_property( + [](const ESM::MagicSchool& rec) -> std::string { return rec.mHitSound.serializeText(); }); + + return LuaUtil::makeReadOnly(statsApi); + } } diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index 8c5824cc71..4ce2f6b5eb 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -9,6 +9,7 @@ namespace MWLua void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); + sol::table initCoreStatsBindings(const Context& context); } #endif diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index abe151eb73..3bd86de5be 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -429,9 +429,10 @@ -- @usage for _, item in ipairs(inventory:findAll('common_shirt_01')) do ... end ---- Possible @{#ATTRIBUTE} values +--- Possible @{#ATTRIBUTE} values (DEPRECATED use @{#Attribute}) -- @field [parent=#core] #ATTRIBUTE ATTRIBUTE +--- DEPRECATED, use @{#Attribute} --- `core.ATTRIBUTE` -- @type ATTRIBUTE -- @field #string Strength "strength" @@ -444,9 +445,10 @@ -- @field #string Luck "luck" ---- Possible @{#SKILL} values +--- Possible @{#SKILL} values (DEPRECATED use @{#Skill}) -- @field [parent=#core] #SKILL SKILL +--- DEPRECATED, use @{#Skill} --- `core.SKILL` -- @type SKILL -- @field #string Acrobatics "acrobatics" @@ -866,4 +868,56 @@ -- print(sound.fileName) -- end + +--- @{#Stats}: stats +-- @field [parent=#core] #Stats stats + + +--- @{#Attribute} functions +-- @field [parent=#Stats] #Attribute Attribute + +--- `core.stats.Attribute` +-- @type Attribute +-- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database. + +--- +-- Returns a read-only @{#AttributeRecord} +-- @function [parent=#Attribute] record +-- @param #string recordId +-- @return #AttributeRecord + +--- @{#Skill} functions +-- @field [parent=#Stats] #Skill Skill + +--- `core.stats.Skill` +-- @type Skill +-- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database. + +--- +-- Returns a read-only @{#SkillRecord} +-- @function [parent=#Skill] record +-- @param #string recordId +-- @return #SkillRecord + +-- @type AttributeRecord +-- @field #string id Record id +-- @field #string name Human-readable name +-- @field #string description Human-readable description +-- @field #string icon VFS path to the icon + +-- @type SkillRecord +-- @field #string id Record id +-- @field #string name Human-readable name +-- @field #string description Human-readable description +-- @field #string icon VFS path to the icon +-- @field #MagicSchoolData school Optional magic school + +-- @type MagicSchoolData +-- @field #string name Human-readable name +-- @field #string areaSound VFS path to the area sound +-- @field #string boltSound VFS path to the bolt sound +-- @field #string castSound VFS path to the cast sound +-- @field #string failureSound VFS path to the failure sound +-- @field #string hitSound VFS path to the hit sound + return nil diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 557bbabd86..b491203052 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -919,7 +919,7 @@ -- @field #string speechcraft "speechcraft" -- @field #string unarmored "unarmored" ---- DEPRECATED, use @{openmw.core#SKILL} +--- DEPRECATED, use @{openmw.core#Skill} -- @field [parent=#Book] #BookSKILL SKILL --- From c77b88cd380c0a6276f90ff0d9963a2e710560bf Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 7 Sep 2023 21:42:21 +0800 Subject: [PATCH 0094/2167] fix string format --- components/lua/utf8.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index 45e05b45d3..b486766b6a 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -23,7 +23,7 @@ namespace if (std::modf(arg, &integer) != 0) throw std::runtime_error( - Misc::StringUtils::format("bad argument #{} to '{}' (number has no integer representation)", n, name)); + Misc::StringUtils::format("bad argument #%i to '%s' (number has no integer representation)", n, name)); return integer; } @@ -102,7 +102,7 @@ namespace LuaUtf8 int64_t codepoint = getInteger(args[i], (i + 1), "char"); if (codepoint < 0 || codepoint > MAXUTF) throw std::runtime_error( - Misc::StringUtils::format("bad argument #{} to 'char' (value out of range)", (i + 1))); + "bad argument #" + std::to_string(i + 1) + " to 'char' (value out of range)"); result += converter.to_bytes(codepoint); } From 7c5caec443d2cbe445f791deb1be107ba652a21a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Sep 2023 08:30:06 +0400 Subject: [PATCH 0095/2167] Allow creatures to be companions again --- apps/openmw/mwgui/companionwindow.cpp | 12 ++++++------ apps/openmw/mwgui/companionwindow.hpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index ff6cf2d5f7..240198eddc 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -71,7 +71,7 @@ namespace MWGui const ItemStack& item = mSortModel->getItem(index); - // We can't take conjured items from a companion NPC + // We can't take conjured items from a companion actor if (item.mFlags & ItemStack::Flag_Bound) { MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); @@ -119,13 +119,13 @@ namespace MWGui } } - void CompanionWindow::setPtr(const MWWorld::Ptr& npc) + void CompanionWindow::setPtr(const MWWorld::Ptr& actor) { - if (npc.isEmpty() || npc.getType() != ESM::REC_NPC_) + if (actor.isEmpty() || !actor.getClass().isActor()) throw std::runtime_error("Invalid argument in CompanionWindow::setPtr"); - mPtr = npc; + mPtr = actor; updateEncumbranceBar(); - auto model = std::make_unique(npc); + auto model = std::make_unique(actor); mModel = model.get(); auto sortModel = std::make_unique(std::move(model)); mSortModel = sortModel.get(); @@ -133,7 +133,7 @@ namespace MWGui mItemView->setModel(std::move(sortModel)); mItemView->resetScrollBars(); - setTitle(npc.getClass().getName(npc)); + setTitle(actor.getClass().getName(actor)); } void CompanionWindow::onFrame(float dt) diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index c85044b472..97f3a0072e 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -26,7 +26,7 @@ namespace MWGui void resetReference() override; - void setPtr(const MWWorld::Ptr& npc) override; + void setPtr(const MWWorld::Ptr& actor) override; void onFrame(float dt) override; void clear() override { resetReference(); } From 5d211d3c9360326120abb6ed1a5d9decadb5821e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 8 Sep 2023 09:25:13 +0400 Subject: [PATCH 0096/2167] Return nil when we try to use an invalid store index from Lua --- apps/openmw/mwlua/luabindings.cpp | 6 ++++- apps/openmw/mwlua/magicbindings.cpp | 34 +++++++++++++++++----------- apps/openmw/mwlua/objectbindings.cpp | 4 ++-- apps/openmw/mwlua/soundbindings.cpp | 8 +++++-- apps/openmw/mwlua/types/player.cpp | 10 ++++---- apps/openmw/mwlua/types/types.hpp | 4 +++- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index d844670eca..26cd10155f 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -231,7 +231,11 @@ namespace MWLua sol::usertype cells = context.mLua->sol().new_usertype("Cells"); cells[sol::meta_function::length] = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; - cells[sol::meta_function::index] = [cells3Store, cells4Store](const CellsStore&, size_t index) -> GCell { + cells[sol::meta_function::index] + = [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional { + if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0) + return sol::nullopt; + index--; // Translate from Lua's 1-based indexing. if (index < cells3Store->getSize()) { diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 4d4f111ab1..afcc5bc54a 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -243,9 +243,13 @@ namespace MWLua = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; spellStoreT[sol::meta_function::index] = sol::overload( - [](const SpellStore& store, size_t index) -> const ESM::Spell* { return store.at(index - 1); }, + [](const SpellStore& store, size_t index) -> const ESM::Spell* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); + }, [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { - return store.find(ESM::RefId::deserializeText(spellId)); + return store.search(ESM::RefId::deserializeText(spellId)); }); spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); @@ -262,9 +266,13 @@ namespace MWLua }; enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); }; enchantmentStoreT[sol::meta_function::index] = sol::overload( - [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { return store.at(index - 1); }, + [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); + }, [](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* { - return store.find(ESM::RefId::deserializeText(enchantmentId)); + return store.search(ESM::RefId::deserializeText(enchantmentId)); }); enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); @@ -280,10 +288,10 @@ namespace MWLua return "ESM3_MagicEffectStore{" + std::to_string(store.getSize()) + " effects}"; }; magicEffectStoreT[sol::meta_function::index] = sol::overload( - [](const MagicEffectStore& store, int id) -> const ESM::MagicEffect* { return store.find(id); }, + [](const MagicEffectStore& store, int id) -> const ESM::MagicEffect* { return store.search(id); }, [](const MagicEffectStore& store, std::string_view id) -> const ESM::MagicEffect* { int index = ESM::MagicEffect::indexNameToIndex(id); - return store.find(index); + return store.search(index); }); auto magicEffectsIter = [magicEffectStore](sol::this_state lua, const sol::object& /*store*/, sol::optional id) -> std::tuple { @@ -665,20 +673,20 @@ namespace MWLua // types.Actor.spells(o)[i] spellsT[sol::meta_function::index] = sol::overload( - [](const ActorSpells& spells, size_t index) -> sol::optional { + [](const ActorSpells& spells, size_t index) -> const ESM::Spell* { if (auto* store = spells.getStore()) - if (index <= store->count()) + if (index <= store->count() && index > 0) return store->at(index - 1); - return sol::nullopt; + return nullptr; }, - [spellStore](const ActorSpells& spells, std::string_view spellId) -> sol::optional { + [spellStore](const ActorSpells& spells, std::string_view spellId) -> const ESM::Spell* { if (auto* store = spells.getStore()) { - const ESM::Spell* spell = spellStore->find(ESM::RefId::deserializeText(spellId)); - if (store->hasSpell(spell)) + const ESM::Spell* spell = spellStore->search(ESM::RefId::deserializeText(spellId)); + if (spell && store->hasSpell(spell)) return spell; } - return sol::nullopt; + return nullptr; }); // pairs(types.Actor.spells(o)) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index a7b2252a31..ee746001ae 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -136,11 +136,11 @@ namespace MWLua listT[sol::meta_function::to_string] = [](const ListT& list) { return "{" + std::to_string(list.mIds->size()) + " objects}"; }; listT[sol::meta_function::length] = [](const ListT& list) { return list.mIds->size(); }; - listT[sol::meta_function::index] = [](const ListT& list, size_t index) { + listT[sol::meta_function::index] = [](const ListT& list, size_t index) -> sol::optional { if (index > 0 && index <= list.mIds->size()) return ObjectT((*list.mIds)[index - 1]); else - throw std::runtime_error("Index out of range"); + return sol::nullopt; }; listT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index 355ede9f27..b5dae8a7a8 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -169,9 +169,13 @@ namespace MWLua = [](const SoundStore& store) { return "ESM3_SoundStore{" + std::to_string(store.getSize()) + " sounds}"; }; soundStoreT[sol::meta_function::length] = [](const SoundStore& store) { return store.getSize(); }; soundStoreT[sol::meta_function::index] = sol::overload( - [](const SoundStore& store, size_t index) -> const ESM::Sound* { return store.at(index - 1); }, + [](const SoundStore& store, size_t index) -> const ESM::Sound* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); + }, [](const SoundStore& store, std::string_view soundId) -> const ESM::Sound* { - return store.find(ESM::RefId::deserializeText(soundId)); + return store.search(ESM::RefId::deserializeText(soundId)); }); soundStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); soundStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 5e44b47f1a..c0eb61bfc7 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -48,13 +48,13 @@ namespace MWLua }; sol::usertype quests = context.mLua->sol().new_usertype("Quests"); quests[sol::meta_function::to_string] = [](const Quests& quests) { return "Quests"; }; - quests[sol::meta_function::index] = sol::overload([](const Quests& quests, std::string_view questId) -> Quest { + quests[sol::meta_function::index] = [](const Quests& quests, std::string_view questId) -> sol::optional { ESM::RefId quest = ESM::RefId::deserializeText(questId); - const ESM::Dialogue* dial = MWBase::Environment::get().getESMStore()->get().find(quest); - if (dial->mType != ESM::Dialogue::Journal) - throw std::runtime_error("Not a quest:" + std::string(questId)); + const ESM::Dialogue* dial = MWBase::Environment::get().getESMStore()->get().search(quest); + if (dial == nullptr || dial->mType != ESM::Dialogue::Journal) + return sol::nullopt; return Quest{ .mQuestId = quest, .mMutable = quests.mMutable }; - }); + }; quests[sol::meta_function::pairs] = [journal](const Quests& quests) { std::vector ids; for (auto it = journal->questBegin(); it != journal->questEnd(); ++it) diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 8cd126b007..8fc0932dfe 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -77,7 +77,7 @@ namespace MWLua const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, - [&store](std::string_view id) -> const T* { return store.find(ESM::RefId::deserializeText(id)); }); + [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); // Define a custom user type for the store. // Provide the interface of a read-only array. @@ -89,6 +89,8 @@ namespace MWLua }; storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; storeT[sol::meta_function::index] = [](const StoreT& store, size_t index) -> const T* { + if (index == 0 || index > store.getSize()) + return nullptr; return store.at(index - 1); // Translate from Lua's 1-based indexing. }; storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); From b9f552b7f45ec614f464462371f5b59508061e58 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 6 Sep 2023 23:26:43 +0300 Subject: [PATCH 0097/2167] Preallocate lines in NiLinesData --- components/nif/data.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 08abcc1621..799d0cbe43 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -126,6 +126,8 @@ namespace Nif // Can't construct a line from a single vertex. if (mNumVertices < 2) return; + // There can't be more than 2 indices for each vertex + mLines.reserve(mNumVertices * 2); // Convert connectivity flags into usable geometry. The last element needs special handling. for (uint16_t i = 0; i < mNumVertices - 1; ++i) { @@ -141,6 +143,7 @@ namespace Nif mLines.emplace_back(mNumVertices - 1); mLines.emplace_back(0); } + mLines.shrink_to_fit(); } void NiParticlesData::read(NIFStream* nif) From ad509bb95485b10f511e9044d6da55573c5fdfcd Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Sep 2023 13:12:08 +0200 Subject: [PATCH 0098/2167] Use settings values for Models settings --- apps/openmw/mwclass/npc.cpp | 12 ++++---- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 6 ++-- apps/openmw/mwrender/npcanimation.cpp | 4 +-- apps/openmw/mwrender/renderingmanager.cpp | 20 ++++++------- apps/openmw/mwrender/sky.cpp | 34 ++++++++++------------ components/sceneutil/actorutil.cpp | 18 ++++++------ 7 files changed, 46 insertions(+), 50 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 97debd3298..4c0f872344 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -424,10 +424,10 @@ namespace MWClass { const MWWorld::LiveCellRef* ref = ptr.get(); - std::string model = Settings::Manager::getString("baseanim", "Models"); + std::string model = Settings::models().mBaseanim; const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); if (race->mData.mFlags & ESM::Race::Beast) - model = Settings::Manager::getString("baseanimkna", "Models"); + model = Settings::models().mBaseanimkna; return model; } @@ -437,12 +437,12 @@ namespace MWClass const MWWorld::LiveCellRef* npc = ptr.get(); const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mRace); if (race && race->mData.mFlags & ESM::Race::Beast) - models.emplace_back(Settings::Manager::getString("baseanimkna", "Models")); + models.push_back(Settings::models().mBaseanimkna); // keep these always loaded just in case - models.emplace_back(Settings::Manager::getString("xargonianswimkna", "Models")); - models.emplace_back(Settings::Manager::getString("xbaseanimfemale", "Models")); - models.emplace_back(Settings::Manager::getString("xbaseanim", "Models")); + models.push_back(Settings::models().mXargonianswimkna); + models.push_back(Settings::models().mXbaseanimfemale); + models.push_back(Settings::models().mXbaseanim); const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 74ec8e6d78..aaf2c9390b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1394,7 +1394,7 @@ namespace MWRender MWWorld::LiveCellRef* ref = mPtr.get(); if (ref->mBase->mFlags & ESM::Creature::Bipedal) { - defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models"); + defaultSkeleton = Settings::models().mXbaseanim; inject = true; } } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 77faee7231..7767520715 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "../mwmechanics/weapontype.hpp" @@ -27,7 +27,7 @@ namespace MWRender setObjectRoot(model, false, false, true); if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) - addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); + addAnimSource(Settings::models().mXbaseanim.get(), model); if (animated) addAnimSource(model, model); @@ -47,7 +47,7 @@ namespace MWRender setObjectRoot(model, true, false, true); if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) - addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); + addAnimSource(Settings::models().mXbaseanim.get(), model); if (animated) addAnimSource(model, model); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6ddca9a674..00f16e4ddc 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -515,7 +515,7 @@ namespace MWRender if (!is1stPerson) { - const std::string& base = Settings::Manager::getString("xbaseanim", "Models"); + const std::string& base = Settings::models().mXbaseanim; if (smodel != base && !isWerewolf) addAnimSource(base, smodel); @@ -529,7 +529,7 @@ namespace MWRender } else { - const std::string& base = Settings::Manager::getString("xbaseanim1st", "Models"); + const std::string& base = Settings::models().mXbaseanim1st; if (smodel != base && !isWerewolf) addAnimSource(base, smodel); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f9058fa18a..c9750ec0b9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -543,8 +543,8 @@ namespace MWRender MWBase::Environment::get().getWindowManager()->setCullMask(mask); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); - Nif::Reader::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); - Nif::Reader::setWriteNifDebugLog(Settings::Manager::getBool("write nif debug log", "Models")); + Nif::Reader::setLoadUnsupportedFiles(Settings::models().mLoadUnsupportedNifFiles); + Nif::Reader::setWriteNifDebugLog(Settings::models().mWriteNifDebugLog); mStateUpdater->setFogEnd(mViewDistance); @@ -606,15 +606,15 @@ namespace MWRender mSky->listAssetsToPreload(workItem->mModels, workItem->mTextures); mWater->listAssetsToPreload(workItem->mTextures); - workItem->mModels.push_back(Settings::Manager::getString("xbaseanim", "Models")); - workItem->mModels.push_back(Settings::Manager::getString("xbaseanim1st", "Models")); - workItem->mModels.push_back(Settings::Manager::getString("xbaseanimfemale", "Models")); - workItem->mModels.push_back(Settings::Manager::getString("xargonianswimkna", "Models")); + workItem->mModels.push_back(Settings::models().mXbaseanim); + workItem->mModels.push_back(Settings::models().mXbaseanim1st); + workItem->mModels.push_back(Settings::models().mXbaseanimfemale); + workItem->mModels.push_back(Settings::models().mXargonianswimkna); - workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimkf", "Models")); - workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanim1stkf", "Models")); - workItem->mKeyframes.push_back(Settings::Manager::getString("xbaseanimfemalekf", "Models")); - workItem->mKeyframes.push_back(Settings::Manager::getString("xargonianswimknakf", "Models")); + workItem->mKeyframes.push_back(Settings::models().mXbaseanimkf); + workItem->mKeyframes.push_back(Settings::models().mXbaseanim1stkf); + workItem->mKeyframes.push_back(Settings::models().mXbaseanimfemalekf); + workItem->mKeyframes.push_back(Settings::models().mXargonianswimknakf); workItem->mTextures.emplace_back("textures/_land_default.dds"); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index d2f725c4b8..05c7a1bab2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -302,8 +302,7 @@ namespace MWRender bool forceShaders = mSceneManager->getForceShaders(); - mAtmosphereDay - = mSceneManager->getInstance(Settings::Manager::getString("skyatmosphere", "Models"), mEarlyRenderBinRoot); + mAtmosphereDay = mSceneManager->getInstance(Settings::models().mSkyatmosphere, mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); mAtmosphereDay->accept(modAtmosphere); @@ -315,12 +314,10 @@ namespace MWRender mEarlyRenderBinRoot->addChild(mAtmosphereNightNode); osg::ref_ptr atmosphereNight; - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - atmosphereNight = mSceneManager->getInstance( - Settings::Manager::getString("skynight02", "Models"), mAtmosphereNightNode); + if (mSceneManager->getVFS()->exists(Settings::models().mSkynight02.get())) + atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight02, mAtmosphereNightNode); else - atmosphereNight = mSceneManager->getInstance( - Settings::Manager::getString("skynight01", "Models"), mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight01, mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes( createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); @@ -340,8 +337,7 @@ namespace MWRender mEarlyRenderBinRoot->addChild(mCloudNode); mCloudMesh = new osg::PositionAttitudeTransform; - osg::ref_ptr cloudMeshChild - = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mCloudMesh); + osg::ref_ptr cloudMeshChild = mSceneManager->getInstance(Settings::models().mSkyclouds, mCloudMesh); mCloudUpdater = new CloudUpdater(forceShaders); mCloudUpdater->setOpacity(1.f); cloudMeshChild->addUpdateCallback(mCloudUpdater); @@ -349,7 +345,7 @@ namespace MWRender mNextCloudMesh = new osg::PositionAttitudeTransform; osg::ref_ptr nextCloudMeshChild - = mSceneManager->getInstance(Settings::Manager::getString("skyclouds", "Models"), mNextCloudMesh); + = mSceneManager->getInstance(Settings::models().mSkyclouds, mNextCloudMesh); mNextCloudUpdater = new CloudUpdater(forceShaders); mNextCloudUpdater->setOpacity(0.f); nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); @@ -911,16 +907,16 @@ namespace MWRender void SkyManager::listAssetsToPreload(std::vector& models, std::vector& textures) { - models.emplace_back(Settings::Manager::getString("skyatmosphere", "Models")); - if (mSceneManager->getVFS()->exists(Settings::Manager::getString("skynight02", "Models"))) - models.emplace_back(Settings::Manager::getString("skynight02", "Models")); - models.emplace_back(Settings::Manager::getString("skynight01", "Models")); - models.emplace_back(Settings::Manager::getString("skyclouds", "Models")); + models.push_back(Settings::models().mSkyatmosphere); + if (mSceneManager->getVFS()->exists(Settings::models().mSkynight02.get())) + models.push_back(Settings::models().mSkynight02); + models.push_back(Settings::models().mSkynight01); + models.push_back(Settings::models().mSkyclouds); - models.emplace_back(Settings::Manager::getString("weatherashcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblightcloud", "Models")); - models.emplace_back(Settings::Manager::getString("weathersnow", "Models")); - models.emplace_back(Settings::Manager::getString("weatherblizzard", "Models")); + models.push_back(Settings::models().mWeatherashcloud); + models.push_back(Settings::models().mWeatherblightcloud); + models.push_back(Settings::models().mWeathersnow); + models.push_back(Settings::models().mWeatherblizzard); textures.emplace_back("textures/tx_mooncircle_full_s.dds"); textures.emplace_back("textures/tx_mooncircle_full_m.dds"); diff --git a/components/sceneutil/actorutil.cpp b/components/sceneutil/actorutil.cpp index c13cd093bf..63d3beccb7 100644 --- a/components/sceneutil/actorutil.cpp +++ b/components/sceneutil/actorutil.cpp @@ -1,6 +1,6 @@ #include "actorutil.hpp" -#include +#include namespace SceneUtil { @@ -9,24 +9,24 @@ namespace SceneUtil if (!firstPerson) { if (isWerewolf) - return Settings::Manager::getString("wolfskin", "Models"); + return Settings::models().mWolfskin; else if (isBeast) - return Settings::Manager::getString("baseanimkna", "Models"); + return Settings::models().mBaseanimkna; else if (isFemale) - return Settings::Manager::getString("baseanimfemale", "Models"); + return Settings::models().mBaseanimfemale; else - return Settings::Manager::getString("baseanim", "Models"); + return Settings::models().mBaseanim; } else { if (isWerewolf) - return Settings::Manager::getString("wolfskin1st", "Models"); + return Settings::models().mWolfskin1st; else if (isBeast) - return Settings::Manager::getString("baseanimkna1st", "Models"); + return Settings::models().mBaseanimkna1st; else if (isFemale) - return Settings::Manager::getString("baseanimfemale1st", "Models"); + return Settings::models().mBaseanimfemale1st; else - return Settings::Manager::getString("xbaseanim1st", "Models"); + return Settings::models().mXbaseanim1st; } } } From 053a3caf7bdd2a9726f88689fe11c8a4be90d65d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Sep 2023 19:29:26 +0200 Subject: [PATCH 0099/2167] Pass cache expiry delay to GenericResourceManager constructor --- apps/bulletobjecttool/main.cpp | 7 ++++--- apps/navmeshtool/main.cpp | 8 +++++--- apps/opencs/model/world/data.cpp | 6 ++++-- apps/opencs/view/render/cell.cpp | 3 ++- apps/openmw/engine.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 5 +++-- apps/openmw/mwrender/groundcover.cpp | 3 ++- apps/openmw/mwrender/landmanager.cpp | 3 ++- apps/openmw/mwrender/objectpaging.cpp | 4 ++-- apps/openmw_test_suite/fx/technique.cpp | 2 +- apps/openmw_test_suite/nifosg/testnifloader.cpp | 2 +- components/resource/bulletshapemanager.cpp | 4 ++-- components/resource/bulletshapemanager.hpp | 3 ++- components/resource/imagemanager.cpp | 4 ++-- components/resource/imagemanager.hpp | 2 +- components/resource/keyframemanager.cpp | 4 ++-- components/resource/keyframemanager.hpp | 2 +- components/resource/resourcemanager.hpp | 10 +--------- components/resource/resourcesystem.cpp | 8 ++++---- components/resource/resourcesystem.hpp | 2 +- components/resource/scenemanager.cpp | 6 +++--- components/resource/scenemanager.hpp | 4 ++-- components/terrain/chunkmanager.cpp | 4 ++-- components/terrain/chunkmanager.hpp | 4 ++-- components/terrain/terraingrid.cpp | 5 +++-- components/terrain/terraingrid.hpp | 6 +++--- components/terrain/texturemanager.cpp | 4 ++-- components/terrain/texturemanager.hpp | 2 +- components/terrain/world.cpp | 9 +++++---- components/terrain/world.hpp | 5 +++-- 30 files changed, 69 insertions(+), 64 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index d52d278f9e..c522d69d70 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -172,10 +172,11 @@ namespace const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); - Resource::ImageManager imageManager(&vfs); + constexpr double expiryDelay = 0; + Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); - Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::forEachBulletObject( readers, vfs, bulletShapeManager, esmData, [](const ESM::Cell& cell, const Resource::BulletObject& object) { diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 86ff8c3ddb..f82bd09f9e 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -218,10 +218,12 @@ namespace NavMeshTool const EsmLoader::EsmData esmData = EsmLoader::loadEsmData(query, contentFiles, fileCollections, readers, &encoder); - Resource::ImageManager imageManager(&vfs); + constexpr double expiryDelay = 0; + + Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager); - Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 07e9879795..e8fd138ab4 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -142,12 +142,14 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data , mReaderIndex(1) , mDataPaths(dataPaths) , mArchives(archives) + , mVFS(std::make_unique()) { - mVFS = std::make_unique(); VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths), mArchives, true); mResourcesManager.setVFS(mVFS.get()); - mResourceSystem = std::make_unique(mVFS.get()); + + constexpr double expiryDelay = 0; + mResourceSystem = std::make_unique(mVFS.get(), expiryDelay); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 6238b40e72..c74782f2d1 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -146,8 +146,9 @@ void CSVRender::Cell::updateLand() } else { + constexpr double expiryDelay = 0; mTerrain = std::make_unique(mCellNode, mCellNode, mData.getResourceSystem().get(), - mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId); + mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId, expiryDelay); } mTerrain->loadCell(esmLand.mX, esmLand.mY); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3e7f151fad..b04d36f08f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -645,7 +645,7 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem = std::make_unique(mVFS.get()); + mResourceSystem = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5f13fef571..2d756fedc8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -94,8 +94,9 @@ namespace namespace MWPhysics { PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr parentNode) - : mShapeManager(std::make_unique( - resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager())) + : mShapeManager( + std::make_unique(resourceSystem->getVFS(), resourceSystem->getSceneManager(), + resourceSystem->getNifFileManager(), Settings::cells().mCacheExpiryDelay)) , mResourceSystem(resourceSystem) , mDebugDrawEnabled(false) , mTimeAccum(0.0f) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index e00e3446a6..92be726f09 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -327,7 +328,7 @@ namespace MWRender Groundcover::Groundcover( Resource::SceneManager* sceneManager, float density, float viewDistance, const MWWorld::GroundcoverStore& store) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , mSceneManager(sceneManager) , mDensity(density) , mStateset(new osg::StateSet) diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 6ab9f12139..835fb9b204 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -12,7 +13,7 @@ namespace MWRender { LandManager::LandManager(int loadFlags) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , mLoadFlags(loadFlags) { } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index c3c473de6b..3579badee2 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/world.hpp" @@ -454,7 +454,7 @@ namespace MWRender }; ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager, ESM::RefId worldspace) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , Terrain::QuadTreeWorld::ChunkManager(worldspace) , mSceneManager(sceneManager) , mRefTrackerLocked(false) diff --git a/apps/openmw_test_suite/fx/technique.cpp b/apps/openmw_test_suite/fx/technique.cpp index e59cca43cf..352dd9ca09 100644 --- a/apps/openmw_test_suite/fx/technique.cpp +++ b/apps/openmw_test_suite/fx/technique.cpp @@ -99,7 +99,7 @@ namespace { "shaders/missing_sampler_source.omwfx", &missing_sampler_source }, { "shaders/repeated_shared_block.omwfx", &repeated_shared_block }, })) - , mImageManager(mVFS.get()) + , mImageManager(mVFS.get(), 0) { } diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 5c9caf4799..b8ef6ecacd 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -28,7 +28,7 @@ namespace struct BaseNifOsgLoaderTest { VFS::Manager mVfs; - Resource::ImageManager mImageManager{ &mVfs }; + Resource::ImageManager mImageManager{ &mVfs, 0 }; const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); osg::ref_ptr mOptions = new osgDB::Options; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 0e2089cb06..f817d6b89a 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -98,8 +98,8 @@ namespace Resource }; BulletShapeManager::BulletShapeManager( - const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager) - : ResourceManager(vfs) + const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager, double expiryDelay) + : ResourceManager(vfs, expiryDelay) , mInstanceCache(new MultiObjectCache) , mSceneManager(sceneMgr) , mNifFileManager(nifFileManager) diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index 1b4ce849fe..a81296fdfd 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -25,7 +25,8 @@ namespace Resource class BulletShapeManager : public ResourceManager { public: - BulletShapeManager(const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager); + BulletShapeManager( + const VFS::Manager* vfs, SceneManager* sceneMgr, NifFileManager* nifFileManager, double expiryDelay); ~BulletShapeManager(); /// @note May return a null pointer if the object has no shape. diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 178e681d4e..26fd60d7ea 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -46,8 +46,8 @@ namespace namespace Resource { - ImageManager::ImageManager(const VFS::Manager* vfs) - : ResourceManager(vfs) + ImageManager::ImageManager(const VFS::Manager* vfs, double expiryDelay) + : ResourceManager(vfs, expiryDelay) , mWarningImage(createWarningImage()) , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba ignoreTga2Fields")) , mOptionsNoFlip(new osgDB::Options("dds_dxt1_detect_rgba ignoreTga2Fields")) diff --git a/components/resource/imagemanager.hpp b/components/resource/imagemanager.hpp index ddb2fcc00c..2fad70d576 100644 --- a/components/resource/imagemanager.hpp +++ b/components/resource/imagemanager.hpp @@ -23,7 +23,7 @@ namespace Resource class ImageManager : public ResourceManager { public: - ImageManager(const VFS::Manager* vfs); + explicit ImageManager(const VFS::Manager* vfs, double expiryDelay); ~ImageManager(); /// Create or retrieve an Image diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 8820469e87..574d761d09 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -207,8 +207,8 @@ namespace Resource namespace Resource { - KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager) - : ResourceManager(vfs) + KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay) + : ResourceManager(vfs, expiryDelay) , mSceneManager(sceneManager) { } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index d075e65d4c..0c92553949 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -48,7 +48,7 @@ namespace Resource class KeyframeManager : public ResourceManager { public: - KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager); + explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay); ~KeyframeManager() = default; /// Retrieve a read-only keyframe resource by name (case-insensitive). diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 09b99faf28..55e4e142b5 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,8 +3,6 @@ #include -#include - #include "objectcache.hpp" namespace VFS @@ -41,8 +39,7 @@ namespace Resource public: typedef GenericObjectCache CacheType; - explicit GenericResourceManager( - const VFS::Manager* vfs, double expiryDelay = Settings::cells().mCacheExpiryDelay) + explicit GenericResourceManager(const VFS::Manager* vfs, double expiryDelay) : mVFS(vfs) , mCache(new CacheType) , mExpiryDelay(expiryDelay) @@ -80,11 +77,6 @@ namespace Resource class ResourceManager : public GenericResourceManager { public: - explicit ResourceManager(const VFS::Manager* vfs) - : GenericResourceManager(vfs) - { - } - explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) : GenericResourceManager(vfs, expiryDelay) { diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 1f3f6bf198..0bee08e9ac 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -10,13 +10,13 @@ namespace Resource { - ResourceSystem::ResourceSystem(const VFS::Manager* vfs) + ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay) : mVFS(vfs) { mNifFileManager = std::make_unique(vfs); - mImageManager = std::make_unique(vfs); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get()); - mKeyframeManager = std::make_unique(vfs, mSceneManager.get()); + mImageManager = std::make_unique(vfs, expiryDelay); + mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); + mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay); addResourceManager(mNifFileManager.get()); addResourceManager(mKeyframeManager.get()); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 72440d17ee..d06ac79640 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -30,7 +30,7 @@ namespace Resource class ResourceSystem { public: - ResourceSystem(const VFS::Manager* vfs); + explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay); ~ResourceSystem(); SceneManager* getSceneManager(); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 78b2d5165a..dde39c5d65 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -352,9 +352,9 @@ namespace Resource std::vector> mRigGeometryHolders; }; - SceneManager::SceneManager( - const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) - : ResourceManager(vfs) + SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, + Resource::NifFileManager* nifFileManager, double expiryDelay) + : ResourceManager(vfs, expiryDelay) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) , mClampLighting(true) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 54643b561d..e8a9640ce9 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -89,8 +89,8 @@ namespace Resource class SceneManager : public ResourceManager { public: - SceneManager( - const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); + explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, + Resource::NifFileManager* nifFileManager, double expiryDelay); ~SceneManager(); Shader::ShaderManager& getShaderManager(); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 6273041f72..8df5dc3a77 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -20,8 +20,8 @@ namespace Terrain { ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, - CompositeMapRenderer* renderer, ESM::RefId worldspace) - : GenericResourceManager(nullptr) + CompositeMapRenderer* renderer, ESM::RefId worldspace, double expiryDelay) + : GenericResourceManager(nullptr, expiryDelay) , QuadTreeWorld::ChunkManager(worldspace) , mStorage(storage) , mSceneManager(sceneMgr) diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index a55dd15cc1..20d6ba9327 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -75,8 +75,8 @@ namespace Terrain class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: - ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, - CompositeMapRenderer* renderer, ESM::RefId worldspace); + explicit ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, + CompositeMapRenderer* renderer, ESM::RefId worldspace, double expiryDelay); osg::ref_ptr getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index cdbfdea723..2849c4d401 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -23,9 +23,10 @@ namespace Terrain }; TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, - Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, unsigned int preCompileMask, + Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, double expiryDelay, unsigned int preCompileMask, unsigned int borderMask) - : Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace) + : Terrain::World( + parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace, expiryDelay) , mNumSplits(4) { } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 5204af7093..8483338f23 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -26,9 +26,9 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, - Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, unsigned int preCompileMask = ~0u, - unsigned int borderMask = 0); + explicit TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, + Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, double expiryDelay, + unsigned int preCompileMask = ~0u, unsigned int borderMask = 0); TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask = ~0u); ~TerrainGrid(); diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index ff4a84707e..360d87bf48 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -10,8 +10,8 @@ namespace Terrain { - TextureManager::TextureManager(Resource::SceneManager* sceneMgr) - : ResourceManager(sceneMgr->getVFS()) + TextureManager::TextureManager(Resource::SceneManager* sceneMgr, double expiryDelay) + : ResourceManager(sceneMgr->getVFS(), expiryDelay) , mSceneManager(sceneMgr) { } diff --git a/components/terrain/texturemanager.hpp b/components/terrain/texturemanager.hpp index ae3e41f138..96fb43abfa 100644 --- a/components/terrain/texturemanager.hpp +++ b/components/terrain/texturemanager.hpp @@ -21,7 +21,7 @@ namespace Terrain class TextureManager : public Resource::ResourceManager { public: - TextureManager(Resource::SceneManager* sceneMgr); + explicit TextureManager(Resource::SceneManager* sceneMgr, double expiryDelay); void updateTextureFiltering(); diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 98ce982285..93a9c563af 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "chunkmanager.hpp" #include "compositemaprenderer.hpp" @@ -17,7 +18,7 @@ namespace Terrain World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, - ESM::RefId worldspace) + ESM::RefId worldspace, double expiryDelay) : mStorage(storage) , mParent(parent) , mResourceSystem(resourceSystem) @@ -45,9 +46,9 @@ namespace Terrain mParent->addChild(mTerrainRoot); - mTextureManager = std::make_unique(mResourceSystem->getSceneManager()); - mChunkManager = std::make_unique( - mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer, mWorldspace); + mTextureManager = std::make_unique(mResourceSystem->getSceneManager(), expiryDelay); + mChunkManager = std::make_unique(mStorage, mResourceSystem->getSceneManager(), + mTextureManager.get(), mCompositeMapRenderer, mWorldspace, expiryDelay); mChunkManager->setNodeMask(nodeMask); mCellBorder = std::make_unique(this, mTerrainRoot.get(), borderMask, mResourceSystem->getSceneManager()); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 16b39c1385..63b920146b 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -49,8 +49,9 @@ namespace Terrain /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param nodeMask mask for the terrain root /// @param preCompileMask mask for pre compiling textures - World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, - unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, ESM::RefId worldspace); + explicit World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, + Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, + ESM::RefId worldspace, double expiryDelay); World(osg::Group* parent, Storage* storage, unsigned int nodeMask, ESM::RefId worldspace); virtual ~World(); From 817078cbea0354f635f43bf18cc886be3d457f8b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 9 Sep 2023 20:36:55 +0300 Subject: [PATCH 0100/2167] Reset the rotation for ESM3 door-based COC destinations again --- apps/openmw/mwworld/worldimp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5a366ad9e0..241d252615 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2693,7 +2693,11 @@ namespace MWWorld for (const MWWorld::LiveCellRef& destDoor : source.getReadOnlyDoors().mList) { if (cellId == destDoor.mRef.getDestCell()) - return destDoor.mRef.getDoorDest(); + { + ESM::Position doorDest = destDoor.mRef.getDoorDest(); + doorDest.rot[0] = doorDest.rot[1] = doorDest.rot[2] = 0; + return doorDest; + } } for (const MWWorld::LiveCellRef& destDoor : source.getReadOnlyEsm4Doors().mList) { From 0f2e5f9db604ab37251be5b29edede01fd356e77 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 9 Sep 2023 20:23:07 +0300 Subject: [PATCH 0101/2167] Uncap Drain effects again (bug #7573) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd757b631c..1fc0f0e068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading + Bug #7573: Drain Fatigue can't bring fatigue below zero by default Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 253c4015b5..d9137dcc3d 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -717,8 +717,8 @@ namespace MWMechanics if (!godmode) { int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; - adjustDynamicStat( - target, index, -effect.mMagnitude, Settings::game().mUncappedDamageFatigue && index == 2); + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, index, -effect.mMagnitude, true); if (index == 0) receivedMagicDamage = affectedHealth = true; } From 892f6d1aee6699e24a1bf7f58d08c1cbed5cfc84 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 8 Sep 2023 22:47:15 +0200 Subject: [PATCH 0102/2167] Make LiveCellRef to remove itself from PtrRegistry in destructor (to prevent potential use after free); Update ContainerStore::mPtr after copying container/actor. --- apps/openmw/engine.cpp | 8 ++++---- apps/openmw/mwclass/container.cpp | 9 +++++++-- apps/openmw/mwclass/creature.cpp | 9 +++++++-- apps/openmw/mwclass/npc.cpp | 9 +++++++-- apps/openmw/mwlua/luamanagerimp.cpp | 3 --- apps/openmw/mwmechanics/summoning.cpp | 8 +++----- apps/openmw/mwworld/containerstore.cpp | 14 -------------- apps/openmw/mwworld/containerstore.hpp | 2 +- apps/openmw/mwworld/livecellref.cpp | 6 ++++++ apps/openmw/mwworld/livecellref.hpp | 2 +- apps/openmw/mwworld/ptrregistry.hpp | 13 ++++++++++--- apps/openmw/mwworld/worldimp.hpp | 4 ++-- apps/openmw/mwworld/worldmodel.hpp | 2 +- 13 files changed, 49 insertions(+), 40 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3e7f151fad..4b3b93594b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -740,6 +740,9 @@ void OMW::Engine::prepareEngine() // Create the world mWorld = std::make_unique( mResourceSystem.get(), mActivationDistanceOverride, mCellName, mCfgMgr.getUserDataPath()); + mEnvironment.setWorld(*mWorld); + mEnvironment.setWorldModel(mWorld->getWorldModel()); + mEnvironment.setESMStore(mWorld->getStore()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::AsyncListener asyncListener(*listener); @@ -763,12 +766,9 @@ void OMW::Engine::prepareEngine() listener->loadingOff(); mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue); + mEnvironment.setWorldScene(mWorld->getWorldScene()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); - mEnvironment.setWorld(*mWorld); - mEnvironment.setWorldModel(mWorld->getWorldModel()); - mEnvironment.setWorldScene(mWorld->getWorldScene()); - mEnvironment.setESMStore(mWorld->getStore()); const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader( diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e947951e33..db9e2fc08d 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -302,8 +302,13 @@ namespace MWClass MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { const MWWorld::LiveCellRef* ref = ptr.get(); - - return MWWorld::Ptr(cell.insert(ref), &cell); + MWWorld::Ptr newPtr(cell.insert(ref), &cell); + if (newPtr.getRefData().getCustomData()) + { + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + newPtr.getContainerStore()->setPtr(newPtr); + } + return newPtr; } void Container::readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2ee412a190..62dd48deb6 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -694,8 +694,13 @@ namespace MWClass MWWorld::Ptr Creature::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { const MWWorld::LiveCellRef* ref = ptr.get(); - - return MWWorld::Ptr(cell.insert(ref), &cell); + MWWorld::Ptr newPtr(cell.insert(ref), &cell); + if (newPtr.getRefData().getCustomData()) + { + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + newPtr.getContainerStore()->setPtr(newPtr); + } + return newPtr; } bool Creature::isBipedal(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 97debd3298..de0d41d67a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1334,8 +1334,13 @@ namespace MWClass MWWorld::Ptr Npc::copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const { const MWWorld::LiveCellRef* ref = ptr.get(); - - return MWWorld::Ptr(cell.insert(ref), &cell); + MWWorld::Ptr newPtr(cell.insert(ref), &cell); + if (newPtr.getRefData().getCustomData()) + { + MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); + newPtr.getContainerStore()->setPtr(newPtr); + } + return newPtr; } float Npc::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index ab0aa6c890..23aca1fd46 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -475,9 +475,6 @@ namespace MWLua scripts->setSerializer(mLocalSerializer.get()); scripts->setSavedDataDeserializer(mLocalLoader.get()); scripts->load(data); - - // LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered. - MWBase::Environment::get().getWorldModel()->deregisterPtr(ptr); } void LuaManager::reloadAllScripts() diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 2af5a2dc83..9b641e5e5c 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -90,17 +90,15 @@ namespace MWMechanics { auto world = MWBase::Environment::get().getWorld(); MWWorld::ManualRef ref(world->getStore(), creatureID, 1); + MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); - MWMechanics::CreatureStats& summonedCreatureStats - = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + MWMechanics::CreatureStats& summonedCreatureStats = placed.getClass().getCreatureStats(placed); // Make the summoned creature follow its master and help in fights AiFollow package(summoner); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + summonedCreatureStats.getAiSequence().stack(package, placed); creatureActorId = summonedCreatureStats.getActorId(); - MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); - MWRender::Animation* anim = world->getAnimation(placed); if (anim) { diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index e643f947aa..d82125a5ce 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -157,20 +157,6 @@ MWWorld::ContainerStore::ContainerStore() { } -MWWorld::ContainerStore::~ContainerStore() -{ - try - { - MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); - for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) - worldModel->deregisterPtr(*iter); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "Failed to deregister container store: " << e.what(); - } -} - MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const { return ConstContainerStoreIterator(mask, this); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index a8392d38b8..889fcf7463 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -175,7 +175,7 @@ namespace MWWorld public: ContainerStore(); - virtual ~ContainerStore(); + virtual ~ContainerStore() = default; virtual std::unique_ptr clone() { diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 93470a593a..d4e2ac40c0 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -13,6 +13,7 @@ #include "class.hpp" #include "esmstore.hpp" #include "ptr.hpp" +#include "worldmodel.hpp" MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM::CellRef& cref) : mClass(&Class::get(type)) @@ -35,6 +36,11 @@ MWWorld::LiveCellRefBase::LiveCellRefBase(unsigned int type, const ESM4::ActorCh { } +MWWorld::LiveCellRefBase::~LiveCellRefBase() +{ + MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(*this); +} + void MWWorld::LiveCellRefBase::loadImp(const ESM::ObjectState& state) { mRef = MWWorld::CellRef(state.mRef); diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index f80690fbe8..1e4d1441f5 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -38,7 +38,7 @@ namespace MWWorld LiveCellRefBase(unsigned int type, const ESM4::Reference& cref); LiveCellRefBase(unsigned int type, const ESM4::ActorCharacter& cref); /* Need this for the class to be recognized as polymorphic */ - virtual ~LiveCellRefBase() {} + virtual ~LiveCellRefBase(); virtual void load(const ESM::ObjectState& state) = 0; ///< Load state into a LiveCellRef, that has already been initialised with base and class. diff --git a/apps/openmw/mwworld/ptrregistry.hpp b/apps/openmw/mwworld/ptrregistry.hpp index c40ea8e434..3b535a220c 100644 --- a/apps/openmw/mwworld/ptrregistry.hpp +++ b/apps/openmw/mwworld/ptrregistry.hpp @@ -43,10 +43,17 @@ namespace MWWorld ++mRevision; } - void remove(const Ptr& ptr) + void remove(const LiveCellRefBase& ref) noexcept { - mIndex.erase(ptr.getCellRef().getRefNum()); - ++mRevision; + const ESM::RefNum& refNum = ref.mRef.getRefNum(); + if (!refNum.isSet()) + return; + auto it = mIndex.find(refNum); + if (it != mIndex.end() && it->second.getBase() == &ref) + { + mIndex.erase(it); + ++mRevision; + } } private: diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 64bc3666de..6bf256b083 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -136,8 +136,8 @@ namespace MWWorld uint32_t mRandomSeed{}; // not implemented - World(const World&); - World& operator=(const World&); + World(const World&) = delete; + World& operator=(const World&) = delete; void updateWeather(float duration, bool paused = false); diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp index 58b66e2411..324ff48023 100644 --- a/apps/openmw/mwworld/worldmodel.hpp +++ b/apps/openmw/mwworld/worldmodel.hpp @@ -79,7 +79,7 @@ namespace MWWorld void registerPtr(const Ptr& ptr) { mPtrRegistry.insert(ptr); } - void deregisterPtr(const Ptr& ptr) { mPtrRegistry.remove(ptr); } + void deregisterLiveCellRef(const LiveCellRefBase& ref) noexcept { mPtrRegistry.remove(ref); } template void forEachLoadedCellStore(Fn&& fn) From ef896faa900f99576b86190d06b7e1a5e50a1350 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 9 Sep 2023 21:32:42 +0300 Subject: [PATCH 0103/2167] Rename Named->NiObjectNET and update everything directly related to it BulletNifLoader: properly check if the node has animation controllers Flatten extra data linked list --- apps/openmw_test_suite/nif/node.hpp | 12 +-- .../nifloader/testbulletnifloader.cpp | 20 ++-- .../nifosg/testnifloader.cpp | 4 +- components/nif/base.cpp | 26 ++++-- components/nif/base.hpp | 22 +++-- components/nif/node.cpp | 6 +- components/nif/node.hpp | 6 +- components/nif/property.hpp | 2 +- components/nif/recordptr.hpp | 4 +- components/nif/texture.hpp | 2 +- components/nifbullet/bulletnifloader.cpp | 30 +++--- components/nifosg/nifloader.cpp | 92 +++++++++---------- 12 files changed, 123 insertions(+), 103 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 19276059c8..ce42faef08 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -16,16 +16,16 @@ namespace Nif::Testing value.mNext = ExtraPtr(nullptr); } - inline void init(Named& value) + inline void init(NiObjectNET& value) { - value.extra = ExtraPtr(nullptr); - value.extralist = ExtraList(); - value.controller = ControllerPtr(nullptr); + value.mExtra = ExtraPtr(nullptr); + value.mExtraList = ExtraList(); + value.mController = ControllerPtr(nullptr); } inline void init(Node& value) { - init(static_cast(value)); + init(static_cast(value)); value.flags = 0; init(value.trafo); value.hasBounds = false; @@ -65,7 +65,7 @@ namespace Nif::Testing value.phase = 0; value.timeStart = 0; value.timeStop = 0; - value.target = NamedPtr(nullptr); + value.target = NiObjectNETPtr(nullptr); } } diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 99f91aacb9..7c675fd22d 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -837,7 +837,7 @@ namespace copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; mNiTriShape.parents.push_back(&mNiNode); - mNiTriShape.controller = Nif::ControllerPtr(&mController); + mNiTriShape.mController = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); mNiNode.trafo.scale = 4; @@ -870,7 +870,7 @@ namespace copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parents.push_back(&mNiNode); - mNiTriShape2.controller = Nif::ControllerPtr(&mController); + mNiTriShape2.mController = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2), @@ -998,7 +998,7 @@ namespace { mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1027,7 +1027,7 @@ namespace mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1054,7 +1054,7 @@ namespace { mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1082,7 +1082,7 @@ namespace mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.mData = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1143,7 +1143,7 @@ namespace { mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1162,9 +1162,9 @@ namespace { mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" mNiIntegerExtraData.recType = Nif::RC_BSXFlags; - mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); + mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.parents.push_back(&mNiNode); - mNiTriShape.name = "EditorMarker"; + mNiTriShape.mName = "EditorMarker"; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); Nif::NIFFile file("test.nif"); @@ -1183,7 +1183,7 @@ namespace { mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode2); mNiNode2.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); mNiNode2.recType = Nif::RC_RootCollisionNode; diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 5c9caf4799..6bcef223d9 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -188,7 +188,7 @@ osg::Group { Nif::BSShaderPPLightingProperty property; property.recType = Nif::RC_BSShaderPPLightingProperty; property.textureSet = nullptr; - property.controller = nullptr; + property.mController = nullptr; property.type = GetParam().mShaderType; node.props.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); @@ -216,7 +216,7 @@ osg::Group { Nif::BSLightingShaderProperty property; property.recType = Nif::RC_BSLightingShaderProperty; property.mTextureSet = nullptr; - property.controller = nullptr; + property.mController = nullptr; property.type = GetParam().mShaderType; node.props.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); diff --git a/components/nif/base.cpp b/components/nif/base.cpp index af98cfa16d..ee5851bda6 100644 --- a/components/nif/base.cpp +++ b/components/nif/base.cpp @@ -13,20 +13,28 @@ namespace Nif } } - void Named::read(NIFStream* nif) + void NiObjectNET::read(NIFStream* nif) { - name = nif->getString(); + nif->read(mName); if (nif->getVersion() < NIFStream::generateVersion(10, 0, 1, 0)) - extra.read(nif); + mExtra.read(nif); else - readRecordList(nif, extralist); - controller.read(nif); + readRecordList(nif, mExtraList); + mController.read(nif); } - void Named::post(Reader& nif) + void NiObjectNET::post(Reader& nif) { - extra.post(nif); - postRecordList(nif, extralist); - controller.post(nif); + mExtra.post(nif); + postRecordList(nif, mExtraList); + mController.post(nif); + } + + ExtraList NiObjectNET::getExtraList() const + { + ExtraList list = mExtraList; + for (ExtraPtr extra = mExtra; !extra.empty(); extra = extra->mNext) + list.emplace_back(extra); + return list; } } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 69094ffc51..6b4d5710b4 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -40,7 +40,7 @@ namespace Nif int flags; float frequency, phase; float timeStart, timeStop; - NamedPtr target; + NiObjectNETPtr target; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -49,18 +49,20 @@ namespace Nif ExtrapolationMode extrapolationMode() const { return static_cast(flags & Mask); } }; - /// Has name, extra-data and controller - struct Named : public Record + /// Abstract object that has a name, extra data and controllers + struct NiObjectNET : public Record { - std::string name; - ExtraPtr extra; - ExtraList extralist; - ControllerPtr controller; + std::string mName; + ExtraPtr mExtra; + ExtraList mExtraList; + ControllerPtr mController; void read(NIFStream* nif) override; void post(Reader& nif) override; - }; - using NiSequenceStreamHelper = Named; -} // Namespace + // Collect extra records attached to the object + ExtraList getExtraList() const; + }; + +} #endif diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 6028ac253f..733d5319e9 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -77,7 +77,7 @@ namespace Nif void Node::read(NIFStream* nif) { - Named::read(nif); + NiObjectNET::read(nif); flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); trafo = nif->getTrafo(); @@ -101,7 +101,7 @@ namespace Nif void Node::post(Reader& nif) { - Named::post(nif); + NiObjectNET::post(nif); postRecordList(nif, props); collision.post(nif); } @@ -128,7 +128,7 @@ namespace Nif // FIXME: if node 0 is *not* the only root node, this must not happen. // FIXME: doing this here is awful. // We want to do this on world scene graph level rather than local scene graph level. - if (0 == recIndex && !Misc::StringUtils::ciEqual(name, "bip01")) + if (0 == recIndex && !Misc::StringUtils::ciEqual(mName, "bip01")) { trafo = Nif::Transformation::getIdentity(); } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 2d8021eb49..129e74a617 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -62,11 +62,15 @@ namespace Nif void read(NIFStream* nif); }; + struct NiSequenceStreamHelper : NiObjectNET + { + }; + /** A Node is an object that's part of the main NIF tree. It has parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. */ - struct Node : public Named + struct Node : public NiObjectNET { enum Flags { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index a0800d2700..90f1e9ffb5 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -29,7 +29,7 @@ namespace Nif { - struct Property : public Named + struct Property : public NiObjectNET { }; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index e2836d458d..5b890eec54 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -115,7 +115,7 @@ namespace Nif struct NiPosData; struct NiVisData; struct Controller; - struct Named; + struct NiObjectNET; struct NiSkinData; struct NiFloatData; struct NiMorphData; @@ -156,7 +156,7 @@ namespace Nif using NiPosDataPtr = RecordPtrT; using NiVisDataPtr = RecordPtrT; using ControllerPtr = RecordPtrT; - using NamedPtr = RecordPtrT; + using NiObjectNETPtr = RecordPtrT; using NiSkinDataPtr = RecordPtrT; using NiMorphDataPtr = RecordPtrT; using NiPixelDataPtr = RecordPtrT; diff --git a/components/nif/texture.hpp b/components/nif/texture.hpp index a326b47f14..8d7e44990f 100644 --- a/components/nif/texture.hpp +++ b/components/nif/texture.hpp @@ -6,7 +6,7 @@ namespace Nif { - struct NiTexture : public Named + struct NiTexture : public NiObjectNET { }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index be27b04603..31be54b030 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -277,9 +277,23 @@ namespace NifBullet if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) return; - if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController - && node.controller->isActive()) - args.mAnimated = true; + for (Nif::ControllerPtr ctrl = node.mController; !ctrl.empty(); ctrl = ctrl->next) + { + if (args.mAnimated) + break; + if (!ctrl->isActive()) + continue; + switch (ctrl->recType) + { + case Nif::RC_NiKeyframeController: + case Nif::RC_NiPathController: + case Nif::RC_NiRollController: + args.mAnimated = true; + break; + default: + continue; + } + } if (node.recType == Nif::RC_RootCollisionNode) { @@ -304,13 +318,7 @@ namespace NifBullet args.mAvoid = true; // Check for extra data - std::vector extraCollection; - for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->mNext) - extraCollection.emplace_back(e); - for (const auto& extraNode : node.extralist) - if (!extraNode.empty()) - extraCollection.emplace_back(extraNode); - for (const auto& e : extraCollection) + for (const auto& e : node.getExtraList()) { if (e->recType == Nif::RC_NiStringExtraData) { @@ -378,7 +386,7 @@ namespace NifBullet { // mHasMarkers is specifically BSXFlags editor marker flag. // If this changes, the check must be corrected. - if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.name, "EditorMarker")) + if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "EditorMarker")) return; if (niGeometry.data.empty() || niGeometry.data->mVertices.empty()) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index d1563df63c..1a595a3ab5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -275,21 +275,28 @@ namespace NifOsg return; } - Nif::ExtraPtr extra = seq->extra; - if (extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) + Nif::ExtraList extraList = seq->getExtraList(); + if (extraList.empty()) { - Log(Debug::Warning) << "NIFFile Warning: First extra data was not a NiTextKeyExtraData, but a " - << (extra.empty() ? std::string_view("nil") : std::string_view(extra->recName)) - << ". File: " << nif.getFilename(); + Log(Debug::Warning) << "NIFFile Warning: NiSequenceStreamHelper has no text keys. File: " + << nif.getFilename(); return; } - extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); - - extra = extra->mNext; - Nif::ControllerPtr ctrl = seq->controller; - for (; !extra.empty() && !ctrl.empty(); (extra = extra->mNext), (ctrl = ctrl->next)) + if (extraList[0]->recType != Nif::RC_NiTextKeyExtraData) { + Log(Debug::Warning) << "NIFFile Warning: First extra data was not a NiTextKeyExtraData, but a " + << std::string_view(extraList[0]->recName) << ". File: " << nif.getFilename(); + return; + } + + auto textKeyExtraData = static_cast(extraList[0].getPtr()); + extractTextKeys(textKeyExtraData, target.mTextKeys); + + Nif::ControllerPtr ctrl = seq->mController; + for (size_t i = 1; i < extraList.size() && !ctrl.empty(); i++, (ctrl = ctrl->next)) + { + Nif::ExtraPtr extra = extraList[i]; if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) { Log(Debug::Warning) << "NIFFile Warning: Unexpected extra data " << extra->recName @@ -454,7 +461,7 @@ namespace NifOsg static osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) { osg::ref_ptr lod(new osg::LOD); - lod->setName(niLodNode->name); + lod->setName(niLodNode->mName); lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER); lod->setCenter(niLodNode->lodCenter); for (unsigned int i = 0; i < niLodNode->lodLevels.size(); ++i) @@ -469,7 +476,7 @@ namespace NifOsg static osg::ref_ptr handleSwitchNode(const Nif::NiSwitchNode* niSwitchNode) { osg::ref_ptr switchNode(new osg::Switch); - switchNode->setName(niSwitchNode->name); + switchNode->setName(niSwitchNode->mName); switchNode->setNewChildDefaultValue(false); switchNode->setSingleChildOn(niSwitchNode->initialIndex); return switchNode; @@ -479,7 +486,7 @@ namespace NifOsg { const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::ref_ptr sequenceNode(new osg::Sequence); - sequenceNode->setName(niFltAnimationNode->name); + sequenceNode->setName(niFltAnimationNode->mName); if (!niFltAnimationNode->children.empty()) { if (niFltAnimationNode->swing()) @@ -604,7 +611,7 @@ namespace NifOsg // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you // try). - if (nifNode->parents.empty() && nifNode->controller.empty() && nifNode->trafo.isIdentity()) + if (nifNode->parents.empty() && nifNode->mController.empty() && nifNode->trafo.isIdentity()) node = new osg::Group; dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; @@ -622,7 +629,7 @@ namespace NifOsg osg::ref_ptr handleNode( const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, HandleNodeArgs args) { - if (args.mRootNode && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) + if (args.mRootNode && Misc::StringUtils::ciEqual(nifNode->mName, "Bounding Box")) return nullptr; osg::ref_ptr node = createNode(nifNode); @@ -632,7 +639,7 @@ namespace NifOsg node->addCullCallback(new BillboardCallback); } - node->setName(nifNode->name); + node->setName(nifNode->mName); if (parentNode) parentNode->addChild(node); @@ -646,16 +653,7 @@ namespace NifOsg // - finding a random child NiNode in NiBspArrayController node->setUserValue("recIndex", nifNode->recIndex); - std::vector extraCollection; - - for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->mNext) - extraCollection.emplace_back(e); - - for (const auto& extraNode : nifNode->extralist) - if (!extraNode.empty()) - extraCollection.emplace_back(extraNode); - - for (const auto& e : extraCollection) + for (const auto& e : nifNode->getExtraList()) { if (e->recType == Nif::RC_NiTextKeyExtraData && args.mTextKeys) { @@ -727,7 +725,7 @@ namespace NifOsg if (nifNode->isHidden()) { bool hasVisController = false; - for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { hasVisController |= (ctrl->recType == Nif::RC_NiVisController); if (hasVisController) @@ -755,12 +753,12 @@ namespace NifOsg bool skip; if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) { - skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "tri editormarker")) - || Misc::StringUtils::ciStartsWith(nifNode->name, "shadow") - || Misc::StringUtils::ciStartsWith(nifNode->name, "tri shadow"); + skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker")) + || Misc::StringUtils::ciStartsWith(nifNode->mName, "shadow") + || Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow"); } else - skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "EditorMarker"); + skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); if (!skip) { Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; @@ -770,7 +768,7 @@ namespace NifOsg else handleSkinnedGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); - if (!nifNode->controller.empty()) + if (!nifNode->mController.empty()) handleMeshControllers(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); } } @@ -807,9 +805,9 @@ namespace NifOsg const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); node->addChild(switchNode); - if (niSwitchNode->name == Constants::NightDayLabel) + if (niSwitchNode->mName == Constants::NightDayLabel) mHasNightDayLabel = true; - else if (niSwitchNode->name == Constants::HerbalismLabel) + else if (niSwitchNode->mName == Constants::HerbalismLabel) mHasHerbalismLabel = true; currentNode = switchNode; @@ -860,7 +858,7 @@ namespace NifOsg SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -887,7 +885,7 @@ namespace NifOsg void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) { - for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -965,7 +963,7 @@ namespace NifOsg void handleMaterialControllers(const Nif::Property* materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { - for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = materialProperty->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -1016,7 +1014,7 @@ namespace NifOsg void handleTextureControllers(const Nif::Property* texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet* stateset, int animflags) { - for (Nif::ControllerPtr ctrl = texProperty->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -1256,7 +1254,7 @@ namespace NifOsg partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; - for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -1465,7 +1463,7 @@ namespace NifOsg new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), line.data())); } } - handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->name); + handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->mName); // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1487,7 +1485,7 @@ namespace NifOsg if (geom->empty()) return; osg::ref_ptr drawable; - for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { if (!ctrl->isActive()) continue; @@ -1507,7 +1505,7 @@ namespace NifOsg } if (!drawable.get()) drawable = geom; - drawable->setName(nifNode->name); + drawable->setName(nifNode->mName); parentNode->addChild(drawable); } @@ -1543,7 +1541,7 @@ namespace NifOsg return; osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); - rig->setName(nifNode->name); + rig->setName(nifNode->mName); // Assign bone weights osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); @@ -1553,7 +1551,7 @@ namespace NifOsg const Nif::NodeList& bones = skin->mBones; for (std::size_t i = 0; i < bones.size(); ++i) { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->name); + std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); SceneUtil::RigGeometry::BoneInfluence influence; influence.mWeights = data->mBones[i].mWeights; @@ -1839,7 +1837,7 @@ namespace NifOsg for (size_t i = 0; i < texprop->textures.size(); ++i) { if (texprop->textures[i].inUse - || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->controller.empty())) + || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) { switch (i) { @@ -1866,7 +1864,7 @@ namespace NifOsg if (texprop->textures[i].inUse) { const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; - if (tex.texture.empty() && texprop->controller.empty()) + if (tex.texture.empty() && texprop->mController.empty()) { if (i == 0) Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName @@ -2424,7 +2422,7 @@ namespace NifOsg mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); - if (!matprop->controller.empty()) + if (!matprop->mController.empty()) { hasMatCtrl = true; handleMaterialControllers(matprop, composite, animflags, mat); From 535290a83d11c03edf36ab7b95ffee58ccff831c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 00:04:17 +0300 Subject: [PATCH 0104/2167] Update NIF Reader class and related code Update BSStreamHeader definitions Fix 10.0.1.8 loading Explicitly avoid loading 20.3.1.2 --- components/nif/niffile.cpp | 199 ++++++++++++++++++++----------------- components/nif/niffile.hpp | 45 +++++---- 2 files changed, 133 insertions(+), 111 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index e147ee3528..ae871002c3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -25,13 +25,13 @@ namespace Nif { Reader::Reader(NIFFile& file) - : ver(file.mVersion) - , userVer(file.mUserVersion) - , bethVer(file.mBethVersion) - , filename(file.mPath) - , hash(file.mHash) - , records(file.mRecords) - , roots(file.mRoots) + : mVersion(file.mVersion) + , mUserVersion(file.mUserVersion) + , mBethVersion(file.mBethVersion) + , mFilename(file.mPath) + , mHash(file.mHash) + , mRecords(file.mRecords) + , mRoots(file.mRoots) , mUseSkinning(file.mUseSkinning) { } @@ -315,7 +315,7 @@ namespace Nif /// Make the factory map used for parsing the file static const std::map factories = makeFactory(); - std::string Reader::printVersion(unsigned int version) + std::string Reader::versionToString(std::uint32_t version) { int major = (version >> 24) & 0xFF; int minor = (version >> 16) & 0xFF; @@ -329,8 +329,8 @@ namespace Nif void Reader::parse(Files::IStreamPtr&& stream) { - const std::array fileHash = Files::getHash(filename, *stream); - hash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); + const std::array fileHash = Files::getHash(mFilename, *stream); + mHash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); NIFStream nif(*this, std::move(stream)); @@ -343,151 +343,172 @@ namespace Nif const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), [&](const std::string& verString) { return head.starts_with(verString); }); if (!supportedHeader) - throw Nif::Exception("Invalid NIF header: " + head, filename); + throw Nif::Exception("Invalid NIF header: " + head, mFilename); // Get BCD version - ver = nif.getUInt(); + nif.read(mVersion); // 4.0.0.0 is an older, practically identical version of the format. // It's not used by Morrowind assets but Morrowind supports it. static const std::array supportedVers = { NIFStream::generateVersion(4, 0, 0, 0), NIFFile::VER_MW, }; - const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); + const bool supportedVersion + = std::find(supportedVers.begin(), supportedVers.end(), mVersion) != supportedVers.end(); const bool writeDebugLog = sWriteNifDebugLog; if (!supportedVersion) { if (!sLoadUnsupportedFiles) - throw Nif::Exception("Unsupported NIF version: " + printVersion(ver), filename); + throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename); if (writeDebugLog) - Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << printVersion(ver) - << ". Proceed with caution! File: " << filename; + Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << versionToString(mVersion) + << ". Proceed with caution! File: " << mFilename; } - // NIF data endianness - if (ver >= NIFStream::generateVersion(20, 0, 0, 4)) + const bool hasEndianness = mVersion >= NIFStream::generateVersion(20, 0, 0, 4); + const bool hasUserVersion = mVersion >= NIFStream::generateVersion(10, 0, 1, 8); + const bool hasRecTypeListings = mVersion >= NIFStream::generateVersion(5, 0, 0, 1); + const bool hasRecTypeHashes = mVersion == NIFStream::generateVersion(20, 3, 1, 2); + const bool hasRecordSizes = mVersion >= NIFStream::generateVersion(20, 2, 0, 5); + const bool hasGroups = mVersion >= NIFStream::generateVersion(5, 0, 0, 6); + const bool hasStringTable = mVersion >= NIFStream::generateVersion(20, 1, 0, 1); + const bool hasRecordSeparators + = mVersion >= NIFStream::generateVersion(10, 0, 0, 0) && mVersion < NIFStream::generateVersion(10, 2, 0, 0); + + // Record type list + std::vector recTypes; + // Record type mapping for each record + std::vector recTypeIndices; + { - unsigned char endianness = nif.getChar(); + std::uint8_t endianness = 1; + if (hasEndianness) + nif.read(endianness); + + // TODO: find some big-endian files and investigate the difference if (endianness == 0) - throw Nif::Exception("Big endian NIF files are unsupported", filename); + throw Nif::Exception("Big endian NIF files are unsupported", mFilename); } - // User version - if (ver > NIFStream::generateVersion(10, 0, 1, 8)) - userVer = nif.getUInt(); + if (hasUserVersion) + nif.read(mUserVersion); - // Number of records - const std::size_t recNum = nif.getUInt(); - records.resize(recNum); + mRecords.resize(nif.get()); // Bethesda stream header - // It contains Bethesda format version and (useless) export information - if (ver == NIFFile::VER_OB_OLD - || (userVer >= 3 - && ((ver == NIFFile::VER_OB || ver == NIFFile::VER_BGS) - || (ver >= NIFStream::generateVersion(10, 1, 0, 0) && ver <= NIFStream::generateVersion(20, 0, 0, 4) - && userVer <= 11)))) { - bethVer = nif.getUInt(); - nif.getExportString(); // Author - if (bethVer > NIFFile::BETHVER_FO4) - nif.getUInt(); // Unknown - nif.getExportString(); // Process script - nif.getExportString(); // Export script - if (bethVer == NIFFile::BETHVER_FO4) - nif.getExportString(); // Max file path - } - - std::vector recTypes; - std::vector recTypeIndices; - - const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5, 0, 0, 1); - if (hasRecTypeListings) - { - unsigned short recTypeNum = nif.getUShort(); - // Record type list - nif.getSizedStrings(recTypes, recTypeNum); - // Record type mapping for each record - nif.readVector(recTypeIndices, recNum); - if (ver >= NIFStream::generateVersion(5, 0, 0, 6)) // Groups + bool hasBSStreamHeader = false; + if (mVersion == NIFFile::VER_OB_OLD) + hasBSStreamHeader = true; + else if (mUserVersion >= 3 && mVersion >= NIFStream::generateVersion(10, 1, 0, 0)) { - if (ver >= NIFStream::generateVersion(20, 1, 0, 1)) // String table - { - if (ver >= NIFStream::generateVersion(20, 2, 0, 5)) // Record sizes - { - std::vector recSizes; // Currently unused - nif.readVector(recSizes, recNum); - } - const std::size_t stringNum = nif.getUInt(); - nif.getUInt(); // Max string length - nif.getSizedStrings(strings, stringNum); - } - std::vector groups; // Currently unused - unsigned int groupNum = nif.getUInt(); - nif.readVector(groups, groupNum); + if (mVersion <= NIFFile::VER_OB || mVersion == NIFFile::VER_BGS) + hasBSStreamHeader = mUserVersion <= 11 || mVersion >= NIFFile::VER_OB; + } + + if (hasBSStreamHeader) + { + nif.read(mBethVersion); + nif.getExportString(); // Author + if (mBethVersion >= 131) + nif.get(); // Unknown + else + nif.getExportString(); // Process script + nif.getExportString(); // Export script + if (mBethVersion >= 103) + nif.getExportString(); // Max file path } } - const bool hasRecordSeparators - = ver >= NIFStream::generateVersion(10, 0, 0, 0) && ver < NIFStream::generateVersion(10, 2, 0, 0); - for (std::size_t i = 0; i < recNum; i++) + if (hasRecTypeListings) + { + // TODO: 20.3.1.2 uses DJB hashes instead of strings + if (hasRecTypeHashes) + throw Nif::Exception("Hashed record types are unsupported", mFilename); + else + { + nif.getSizedStrings(recTypes, nif.get()); + nif.readVector(recTypeIndices, mRecords.size()); + } + } + + if (hasRecordSizes) // Record sizes + { + std::vector recSizes; // Currently unused + nif.readVector(recSizes, mRecords.size()); + } + + if (hasStringTable) + { + std::uint32_t stringNum, maxStringLength; + nif.read(stringNum); + nif.read(maxStringLength); + nif.getSizedStrings(mStrings, stringNum); + } + + if (hasGroups) + { + std::vector groups; // Currently unused + nif.readVector(groups, nif.get()); + } + + for (std::size_t i = 0; i < mRecords.size(); i++) { std::unique_ptr r; - std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); + std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.get(); if (rec.empty()) { std::stringstream error; error << "Record type is blank (index " << i << ")"; - throw Nif::Exception(error.str(), filename); + throw Nif::Exception(error.str(), mFilename); } // Record separator. Some Havok records in Oblivion do not have it. if (hasRecordSeparators && !rec.starts_with("bhk")) - if (nif.getInt()) + if (nif.get()) Log(Debug::Warning) << "NIFFile Warning: Record of type " << rec << ", index " << i - << " is preceded by a non-zero separator. File: " << filename; + << " is preceded by a non-zero separator. File: " << mFilename; const auto entry = factories.find(rec); if (entry == factories.end()) - throw Nif::Exception("Unknown record type " + rec, filename); + throw Nif::Exception("Unknown record type " + rec, mFilename); r = entry->second(); if (!supportedVersion && writeDebugLog) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" - << filename << ")"; + << mFilename << ")"; assert(r != nullptr); assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; r->read(&nif); - records[i] = std::move(r); + mRecords[i] = std::move(r); } - const std::size_t rootNum = nif.getUInt(); - roots.resize(rootNum); - // Determine which records are roots - for (std::size_t i = 0; i < rootNum; i++) + mRoots.resize(nif.get()); + for (std::size_t i = 0; i < mRoots.size(); i++) { - int idx = nif.getInt(); - if (idx >= 0 && static_cast(idx) < records.size()) + std::int32_t idx; + nif.read(idx); + if (idx >= 0 && static_cast(idx) < mRecords.size()) { - roots[i] = records[idx].get(); + mRoots[i] = mRecords[idx].get(); } else { - roots[i] = nullptr; + mRoots[i] = nullptr; Log(Debug::Warning) << "NIFFile Warning: Root " << i + 1 << " does not point to a record: index " << idx - << ". File: " << filename; + << ". File: " << mFilename; } } // Once parsing is done, do post-processing. - for (const auto& record : records) + for (const auto& record : mRecords) record->post(*this); } @@ -513,7 +534,7 @@ namespace Nif { if (index == std::numeric_limits::max()) return std::string(); - return strings.at(index); + return mStrings.at(index); } } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 154ab6b140..6f0030af47 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -30,13 +30,14 @@ namespace Nif BETHVER_SKY = 83, // Skyrim BETHVER_SSE = 100, // Skyrim SE BETHVER_FO4 = 130, // Fallout 4 - BETHVER_F76 = 155 // Fallout 76 + BETHVER_F76 = 155, // Fallout 76 + BETHVER_STF = 172, // Starfield }; /// File version, user version, Bethesda version - unsigned int mVersion = 0; - unsigned int mUserVersion = 0; - unsigned int mBethVersion = 0; + std::uint32_t mVersion = 0; + std::uint32_t mUserVersion = 0; + std::uint32_t mBethVersion = 0; /// File name, used for error messages and opening the file std::filesystem::path mPath; @@ -76,13 +77,13 @@ namespace Nif const std::string& getHash() const { return mFile->mHash; } /// Get the version of the NIF format used - unsigned int getVersion() const { return mFile->mVersion; } + std::uint32_t getVersion() const { return mFile->mVersion; } /// Get the user version of the NIF format used - unsigned int getUserVersion() const { return mFile->mUserVersion; } + std::uint32_t getUserVersion() const { return mFile->mUserVersion; } /// Get the Bethesda version of the NIF format used - unsigned int getBethVersion() const { return mFile->mBethVersion; } + std::uint32_t getBethVersion() const { return mFile->mBethVersion; } bool getUseSkinning() const { return mFile->mUseSkinning; } @@ -93,22 +94,22 @@ namespace Nif class Reader { /// File version, user version, Bethesda version - unsigned int& ver; - unsigned int& userVer; - unsigned int& bethVer; + std::uint32_t& mVersion; + std::uint32_t& mUserVersion; + std::uint32_t& mBethVersion; /// File name, used for error messages and opening the file - std::filesystem::path& filename; - std::string& hash; + std::filesystem::path& mFilename; + std::string& mHash; /// Record list - std::vector>& records; + std::vector>& mRecords; /// Root list. This is a select portion of the pointers from records - std::vector& roots; + std::vector& mRoots; /// String table - std::vector strings; + std::vector mStrings; bool& mUseSkinning; @@ -117,7 +118,7 @@ namespace Nif /// Get the file's version in a human readable form ///\returns A string containing a human readable NIF version number - std::string printVersion(unsigned int version); + std::string versionToString(std::uint32_t version); public: /// Open a NIF stream. The name is used for error messages. @@ -127,26 +128,26 @@ namespace Nif void parse(Files::IStreamPtr&& stream); /// Get a given record - Record* getRecord(size_t index) const { return records.at(index).get(); } + Record* getRecord(size_t index) const { return mRecords.at(index).get(); } /// Get a given string from the file's string table - std::string getString(uint32_t index) const; + std::string getString(std::uint32_t index) const; /// Set whether there is skinning contained in this NIF file. /// @note This is just a hint for users of the NIF file and has no effect on the loading procedure. void setUseSkinning(bool skinning); /// Get the name of the file - std::filesystem::path getFilename() const { return filename; } + std::filesystem::path getFilename() const { return mFilename; } /// Get the version of the NIF format used - unsigned int getVersion() const { return ver; } + std::uint32_t getVersion() const { return mVersion; } /// Get the user version of the NIF format used - unsigned int getUserVersion() const { return userVer; } + std::uint32_t getUserVersion() const { return mUserVersion; } /// Get the Bethesda version of the NIF format used - unsigned int getBethVersion() const { return bethVer; } + std::uint32_t getBethVersion() const { return mBethVersion; } static void setLoadUnsupportedFiles(bool load); From 6872c7144e4feb4f4f66798cf6d6aed4aa969422 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 01:38:17 +0300 Subject: [PATCH 0105/2167] Rename Transformation->NiTransform and update everything directly related --- apps/openmw_test_suite/nif/node.hpp | 4 +-- .../nifloader/testbulletnifloader.cpp | 32 +++++++++--------- components/nif/data.cpp | 8 ++--- components/nif/data.hpp | 33 +++---------------- components/nif/nifstream.cpp | 10 +++--- components/nif/nifstream.hpp | 5 ++- components/nif/niftypes.hpp | 18 +++++----- components/nif/node.cpp | 7 ++-- components/nif/node.hpp | 2 +- components/nifosg/matrixtransform.cpp | 8 ++--- components/nifosg/matrixtransform.hpp | 2 +- 11 files changed, 52 insertions(+), 77 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index ce42faef08..70a1fe16be 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -6,9 +6,9 @@ namespace Nif::Testing { - inline void init(Transformation& value) + inline void init(NiTransform& value) { - value = Transformation::getIdentity(); + value = NiTransform::getIdentity(); } inline void init(Extra& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7c675fd22d..2381d2d5a4 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -274,12 +274,12 @@ namespace using namespace Nif::Testing; using NifBullet::BulletNifLoader; - void copy(const btTransform& src, Nif::Transformation& dst) + void copy(const btTransform& src, Nif::NiTransform& dst) { - dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); + dst.mTranslation = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); for (int row = 0; row < 3; ++row) for (int column = 0; column < 3; ++column) - dst.rotation.mValues[row][column] = src.getBasis().getRow(row)[column]; + dst.mRotation.mValues[row][column] = src.getBasis().getRow(row)[column]; } struct TestBulletNifLoader : Test @@ -740,7 +740,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_animated_shape) { copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 3; + mNiTriShape.trafo.mScale = 3; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiTriShape); @@ -764,10 +764,10 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_animated_shape) { copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 3; + mNiTriShape.trafo.mScale = 3; mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.scale = 4; + mNiNode.trafo.mScale = 4; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -792,11 +792,11 @@ namespace TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_animated_shape) { copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 3; + mNiTriShape.trafo.mScale = 3; mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); - mNiTriShape2.trafo.scale = 3; + mNiTriShape2.trafo.mScale = 3; mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ @@ -835,11 +835,11 @@ namespace mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 3; + mNiTriShape.trafo.mScale = 3; mNiTriShape.parents.push_back(&mNiNode); mNiTriShape.mController = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.scale = 4; + mNiNode.trafo.mScale = 4; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -865,17 +865,17 @@ namespace mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 3; + mNiTriShape.trafo.mScale = 3; mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); - mNiTriShape2.trafo.scale = 3; + mNiTriShape2.trafo.mScale = 3; mNiTriShape2.parents.push_back(&mNiNode); mNiTriShape2.mController = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2), })); - mNiNode.trafo.scale = 4; + mNiNode.trafo.mScale = 4; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1352,12 +1352,12 @@ namespace TEST_F(TestBulletNifLoader, should_handle_node_with_multiple_parents) { copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.scale = 4; + mNiTriShape.trafo.mScale = 4; mNiTriShape.parents = { &mNiNode, &mNiNode2 }; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.scale = 2; + mNiNode.trafo.mScale = 2; mNiNode2.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode2.trafo.scale = 3; + mNiNode2.trafo.mScale = 3; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 799d0cbe43..6570ce6e5c 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -347,9 +347,7 @@ namespace Nif void NiSkinData::read(NIFStream* nif) { - nif->read(mTransform.rotation); - nif->read(mTransform.pos); - nif->read(mTransform.scale); + nif->read(mTransform); uint32_t numBones; nif->read(numBones); @@ -366,9 +364,7 @@ namespace Nif mBones.resize(numBones); for (BoneInfo& bi : mBones) { - nif->read(bi.mTransform.rotation); - nif->read(bi.mTransform.pos); - nif->read(bi.mTransform.scale); + nif->read(bi.mTransform); nif->read(bi.mBoundSphere); uint16_t numVertices; diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 42f52ba5f1..d81f6de22f 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -1,33 +1,10 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (data.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_DATA_HPP #define OPENMW_COMPONENTS_NIF_DATA_HPP #include "nifkey.hpp" -#include "niftypes.hpp" // Transformation +#include "niftypes.hpp" // NiTransform +#include "node.hpp" #include "recordptr.hpp" -#include namespace Nif { @@ -278,12 +255,12 @@ namespace Nif struct BoneInfo { - Transformation mTransform; + NiTransform mTransform; osg::BoundingSpheref mBoundSphere; std::vector mWeights; }; - Transformation mTransform; + NiTransform mTransform; std::vector mBones; NiSkinPartitionPtr mPartitions; @@ -413,5 +390,5 @@ namespace Nif void read(NIFStream* nif) override; }; -} // Namespace +} #endif diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index c666a5edab..e33dc4f7b1 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -130,11 +130,11 @@ namespace Nif } template <> - void NIFStream::read(Transformation& t) + void NIFStream::read(NiTransform& transform) { - read(t.pos); - read(t.rotation); - read(t.scale); + read(transform.mRotation); + read(transform.mTranslation); + read(transform.mScale); } template <> @@ -192,7 +192,7 @@ namespace Nif } template <> - void NIFStream::read(Transformation* dest, size_t size) + void NIFStream::read(NiTransform* dest, size_t size) { readRange(*this, dest, size); } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 217d924059..f4c35e6625 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -159,7 +159,6 @@ namespace Nif osg::Vec4f getVector4() { return get(); } Matrix3 getMatrix3() { return get(); } osg::Quat getQuaternion() { return get(); } - Transformation getTrafo() { return get(); } bool getBoolean() { return get(); } std::string getString() { return get(); } }; @@ -177,7 +176,7 @@ namespace Nif template <> void NIFStream::read(osg::BoundingSpheref& sphere); template <> - void NIFStream::read(Transformation& t); + void NIFStream::read(NiTransform& transform); template <> void NIFStream::read(bool& data); template <> @@ -196,7 +195,7 @@ namespace Nif template <> void NIFStream::read(osg::BoundingSpheref* dest, size_t size); template <> - void NIFStream::read(Transformation* dest, size_t size); + void NIFStream::read(NiTransform* dest, size_t size); template <> void NIFStream::read(bool* dest, size_t size); template <> diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index b6a97b6ecb..3b2a4c1578 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -53,29 +53,29 @@ namespace Nif } }; - struct Transformation + struct NiTransform { - osg::Vec3f pos; - Matrix3 rotation; // this can contain scale components too, including negative and nonuniform scales - float scale; + Matrix3 mRotation; // this can contain scale components too, including negative and nonuniform scales + osg::Vec3f mTranslation; + float mScale; osg::Matrixf toMatrix() const { osg::Matrixf transform; - transform.setTrans(pos); + transform.setTrans(mTranslation); for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) - transform(j, i) = rotation.mValues[i][j] * scale; // NB column/row major difference + transform(j, i) = mRotation.mValues[i][j] * mScale; // NB column/row major difference return transform; } - bool isIdentity() const { return pos == osg::Vec3f(0, 0, 0) && rotation.isIdentity() && scale == 1.f; } + bool isIdentity() const { return mRotation.isIdentity() && mTranslation == osg::Vec3f() && mScale == 1.f; } - static const Transformation& getIdentity() + static const NiTransform& getIdentity() { - static const Transformation identity = { osg::Vec3f(), Matrix3(), 1.0f }; + static const NiTransform identity = { Matrix3(), osg::Vec3f(), 1.0f }; return identity; } }; diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 733d5319e9..fba0e1a69a 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -80,7 +80,10 @@ namespace Nif NiObjectNET::read(nif); flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); - trafo = nif->getTrafo(); + nif->read(trafo.mTranslation); + nif->read(trafo.mRotation); + nif->read(trafo.mScale); + if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) velocity = nif->getVector3(); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) @@ -130,7 +133,7 @@ namespace Nif // We want to do this on world scene graph level rather than local scene graph level. if (0 == recIndex && !Misc::StringUtils::ciEqual(mName, "bip01")) { - trafo = Nif::Transformation::getIdentity(); + trafo = Nif::NiTransform::getIdentity(); } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 129e74a617..2a1fcd9af6 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -83,7 +83,7 @@ namespace Nif // Node flags. Interpretation depends somewhat on the type of node. unsigned int flags; - Transformation trafo; + NiTransform trafo; osg::Vec3f velocity; // Unused? Might be a run-time game state PropertyList props; diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index 44eac28def..a59f10360a 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -2,10 +2,10 @@ namespace NifOsg { - MatrixTransform::MatrixTransform(const Nif::Transformation& trafo) - : osg::MatrixTransform(trafo.toMatrix()) - , mScale(trafo.scale) - , mRotationScale(trafo.rotation) + MatrixTransform::MatrixTransform(const Nif::NiTransform& transform) + : osg::MatrixTransform(transform.toMatrix()) + , mScale(transform.mScale) + , mRotationScale(transform.mRotation) { } diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index e1e7ff1828..4e42d00787 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -12,7 +12,7 @@ namespace NifOsg { public: MatrixTransform() = default; - MatrixTransform(const Nif::Transformation& trafo); + MatrixTransform(const Nif::NiTransform& transform); MatrixTransform(const MatrixTransform& copy, const osg::CopyOp& copyop); META_Node(NifOsg, MatrixTransform) From 878d4ddaa7703561232eb17f09e8e6d52f694d29 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 02:45:51 +0300 Subject: [PATCH 0106/2167] Update record pointer loading --- components/nif/recordptr.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 5b890eec54..eb704dbc21 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -1,9 +1,10 @@ #ifndef OPENMW_COMPONENTS_NIF_RECORDPTR_HPP #define OPENMW_COMPONENTS_NIF_RECORDPTR_HPP +#include + #include "niffile.hpp" #include "nifstream.hpp" -#include namespace Nif { @@ -39,7 +40,7 @@ namespace Nif assert(index == -2); // Store the index for later - index = nif->getInt(); + index = nif->get(); assert(index >= -1); } @@ -90,12 +91,13 @@ namespace Nif template void readRecordList(NIFStream* nif, RecordListT& list) { - const int length = nif->getInt(); + const std::uint32_t length = nif->get(); - if (length < 0) - throw std::runtime_error("Negative NIF record list length: " + std::to_string(length)); + // No reasonable list can hit this generous limit + if (length >= (1 << 24)) + throw std::runtime_error("Record list too long: " + std::to_string(length)); - list.resize(static_cast(length)); + list.resize(length); for (auto& value : list) value.read(nif); From a7cc4e6ba64e7a4ff30348fd370f97c5f853a8c8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 05:23:25 +0300 Subject: [PATCH 0107/2167] Rename Node->NiAVObject and update everything directly related Update NiNode Simplify RootCollisionNode handling in BulletNifLoader --- apps/openmw_test_suite/nif/node.hpp | 12 +- .../nifloader/testbulletnifloader.cpp | 286 +++++++++--------- .../nifosg/testnifloader.cpp | 10 +- components/nif/controller.cpp | 2 +- components/nif/controller.hpp | 6 +- components/nif/data.hpp | 4 +- components/nif/effect.cpp | 2 +- components/nif/effect.hpp | 2 +- components/nif/node.cpp | 77 +++-- components/nif/node.hpp | 74 ++--- components/nif/physics.hpp | 4 +- components/nif/recordptr.hpp | 6 +- components/nifbullet/bulletnifloader.cpp | 113 +++---- components/nifbullet/bulletnifloader.hpp | 13 +- components/nifosg/nifloader.cpp | 66 ++-- 15 files changed, 310 insertions(+), 367 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 70a1fe16be..b39729c876 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -23,18 +23,16 @@ namespace Nif::Testing value.mController = ControllerPtr(nullptr); } - inline void init(Node& value) + inline void init(NiAVObject& value) { init(static_cast(value)); - value.flags = 0; - init(value.trafo); - value.hasBounds = false; - value.isBone = false; + value.mFlags = 0; + init(value.mTransform); } inline void init(NiGeometry& value) { - init(static_cast(value)); + init(static_cast(value)); value.data = NiGeometryDataPtr(nullptr); value.skin = NiSkinInstancePtr(nullptr); } @@ -54,7 +52,7 @@ namespace Nif::Testing inline void init(NiSkinInstance& value) { value.mData = NiSkinDataPtr(nullptr); - value.mRoot = NodePtr(nullptr); + value.mRoot = NiAVObjectPtr(nullptr); } inline void init(Controller& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 2381d2d5a4..fc6a21f62c 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -285,8 +285,8 @@ namespace struct TestBulletNifLoader : Test { BulletNifLoader mLoader; - Nif::Node mNode; - Nif::Node mNode2; + Nif::NiAVObject mNode; + Nif::NiAVObject mNode2; Nif::NiNode mNiNode; Nif::NiNode mNiNode2; Nif::NiNode mNiNode3; @@ -414,11 +414,10 @@ namespace TEST_F( TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) { - mNode.hasBounds = true; - mNode.flags |= Nif::Node::Flag_BBoxCollision; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNode); @@ -439,13 +438,12 @@ namespace TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) { - mNode.hasBounds = true; - mNode.flags |= Nif::Node::Flag_BBoxCollision; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNode) })); + mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -467,18 +465,16 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) { - mNode.hasBounds = true; - mNode.flags |= Nif::Node::Flag_BBoxCollision; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parents.push_back(&mNiNode); + mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mParents.push_back(&mNiNode); - mNiNode.hasBounds = true; - mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.bounds.box.extents = osg::Vec3f(4, 5, 6); - mNiNode.bounds.box.center = osg::Vec3f(-4, -5, -6); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNode) })); + mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.box.extents = osg::Vec3f(4, 5, 6); + mNiNode.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -500,24 +496,21 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) { - mNode.hasBounds = true; - mNode.flags |= Nif::Node::Flag_BBoxCollision; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parents.push_back(&mNiNode); + mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mParents.push_back(&mNiNode); - mNode2.hasBounds = true; - mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); - mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); - mNode2.parents.push_back(&mNiNode); + mNode2.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.mParents.push_back(&mNiNode); - mNiNode.hasBounds = true; - mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); - mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2) })); + mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.mBounds.box.center = osg::Vec3f(-7, -8, -9); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -539,24 +532,21 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { - mNode.hasBounds = true; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); - mNode.parents.push_back(&mNiNode); + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mParents.push_back(&mNiNode); - mNode2.hasBounds = true; - mNode2.flags |= Nif::Node::Flag_BBoxCollision; - mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); - mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); - mNode2.parents.push_back(&mNiNode); + mNode2.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode2.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.mParents.push_back(&mNiNode); - mNiNode.hasBounds = true; - mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); - mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2) })); + mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.mBounds.box.center = osg::Vec3f(-7, -8, -9); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -578,10 +568,9 @@ namespace TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { - mNode.hasBounds = true; - mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNode); @@ -619,10 +608,9 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_static_shape_with_bounds_but_with_null_collision_shape) { - mNiTriShape.hasBounds = true; - mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiTriShape.bounds.box.extents = osg::Vec3f(1, 2, 3); - mNiTriShape.bounds.box.center = osg::Vec3f(-1, -2, -3); + mNiTriShape.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiTriShape.mBounds.box.extents = osg::Vec3f(1, 2, 3); + mNiTriShape.mBounds.box.center = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriShape); @@ -639,8 +627,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_static_shape) { - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -662,10 +650,10 @@ namespace TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_static_shape) { - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiNode2) })); - mNiNode2.parents.push_back(&mNiNode); - mNiNode2.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiTriShape.parents.push_back(&mNiNode2); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) }; + mNiNode2.mParents.push_back(&mNiNode); + mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + mNiTriShape.mParents.push_back(&mNiNode2); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -687,10 +675,9 @@ namespace TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_static_shape_with_all_meshes) { - mNiTriShape.parents.push_back(&mNiNode); - mNiTriShape2.parents.push_back(&mNiNode); - mNiNode.children - = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiTriShape2.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape), Nif::NiAVObjectPtr(&mNiTriShape2) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -717,8 +704,8 @@ namespace for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_static_shape) { mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -739,8 +726,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_animated_shape) { - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 3; + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 3; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiTriShape); @@ -763,11 +750,11 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_animated_shape) { - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 3; - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.mScale = 4; + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 3; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + mNiNode.mTransform.mScale = 4; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -791,18 +778,15 @@ namespace TEST_F( TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_animated_shape) { - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 3; - mNiTriShape.parents.push_back(&mNiNode); + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 3; + mNiTriShape.mParents.push_back(&mNiNode); - copy(mTransform, mNiTriShape2.trafo); - mNiTriShape2.trafo.mScale = 3; - mNiTriShape2.parents.push_back(&mNiNode); + copy(mTransform, mNiTriShape2.mTransform); + mNiTriShape2.mTransform.mScale = 3; + mNiTriShape2.mParents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ - Nif::NodePtr(&mNiTriShape), - Nif::NodePtr(&mNiTriShape2), - })); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape), Nif::NiAVObjectPtr(&mNiTriShape2) }; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -834,12 +818,12 @@ namespace { mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 3; - mNiTriShape.parents.push_back(&mNiNode); + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 3; + mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mController = Nif::ControllerPtr(&mController); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.mScale = 4; + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + mNiNode.mTransform.mScale = 4; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -864,18 +848,18 @@ namespace { mController.recType = Nif::RC_NiKeyframeController; mController.flags |= Nif::Controller::Flag_Active; - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 3; - mNiTriShape.parents.push_back(&mNiNode); - copy(mTransform, mNiTriShape2.trafo); - mNiTriShape2.trafo.mScale = 3; - mNiTriShape2.parents.push_back(&mNiNode); + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 3; + mNiTriShape.mParents.push_back(&mNiNode); + copy(mTransform, mNiTriShape2.mTransform); + mNiTriShape2.mTransform.mScale = 3; + mNiTriShape2.mParents.push_back(&mNiNode); mNiTriShape2.mController = Nif::ControllerPtr(&mController); - mNiNode.children = Nif::NodeList(std::vector({ - Nif::NodePtr(&mNiTriShape), - Nif::NodePtr(&mNiTriShape2), - })); - mNiNode.trafo.mScale = 4; + mNiNode.mChildren = Nif::NiAVObjectList{ + Nif::NiAVObjectPtr(&mNiTriShape), + Nif::NiAVObjectPtr(&mNiTriShape2), + }; + mNiNode.mTransform.mScale = 4; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -905,8 +889,8 @@ namespace TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) { - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -936,8 +920,8 @@ namespace TEST_F( TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode.recType = Nif::RC_AvoidNode; Nif::NIFFile file("test.nif"); @@ -960,8 +944,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -979,8 +963,8 @@ namespace { auto data = static_cast(mNiTriShape.data.getPtr()); data->mTriangles.clear(); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -999,8 +983,8 @@ namespace mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1028,8 +1012,8 @@ namespace mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1055,8 +1039,8 @@ namespace mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1083,8 +1067,8 @@ namespace mNiStringExtraData2.mData = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1112,13 +1096,13 @@ namespace init(emptyCollisionNode); niTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); - niTriShape.parents.push_back(&mNiNode); + niTriShape.mParents.push_back(&mNiNode); emptyCollisionNode.recType = Nif::RC_RootCollisionNode; - emptyCollisionNode.parents.push_back(&mNiNode); + emptyCollisionNode.mParents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList( - std::vector({ Nif::NodePtr(&niTriShape), Nif::NodePtr(&emptyCollisionNode) })); + mNiNode.mChildren + = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&niTriShape), Nif::NiAVObjectPtr(&emptyCollisionNode) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1144,8 +1128,8 @@ namespace mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1163,9 +1147,9 @@ namespace mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" mNiIntegerExtraData.recType = Nif::RC_BSXFlags; mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); - mNiTriShape.parents.push_back(&mNiNode); + mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mName = "EditorMarker"; - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1184,11 +1168,11 @@ namespace mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parents.push_back(&mNiNode2); - mNiNode2.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode2); + mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiNode2) })); + mNiNode2.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) }; mNiNode.recType = Nif::RC_NiNode; Nif::NIFFile file("test.nif"); @@ -1290,8 +1274,8 @@ namespace TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode.recType = Nif::RC_AvoidNode; mNiTriStripsData.mStrips.front() = { 0, 1 }; @@ -1309,8 +1293,8 @@ namespace TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriStripsData.mStrips.front() = { 0, 1 }; - mNiTriStrips.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriStrips) })); + mNiTriStrips.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriStrips) }; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -1326,8 +1310,8 @@ namespace TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) { mNiTriStripsData.mStrips.front() = { 0, 1 }; - mNiTriShape.parents.push_back(&mNiNode); - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); @@ -1351,13 +1335,13 @@ namespace TEST_F(TestBulletNifLoader, should_handle_node_with_multiple_parents) { - copy(mTransform, mNiTriShape.trafo); - mNiTriShape.trafo.mScale = 4; - mNiTriShape.parents = { &mNiNode, &mNiNode2 }; - mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode.trafo.mScale = 2; - mNiNode2.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); - mNiNode2.trafo.mScale = 3; + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 4; + mNiTriShape.mParents = { &mNiNode, &mNiNode2 }; + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + mNiNode.mTransform.mScale = 2; + mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + mNiNode2.mTransform.mScale = 3; Nif::NIFFile file("xtest.nif"); file.mRoots.push_back(&mNiNode); diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 6bcef223d9..a8b9e2fd09 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -66,7 +66,7 @@ namespace TEST_F(NifOsgLoaderTest, shouldLoadFileWithDefaultNode) { - Nif::Node node; + Nif::NiAVObject node; init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); @@ -183,14 +183,14 @@ osg::Group { TEST_P(NifOsgLoaderBSShaderPrefixTest, shouldAddShaderPrefix) { - Nif::Node node; + Nif::NiAVObject node; init(node); Nif::BSShaderPPLightingProperty property; property.recType = Nif::RC_BSShaderPPLightingProperty; property.textureSet = nullptr; property.mController = nullptr; property.type = GetParam().mShaderType; - node.props.push_back(Nif::RecordPtrT(&property)); + node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); @@ -211,14 +211,14 @@ osg::Group { TEST_P(NifOsgLoaderBSLightingShaderPrefixTest, shouldAddShaderPrefix) { - Nif::Node node; + Nif::NiAVObject node; init(node); Nif::BSLightingShaderProperty property; property.recType = Nif::RC_BSLightingShaderProperty; property.mTextureSet = nullptr; property.mController = nullptr; property.type = GetParam().mShaderType; - node.props.push_back(Nif::RecordPtrT(&property)); + node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 1355ce21d3..a3d7f33e45 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -308,7 +308,7 @@ namespace Nif { NiInterpController::read(nif); size_t numTargets = nif->getUShort(); - std::vector targets; + std::vector targets; targets.resize(numTargets); for (size_t i = 0; i < targets.size(); i++) targets[i].read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index d194ea3060..68e795e7fc 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -160,7 +160,7 @@ namespace Nif osg::Vec3f offsetRandom; - NodePtr emitter; + NiAVObjectPtr emitter; int numParticles; int activeCount; @@ -211,7 +211,7 @@ namespace Nif struct NiLookAtController : public Controller { - NodePtr target; + NiAVObjectPtr target; unsigned short lookAtFlags{ 0 }; void read(NIFStream* nif) override; @@ -237,7 +237,7 @@ namespace Nif struct NiMultiTargetTransformController : public NiInterpController { - NodeList mExtraTargets; + NiAVObjectList mExtraTargets; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/data.hpp b/components/nif/data.hpp index d81f6de22f..849cbde7bd 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -229,8 +229,8 @@ namespace Nif { NiSkinDataPtr mData; NiSkinPartitionPtr mPartitions; - NodePtr mRoot; - NodeList mBones; + NiAVObjectPtr mRoot; + NiAVObjectList mBones; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 36407f8dc2..8db6de06d4 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -8,7 +8,7 @@ namespace Nif void NiDynamicEffect::read(NIFStream* nif) { - Node::read(nif); + NiAVObject::read(nif); if (nif->getVersion() > NIFFile::VER_MW && nif->getVersion() < nif->generateVersion(10, 1, 0, 0)) return; diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 06f85cd5d5..906a7fdedf 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -30,7 +30,7 @@ namespace Nif { // Abstract - struct NiDynamicEffect : public Node + struct NiDynamicEffect : public NiAVObject { bool mSwitchState{ true }; void read(NIFStream* nif) override; diff --git a/components/nif/node.cpp b/components/nif/node.cpp index fba0e1a69a..010a363436 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -75,51 +75,47 @@ namespace Nif } } - void Node::read(NIFStream* nif) + void NiAVObject::read(NIFStream* nif) { NiObjectNET::read(nif); - flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); - nif->read(trafo.mTranslation); - nif->read(trafo.mRotation); - nif->read(trafo.mScale); - + if (nif->getBethVersion() <= 26) + mFlags = nif->get(); + else + nif->read(mFlags); + nif->read(mTransform.mTranslation); + nif->read(mTransform.mRotation); + nif->read(mTransform.mScale); if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) - velocity = nif->getVector3(); + nif->read(mVelocity); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) - readRecordList(nif, props); - - if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) - hasBounds = nif->getBoolean(); - if (hasBounds) - bounds.read(nif); - // Reference to the collision object in Gamebryo files. + readRecordList(nif, mProperties); + if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->get()) + mBounds.read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - collision.read(nif); - - parents.clear(); - - isBone = false; + mCollision.read(nif); } - void Node::post(Reader& nif) + void NiAVObject::post(Reader& nif) { NiObjectNET::post(nif); - postRecordList(nif, props); - collision.post(nif); + + postRecordList(nif, mProperties); + mCollision.post(nif); } - void Node::setBone() + void NiAVObject::setBone() { - isBone = true; + mIsBone = true; } void NiNode::read(NIFStream* nif) { - Node::read(nif); - readRecordList(nif, children); + NiAVObject::read(nif); + + readRecordList(nif, mChildren); if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) - readRecordList(nif, effects); + readRecordList(nif, mEffects); // FIXME: stopgap solution until we figure out what Oblivion does if it does anything if (nif->getVersion() > NIFFile::NIFVersion::VER_MW && nif->getVersion() < NIFFile::NIFVersion::VER_BGS) @@ -131,23 +127,24 @@ namespace Nif // FIXME: if node 0 is *not* the only root node, this must not happen. // FIXME: doing this here is awful. // We want to do this on world scene graph level rather than local scene graph level. - if (0 == recIndex && !Misc::StringUtils::ciEqual(mName, "bip01")) + if (recIndex == 0 && !Misc::StringUtils::ciEqual(mName, "bip01")) { - trafo = Nif::NiTransform::getIdentity(); + mTransform = Nif::NiTransform::getIdentity(); } } void NiNode::post(Reader& nif) { - Node::post(nif); - postRecordList(nif, children); - postRecordList(nif, effects); + NiAVObject::post(nif); - for (auto& child : children) + postRecordList(nif, mChildren); + postRecordList(nif, mEffects); + + for (auto& child : mChildren) { // Why would a unique list of children contain empty refs? if (!child.empty()) - child->parents.push_back(this); + child->mParents.push_back(this); } } @@ -171,7 +168,7 @@ namespace Nif void NiGeometry::read(NIFStream* nif) { - Node::read(nif); + NiAVObject::read(nif); data.read(nif); skin.read(nif); material.read(nif); @@ -185,7 +182,7 @@ namespace Nif void NiGeometry::post(Reader& nif) { - Node::post(nif); + NiAVObject::post(nif); data.post(nif); skin.post(nif); shaderprop.post(nif); @@ -224,7 +221,7 @@ namespace Nif void NiCamera::read(NIFStream* nif) { - Node::read(nif); + NiAVObject::read(nif); cam.read(nif); @@ -290,7 +287,7 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) mMode = nif->getUShort() & 0x7; else - mMode = (flags >> 5) & 0x3; + mMode = (mFlags >> 5) & 0x3; } void NiDefaultAVObjectPalette::read(NIFStream* nif) @@ -338,7 +335,7 @@ namespace Nif void BSTriShape::read(NIFStream* nif) { - Node::read(nif); + NiAVObject::read(nif); nif->read(mBoundingSphere); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) @@ -392,7 +389,7 @@ namespace Nif void BSTriShape::post(Reader& nif) { - Node::post(nif); + NiAVObject::post(nif); mSkin.post(nif); mShaderProperty.post(nif); mAlphaProperty.post(nif); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 2a1fcd9af6..76651b05db 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -51,7 +51,7 @@ namespace Nif osg::Vec3f origin; }; - unsigned int type; + unsigned int type{ BASE_BV }; osg::BoundingSpheref sphere; NiBoxBV box; NiCapsuleBV capsule; @@ -66,11 +66,9 @@ namespace Nif { }; - /** A Node is an object that's part of the main NIF tree. It has - parent node (unless it's the root), and transformation (location - and rotation) relative to it's parent. - */ - struct Node : public NiObjectNET + // NiAVObject is an object that is a part of the main NIF tree. It has + // a parent node (unless it's the root) and transformation relative to its parent. + struct NiAVObject : public NiObjectNET { enum Flags { @@ -80,57 +78,48 @@ namespace Nif Flag_ActiveCollision = 0x0020 }; - // Node flags. Interpretation depends somewhat on the type of node. - unsigned int flags; - - NiTransform trafo; - osg::Vec3f velocity; // Unused? Might be a run-time game state - PropertyList props; - - // Bounding box info - bool hasBounds{ false }; - NiBoundingVolume bounds; - - // Collision object info - NiCollisionObjectPtr collision; + // Node flags. Interpretation depends on the record type. + uint32_t mFlags; + NiTransform mTransform; + osg::Vec3f mVelocity; + PropertyList mProperties; + NiBoundingVolume mBounds; + NiCollisionObjectPtr mCollision; + // Parent nodes for the node. Only types derived from NiNode can be parents. + std::vector mParents; + bool mIsBone{ false }; void read(NIFStream* nif) override; void post(Reader& nif) override; - // Parent node, or nullptr for the root node. As far as I'm aware, only - // NiNodes (or types derived from NiNodes) can be parents. - std::vector parents; - - bool isBone{ false }; - void setBone(); - - bool isHidden() const { return flags & Flag_Hidden; } - bool hasMeshCollision() const { return flags & Flag_MeshCollision; } - bool hasBBoxCollision() const { return flags & Flag_BBoxCollision; } - bool collisionActive() const { return flags & Flag_ActiveCollision; } + bool isHidden() const { return mFlags & Flag_Hidden; } + bool hasMeshCollision() const { return mFlags & Flag_MeshCollision; } + bool hasBBoxCollision() const { return mFlags & Flag_BBoxCollision; } + bool collisionActive() const { return mFlags & Flag_ActiveCollision; } }; - struct NiNode : Node + struct NiNode : NiAVObject { - NodeList children; - NodeList effects; - enum BSAnimFlags { AnimFlag_AutoPlay = 0x0020 }; + enum BSParticleFlags { ParticleFlag_AutoPlay = 0x0020, ParticleFlag_LocalSpace = 0x0080 }; + NiAVObjectList mChildren; + NiAVObjectList mEffects; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; - struct NiGeometry : Node + struct NiGeometry : NiAVObject { /* Possible flags: 0x40 - mesh has no vertex normals ? @@ -176,7 +165,7 @@ namespace Nif { }; - struct NiCamera : Node + struct NiCamera : NiAVObject { struct Camera { @@ -234,7 +223,7 @@ namespace Nif void read(NIFStream* nif) override; - bool swing() const { return flags & Flag_Swing; } + bool swing() const { return mFlags & Flag_Swing; } }; // Abstract @@ -276,8 +265,8 @@ namespace Nif struct NiDefaultAVObjectPalette : Record { - NodePtr mScene; - std::unordered_map mObjects; + NiAVObjectPtr mScene; + std::unordered_map mObjects; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -285,7 +274,7 @@ namespace Nif struct BSTreeNode : NiNode { - NodeList mBones1, mBones2; + NiAVObjectList mBones1, mBones2; void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -350,7 +339,7 @@ namespace Nif void read(NIFStream* nif, uint16_t flags); }; - struct BSTriShape : Node + struct BSTriShape : NiAVObject { osg::BoundingSpheref mBoundingSphere; std::array mBoundMinMax; @@ -397,5 +386,6 @@ namespace Nif void read(NIFStream* nif) override; }; -} // Namespace + +} #endif diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index a7bfa1425d..7c7c3df21e 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -374,7 +374,7 @@ namespace Nif struct NiCollisionObject : public Record { // The node that references this object - NodePtr mTarget; + NiAVObjectPtr mTarget; void read(NIFStream* nif) override { mTarget.read(nif); } void post(Reader& nif) override { mTarget.post(nif); } @@ -541,7 +541,7 @@ namespace Nif struct bhkCompressedMeshShape : public bhkShape { - NodePtr mTarget; + NiAVObjectPtr mTarget; uint32_t mUserData; float mRadius; osg::Vec4f mScale; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index eb704dbc21..9f75b31d74 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -110,7 +110,7 @@ namespace Nif value.post(nif); } - struct Node; + struct NiAVObject; struct Extra; struct Property; struct NiUVData; @@ -152,7 +152,7 @@ namespace Nif struct BSMultiBound; struct BSMultiBoundData; - using NodePtr = RecordPtrT; + using NiAVObjectPtr = RecordPtrT; using ExtraPtr = RecordPtrT; using NiUVDataPtr = RecordPtrT; using NiPosDataPtr = RecordPtrT; @@ -190,7 +190,7 @@ namespace Nif using BSMultiBoundPtr = RecordPtrT; using BSMultiBoundDataPtr = RecordPtrT; - using NodeList = RecordListT; + using NiAVObjectList = RecordListT; using PropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 31be54b030..bc207e52bf 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -132,13 +132,13 @@ namespace NifBullet mShape->mFileHash = nif.getHash(); const size_t numRoots = nif.numRoots(); - std::vector roots; + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif.getRoot(i); if (!r) continue; - const Nif::Node* node = dynamic_cast(r); + const Nif::NiAVObject* node = dynamic_cast(r); if (node) roots.emplace_back(node); } @@ -151,7 +151,7 @@ namespace NifBullet } // Try to find a valid bounding box first. If one's found for any root node, use that. - for (const Nif::Node* node : roots) + for (const Nif::NiAVObject* node : roots) { if (findBoundingBox(*node, filename)) { @@ -175,15 +175,19 @@ namespace NifBullet // If there's no bounding box, we'll have to generate a Bullet collision shape // from the collision data present in every root node. - for (const Nif::Node* node : roots) + for (const Nif::NiAVObject* node : roots) { - bool hasCollisionNode = hasRootCollisionNode(*node); - bool hasCollisionShape = hasCollisionNode && !collisionShapeIsEmpty(*node); - if (hasCollisionNode && !hasCollisionShape) - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - bool generateCollisionShape = !hasCollisionShape; + const Nif::NiNode* colNode = findRootCollisionNode(*node); + bool hasCollisionShape = false; + if (colNode != nullptr) + { + if (colNode->mBounds.type == Nif::NiBoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) + hasCollisionShape = true; + else + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; + } HandleNodeArgs args; - args.mAutogenerated = args.mIsCollisionNode = generateCollisionShape; + args.mAutogenerated = args.mIsCollisionNode = !hasCollisionShape; args.mAnimated = isAnimated; handleNode(filename, *node, nullptr, args, mShape->mVisualCollisionType); } @@ -199,79 +203,53 @@ namespace NifBullet // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? - bool BulletNifLoader::findBoundingBox(const Nif::Node& node, const std::string& filename) + bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node, const std::string& filename) { - if (node.hasBounds) + unsigned int type = node.mBounds.type; + switch (type) { - unsigned int type = node.bounds.type; - switch (type) + case Nif::NiBoundingVolume::Type::BASE_BV: + break; + case Nif::NiBoundingVolume::Type::BOX_BV: + mShape->mCollisionBox.mExtents = node.mBounds.box.extents; + mShape->mCollisionBox.mCenter = node.mBounds.box.center; + break; + default: { - case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBox.mExtents = node.bounds.box.extents; - mShape->mCollisionBox.mCenter = node.bounds.box.center; - break; - default: - { - std::stringstream warning; - warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; - warning << " in file " << filename; - warn(warning.str()); - } - } - - if (node.hasBBoxCollision()) - { - return true; + std::stringstream warning; + warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; + warning << " in file " << filename; + warn(warning.str()); } } + if (type != Nif::NiBoundingVolume::Type::BASE_BV && node.hasBBoxCollision()) + return true; + if (const Nif::NiNode* ninode = dynamic_cast(&node)) { - const Nif::NodeList& list = ninode->children; - for (const auto& child : list) + for (const auto& child : ninode->mChildren) if (!child.empty() && findBoundingBox(child.get(), filename)) return true; } return false; } - bool BulletNifLoader::hasRootCollisionNode(const Nif::Node& rootNode) const + const Nif::NiNode* BulletNifLoader::findRootCollisionNode(const Nif::NiAVObject& rootNode) const { if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { - for (const auto& child : ninode->children) + for (const auto& child : ninode->mChildren) { - if (child.empty()) - continue; - if (child.getPtr()->recType == Nif::RC_RootCollisionNode) - return true; + if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode) + return static_cast(child.getPtr()); } } - return false; + return nullptr; } - bool BulletNifLoader::collisionShapeIsEmpty(const Nif::Node& rootNode) const - { - if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) - { - for (const auto& child : ninode->children) - { - if (child.empty()) - continue; - const Nif::Node* childNode = child.getPtr(); - if (childNode->recType != Nif::RC_RootCollisionNode) - continue; - const Nif::NiNode* niChildnode - = static_cast(childNode); // RootCollisionNode is always a NiNode - if (childNode->hasBounds || niChildnode->children.size() > 0) - return false; - } - } - return true; - } - - void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, - HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType) + void BulletNifLoader::handleNode(const std::string& fileName, const Nif::NiAVObject& node, + const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) @@ -354,10 +332,10 @@ namespace NifBullet if (args.mIsCollisionNode) { - // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! + // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if (!node.hasBounds + if (node.mBounds.type == Nif::NiBoundingVolume::Type::BASE_BV && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { @@ -368,14 +346,13 @@ namespace NifBullet // For NiNodes, loop through children if (const Nif::NiNode* ninode = dynamic_cast(&node)) { - const Nif::NodeList& list = ninode->children; const Nif::Parent currentParent{ *ninode, parent }; - for (const auto& child : list) + for (const auto& child : ninode->mChildren) { if (child.empty()) continue; - assert(std::find(child->parents.begin(), child->parents.end(), ninode) != child->parents.end()); + assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); handleNode(fileName, child.get(), ¤tParent, args, visualCollisionType); } } @@ -403,9 +380,9 @@ namespace NifBullet auto childShape = std::make_unique(childMesh.get(), true); std::ignore = childMesh.release(); - osg::Matrixf transform = niGeometry.trafo.toMatrix(); + osg::Matrixf transform = niGeometry.mTransform.toMatrix(); for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) - transform *= parent->mNiNode.trafo.toMatrix(); + transform *= parent->mNiNode.mTransform.toMatrix(); childShape->setLocalScaling(Misc::Convert::toBullet(transform.getScale())); transform.orthoNormalize(transform); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index c70eb997fa..819ba34b34 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -22,10 +22,8 @@ class btCollisionShape; namespace Nif { - struct Node; - struct Transformation; - struct NiTriShape; - struct NiTriStrips; + struct NiAVObject; + struct NiNode; struct NiGeometry; struct Parent; } @@ -50,7 +48,7 @@ namespace NifBullet osg::ref_ptr load(Nif::FileView file); private: - bool findBoundingBox(const Nif::Node& node, const std::string& filename); + bool findBoundingBox(const Nif::NiAVObject& node, const std::string& filename); struct HandleNodeArgs { @@ -61,11 +59,10 @@ namespace NifBullet bool mAvoid{ false }; }; - void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, + void handleNode(const std::string& fileName, const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType); - bool hasRootCollisionNode(const Nif::Node& rootNode) const; - bool collisionShapeIsEmpty(const Nif::Node& rootNode) const; + const Nif::NiNode* findRootCollisionNode(const Nif::NiAVObject& rootNode) const; void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 1a595a3ab5..9683de9a23 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -76,12 +76,12 @@ namespace void apply(osg::Drawable& node) override { traverse(node); } }; - void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) + void getAllNiNodes(const Nif::NiAVObject* node, std::vector& outIndices) { if (const Nif::NiNode* ninode = dynamic_cast(node)) { outIndices.push_back(ninode->recIndex); - for (const auto& child : ninode->children) + for (const auto& child : ninode->mChildren) if (!child.empty()) getAllNiNodes(child.getPtr(), outIndices); } @@ -103,11 +103,11 @@ namespace // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the // node hierarchy above it. void collectDrawableProperties( - const Nif::Node* nifNode, const Nif::Parent* parent, std::vector& out) + const Nif::NiAVObject* nifNode, const Nif::Parent* parent, std::vector& out) { if (parent != nullptr) collectDrawableProperties(&parent->mNiNode, parent->mParent, out); - for (const auto& property : nifNode->props) + for (const auto& property : nifNode->mProperties) { if (!property.empty()) { @@ -345,13 +345,13 @@ namespace NifOsg osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) { const size_t numRoots = nif.numRoots(); - std::vector roots; + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif.getRoot(i); if (!r) continue; - const Nif::Node* nifNode = dynamic_cast(r); + const Nif::NiAVObject* nifNode = dynamic_cast(r); if (nifNode) roots.emplace_back(nifNode); } @@ -362,7 +362,7 @@ namespace NifOsg osg::ref_ptr created(new osg::Group); created->setDataVariance(osg::Object::STATIC); - for (const Nif::Node* root : roots) + for (const Nif::NiAVObject* root : roots) { auto node = handleNode(root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), @@ -397,13 +397,13 @@ namespace NifOsg return created; } - void applyNodeProperties(const Nif::Node* nifNode, osg::Node* applyTo, + void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { bool hasStencilProperty = false; - for (const auto& property : nifNode->props) + for (const auto& property : nifNode->mProperties) { if (property.empty()) continue; @@ -420,13 +420,13 @@ namespace NifOsg } } - for (const auto& property : nifNode->props) + for (const auto& property : nifNode->mProperties) { if (!property.empty()) { // Get the lowest numbered recIndex of the NiTexturingProperty root node. // This is what is overridden when a spell effect "particle texture" is used. - if (nifNode->parents.empty() && !mFoundFirstRootTexturingProperty + if (nifNode->mParents.empty() && !mFoundFirstRootTexturingProperty && property.getPtr()->recType == Nif::RC_NiTexturingProperty) { mFirstRootTextureIndex = property.getPtr()->recIndex; @@ -482,23 +482,23 @@ namespace NifOsg return switchNode; } - static osg::ref_ptr prepareSequenceNode(const Nif::Node* nifNode) + static osg::ref_ptr prepareSequenceNode(const Nif::NiAVObject* nifNode) { const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::ref_ptr sequenceNode(new osg::Sequence); sequenceNode->setName(niFltAnimationNode->mName); - if (!niFltAnimationNode->children.empty()) + if (!niFltAnimationNode->mChildren.empty()) { if (niFltAnimationNode->swing()) sequenceNode->setDefaultTime( - niFltAnimationNode->mDuration / (niFltAnimationNode->children.size() * 2)); + niFltAnimationNode->mDuration / (niFltAnimationNode->mChildren.size() * 2)); else - sequenceNode->setDefaultTime(niFltAnimationNode->mDuration / niFltAnimationNode->children.size()); + sequenceNode->setDefaultTime(niFltAnimationNode->mDuration / niFltAnimationNode->mChildren.size()); } return sequenceNode; } - static void activateSequenceNode(osg::Group* osgNode, const Nif::Node* nifNode) + static void activateSequenceNode(osg::Group* osgNode, const Nif::NiAVObject* nifNode) { const Nif::NiFltAnimationNode* niFltAnimationNode = static_cast(nifNode); osg::Sequence* sequenceNode = static_cast(osgNode); @@ -535,7 +535,7 @@ namespace NifOsg texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); } - bool handleEffect(const Nif::Node* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) + bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) { if (nifNode->recType != Nif::RC_NiTextureEffect) { @@ -596,7 +596,7 @@ namespace NifOsg } // Get a default dataVariance for this node to be used as a hint by optimization (post)routines - osg::ref_ptr createNode(const Nif::Node* nifNode) + osg::ref_ptr createNode(const Nif::NiAVObject* nifNode) { osg::ref_ptr node; osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED; @@ -611,15 +611,15 @@ namespace NifOsg // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you // try). - if (nifNode->parents.empty() && nifNode->mController.empty() && nifNode->trafo.isIdentity()) + if (nifNode->mParents.empty() && nifNode->mController.empty() && nifNode->mTransform.isIdentity()) node = new osg::Group; - dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; + dataVariance = nifNode->mIsBone ? osg::Object::DYNAMIC : osg::Object::STATIC; break; } if (!node) - node = new NifOsg::MatrixTransform(nifNode->trafo); + node = new NifOsg::MatrixTransform(nifNode->mTransform); node->setDataVariance(dataVariance); @@ -627,7 +627,7 @@ namespace NifOsg } osg::ref_ptr handleNode( - const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, HandleNodeArgs args) + const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, HandleNodeArgs args) { if (args.mRootNode && Misc::StringUtils::ciEqual(nifNode->mName, "Bounding Box")) return nullptr; @@ -692,7 +692,7 @@ namespace NifOsg } if (nifNode->recType == Nif::RC_NiBSAnimationNode || nifNode->recType == Nif::RC_NiBSParticleNode) - args.mAnimFlags = nifNode->flags; + args.mAnimFlags = nifNode->mFlags; if (nifNode->recType == Nif::RC_NiSortAdjustNode) { @@ -829,7 +829,7 @@ namespace NifOsg const Nif::NiNode* ninode = dynamic_cast(nifNode); if (ninode) { - const Nif::NodeList& children = ninode->children; + const Nif::NiAVObjectList& children = ninode->mChildren; const Nif::Parent currentParent{ *ninode, parent }; for (const auto& child : children) if (!child.empty()) @@ -838,7 +838,7 @@ namespace NifOsg // Propagate effects to the the direct subgraph instead of the node itself // This simulates their "affected node list" which Morrowind appears to replace with the subgraph (?) // Note that the serialized affected node list is actually unused - for (const auto& effect : ninode->effects) + for (const auto& effect : ninode->mEffects) if (!effect.empty()) { osg::ref_ptr effectStateSet = new osg::StateSet; @@ -854,7 +854,7 @@ namespace NifOsg return node; } - void handleMeshControllers(const Nif::Node* nifNode, osg::Node* node, + void handleMeshControllers(const Nif::NiAVObject* nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -883,7 +883,7 @@ namespace NifOsg } } - void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) + void handleNodeControllers(const Nif::NiAVObject* nifNode, osg::Node* node, int animflags, bool& isAnimated) { for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) { @@ -1121,7 +1121,7 @@ namespace NifOsg // Load the initial state of the particle system, i.e. the initial particles and their positions, velocity and // colors. void handleParticleInitialState( - const Nif::Node* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) + const Nif::NiAVObject* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { auto particleNode = static_cast(nifNode); if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) @@ -1247,7 +1247,7 @@ namespace NifOsg mEmitterQueue.clear(); } - void handleParticleSystem(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + void handleParticleSystem(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys(new ParticleSystem); @@ -1375,7 +1375,7 @@ namespace NifOsg } } - void handleNiGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, + void handleNiGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -1474,7 +1474,7 @@ namespace NifOsg applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->mColors.empty(), animflags); } - void handleGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + void handleGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -1530,7 +1530,7 @@ namespace NifOsg return morphGeom; } - void handleSkinnedGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + void handleSkinnedGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -1548,7 +1548,7 @@ namespace NifOsg const Nif::NiSkinInstance* skin = static_cast(nifNode)->skin.getPtr(); const Nif::NiSkinData* data = skin->mData.getPtr(); - const Nif::NodeList& bones = skin->mBones; + const Nif::NiAVObjectList& bones = skin->mBones; for (std::size_t i = 0; i < bones.size(); ++i) { std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); From 4dd2f34e3088307ac45dd45f29a44f351a0e3e7d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 09:20:06 +0300 Subject: [PATCH 0108/2167] Fix Windows build --- components/nif/node.cpp | 2 +- components/nif/node.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 010a363436..d7b6c3945b 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -12,7 +12,7 @@ namespace Nif { void NiBoundingVolume::read(NIFStream* nif) { - type = nif->getUInt(); + nif->read(type); switch (type) { case BASE_BV: diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 76651b05db..51781a5290 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -15,7 +15,7 @@ namespace Nif struct NiBoundingVolume { - enum Type + enum Type : uint32_t { BASE_BV = 0xFFFFFFFF, SPHERE_BV = 0, @@ -51,7 +51,7 @@ namespace Nif osg::Vec3f origin; }; - unsigned int type{ BASE_BV }; + uint32_t type{ BASE_BV }; osg::BoundingSpheref sphere; NiBoxBV box; NiCapsuleBV capsule; From 9b801b087693f6ae20f9d737a76982c94e05b02c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Sep 2023 02:57:14 +0300 Subject: [PATCH 0109/2167] Fix NiParticlesData loading in Mistify --- components/nif/data.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 799d0cbe43..58cf2f0eff 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -150,31 +150,31 @@ namespace Nif { NiGeometryData::read(nif); - // Should always match the number of vertices + // Should always match the number of vertices in theory, but doesn't: + // see mist.nif in Mistify mod (https://www.nexusmods.com/morrowind/mods/48112). if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) nif->read(mNumParticles); - else if (nif->getVersion() != NIFFile::NIFVersion::VER_BGS || nif->getBethVersion() == 0) - mNumParticles = mNumVertices; + bool isBs202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() != 0; bool numRadii = 1; if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) - numRadii = nif->get() ? mNumParticles : 0; + numRadii = (nif->get() && !isBs202) ? mNumVertices : 0; nif->readVector(mRadii, numRadii); nif->read(mActiveCount); - if (nif->get()) - nif->readVector(mSizes, mNumParticles); + if (nif->get() && !isBs202) + nif->readVector(mSizes, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) { - if (nif->get()) - nif->readVector(mRotations, mNumParticles); + if (nif->get() && !isBs202) + nif->readVector(mRotations, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) { - if (nif->get()) - nif->readVector(mRotationAngles, mNumParticles); - if (nif->get()) - nif->readVector(mRotationAxes, mNumParticles); - if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) + if (nif->get() && !isBs202) + nif->readVector(mRotationAngles, mNumVertices); + if (nif->get() && !isBs202) + nif->readVector(mRotationAxes, mNumVertices); + if (isBs202) { nif->read(mHasTextureIndices); uint32_t numSubtextureOffsets; @@ -201,7 +201,7 @@ namespace Nif NiParticlesData::read(nif); if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->get()) - nif->readVector(mRotations, mNumParticles); + nif->readVector(mRotations, mNumVertices); } void NiPosData::read(NIFStream* nif) From fb8ccf52d8c434fbd6e244cbee4f51dd54c44deb Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 11 Sep 2023 16:21:34 +0200 Subject: [PATCH 0110/2167] Add missing argument `force` in UseItem --- apps/openmw/mwbase/luamanager.hpp | 2 +- apps/openmw/mwgui/inventorywindow.cpp | 2 +- apps/openmw/mwlua/engineevents.cpp | 2 +- apps/openmw/mwlua/engineevents.hpp | 1 + apps/openmw/mwlua/globalscripts.hpp | 5 ++++- apps/openmw/mwlua/luabindings.cpp | 8 ++++---- apps/openmw/mwlua/luamanagerimp.hpp | 4 ++-- docs/source/reference/lua-scripting/events.rst | 4 ++-- files/data/scripts/omw/usehandlers.lua | 15 ++++++++------- 9 files changed, 24 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e120b1403e..06a68efe4a 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -51,7 +51,7 @@ namespace MWBase virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0; virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; - virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index decbede856..610e34e3cd 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -601,7 +601,7 @@ namespace MWGui } } else - MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer()); + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index ac1ac687ed..c838ccfcba 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -71,7 +71,7 @@ namespace MWLua MWWorld::Ptr actor = getPtr(event.mActor); if (actor.isEmpty() || obj.isEmpty()) return; - mGlobalScripts.onUseItem(GObject(obj), GObject(actor)); + mGlobalScripts.onUseItem(GObject(obj), GObject(actor), event.mForce); } void operator()(const OnConsume& event) const diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 3cbc366623..7c706edcd0 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -40,6 +40,7 @@ namespace MWLua { ESM::RefNum mActor; ESM::RefNum mObject; + bool mForce; }; struct OnConsume { diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp index 314a4118e6..afaadb9d0a 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -40,7 +40,10 @@ namespace MWLua { callEngineHandlers(mOnActivateHandlers, obj, actor); } - void onUseItem(const GObject& obj, const GObject& actor) { callEngineHandlers(mOnUseItemHandlers, obj, actor); } + void onUseItem(const GObject& obj, const GObject& actor, bool force) + { + callEngineHandlers(mOnUseItemHandlers, obj, actor, force); + } void onNewExterior(const GCell& cell) { callEngineHandlers(mOnNewExteriorHandlers, cell); } private: diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 7f40ae0ad4..cbc07c70c0 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -324,16 +324,16 @@ namespace MWLua }, "_runStandardActivationAction"); }; - api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor) { + api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor, bool force) { context.mLuaManager->addAction( - [object, actor] { + [object, actor, force] { const MWWorld::Ptr& actorPtr = actor.ptr(); const MWWorld::Ptr& objectPtr = object.ptr(); if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - MWBase::Environment::get().getWindowManager()->useItem(objectPtr, true); + MWBase::Environment::get().getWindowManager()->useItem(objectPtr, force); else { - std::unique_ptr action = objectPtr.getClass().use(objectPtr, true); + std::unique_ptr action = objectPtr.getClass().use(objectPtr, force); action->execute(actorPtr, true); } }, diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index c14c9ace5b..3cbef6d945 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,9 +77,9 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } - void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override { - mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object) }); + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); } void exteriorCreated(MWWorld::CellStore& cell) override { diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 3e689078d0..b0e600413a 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -20,14 +20,14 @@ Examples: **UseItem** -Any script can send global event ``UseItem`` with arguments ``object`` and ``actor``. +Any script can send global event ``UseItem`` with arguments ``object``, ``actor``, and optional boolean ``force``. The actor will use (e.g. equip or consume) the object. The object should be in the actor's inventory. Example: .. code-block:: Lua - core.sendGlobalEvent('UseItem', {object = potion, actor = player}) + core.sendGlobalEvent('UseItem', {object = potion, actor = player, force = true}) UI events --------- diff --git a/files/data/scripts/omw/usehandlers.lua b/files/data/scripts/omw/usehandlers.lua index 7cc7e12f94..01203b225c 100644 --- a/files/data/scripts/omw/usehandlers.lua +++ b/files/data/scripts/omw/usehandlers.lua @@ -4,11 +4,12 @@ local world = require('openmw.world') local handlersPerObject = {} local handlersPerType = {} -local function useItem(obj, actor) +local function useItem(obj, actor, force) + local options = { force = force or false } local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do - if handlers[i](obj, actor) == false then + if handlers[i](obj, actor, options) == false then return -- skip other handlers end end @@ -16,12 +17,12 @@ local function useItem(obj, actor) handlers = handlersPerType[obj.type] if handlers then for i = #handlers, 1, -1 do - if handlers[i](obj, actor) == false then + if handlers[i](obj, actor, options) == false then return -- skip other handlers end end end - world._runStandardUseAction(obj, actor) + world._runStandardUseAction(obj, actor, force) end return { @@ -53,7 +54,7 @@ return { version = 0, --- Add new use action handler for a specific object. - -- If `handler(object, actor)` returns false, other handlers for + -- If `handler(object, actor, options)` returns false, other handlers for -- the same object (including type handlers) will be skipped. -- @function [parent=#ItemUsage] addHandlerForObject -- @param openmw.core#GameObject obj The object. @@ -68,7 +69,7 @@ return { end, --- Add new use action handler for a type of objects. - -- If `handler(object, actor)` returns false, other handlers for + -- If `handler(object, actor, options)` returns false, other handlers for -- the same object (including type handlers) will be skipped. -- @function [parent=#ItemUsage] addHandlerForType -- @param #any type A type from the `openmw.types` package. @@ -91,7 +92,7 @@ return { if not data.actor or not types.Actor.objectIsInstance(data.actor) then error('UseItem: invalid argument "actor"') end - useItem(data.object, data.actor) + useItem(data.object, data.actor, data.force) end } } From 6ee86dea827e6f72ad3cb4274c982fd4d77e932c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 6 Sep 2023 20:28:35 +0400 Subject: [PATCH 0111/2167] Implement Lua API for factions (feature 7468) --- CHANGELOG.md | 1 + CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/factionbindings.cpp | 135 +++++++++++ apps/openmw/mwlua/factionbindings.hpp | 13 ++ apps/openmw/mwlua/luabindings.cpp | 6 + apps/openmw/mwlua/types/npc.cpp | 209 +++++++++++++++++- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/npcstats.cpp | 7 +- apps/openmw/mwmechanics/npcstats.hpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 2 +- components/esm3/loadfact.hpp | 2 +- files/lua_api/openmw/core.lua | 24 +- files/lua_api/openmw/types.lua | 111 ++++++++++ 14 files changed, 509 insertions(+), 11 deletions(-) create mode 100644 apps/openmw/mwlua/factionbindings.cpp create mode 100644 apps/openmw/mwlua/factionbindings.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index fd757b631c..5937ae65ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Feature #7194: Ori to show texture paths Feature #7214: Searching in the in-game console Feature #7284: Searching in the console with regex and toggleable case-sensitivity + Feature #7468: Factions API for Lua Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas diff --git a/CMakeLists.txt b/CMakeLists.txt index adfb7ca7f5..380903f25c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 46) +set(OPENMW_LUA_API_REVISION 47) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a8c233ff56..96d54949b1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,7 +63,7 @@ add_openmw_dir (mwlua context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal - worker magicbindings + worker magicbindings factionbindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp new file mode 100644 index 0000000000..a509a45487 --- /dev/null +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -0,0 +1,135 @@ +#include "factionbindings.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "luamanagerimp.hpp" + +namespace +{ + struct FactionRank : ESM::RankData + { + std::string mRankName; + ESM::RefId mFactionId; + size_t mRankIndex; + + FactionRank(const ESM::RefId& factionId, const ESM::RankData& data, std::string_view rankName, size_t rankIndex) + : ESM::RankData(data) + , mRankName(rankName) + , mFactionId(factionId) + { + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + using FactionStore = MWWorld::Store; + + void initCoreFactionBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::usertype factionStoreT = lua.new_usertype("ESM3_FactionStore"); + factionStoreT[sol::meta_function::to_string] = [](const FactionStore& store) { + return "ESM3_FactionStore{" + std::to_string(store.getSize()) + " factions}"; + }; + factionStoreT[sol::meta_function::length] = [](const FactionStore& store) { return store.getSize(); }; + factionStoreT[sol::meta_function::index] = sol::overload( + [](const FactionStore& store, size_t index) -> const ESM::Faction* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); + }, + [](const FactionStore& store, std::string_view factionId) -> const ESM::Faction* { + return store.search(ESM::RefId::deserializeText(factionId)); + }); + factionStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + factionStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + // Faction record + auto factionT = lua.new_usertype("ESM3_Faction"); + factionT[sol::meta_function::to_string] + = [](const ESM::Faction& rec) -> std::string { return "ESM3_Faction[" + rec.mId.toDebugString() + "]"; }; + factionT["id"] = sol::readonly_property([](const ESM::Faction& rec) { return rec.mId.serializeText(); }); + factionT["name"] + = sol::readonly_property([](const ESM::Faction& rec) -> std::string_view { return rec.mName; }); + factionT["hidden"] + = sol::readonly_property([](const ESM::Faction& rec) -> bool { return rec.mData.mIsHidden; }); + factionT["ranks"] = sol::readonly_property([&lua](const ESM::Faction& rec) { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mRanks.size() && i < rec.mData.mRankData.size(); i++) + { + if (rec.mRanks[i].empty()) + break; + + res.add(FactionRank(rec.mId, rec.mData.mRankData[i], rec.mRanks[i], i)); + } + + return res; + }); + factionT["reactions"] = sol::readonly_property([&lua](const ESM::Faction& rec) { + sol::table res(lua, sol::create); + for (const auto& [factionId, reaction] : rec.mReactions) + res[factionId.serializeText()] = reaction; + return res; + }); + factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { + sol::table res(lua, sol::create); + for (auto attributeIndex : rec.mData.mAttribute) + { + ESM::RefId id = ESM::Attribute::indexToRefId(attributeIndex); + if (!id.empty()) + res.add(id.serializeText()); + } + + return res; + }); + factionT["skills"] = sol::readonly_property([&lua](const ESM::Faction& rec) { + sol::table res(lua, sol::create); + for (auto skillIndex : rec.mData.mSkills) + { + ESM::RefId id = ESM::Skill::indexToRefId(skillIndex); + if (!id.empty()) + res.add(id.serializeText()); + } + + return res; + }); + auto rankT = lua.new_usertype("ESM3_FactionRank"); + rankT[sol::meta_function::to_string] = [](const FactionRank& rec) -> std::string { + return "ESM3_FactionRank[" + rec.mFactionId.toDebugString() + ", " + std::to_string(rec.mRankIndex + 1) + + "]"; + }; + rankT["name"] = sol::readonly_property([](const FactionRank& rec) { return rec.mRankName; }); + rankT["primarySkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mPrimarySkill; }); + rankT["favouredSkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFavouredSkill; }); + rankT["factionReaction"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFactReaction; }); + rankT["attributeValues"] = sol::readonly_property([&lua](const FactionRank& rec) { + sol::table res(lua, sol::create); + res.add(rec.mAttribute1); + res.add(rec.mAttribute2); + return res; + }); + } +} diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp new file mode 100644 index 0000000000..fe37133dbe --- /dev/null +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_FACTIONBINDINGS_H +#define MWLUA_FACTIONBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + void initCoreFactionBindings(const Context& context); +} + +#endif // MWLUA_FACTIONBINDINGS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 7f40ae0ad4..ef13adb936 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include "camerabindings.hpp" #include "cellbindings.hpp" #include "debugbindings.hpp" +#include "factionbindings.hpp" #include "inputbindings.hpp" #include "magicbindings.hpp" #include "nearbybindings.hpp" @@ -153,6 +155,10 @@ namespace MWLua addTimeBindings(api, context, false); api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); + + initCoreFactionBindings(context); + api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); + api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index e612f228e0..ecb6b80dc9 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -1,6 +1,7 @@ #include "actor.hpp" #include "types.hpp" +#include #include #include @@ -11,6 +12,7 @@ #include #include +#include "../localscripts.hpp" #include "../stats.hpp" namespace sol @@ -21,6 +23,32 @@ namespace sol }; } +namespace +{ + size_t getValidRanksCount(const ESM::Faction* faction) + { + if (!faction) + return 0; + + for (size_t i = 0; i < faction->mRanks.size(); i++) + { + if (faction->mRanks[i].empty()) + return i; + } + + return faction->mRanks.size(); + } + + ESM::RefId parseFactionId(std::string_view faction) + { + ESM::RefId id = ESM::RefId::deserializeText(faction); + const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); + if (!store->get().search(id)) + throw std::runtime_error("Faction '" + std::string(faction) + "' does not exist"); + return id; + } +} + namespace MWLua { void addNpcBindings(sol::table npc, const Context& context) @@ -29,7 +57,9 @@ namespace MWLua addRecordFunctionBinding(npc, context); - sol::usertype record = context.mLua->sol().new_usertype("ESM3_NPC"); + sol::state_view& lua = context.mLua->sol(); + + sol::usertype record = lua.new_usertype("ESM3_NPC"); record[sol::meta_function::to_string] = [](const ESM::NPC& rec) { return "ESM3_NPC[" + rec.mId.toDebugString() + "]"; }; record["id"] @@ -68,5 +98,182 @@ namespace MWLua throw std::runtime_error("NPC expected"); return MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(o.ptr()); }; + + npc["getFactionRank"] = [](const Object& actor, std::string_view faction) { + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + const MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + + int factionRank = npcStats.getFactionRank(factionId); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + if (npcStats.isInFaction(factionId)) + return factionRank + 1; + else + return 0; + } + else + { + ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); + if (factionId == primaryFactionId && factionRank == -1) + return ptr.getClass().getPrimaryFactionRank(ptr); + else if (primaryFactionId == factionId) + return factionRank + 1; + else + return 0; + } + }; + + npc["setFactionRank"] = [](Object& actor, std::string_view faction, int value) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + const ESM::Faction* factionPtr + = MWBase::Environment::get().getESMStore()->get().find(factionId); + + auto ranksCount = static_cast(getValidRanksCount(factionPtr)); + + if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); + if (value <= 0 || factionId != primaryFactionId) + return; + } + + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + + if (!npcStats.isInFaction(factionId) && value > 0) + npcStats.joinFaction(factionId); + + npcStats.setFactionRank(factionId, std::min(value - 1, ranksCount - 1)); + }; + + npc["modifyFactionRank"] = [](Object& actor, std::string_view faction, int value) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + if (value == 0) + return; + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + const ESM::Faction* factionPtr + = MWBase::Environment::get().getESMStore()->get().search(factionId); + if (!factionPtr) + return; + + auto ranksCount = static_cast(getValidRanksCount(factionPtr)); + + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + int currentRank = npcStats.getFactionRank(factionId); + if (currentRank >= 0) + { + npcStats.setFactionRank(factionId, currentRank + value); + } + else if (value > 0) + { + npcStats.joinFaction(factionId); + npcStats.setFactionRank(factionId, std::min(value - 1, ranksCount - 1)); + } + + return; + } + + ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); + if (factionId != primaryFactionId) + return; + + // If we already changed rank for this NPC, modify current rank in the NPC stats. + // Otherwise take rank from base NPC record, adjust it and put it to NPC data. + int currentRank = npcStats.getFactionRank(factionId); + if (currentRank < 0) + { + int rank = ptr.getClass().getPrimaryFactionRank(ptr); + npcStats.joinFaction(factionId); + npcStats.setFactionRank(factionId, std::clamp(0, rank + value, ranksCount - 1)); + + return; + } + else + npcStats.setFactionRank(factionId, std::clamp(0, currentRank + value, ranksCount - 1)); + }; + + npc["getFactionReputation"] = [](const Object& actor, std::string_view faction) { + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + return ptr.getClass().getNpcStats(ptr).getFactionReputation(factionId); + }; + + npc["setFactionReputation"] = [](Object& actor, std::string_view faction, int value) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + ptr.getClass().getNpcStats(ptr).setFactionReputation(factionId, value); + }; + + npc["modifyFactionReputation"] = [](Object& actor, std::string_view faction, int value) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + int existingReputation = npcStats.getFactionReputation(factionId); + npcStats.setFactionReputation(factionId, existingReputation + value); + }; + + npc["expell"] = [](Object& actor, std::string_view faction) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + ptr.getClass().getNpcStats(ptr).expell(factionId, false); + }; + npc["clearExpelled"] = [](Object& actor, std::string_view faction) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + ptr.getClass().getNpcStats(ptr).clearExpelled(factionId); + }; + npc["isExpelled"] = [](const Object& actor, std::string_view faction) { + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + return ptr.getClass().getNpcStats(ptr).getExpelled(factionId); + }; + npc["getFactions"] = [&lua](const Object& actor) { + const MWWorld::Ptr ptr = actor.ptr(); + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + sol::table res(lua, sol::create); + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + for (const auto& [factionId, _] : npcStats.getFactionRanks()) + res.add(factionId.serializeText()); + return res; + } + + ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); + if (primaryFactionId.empty()) + return res; + + res.add(primaryFactionId.serializeText()); + + return res; + }; } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 36ff9db7ac..b960e0d38f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1364,7 +1364,7 @@ namespace MWMechanics const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); if (playerRanks.find(factionID) != playerRanks.end()) { - player.getClass().getNpcStats(player).expell(factionID); + player.getClass().getNpcStats(player).expell(factionID, true); } } else if (!factionId.empty()) @@ -1372,7 +1372,7 @@ namespace MWMechanics const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); if (playerRanks.find(factionId) != playerRanks.end()) { - player.getClass().getNpcStats(player).expell(factionId); + player.getClass().getNpcStats(player).expell(factionId, true); } } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 1ee463e622..b9df650fc3 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -112,14 +112,17 @@ bool MWMechanics::NpcStats::getExpelled(const ESM::RefId& factionID) const return mExpelled.find(factionID) != mExpelled.end(); } -void MWMechanics::NpcStats::expell(const ESM::RefId& factionID) +void MWMechanics::NpcStats::expell(const ESM::RefId& factionID, bool printMessage) { if (mExpelled.find(factionID) == mExpelled.end()) { + mExpelled.insert(factionID); + if (!printMessage) + return; + std::string message = "#{sExpelledMessage}"; message += MWBase::Environment::get().getESMStore()->get().find(factionID)->mName; MWBase::Environment::get().getWindowManager()->messageBox(message); - mExpelled.insert(factionID); } } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index cae94414c9..b6a655e84f 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -74,7 +74,7 @@ namespace MWMechanics const std::set& getExpelled() const { return mExpelled; } bool getExpelled(const ESM::RefId& factionID) const; - void expell(const ESM::RefId& factionID); + void expell(const ESM::RefId& factionID, bool printMessage); void clearExpelled(const ESM::RefId& factionID); bool isInFaction(const ESM::RefId& faction) const; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 0363f21fa6..745286e109 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -938,7 +938,7 @@ namespace MWScript MWWorld::Ptr player = MWMechanics::getPlayer(); if (!factionID.empty()) { - player.getClass().getNpcStats(player).expell(factionID); + player.getClass().getNpcStats(player).expell(factionID, true); } } }; diff --git a/components/esm3/loadfact.hpp b/components/esm3/loadfact.hpp index 0ba71dc06f..4cbf6c1d23 100644 --- a/components/esm3/loadfact.hpp +++ b/components/esm3/loadfact.hpp @@ -68,7 +68,7 @@ namespace ESM std::map mReactions; // Name of faction ranks (may be empty for NPC factions) - std::string mRanks[10]; + std::array mRanks; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 3bd86de5be..af09274981 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -10,6 +10,10 @@ -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. See the actual value at the top of the page. -- @field [parent=#core] #number API_REVISION +--- +-- A read-only list of all @{#FactionRecord}s in the world database. +-- @field [parent=#core] #list<#FactionRecord> factions + --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit @@ -868,7 +872,6 @@ -- print(sound.fileName) -- end - --- @{#Stats}: stats -- @field [parent=#core] #Stats stats @@ -920,4 +923,23 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound +--- +-- Faction data record +-- @type FactionRecord +-- @field #string id Faction id +-- @field #string name Faction name +-- @field #list<#FactionRank> ranks A read-only list containing data for all ranks in the faction, in order. +-- @field #map<#string, #number> reactions A read-only map containing reactions of other factions to this faction. +-- @field #list<#string> attributes A read-only list containing IDs of attributes to advance ranks in the faction. +-- @field #list<#string> skills A read-only list containing IDs of skills to advance ranks in the faction. + +--- +-- Faction rank data record +-- @type FactionRank +-- @field #string name Faction name Rank display name +-- @field #list<#number> attributeValues Attributes values required to get this rank. +-- @field #number primarySkillValue Primary skill value required to get this rank. +-- @field #number favouredSkillValue Secondary skill value required to get this rank. +-- @field #number factionReaction Reaction of faction members if player is in this faction. + return nil diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index b491203052..283be0b2fc 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -720,6 +720,117 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Get all factions in which NPC has a membership. +-- Note: this function does not take in account an expelling state. +-- @function [parent=#NPC] getFactions +-- @param openmw.core#GameObject actor NPC object +-- @return #list<#string> factionIds List of faction IDs. +-- @usage local NPC = require('openmw.types').NPC; +-- for _, factionId in pairs(types.NPC.getFactions(actor)) do +-- print(factionId); +-- end + +--- +-- Get rank of given NPC in given faction. +-- Throws an exception if there is no such faction. +-- Note: this function does not take in account an expelling state. +-- @function [parent=#NPC] getFactionRank +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @return #number rank Rank index (from 1), 0 if NPC is not in faction. +-- @usage local NPC = require('openmw.types').NPC; +-- print(NPC.getFactionRank(player, "mages guild"); + +--- +-- Set rank of given NPC in given faction. +-- Throws an exception if there is no such faction. +-- For NPCs faction should be an NPC's primary faction. +-- Notes: +-- +-- * "value" <= 0 does nothing for NPCs and make the player character to leave the faction (purge his rank and reputation in faction). +-- * "value" > 0 set rank to given value if rank is valid (name is not empty), and to the highest valid rank otherwise. +-- @function [parent=#NPC] setFactionRank +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @param #number value Rank index (from 1). +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.setFactionRank(player, "mages guild", 6); + +--- +-- Adjust rank of given NPC in given faction. +-- For NPCs faction should be an NPC's primary faction. +-- Throws an exception if there is no such faction. +-- Notes: +-- +-- * If rank should become <= 0 after modification, function does nothing for NPCs and makes the player character to leave the faction (purge his rank and reputation in faction). +-- * If rank should become > 0 after modification, function set rank to given value if rank is valid (name is not empty), and to the highest valid rank otherwise. +-- @function [parent=#NPC] modifyFactionRank +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @param #number value Rank index (from 1) modifier. If rank reaches 0 for player character, he leaves the faction. +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.modifyFactionRank(player, "mages guild", 1); + +--- +-- Get reputation of given actor in given faction. +-- Throws an exception if there is no such faction. +-- @function [parent=#NPC] getFactionReputation +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @return #number reputation Reputation level, 0 if NPC is not in faction. +-- @usage local NPC = require('openmw.types').NPC; +-- print(NPC.getFactionReputation(player, "mages guild")); + +--- +-- Set reputation of given actor in given faction. +-- Throws an exception if there is no such faction. +-- @function [parent=#NPC] setFactionReputation +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @param #number value Reputation value +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.setFactionReputation(player, "mages guild", 100); + +--- +-- Adjust reputation of given actor in given faction. +-- Throws an exception if there is no such faction. +-- @function [parent=#NPC] modifyFactionReputation +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @param #number value Reputation modifier value +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.modifyFactionReputation(player, "mages guild", 5); + +--- +-- Expell NPC from given faction. +-- Throws an exception if there is no such faction. +-- Note: expelled NPC still keeps his rank and reputation in faction, he just get an additonal flag for given faction. +-- @function [parent=#NPC] expell +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.expell(player, "mages guild"); + +--- +-- Clear expelling of NPC from given faction. +-- Throws an exception if there is no such faction. +-- @function [parent=#NPC] clearExpelled +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.clearExpell(player, "mages guild"); + +--- +-- Check if NPC is expelled from given faction. +-- Throws an exception if there is no such faction. +-- @function [parent=#NPC] isExpelled +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @return #bool isExpelled True if NPC is expelled from the faction. +-- @usage local NPC = require('openmw.types').NPC; +-- local result = NPC.isExpelled(player, "mages guild"); + --- -- Returns the current disposition of the provided NPC. This is their derived disposition, after modifiers such as personality and faction relations are taken into account. -- @function [parent=#NPC] getDisposition From 02dcf1fb31b7292d357c4a4a2bc0f5f73bb8af74 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Sep 2023 11:48:59 +0400 Subject: [PATCH 0112/2167] Split setFactionRank to separate functions --- apps/openmw/mwlua/types/npc.cpp | 68 ++++++++++++++++++++++++--------- files/lua_api/openmw/types.lua | 37 +++++++++++++----- 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index ecb6b80dc9..a3bb82098d 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -136,20 +136,23 @@ namespace MWLua = MWBase::Environment::get().getESMStore()->get().find(factionId); auto ranksCount = static_cast(getValidRanksCount(factionPtr)); + if (value <= 0 || value > ranksCount) + throw std::runtime_error("Requested rank does not exist"); + + auto targetRank = std::clamp(value, 1, ranksCount) - 1; if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) { ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); - if (value <= 0 || factionId != primaryFactionId) - return; + if (factionId != primaryFactionId) + throw std::runtime_error("Only players can modify ranks in non-primary factions"); } MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + if (!npcStats.isInFaction(factionId)) + throw std::runtime_error("Target actor is not a member of faction " + factionId.toDebugString()); - if (!npcStats.isInFaction(factionId) && value > 0) - npcStats.joinFaction(factionId); - - npcStats.setFactionRank(factionId, std::min(value - 1, ranksCount - 1)); + npcStats.setFactionRank(factionId, targetRank); }; npc["modifyFactionRank"] = [](Object& actor, std::string_view faction, int value) { @@ -175,35 +178,62 @@ namespace MWLua { int currentRank = npcStats.getFactionRank(factionId); if (currentRank >= 0) - { - npcStats.setFactionRank(factionId, currentRank + value); - } - else if (value > 0) - { - npcStats.joinFaction(factionId); - npcStats.setFactionRank(factionId, std::min(value - 1, ranksCount - 1)); - } + npcStats.setFactionRank(factionId, std::clamp(currentRank + value, 0, ranksCount - 1)); + else + throw std::runtime_error("Target actor is not a member of faction " + factionId.toDebugString()); return; } ESM::RefId primaryFactionId = ptr.getClass().getPrimaryFaction(ptr); if (factionId != primaryFactionId) - return; + throw std::runtime_error("Only players can modify ranks in non-primary factions"); // If we already changed rank for this NPC, modify current rank in the NPC stats. // Otherwise take rank from base NPC record, adjust it and put it to NPC data. int currentRank = npcStats.getFactionRank(factionId); if (currentRank < 0) { - int rank = ptr.getClass().getPrimaryFactionRank(ptr); + currentRank = ptr.getClass().getPrimaryFactionRank(ptr); npcStats.joinFaction(factionId); - npcStats.setFactionRank(factionId, std::clamp(0, rank + value, ranksCount - 1)); + } + npcStats.setFactionRank(factionId, std::clamp(currentRank + value, 0, ranksCount - 1)); + }; + + npc["joinFaction"] = [](Object& actor, std::string_view faction) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + int currentRank = npcStats.getFactionRank(factionId); + if (currentRank < 0) + npcStats.joinFaction(factionId); return; } - else - npcStats.setFactionRank(factionId, std::clamp(0, currentRank + value, ranksCount - 1)); + + throw std::runtime_error("Only player can join factions"); + }; + + npc["leaveFaction"] = [](Object& actor, std::string_view faction) { + if (dynamic_cast(&actor) && !dynamic_cast(&actor)) + throw std::runtime_error("Local scripts can modify only self"); + + const MWWorld::Ptr ptr = actor.ptr(); + ESM::RefId factionId = parseFactionId(faction); + + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + ptr.getClass().getNpcStats(ptr).setFactionRank(factionId, -1); + return; + } + + throw std::runtime_error("Only player can leave factions"); }; npc["getFactionReputation"] = [](const Object& actor, std::string_view faction) { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 283be0b2fc..03efe885b5 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -744,12 +744,8 @@ --- -- Set rank of given NPC in given faction. --- Throws an exception if there is no such faction. --- For NPCs faction should be an NPC's primary faction. --- Notes: --- --- * "value" <= 0 does nothing for NPCs and make the player character to leave the faction (purge his rank and reputation in faction). --- * "value" > 0 set rank to given value if rank is valid (name is not empty), and to the highest valid rank otherwise. +-- Throws an exception if there is no such faction, target rank does not exist or actor is not a member of given faction. +-- For NPCs faction also should be an NPC's primary faction. -- @function [parent=#NPC] setFactionRank -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID @@ -759,12 +755,12 @@ --- -- Adjust rank of given NPC in given faction. --- For NPCs faction should be an NPC's primary faction. --- Throws an exception if there is no such faction. +-- Throws an exception if there is no such faction or actor is not a member of given faction. +-- For NPCs faction also should be an NPC's primary faction. -- Notes: -- --- * If rank should become <= 0 after modification, function does nothing for NPCs and makes the player character to leave the faction (purge his rank and reputation in faction). --- * If rank should become > 0 after modification, function set rank to given value if rank is valid (name is not empty), and to the highest valid rank otherwise. +-- * If rank should become <= 0 after modification, function set rank to lowest available rank. +-- * If rank should become > 0 after modification, but target rank does not exist, function set rank to the highest valid rank. -- @function [parent=#NPC] modifyFactionRank -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID @@ -772,6 +768,27 @@ -- @usage local NPC = require('openmw.types').NPC; -- NPC.modifyFactionRank(player, "mages guild", 1); +--- +-- Add given actor to given faction. +-- Throws an exception if there is no such faction or target actor is not player. +-- Function does nothing if valid target actor is already a member of target faction. +-- @function [parent=#NPC] joinFaction +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.joinFaction(player, "mages guild"); + +--- +-- Remove given actor from given faction. +-- Function removes rank data and expelling state, but keeps a reputation in target faction. +-- Throws an exception if there is no such faction or target actor is not player. +-- Function does nothing if valid target actor is already not member of target faction. +-- @function [parent=#NPC] leaveFaction +-- @param openmw.core#GameObject actor NPC object +-- @param #string faction Faction ID +-- @usage local NPC = require('openmw.types').NPC; +-- NPC.leaveFaction(player, "mages guild"); + --- -- Get reputation of given actor in given faction. -- Throws an exception if there is no such faction. From e1cae5a029632c36b4bb63b41baa73057686671d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 18 Aug 2023 11:03:37 +0400 Subject: [PATCH 0113/2167] Rework music system --- CHANGELOG.md | 2 ++ apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/mechanicsmanager.hpp | 8 +++++ apps/openmw/mwbase/soundmanager.hpp | 16 +++++++-- apps/openmw/mwgui/levelupdialog.cpp | 3 +- apps/openmw/mwlua/luabindings.cpp | 2 ++ apps/openmw/mwlua/musicbindings.cpp | 24 +++++++++++++ apps/openmw/mwlua/musicbindings.hpp | 13 +++++++ apps/openmw/mwmechanics/actors.cpp | 21 +++-------- apps/openmw/mwmechanics/actors.hpp | 13 ++----- .../mwmechanics/mechanicsmanagerimp.cpp | 29 +++++++++++++++ .../mwmechanics/mechanicsmanagerimp.hpp | 11 ++++++ apps/openmw/mwscript/soundextensions.cpp | 5 +-- apps/openmw/mwsound/soundmanagerimp.cpp | 36 +++++++++++++------ apps/openmw/mwsound/soundmanagerimp.hpp | 9 +++-- apps/openmw/mwworld/worldimp.cpp | 3 ++ components/misc/resourcehelpers.cpp | 5 +++ components/misc/resourcehelpers.hpp | 3 ++ docs/source/reference/lua-scripting/api.rst | 1 + .../reference/lua-scripting/openmw_music.rst | 7 ++++ .../lua-scripting/tables/packages.rst | 2 ++ files/data/scripts/omw/console/player.lua | 1 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/music.lua | 19 ++++++++++ 24 files changed, 189 insertions(+), 47 deletions(-) create mode 100644 apps/openmw/mwlua/musicbindings.cpp create mode 100644 apps/openmw/mwlua/musicbindings.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_music.rst create mode 100644 files/lua_api/openmw/music.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0cb5d54e..83892500dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION + Feature #6152: Playing music via lua scripts Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds @@ -99,6 +100,7 @@ Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas + Feature #7568: Uninterruptable scripted music Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 96d54949b1..1fe34b9fd1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings + camerabindings vfsbindings musicbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal worker magicbindings factionbindings ) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 5319ab6dde..b8e0fd1bde 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -26,6 +26,11 @@ namespace ESM class ESMWriter; } +namespace MWSound +{ + enum class MusicType; +} + namespace MWWorld { class Ptr; @@ -282,6 +287,9 @@ namespace MWBase virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; + + virtual MWSound::MusicType getMusicType() const = 0; + virtual void setMusicType(MWSound::MusicType type) = 0; }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 3dd9cd3b33..bf19255e6f 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -29,6 +29,14 @@ namespace MWSound MaxCount }; + enum class MusicType + { + Special, + Explore, + Battle, + Scripted + }; + class Sound; class Stream; struct Sound_Decoder; @@ -104,9 +112,13 @@ namespace MWBase virtual void stopMusic() = 0; ///< Stops music if it's playing - virtual void streamMusic(const std::string& filename) = 0; + virtual void streamMusic( + const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, float fade = 1.f) + = 0; ///< Play a soundifle - /// \param filename name of a sound file in "Music/" in the data directory. + /// \param filename name of a sound file in the data directory. + /// \param type music type. + /// \param fade time in seconds to fade out current track before start this one. virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index b13fdbeeb9..41b2dadeb9 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -214,7 +214,8 @@ namespace MWGui center(); // Play LevelUp Music - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Triumph.mp3"); + MWBase::Environment::get().getSoundManager()->streamMusic( + "Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index ef13adb936..25385b7404 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -40,6 +40,7 @@ #include "factionbindings.hpp" #include "inputbindings.hpp" #include "magicbindings.hpp" +#include "musicbindings.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" #include "postprocessingbindings.hpp" @@ -392,6 +393,7 @@ namespace MWLua { "openmw.input", initInputPackage(context) }, { "openmw.postprocessing", initPostprocessingPackage(context) }, { "openmw.ui", initUserInterfacePackage(context) }, + { "openmw.music", initMusicPackage(context) }, }; } diff --git a/apps/openmw/mwlua/musicbindings.cpp b/apps/openmw/mwlua/musicbindings.cpp new file mode 100644 index 0000000000..f319070c06 --- /dev/null +++ b/apps/openmw/mwlua/musicbindings.cpp @@ -0,0 +1,24 @@ +#include "musicbindings.hpp" +#include "luabindings.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "context.hpp" +#include "luamanagerimp.hpp" + +namespace MWLua +{ + sol::table initMusicPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + api["streamMusic"] = [](std::string_view fileName) { + MWBase::Environment::get().getSoundManager()->streamMusic(std::string(fileName)); + }; + + api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/musicbindings.hpp b/apps/openmw/mwlua/musicbindings.hpp new file mode 100644 index 0000000000..0d4f88bd4c --- /dev/null +++ b/apps/openmw/mwlua/musicbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_MUSICBINDINGS_H +#define MWLUA_MUSICBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMusicPackage(const Context&); +} + +#endif // MWLUA_MUSICBINDINGS_H diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ec53bdec71..743c5d5ab5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1285,7 +1285,7 @@ namespace MWMechanics } } - void Actors::updateCombatMusic() + bool Actors::playerHasHostiles() const { const MWWorld::Ptr player = getPlayer(); const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); @@ -1315,19 +1315,7 @@ namespace MWMechanics } } - // check if we still have any player enemies to switch music - if (mCurrentMusic != MusicType::Explore && !hasHostiles - && !(player.getClass().getCreatureStats(player).isDead() - && MWBase::Environment::get().getSoundManager()->isMusicPlaying())) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - mCurrentMusic = MusicType::Explore; - } - else if (mCurrentMusic != MusicType::Battle && hasHostiles) - { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); - mCurrentMusic = MusicType::Battle; - } + return hasHostiles; } void Actors::predictAndAvoidCollisions(float duration) const @@ -1735,8 +1723,6 @@ namespace MWMechanics killDeadActors(); updateSneaking(playerCharacter, duration); } - - updateCombatMusic(); } void Actors::notifyDied(const MWWorld::Ptr& actor) @@ -1806,7 +1792,8 @@ namespace MWMechanics // player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); // Play Death Music if it was the player dying - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); + MWBase::Environment::get().getSoundManager()->streamMusic( + "Music/Special/MW_Death.mp3", MWSound::MusicType::Special); } else { diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 1c5799159e..98c64397ab 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -74,9 +74,6 @@ namespace MWMechanics void dropActors(const MWWorld::CellStore* cellStore, const MWWorld::Ptr& ignore); ///< Deregister all actors (except for \a ignore) in the given cell. - void updateCombatMusic(); - ///< Update combat music state - void update(float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement @@ -159,19 +156,14 @@ namespace MWMechanics bool isReadyToBlock(const MWWorld::Ptr& ptr) const; bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; + bool playerHasHostiles() const; + int getGreetingTimer(const MWWorld::Ptr& ptr) const; float getAngleToPlayer(const MWWorld::Ptr& ptr) const; GreetingState getGreetingState(const MWWorld::Ptr& ptr) const; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const; private: - enum class MusicType - { - Title, - Explore, - Battle - }; - std::map mDeathCount; std::list mActors; std::map::iterator> mIndex; @@ -182,7 +174,6 @@ namespace MWMechanics float mTimerUpdateHello = 0; float mSneakTimer = 0; // Times update of sneak icon float mSneakSkillTimer = 0; // Times sneak skill progress from "avoid notice" - MusicType mCurrentMusic = MusicType::Title; void updateVisibility(const MWWorld::Ptr& ptr, CharacterController& ctrl) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b960e0d38f..071ac164f3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -24,6 +24,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -257,6 +258,7 @@ namespace MWMechanics , mClassSelected(false) , mRaceSelected(false) , mAI(true) + , mMusicType(MWSound::MusicType::Special) { // buildPlayer no longer here, needs to be done explicitly after all subsystems are up and running } @@ -340,6 +342,8 @@ namespace MWMechanics mActors.update(duration, paused); mObjects.update(duration, paused); + + updateMusicState(); } void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector& changed) @@ -1572,6 +1576,31 @@ namespace MWMechanics return (Misc::Rng::roll0to99(prng) >= target); } + void MechanicsManager::updateMusicState() + { + bool musicPlaying = MWBase::Environment::get().getSoundManager()->isMusicPlaying(); + + // Can not interrupt scripted music by built-in playlists + if (mMusicType == MWSound::MusicType::Scripted && musicPlaying) + return; + + const MWWorld::Ptr& player = MWMechanics::getPlayer(); + bool hasHostiles = mActors.playerHasHostiles(); + + // check if we still have any player enemies to switch music + if (mMusicType != MWSound::MusicType::Explore && !hasHostiles + && !(player.getClass().getCreatureStats(player).isDead() && musicPlaying)) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + mMusicType = MWSound::MusicType::Explore; + } + else if (mMusicType != MWSound::MusicType::Battle && hasHostiles) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + mMusicType = MWSound::MusicType::Battle; + } + } + void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 748965a682..36bb18e022 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -11,6 +11,11 @@ #include "npcstats.hpp" #include "objects.hpp" +namespace MWSound +{ + enum class MusicType; +} + namespace MWWorld { class CellStore; @@ -33,6 +38,8 @@ namespace MWMechanics typedef std::map StolenItemsMap; StolenItemsMap mStolenItems; + MWSound::MusicType mMusicType; + public: void buildPlayer(); ///< build player according to stored class/race/birthsign information. Will @@ -232,7 +239,11 @@ namespace MWMechanics GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; + MWSound::MusicType getMusicType() const override { return mMusicType; } + void setMusicType(MWSound::MusicType type) override { mMusicType = type; } + private: + void updateMusicState(); bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime( const MWWorld::Ptr& actor, const MWWorld::Ptr& victim, std::set& playerFollowers); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index f1ac2a7a08..3d982c8eab 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -63,10 +63,11 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - std::string sound{ runtime.getStringLiteral(runtime[0].mInteger) }; + std::string music{ runtime.getStringLiteral(runtime[0].mInteger) }; runtime.pop(); - MWBase::Environment::get().getSoundManager()->streamMusic(sound); + MWBase::Environment::get().getSoundManager()->streamMusic( + Misc::ResourceHelpers::correctMusicPath(music)); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index d5f1758f03..9cc67f5af4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -14,6 +14,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" @@ -250,7 +251,7 @@ namespace MWSound if (filename.empty()) return; - Log(Debug::Info) << "Playing " << filename; + Log(Debug::Info) << "Playing \"" << filename << "\""; mLastPlayedMusic = filename; DecoderPtr decoder = getDecoder(); @@ -260,7 +261,7 @@ namespace MWSound } catch (std::exception& e) { - Log(Debug::Error) << "Failed to load audio from " << filename << ": " << e.what(); + Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what(); return; } @@ -274,7 +275,7 @@ namespace MWSound mOutput->streamSound(decoder, mMusic.get()); } - void SoundManager::advanceMusic(const std::string& filename) + void SoundManager::advanceMusic(const std::string& filename, float fadeOut) { if (!isMusicPlaying()) { @@ -284,7 +285,7 @@ namespace MWSound mNextMusic = filename; - mMusic->setFadeout(1.f); + mMusic->setFadeout(fadeOut); } void SoundManager::startRandomTitle() @@ -319,16 +320,30 @@ namespace MWSound tracklist.pop_back(); } - void SoundManager::streamMusic(const std::string& filename) - { - advanceMusic("Music/" + filename); - } - bool SoundManager::isMusicPlaying() { return mMusic && mOutput->isStreamPlaying(mMusic.get()); } + void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade) + { + const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + + // Can not interrupt scripted music by built-in playlists + if (mechanicsManager->getMusicType() == MusicType::Scripted && type != MusicType::Scripted + && type != MusicType::Special) + return; + + std::string normalizedName = VFS::Path::normalizeFilename(filename); + + mechanicsManager->setMusicType(type); + advanceMusic(normalizedName, fade); + if (type == MWSound::MusicType::Battle) + mCurrentPlaylist = "Battle"; + else if (type == MWSound::MusicType::Explore) + mCurrentPlaylist = "Explore"; + } + void SoundManager::playPlaylist(const std::string& playlist) { if (mCurrentPlaylist == playlist) @@ -337,7 +352,8 @@ namespace MWSound if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; - for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist + '/')) + auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; + for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) filelist.push_back(name); mMusicFiles[playlist] = filelist; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 7453ce86f4..a8eb37f55b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -127,7 +127,7 @@ namespace MWSound StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); void streamMusicFull(const std::string& filename); - void advanceMusic(const std::string& filename); + void advanceMusic(const std::string& filename, float fadeOut = 1.f); void startRandomTitle(); void cull3DSound(SoundBase* sound); @@ -176,9 +176,12 @@ namespace MWSound void stopMusic() override; ///< Stops music if it's playing - void streamMusic(const std::string& filename) override; + void streamMusic(const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, + float fade = 1.f) override; ///< Play a soundifle - /// \param filename name of a sound file in "Music/" in the data directory. + /// \param filename name of a sound file in the data directory. + /// \param type music type. + /// \param fade time in seconds to fade out current track before start this one. bool isMusicPlaying() override; ///< Returns true if music is playing diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 241d252615..28407b29b3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -391,7 +391,10 @@ namespace MWWorld { std::string_view video = Fallback::Map::getString("Movies_New_Game"); if (!video.empty()) + { + MWBase::Environment::get().getSoundManager()->stopMusic(); MWBase::Environment::get().getWindowManager()->playVideo(video, true); + } } // enable collision diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 55fef5cdbd..0ae45c6c26 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -156,6 +156,11 @@ std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath) return "sound\\" + resPath; } +std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) +{ + return "music\\" + resPath; +} + std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) { constexpr std::string_view prefix = "meshes"; diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 0597c7bc16..37932ea155 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -38,6 +38,9 @@ namespace Misc // Adds "sound\\". std::string correctSoundPath(const std::string& resPath); + // Adds "music\\". + std::string correctMusicPath(const std::string& resPath); + // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 6d27db0515..acb5963992 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -23,6 +23,7 @@ Lua API reference openmw_nearby openmw_input openmw_ambient + openmw_music openmw_ui openmw_camera openmw_postprocessing diff --git a/docs/source/reference/lua-scripting/openmw_music.rst b/docs/source/reference/lua-scripting/openmw_music.rst new file mode 100644 index 0000000000..87fbb71899 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_music.rst @@ -0,0 +1,7 @@ +Package openmw.music +==================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_music.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 67709bbf7b..dbe05b0adc 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -27,6 +27,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.input ` | by player scripts | | User input. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.music ` | by player scripts | | Music system. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface `. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.camera ` | by player scripts | | Controls camera. | diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index c614d2d962..be818f6145 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -77,6 +77,7 @@ local env = { nearby = require('openmw.nearby'), self = require('openmw.self'), input = require('openmw.input'), + music = require('openmw.music'), ui = require('openmw.ui'), camera = require('openmw.camera'), aux_util = require('openmw_aux.util'), diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 96409e803e..3d25006735 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -13,6 +13,7 @@ set(LUA_API_FILES openmw/async.lua openmw/core.lua openmw/debug.lua + openmw/music.lua openmw/nearby.lua openmw/postprocessing.lua openmw/self.lua diff --git a/files/lua_api/openmw/music.lua b/files/lua_api/openmw/music.lua new file mode 100644 index 0000000000..fe7c30ffa8 --- /dev/null +++ b/files/lua_api/openmw/music.lua @@ -0,0 +1,19 @@ +--- +-- `openmw.music` provides access to music system. +-- @module music +-- @usage local music = require('openmw.music') + + + +--- +-- Play a sound file as a music track +-- @function [parent=#music] streamMusic +-- @param #string fileName Path to file in VFS +-- @usage music.streamMusic("Music\\Test\\Test.mp3"); + +--- +-- Stop to play current music +-- @function [parent=#music] stopMusic +-- @usage music.stopMusic(); + +return nil From 18fe6a8ae70fbcb0ffe71b899966789f220c2b98 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Sep 2023 14:04:30 +0400 Subject: [PATCH 0114/2167] Remove default argument --- apps/openmw/mwbase/soundmanager.hpp | 4 +--- apps/openmw/mwlua/musicbindings.cpp | 3 ++- apps/openmw/mwscript/soundextensions.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.hpp | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index bf19255e6f..85062ace8d 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -112,9 +112,7 @@ namespace MWBase virtual void stopMusic() = 0; ///< Stops music if it's playing - virtual void streamMusic( - const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, float fade = 1.f) - = 0; + virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. diff --git a/apps/openmw/mwlua/musicbindings.cpp b/apps/openmw/mwlua/musicbindings.cpp index f319070c06..919e8b0590 100644 --- a/apps/openmw/mwlua/musicbindings.cpp +++ b/apps/openmw/mwlua/musicbindings.cpp @@ -14,7 +14,8 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); api["streamMusic"] = [](std::string_view fileName) { - MWBase::Environment::get().getSoundManager()->streamMusic(std::string(fileName)); + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 3d982c8eab..44cdc25064 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -67,7 +67,7 @@ namespace MWScript runtime.pop(); MWBase::Environment::get().getSoundManager()->streamMusic( - Misc::ResourceHelpers::correctMusicPath(music)); + Misc::ResourceHelpers::correctMusicPath(music), MWSound::MusicType::Scripted); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index a8eb37f55b..87fb109791 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -176,8 +176,7 @@ namespace MWSound void stopMusic() override; ///< Stops music if it's playing - void streamMusic(const std::string& filename, MWSound::MusicType type = MWSound::MusicType::Scripted, - float fade = 1.f) override; + void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. From e25e867d775ebeb296caeda7e7546e894eb9f71d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Sep 2023 14:30:24 +0400 Subject: [PATCH 0115/2167] Remove 'music' package --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 2 -- apps/openmw/mwlua/musicbindings.cpp | 25 ------------------- apps/openmw/mwlua/musicbindings.hpp | 13 ---------- apps/openmw/mwlua/soundbindings.cpp | 7 ++++++ docs/source/reference/lua-scripting/api.rst | 1 - .../reference/lua-scripting/openmw_music.rst | 7 ------ .../lua-scripting/tables/packages.rst | 2 -- files/data/scripts/omw/console/player.lua | 1 - files/lua_api/CMakeLists.txt | 1 - files/lua_api/openmw/ambient.lua | 11 ++++++++ files/lua_api/openmw/music.lua | 19 -------------- 12 files changed, 19 insertions(+), 72 deletions(-) delete mode 100644 apps/openmw/mwlua/musicbindings.cpp delete mode 100644 apps/openmw/mwlua/musicbindings.hpp delete mode 100644 docs/source/reference/lua-scripting/openmw_music.rst delete mode 100644 files/lua_api/openmw/music.lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 1fe34b9fd1..96d54949b1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings vfsbindings musicbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings + camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal worker magicbindings factionbindings ) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 25385b7404..ef13adb936 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -40,7 +40,6 @@ #include "factionbindings.hpp" #include "inputbindings.hpp" #include "magicbindings.hpp" -#include "musicbindings.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" #include "postprocessingbindings.hpp" @@ -393,7 +392,6 @@ namespace MWLua { "openmw.input", initInputPackage(context) }, { "openmw.postprocessing", initPostprocessingPackage(context) }, { "openmw.ui", initUserInterfacePackage(context) }, - { "openmw.music", initMusicPackage(context) }, }; } diff --git a/apps/openmw/mwlua/musicbindings.cpp b/apps/openmw/mwlua/musicbindings.cpp deleted file mode 100644 index 919e8b0590..0000000000 --- a/apps/openmw/mwlua/musicbindings.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "musicbindings.hpp" -#include "luabindings.hpp" - -#include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "context.hpp" -#include "luamanagerimp.hpp" - -namespace MWLua -{ - sol::table initMusicPackage(const Context& context) - { - sol::table api(context.mLua->sol(), sol::create); - api["streamMusic"] = [](std::string_view fileName) { - MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); - }; - - api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; - - return LuaUtil::makeReadOnly(api); - } -} diff --git a/apps/openmw/mwlua/musicbindings.hpp b/apps/openmw/mwlua/musicbindings.hpp deleted file mode 100644 index 0d4f88bd4c..0000000000 --- a/apps/openmw/mwlua/musicbindings.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef MWLUA_MUSICBINDINGS_H -#define MWLUA_MUSICBINDINGS_H - -#include - -#include "context.hpp" - -namespace MWLua -{ - sol::table initMusicPackage(const Context&); -} - -#endif // MWLUA_MUSICBINDINGS_H diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index b5dae8a7a8..318f120365 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -95,6 +95,13 @@ namespace MWLua return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName); }; + api["streamMusic"] = [](std::string_view fileName) { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); + }; + + api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; + return LuaUtil::makeReadOnly(api); } diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index acb5963992..6d27db0515 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -23,7 +23,6 @@ Lua API reference openmw_nearby openmw_input openmw_ambient - openmw_music openmw_ui openmw_camera openmw_postprocessing diff --git a/docs/source/reference/lua-scripting/openmw_music.rst b/docs/source/reference/lua-scripting/openmw_music.rst deleted file mode 100644 index 87fbb71899..0000000000 --- a/docs/source/reference/lua-scripting/openmw_music.rst +++ /dev/null @@ -1,7 +0,0 @@ -Package openmw.music -==================== - -.. include:: version.rst - -.. raw:: html - :file: generated_html/openmw_music.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index dbe05b0adc..67709bbf7b 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -27,8 +27,6 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.input ` | by player scripts | | User input. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.music ` | by player scripts | | Music system. | -+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface `. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.camera ` | by player scripts | | Controls camera. | diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index be818f6145..c614d2d962 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -77,7 +77,6 @@ local env = { nearby = require('openmw.nearby'), self = require('openmw.self'), input = require('openmw.input'), - music = require('openmw.music'), ui = require('openmw.ui'), camera = require('openmw.camera'), aux_util = require('openmw_aux.util'), diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 3d25006735..96409e803e 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -13,7 +13,6 @@ set(LUA_API_FILES openmw/async.lua openmw/core.lua openmw/debug.lua - openmw/music.lua openmw/nearby.lua openmw/postprocessing.lua openmw/self.lua diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 3722cb8c0f..671e0b0ee6 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -72,4 +72,15 @@ -- @return #boolean -- @usage local isPlaying = ambient.isSoundFilePlaying("Sound\\test.mp3"); +--- +-- Play a sound file as a music track +-- @function [parent=#ambient] streamMusic +-- @param #string fileName Path to file in VFS +-- @usage ambient.streamMusic("Music\\Test\\Test.mp3"); + +--- +-- Stop to play current music +-- @function [parent=#ambient] stopMusic +-- @usage ambient.stopMusic(); + return nil diff --git a/files/lua_api/openmw/music.lua b/files/lua_api/openmw/music.lua deleted file mode 100644 index fe7c30ffa8..0000000000 --- a/files/lua_api/openmw/music.lua +++ /dev/null @@ -1,19 +0,0 @@ ---- --- `openmw.music` provides access to music system. --- @module music --- @usage local music = require('openmw.music') - - - ---- --- Play a sound file as a music track --- @function [parent=#music] streamMusic --- @param #string fileName Path to file in VFS --- @usage music.streamMusic("Music\\Test\\Test.mp3"); - ---- --- Stop to play current music --- @function [parent=#music] stopMusic --- @usage music.stopMusic(); - -return nil From cbb4c1bb9a6faad6b3b55689ba581385768d2d02 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Sep 2023 14:50:19 +0400 Subject: [PATCH 0116/2167] Fix music during a new game start --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 28407b29b3..748187d868 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -392,7 +392,9 @@ namespace MWWorld std::string_view video = Fallback::Map::getString("Movies_New_Game"); if (!video.empty()) { + // Make sure that we do not continue to play a Title music after a new game video. MWBase::Environment::get().getSoundManager()->stopMusic(); + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); MWBase::Environment::get().getWindowManager()->playVideo(video, true); } } From 64db68e0aa46fa7c9e3dc57531be92edd6d1b304 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Sep 2023 09:12:42 +0400 Subject: [PATCH 0117/2167] Increase Lua API version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 380903f25c..d3a16c285f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 47) +set(OPENMW_LUA_API_REVISION 48) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") From ff16ee2d64be745378acae3ae728f821e195fcda Mon Sep 17 00:00:00 2001 From: Kindi Date: Tue, 12 Sep 2023 16:29:00 +0800 Subject: [PATCH 0118/2167] implement lua api for get/set item condition --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/light.cpp | 5 ++++ apps/openmw/mwclass/light.hpp | 2 ++ apps/openmw/mwlua/types/item.cpp | 23 +++++++++++++++---- apps/openmw/mwlua/types/itemstats.cpp | 33 +++++++++++++++++++++++++++ apps/openmw/mwlua/types/itemstats.hpp | 29 +++++++++++++++++++++++ apps/openmw/mwlua/types/types.cpp | 8 ++++--- apps/openmw/mwlua/types/types.hpp | 2 +- apps/openmw/mwworld/class.hpp | 2 ++ 9 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 apps/openmw/mwlua/types/itemstats.cpp create mode 100644 apps/openmw/mwlua/types/itemstats.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 96d54949b1..0b8ed449f9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -62,7 +62,7 @@ add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings - types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal + types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal types/itemstats worker magicbindings factionbindings ) diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 931ed73dfe..8933491e68 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -91,6 +91,11 @@ namespace MWClass return ptr.get()->mBase->mData.mFlags & ESM::Light::Carry; } + bool Light::isLight(const MWWorld::ConstPtr& ptr) const + { + return true; + } + std::unique_ptr Light::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 70d6852ff8..d5119aa0b5 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -38,6 +38,8 @@ namespace MWClass bool isItem(const MWWorld::ConstPtr&) const override; + bool isLight(const MWWorld::ConstPtr&) const override; + std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index e15be1a2e4..789891e8a9 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,18 +1,31 @@ -#include +#include "itemstats.hpp" -#include "../../mwworld/class.hpp" - -#include "types.hpp" +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} namespace MWLua { - void addItemBindings(sol::table item) + + void addItemBindings(sol::table item, const Context& context) { + sol::usertype ItemStats = context.mLua->sol().new_usertype("ItemStat"); + ItemStats[sol::meta_function::new_index] = [](const ItemStat& i, const sol::variadic_args args) { + throw std::runtime_error("Unknown itemStat property '" + args.get() + "'"); + }; + ItemStats["condition"] = sol::property([](const ItemStat& i) { return i.getCondition(); }, + [](const ItemStat& i, float cond) { i.setCondition(cond); }); + item["getEnchantmentCharge"] = [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); }; item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; + item["itemStats"] = [](const sol::object& object) { return ItemStat(object); }; } } diff --git a/apps/openmw/mwlua/types/itemstats.cpp b/apps/openmw/mwlua/types/itemstats.cpp new file mode 100644 index 0000000000..4739475f6d --- /dev/null +++ b/apps/openmw/mwlua/types/itemstats.cpp @@ -0,0 +1,33 @@ +#include "itemstats.hpp" + +namespace MWLua +{ + ItemStat::ItemStat(const sol::object& object) + : mObject(ObjectVariant(object)) + { + } + sol::optional ItemStat::getCondition() const + { + MWWorld::Ptr o = mObject.ptr(); + if (o.getClass().isLight(o)) + return o.getClass().getRemainingUsageTime(o); + else if (o.getClass().hasItemHealth(o)) + return o.getClass().getItemHealth(o); + else + return sol::nullopt; + } + void ItemStat::setCondition(float cond) const + { + if (!mObject.isGObject()) + throw std::runtime_error("This property can only be set in global scripts"); + + MWWorld::Ptr o = mObject.ptr(); + if (o.getClass().isLight(o)) + return o.getClass().setRemainingUsageTime(o, cond); + else if (o.getClass().hasItemHealth(o)) + o.getCellRef().setCharge(std::max(0, static_cast(cond))); + else + throw std::runtime_error("'condition' property does not exist for " + std::string(o.getClass().getName(o)) + + "(" + std::string(o.getTypeDescription()) + ")"); + }; +} diff --git a/apps/openmw/mwlua/types/itemstats.hpp b/apps/openmw/mwlua/types/itemstats.hpp new file mode 100644 index 0000000000..55d55ed234 --- /dev/null +++ b/apps/openmw/mwlua/types/itemstats.hpp @@ -0,0 +1,29 @@ +#ifndef MWLUA_ITEMSTATS_H +#define MWLUA_ITEMSTATS_H + +#include + +#include "../../mwworld/class.hpp" + +#include "../objectvariant.hpp" +#include "types.hpp" + +namespace MWLua +{ + class ItemStat + { + public: + ItemStat(const sol::object& object); + + sol::optional getCondition() const; + + void setCondition(float cond) const; + + /* + * set,get, enchantmentCharge, soul? etc.. + */ + + ObjectVariant mObject; + }; +} +#endif // MWLUA_ITEMSTATS_H diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 6095053eee..b3e39b0264 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -185,9 +185,11 @@ namespace MWLua addActorBindings( addType(ObjectTypeName::Actor, { ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_ }), context); - addItemBindings(addType(ObjectTypeName::Item, - { ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH, - ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA })); + addItemBindings( + addType(ObjectTypeName::Item, + { ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, ESM::REC_LIGH, ESM::REC_MISC, + ESM::REC_ALCH, ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA }), + context); addLockableBindings( addType(ObjectTypeName::Lockable, { ESM::REC_CONT, ESM::REC_DOOR, ESM::REC_CONT4, ESM::REC_DOOR4 })); diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index 8fc0932dfe..adac372277 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -47,7 +47,7 @@ namespace MWLua void addBookBindings(sol::table book, const Context& context); void addContainerBindings(sol::table container, const Context& context); void addDoorBindings(sol::table door, const Context& context); - void addItemBindings(sol::table item); + void addItemBindings(sol::table item, const Context& context); void addActorBindings(sol::table actor, const Context& context); void addWeaponBindings(sol::table weapon, const Context& context); void addNpcBindings(sol::table npc, const Context& context); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 7b7e9135ba..162fa88a34 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -332,6 +332,8 @@ namespace MWWorld // True if it is an item that can be picked up. virtual bool isItem(const MWWorld::ConstPtr& ptr) const { return false; } + virtual bool isLight(const MWWorld::ConstPtr& ptr) const { return false; } + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; From a2854082126bcafd06e95b73bebcfc98f28b23a8 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Sep 2023 12:28:38 +0200 Subject: [PATCH 0119/2167] small coverity fix --- apps/openmw/mwlua/factionbindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index a509a45487..a43cec874f 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -23,6 +23,7 @@ namespace : ESM::RankData(data) , mRankName(rankName) , mFactionId(factionId) + , mRankIndex(rankIndex) { } }; From 655c4442bc3159a740b0bf26b7cc83f85524c099 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Sep 2023 17:48:49 +0400 Subject: [PATCH 0120/2167] Implement isMusicPlaying --- apps/openmw/mwlua/soundbindings.cpp | 2 ++ files/lua_api/openmw/ambient.lua | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index 318f120365..102f88ce61 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -100,6 +100,8 @@ namespace MWLua sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); }; + api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; + api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; return LuaUtil::makeReadOnly(api); diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 671e0b0ee6..917ec86c85 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -83,4 +83,10 @@ -- @function [parent=#ambient] stopMusic -- @usage ambient.stopMusic(); +--- +-- Check if music is playing +-- @function [parent=#ambient] isMusicPlaying +-- @return #boolean +-- @usage local isPlaying = ambient.isMusicPlaying(); + return nil From 956ede52fbd78922f7f903cdf6fc2cbbb3610249 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Sep 2023 21:56:02 +0300 Subject: [PATCH 0121/2167] NIFStream: remove getShort, getMatrix3, getQuaternion --- components/nif/controller.cpp | 6 +++--- components/nif/controller.hpp | 2 +- components/nif/nifstream.hpp | 3 --- components/nif/node.cpp | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index a3d7f33e45..80cc74ce23 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -264,7 +264,7 @@ namespace Nif bankDir = nif->getInt(); maxBankAngle = nif->getFloat(); smoothing = nif->getFloat(); - followAxis = nif->getShort(); + nif->read(followAxis); posData.read(nif); floatData.read(nif); } @@ -507,7 +507,7 @@ namespace Nif void NiTransformInterpolator::read(NIFStream* nif) { defaultPos = nif->getVector3(); - defaultRot = nif->getQuaternion(); + nif->read(defaultRot); defaultScale = nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) { @@ -655,7 +655,7 @@ namespace Nif if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) { mPosValue = nif->getVector3(); - mRotValue = nif->getQuaternion(); + nif->read(mRotValue); mScaleValue = nif->getFloat(); if (!nif->getBoolean()) mPosValue = osg::Vec3f(); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 68e795e7fc..07c042e047 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -203,7 +203,7 @@ namespace Nif int bankDir; float maxBankAngle, smoothing; - short followAxis; + uint16_t followAxis; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index f4c35e6625..9c9a097662 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -149,7 +149,6 @@ namespace Nif /// DEPRECATED: Use read() or get() char getChar() { return get(); } - short getShort() { return get(); } unsigned short getUShort() { return get(); } int getInt() { return get(); } unsigned int getUInt() { return get(); } @@ -157,8 +156,6 @@ namespace Nif osg::Vec2f getVector2() { return get(); } osg::Vec3f getVector3() { return get(); } osg::Vec4f getVector4() { return get(); } - Matrix3 getMatrix3() { return get(); } - osg::Quat getQuaternion() { return get(); } bool getBoolean() { return get(); } std::string getString() { return get(); } }; diff --git a/components/nif/node.cpp b/components/nif/node.cpp index d7b6c3945b..ad966097b0 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -25,7 +25,7 @@ namespace Nif case BOX_BV: { box.center = nif->getVector3(); - box.axes = nif->getMatrix3(); + nif->read(box.axes); box.extents = nif->getVector3(); break; } From b59739a529117a643d13da06dbbcbe515033c903 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 13 Sep 2023 21:51:42 +0200 Subject: [PATCH 0122/2167] Replace more sized reads --- apps/esmtool/record.cpp | 10 +-- .../opencs/model/tools/referenceablecheck.cpp | 22 ++---- apps/opencs/model/world/refidadapterimp.cpp | 69 +++---------------- apps/openmw/mwclass/creature.cpp | 10 +-- components/esm3/loadcell.cpp | 4 +- components/esm3/loadcell.hpp | 8 +-- components/esm3/loadcrea.cpp | 6 +- components/esm3/loadcrea.hpp | 22 +++--- components/esm3/loadfact.cpp | 20 +++--- components/esm3/loadfact.hpp | 22 +++--- components/esm3/loadinfo.cpp | 3 +- components/esm3/loadinfo.hpp | 6 +- components/esm3/loadingr.cpp | 2 +- components/esm3/loadingr.hpp | 10 +-- components/esm3/loadligh.cpp | 2 +- components/esm3/loadligh.hpp | 12 ++-- components/esm3/loadlock.cpp | 2 +- components/esm3/loadlock.hpp | 6 +- 18 files changed, 84 insertions(+), 152 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 0c22114749..55a850cf7e 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -690,14 +690,8 @@ namespace EsmTool std::cout << " Level: " << mData.mData.mLevel << std::endl; std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << mData.mData.mStrength << std::endl; - std::cout << " Intelligence: " << mData.mData.mIntelligence << std::endl; - std::cout << " Willpower: " << mData.mData.mWillpower << std::endl; - std::cout << " Agility: " << mData.mData.mAgility << std::endl; - std::cout << " Speed: " << mData.mData.mSpeed << std::endl; - std::cout << " Endurance: " << mData.mData.mEndurance << std::endl; - std::cout << " Personality: " << mData.mData.mPersonality << std::endl; - std::cout << " Luck: " << mData.mData.mLuck << std::endl; + for (size_t i = 0; i < mData.mData.mAttributes.size(); ++i) + std::cout << " " << ESM::Attribute::indexToRefId(i) << ": " << mData.mData.mAttributes[i] << std::endl; std::cout << " Health: " << mData.mData.mHealth << std::endl; std::cout << " Magicka: " << mData.mData.mMana << std::endl; diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 6effa2cbf6..e2e178f90a 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -456,22 +456,12 @@ void CSMTools::ReferenceableCheckStage::creatureCheck( if (creature.mData.mLevel <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mStrength < 0) - messages.add(id, "Strength is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mIntelligence < 0) - messages.add(id, "Intelligence is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mWillpower < 0) - messages.add(id, "Willpower is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mAgility < 0) - messages.add(id, "Agility is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mSpeed < 0) - messages.add(id, "Speed is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mEndurance < 0) - messages.add(id, "Endurance is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mPersonality < 0) - messages.add(id, "Personality is negative", "", CSMDoc::Message::Severity_Warning); - if (creature.mData.mLuck < 0) - messages.add(id, "Luck is negative", "", CSMDoc::Message::Severity_Warning); + for (size_t i = 0; i < creature.mData.mAttributes.size(); ++i) + { + if (creature.mData.mAttributes[i] < 0) + messages.add(id, ESM::Attribute::indexToRefId(i).toDebugString() + " is negative", {}, + CSMDoc::Message::Severity_Warning); + } if (creature.mData.mCombat < 0) messages.add(id, "Combat is negative", "", CSMDoc::Message::Severity_Warning); diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index ba9c6d65a7..0ddfbbb051 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -1315,30 +1315,9 @@ QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData( if (subColIndex == 0) return subRowIndex; - else if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - return creature.mData.mStrength; - case 1: - return creature.mData.mIntelligence; - case 2: - return creature.mData.mWillpower; - case 3: - return creature.mData.mAgility; - case 4: - return creature.mData.mSpeed; - case 5: - return creature.mData.mEndurance; - case 6: - return creature.mData.mPersonality; - case 7: - return creature.mData.mLuck; - default: - return QVariant(); // throw an exception here? - } - else - return QVariant(); // throw an exception here? + else if (subColIndex == 1 && subRowIndex > 0 && subRowIndex < ESM::Attribute::Length) + return creature.mData.mAttributes[subRowIndex]; + return QVariant(); // throw an exception here? } void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData( @@ -1346,42 +1325,14 @@ void CSMWorld::CreatureAttributesRefIdAdapter::setNestedData( { Record& record = static_cast&>(data.getRecord(RefIdData::LocalIndex(row, UniversalId::Type_Creature))); - ESM::Creature creature = record.get(); - if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - creature.mData.mStrength = value.toInt(); - break; - case 1: - creature.mData.mIntelligence = value.toInt(); - break; - case 2: - creature.mData.mWillpower = value.toInt(); - break; - case 3: - creature.mData.mAgility = value.toInt(); - break; - case 4: - creature.mData.mSpeed = value.toInt(); - break; - case 5: - creature.mData.mEndurance = value.toInt(); - break; - case 6: - creature.mData.mPersonality = value.toInt(); - break; - case 7: - creature.mData.mLuck = value.toInt(); - break; - default: - return; // throw an exception here? - } - else - return; // throw an exception here? - - record.setModified(creature); + if (subColIndex == 1 && subRowIndex > 0 && subRowIndex < ESM::Attribute::Length) + { + ESM::Creature creature = record.get(); + creature.mData.mAttributes[subRowIndex] = value.toInt(); + record.setModified(creature); + } + // throw an exception here? } int CSMWorld::CreatureAttributesRefIdAdapter::getNestedColumnsCount( diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 62dd48deb6..df1ada96f4 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -127,14 +127,8 @@ namespace MWClass MWWorld::LiveCellRef* ref = ptr.get(); // creature stats - data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); - data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence); - data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower); - data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility); - data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed); - data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance); - data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality); - data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck); + for (size_t i = 0; i < ref->mBase->mData.mAttributes.size(); ++i) + data->mCreatureStats.setAttribute(ESM::Attribute::indexToRefId(i), ref->mBase->mData.mAttributes[i]); data->mCreatureStats.setHealth(static_cast(ref->mBase->mData.mHealth)); data->mCreatureStats.setMagicka(static_cast(ref->mBase->mData.mMana)); data->mCreatureStats.setFatigue(static_cast(ref->mBase->mData.mFatigue)); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 25a9878658..b966338ae5 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -93,7 +93,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("DATA"): - esm.getHTSized<12>(mData); + esm.getHT(mData.mFlags, mData.mX, mData.mY); hasData = true; break; case SREC_DELE: @@ -144,7 +144,7 @@ namespace ESM mWater = waterLevel; break; case fourCC("AMBI"): - esm.getHTSized<16>(mAmbi); + esm.getHT(mAmbi.mAmbient, mAmbi.mSunlight, mAmbi.mFog, mAmbi.mFogDensity); mHasAmbi = true; break; case fourCC("RGNN"): diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index b7cf6bf2eb..0ba0777e7c 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -96,8 +96,8 @@ namespace ESM struct DATAstruct { - int mFlags{ 0 }; - int mX{ 0 }, mY{ 0 }; + int32_t mFlags{ 0 }; + int32_t mX{ 0 }, mY{ 0 }; }; struct AMBIstruct @@ -132,11 +132,11 @@ namespace ESM float mWater; // Water level bool mWaterInt; - int mMapColor; + int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. // as that would collide with refs when a content file is upgraded. - int mRefNumCounter; + int32_t mRefNumCounter; // References "leased" from another cell (i.e. a different cell // introduced this ref, and it has been moved here by a plugin) diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 27cc98c1f2..0c0bad2e7c 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -48,7 +48,8 @@ namespace ESM mScript = esm.getRefId(); break; case fourCC("NPDT"): - esm.getHTSized<96>(mData); + esm.getHT(mData.mType, mData.mLevel, mData.mAttributes, mData.mHealth, mData.mMana, mData.mFatigue, + mData.mSoul, mData.mCombat, mData.mMagic, mData.mStealth, mData.mAttack, mData.mGold); hasNpdt = true; break; case fourCC("FLAG"): @@ -139,8 +140,7 @@ namespace ESM mRecordFlags = 0; mData.mType = 0; mData.mLevel = 0; - mData.mStrength = mData.mIntelligence = mData.mWillpower = mData.mAgility = mData.mSpeed = mData.mEndurance - = mData.mPersonality = mData.mLuck = 0; + mData.mAttributes.fill(0); mData.mHealth = mData.mMana = mData.mFatigue = 0; mData.mSoul = 0; mData.mCombat = mData.mMagic = mData.mStealth = 0; diff --git a/components/esm3/loadcrea.hpp b/components/esm3/loadcrea.hpp index 42e5007328..ff4e698fa8 100644 --- a/components/esm3/loadcrea.hpp +++ b/components/esm3/loadcrea.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_CREA_H #define OPENMW_ESM_CREA_H +#include #include #include "aipackage.hpp" @@ -8,6 +9,7 @@ #include "spelllist.hpp" #include "transport.hpp" +#include "components/esm/attr.hpp" #include "components/esm/defs.hpp" #include "components/esm/refid.hpp" @@ -52,30 +54,30 @@ namespace ESM struct NPDTstruct { - int mType; + int32_t mType; // For creatures we obviously have to use ints, not shorts and // bytes like we use for NPCs.... this file format just makes so // much sense! (Still, _much_ easier to decode than the NIFs.) - int mLevel; - int mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; + int32_t mLevel; + std::array mAttributes; - int mHealth, mMana, mFatigue; // Stats - int mSoul; // The creatures soul value (used with soul gems.) + int32_t mHealth, mMana, mFatigue; // Stats + int32_t mSoul; // The creatures soul value (used with soul gems.) // Creatures have generalized combat, magic and stealth stats which substitute for // the specific skills (in the same way as specializations). - int mCombat, mMagic, mStealth; - int mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3 - int mGold; + int32_t mCombat, mMagic, mStealth; + int32_t mAttack[6]; // AttackMin1, AttackMax1, ditto2, ditto3 + int32_t mGold; }; // 96 byte NPDTstruct mData; - int mBloodType; + int32_t mBloodType; unsigned char mFlags; float mScale; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mModel; std::string mName; diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 768de4f513..9d4b832f97 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -7,12 +7,12 @@ namespace ESM { - int& Faction::FADTstruct::getSkill(int index, bool) + int32_t& Faction::FADTstruct::getSkill(size_t index, bool) { return mSkills.at(index); } - int Faction::FADTstruct::getSkill(int index, bool) const + int32_t Faction::FADTstruct::getSkill(size_t index, bool) const { return mSkills.at(index); } @@ -23,10 +23,10 @@ namespace ESM mRecordFlags = esm.getRecordFlags(); mReactions.clear(); - for (int i = 0; i < 10; ++i) - mRanks[i].clear(); + for (auto& rank : mRanks) + rank.clear(); - int rankCounter = 0; + size_t rankCounter = 0; bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) @@ -42,7 +42,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RNAM"): - if (rankCounter >= 10) + if (rankCounter >= mRanks.size()) esm.fail("Rank out of range"); mRanks[rankCounter++] = esm.getHString(); break; @@ -55,7 +55,7 @@ namespace ESM case fourCC("ANAM"): { ESM::RefId faction = esm.getRefId(); - int reaction; + int32_t reaction; esm.getHNT(reaction, "INTV"); // Prefer the lowest reaction in case a faction is listed multiple times auto it = mReactions.find(faction); @@ -93,12 +93,12 @@ namespace ESM esm.writeHNOCString("FNAM", mName); - for (int i = 0; i < 10; i++) + for (const auto& rank : mRanks) { - if (mRanks[i].empty()) + if (rank.empty()) break; - esm.writeHNString("RNAM", mRanks[i], 32); + esm.writeHNString("RNAM", rank, 32); } esm.writeHNT("FADT", mData, 240); diff --git a/components/esm3/loadfact.hpp b/components/esm3/loadfact.hpp index 4cbf6c1d23..2359d276a2 100644 --- a/components/esm3/loadfact.hpp +++ b/components/esm3/loadfact.hpp @@ -21,15 +21,15 @@ namespace ESM // Requirements for each rank struct RankData { - int mAttribute1, mAttribute2; // Attribute level + int32_t mAttribute1, mAttribute2; // Attribute level // Skill level (faction skills given in // skillID below.) You need one skill at // level 'mPrimarySkill' and two skills at level // 'mFavouredSkill' to advance to this rank. - int mPrimarySkill, mFavouredSkill; + int32_t mPrimarySkill, mFavouredSkill; - int mFactReaction; // Reaction from faction members + int32_t mFactReaction; // Reaction from faction members }; struct Faction @@ -39,33 +39,33 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Faction"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mName; RefId mId; struct FADTstruct { // Which attributes we like - std::array mAttribute; + std::array mAttribute; std::array mRankData; - std::array mSkills; // IDs of skills this faction require - // Each element will either contain an Skill index, or -1. + std::array mSkills; // IDs of skills this faction require + // Each element will either contain an Skill index, or -1. - int mIsHidden; // 1 - hidden from player + int32_t mIsHidden; // 1 - hidden from player - int& getSkill(int index, bool ignored = false); + int32_t& getSkill(size_t index, bool ignored = false); ///< Throws an exception for invalid values of \a index. - int getSkill(int index, bool ignored = false) const; + int32_t getSkill(size_t index, bool ignored = false) const; ///< Throws an exception for invalid values of \a index. }; // 240 bytes FADTstruct mData; // - std::map mReactions; + std::map mReactions; // Name of faction ranks (may be empty for NPC factions) std::array mRanks; diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 8807f7b03f..9cff21da3e 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -23,7 +23,8 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("DATA"): - esm.getHTSized<12>(mData); + esm.getHT(mData.mUnknown1, mData.mDisposition, mData.mRank, mData.mGender, mData.mPCrank, + mData.mUnknown2); break; case fourCC("ONAM"): mActor = esm.getRefId(); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index 84db0d4f85..518e2eaa54 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -35,11 +35,11 @@ namespace ESM struct DATAstruct { - int mUnknown1 = 0; + int32_t mUnknown1 = 0; union { - int mDisposition = 0; // Used for dialogue responses - int mJournalIndex; // Used for journal entries + int32_t mDisposition = 0; // Used for dialogue responses + int32_t mJournalIndex; // Used for journal entries }; signed char mRank = -1; // Rank of NPC signed char mGender = Gender::NA; // See Gender enum diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index fef7a93feb..4e409ab63d 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -28,7 +28,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("IRDT"): - esm.getHTSized<56>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mEffectID, mData.mSkills, mData.mAttributes); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadingr.hpp b/components/esm3/loadingr.hpp index 02b237f386..2a8748e067 100644 --- a/components/esm3/loadingr.hpp +++ b/components/esm3/loadingr.hpp @@ -26,14 +26,14 @@ namespace ESM struct IRDTstruct { float mWeight; - int mValue; - int mEffectID[4]; // Effect, -1 means none - int mSkills[4]; // SkillEnum related to effect - int mAttributes[4]; // Attribute related to effect + int32_t mValue; + int32_t mEffectID[4]; // Effect, -1 means none + int32_t mSkills[4]; // SkillEnum related to effect + int32_t mAttributes[4]; // Attribute related to effect }; IRDTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel, mIcon; diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index 150baccec0..e22f6110c2 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -31,7 +31,7 @@ namespace ESM mIcon = esm.getHString(); break; case fourCC("LHDT"): - esm.getHTSized<24>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mTime, mData.mRadius, mData.mColor, mData.mFlags); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadligh.hpp b/components/esm3/loadligh.hpp index 7ee24d26fc..67c171b0bc 100644 --- a/components/esm3/loadligh.hpp +++ b/components/esm3/loadligh.hpp @@ -41,16 +41,16 @@ namespace ESM struct LHDTstruct { float mWeight; - int mValue; - int mTime; // Duration - int mRadius; - unsigned int mColor; // 4-byte rgba value - int mFlags; + int32_t mValue; + int32_t mTime; // Duration + int32_t mRadius; + uint32_t mColor; // 4-byte rgba value + int32_t mFlags; }; // Size = 24 bytes LHDTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mModel, mIcon, mName; ESM::RefId mId, mSound, mScript; diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 0e87558871..578a8a36a7 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -28,7 +28,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("LKDT"): - esm.getHTSized<16>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadlock.hpp b/components/esm3/loadlock.hpp index 1b94fd61bb..ae08243d69 100644 --- a/components/esm3/loadlock.hpp +++ b/components/esm3/loadlock.hpp @@ -22,14 +22,14 @@ namespace ESM struct Data { float mWeight; - int mValue; + int32_t mValue; float mQuality; - int mUses; + int32_t mUses; }; // Size = 16 Data mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel, mIcon; From a224bea6d452276bdf0df04c1206dba90b5e1e9c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Sep 2023 23:15:14 +0300 Subject: [PATCH 0123/2167] Rewrite NiBlendInterpolator+friends loading --- components/nif/controller.cpp | 144 ++++++++++++---------------------- components/nif/controller.hpp | 89 +++++++++------------ components/nif/nifstream.cpp | 22 ++++++ components/nif/nifstream.hpp | 4 + components/nif/niftypes.hpp | 26 ++++++ 5 files changed, 142 insertions(+), 143 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 80cc74ce23..c900f90282 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -540,71 +540,63 @@ namespace Nif void NiBlendInterpolator::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) - mManagerControlled = nif->getChar() & 1; - size_t numInterps = 0; - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) { - numInterps = nif->getUShort(); - mArrayGrowBy = nif->getUShort(); - } - else - { - numInterps = nif->getChar(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) + nif->read(mFlags); + mItems.resize(nif->get()); + nif->read(mWeightThreshold); + if (!(mFlags & Flag_ManagerControlled)) { - mWeightThreshold = nif->getFloat(); - if (!mManagerControlled) - { - mInterpCount = nif->getChar(); - mSingleIndex = nif->getChar(); - mHighPriority = nif->getChar(); - mNextHighPriority = nif->getChar(); - mSingleTime = nif->getFloat(); - mHighWeightsSum = nif->getFloat(); - mNextHighWeightsSum = nif->getFloat(); - mHighEaseSpinner = nif->getFloat(); - } + mInterpCount = nif->get(); + mSingleIndex = nif->get(); + mHighPriority = nif->get(); + mNextHighPriority = nif->get(); + nif->read(mSingleTime); + nif->read(mHighWeightsSum); + nif->read(mNextHighWeightsSum); + nif->read(mHighEaseSpinner); + for (Item& item : mItems) + item.read(nif); } + return; } - if (!mManagerControlled) + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 110)) { - mItems.resize(numInterps); + mItems.resize(nif->get()); for (Item& item : mItems) item.read(nif); + if (nif->get()) + mFlags |= Flag_ManagerControlled; + nif->read(mWeightThreshold); + if (nif->get()) + mFlags |= Flag_OnlyUseHighestWeight; + mInterpCount = nif->get(); + mSingleIndex = nif->get(); + mSingleInterpolator.read(nif); + nif->read(mSingleTime); + mHighPriority = nif->get(); + mNextHighPriority = nif->get(); + return; } - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 111)) + mItems.resize(nif->get()); + nif->read(mArrayGrowBy); + for (Item& item : mItems) + item.read(nif); + if (nif->get()) + mFlags |= Flag_ManagerControlled; + nif->read(mWeightThreshold); + if (nif->get()) + mFlags |= Flag_OnlyUseHighestWeight; + nif->read(mInterpCount); + nif->read(mSingleIndex); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 108)) { - mManagerControlled = nif->getBoolean(); - mWeightThreshold = nif->getFloat(); - mOnlyUseHighestWeight = nif->getBoolean(); - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) - { - mInterpCount = nif->getUShort(); - mSingleIndex = nif->getUShort(); - } - else - { - mInterpCount = nif->getChar(); - mSingleIndex = nif->getChar(); - } - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 108)) - { - mSingleInterpolator.read(nif); - mSingleTime = nif->getFloat(); - } - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) - { - mHighPriority = nif->getInt(); - mNextHighPriority = nif->getInt(); - } - else - { - mHighPriority = nif->getChar(); - mNextHighPriority = nif->getChar(); - } + mSingleInterpolator.read(nif); + nif->read(mSingleTime); } + nif->read(mHighPriority); + nif->read(mNextHighPriority); } void NiBlendInterpolator::post(Reader& nif) @@ -617,13 +609,13 @@ namespace Nif void NiBlendInterpolator::Item::read(NIFStream* nif) { mInterpolator.read(nif); - mWeight = nif->getFloat(); - mNormalizedWeight = nif->getFloat(); - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) - mPriority = nif->getInt(); + nif->read(mWeight); + nif->read(mNormalizedWeight); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 110)) + mPriority = nif->get(); else - mPriority = nif->getChar(); - mEaseSpinner = nif->getFloat(); + nif->read(mPriority); + nif->read(mEaseSpinner); } void NiBlendInterpolator::Item::post(Reader& nif) @@ -631,38 +623,4 @@ namespace Nif mInterpolator.post(nif); } - void NiBlendBoolInterpolator::read(NIFStream* nif) - { - NiBlendInterpolator::read(nif); - mValue = nif->getChar() != 0; - } - - void NiBlendFloatInterpolator::read(NIFStream* nif) - { - NiBlendInterpolator::read(nif); - mValue = nif->getFloat(); - } - - void NiBlendPoint3Interpolator::read(NIFStream* nif) - { - NiBlendInterpolator::read(nif); - mValue = nif->getVector3(); - } - - void NiBlendTransformInterpolator::read(NIFStream* nif) - { - NiBlendInterpolator::read(nif); - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) - { - mPosValue = nif->getVector3(); - nif->read(mRotValue); - mScaleValue = nif->getFloat(); - if (!nif->getBoolean()) - mPosValue = osg::Vec3f(); - if (!nif->getBoolean()) - mRotValue = osg::Quat(); - if (!nif->getBoolean()) - mScaleValue = 1.f; - } - } } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 07c042e047..e21b04bb72 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -1,30 +1,8 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (controller.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_CONTROLLER_HPP #define OPENMW_COMPONENTS_NIF_CONTROLLER_HPP #include "base.hpp" +#include "niftypes.hpp" #include "property.hpp" namespace Nif @@ -378,24 +356,29 @@ namespace Nif // Abstract struct NiBlendInterpolator : public NiInterpolator { + enum Flags + { + Flag_ManagerControlled = 0x1, + Flag_OnlyUseHighestWeight = 0x2, + }; + struct Item { NiInterpolatorPtr mInterpolator; float mWeight, mNormalizedWeight; - int mPriority; + int32_t mPriority; float mEaseSpinner; void read(NIFStream* nif); void post(Reader& nif); }; - bool mManagerControlled{ false }; - bool mOnlyUseHighestWeight{ false }; - unsigned short mArrayGrowBy{ 0 }; + uint8_t mFlags{ 0 }; + uint16_t mArrayGrowBy{ 0 }; float mWeightThreshold; - unsigned short mInterpCount; - unsigned short mSingleIndex; - int mHighPriority, mNextHighPriority; + uint16_t mInterpCount; + uint16_t mSingleIndex; + int32_t mHighPriority, mNextHighPriority; float mSingleTime; float mHighWeightsSum, mNextHighWeightsSum; float mHighEaseSpinner; @@ -406,31 +389,37 @@ namespace Nif void post(Reader& nif) override; }; - struct NiBlendBoolInterpolator : public NiBlendInterpolator + template + struct TypedNiBlendInterpolator : public NiBlendInterpolator { - char mValue; - void read(NIFStream* nif) override; + T mValue; + + void read(NIFStream* nif) override + { + NiBlendInterpolator::read(nif); + + nif->read(mValue); + } }; - struct NiBlendFloatInterpolator : public NiBlendInterpolator + template <> + struct TypedNiBlendInterpolator : public NiBlendInterpolator { - float mValue; - void read(NIFStream* nif) override; + NiQuatTransform mValue; + + void read(NIFStream* nif) override + { + NiBlendInterpolator::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) + nif->read(mValue); + } }; - struct NiBlendPoint3Interpolator : public NiBlendInterpolator - { - osg::Vec3f mValue; - void read(NIFStream* nif) override; - }; + using NiBlendBoolInterpolator = TypedNiBlendInterpolator; + using NiBlendFloatInterpolator = TypedNiBlendInterpolator; + using NiBlendPoint3Interpolator = TypedNiBlendInterpolator; + using NiBlendTransformInterpolator = TypedNiBlendInterpolator; - struct NiBlendTransformInterpolator : public NiBlendInterpolator - { - osg::Vec3f mPosValue; - osg::Quat mRotValue; - float mScaleValue; - void read(NIFStream* nif) override; - }; - -} // Namespace +} #endif diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index e33dc4f7b1..2eba746ccf 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -137,6 +137,22 @@ namespace Nif read(transform.mScale); } + template <> + void NIFStream::read(NiQuatTransform& transform) + { + read(transform.mTranslation); + read(transform.mRotation); + read(transform.mScale); + if (getVersion() >= generateVersion(10, 1, 0, 110)) + return; + if (!get()) + transform.mTranslation = osg::Vec3f(); + if (!get()) + transform.mRotation = osg::Quat(); + if (!get()) + transform.mScale = 1.f; + } + template <> void NIFStream::read(bool& data) { @@ -197,6 +213,12 @@ namespace Nif readRange(*this, dest, size); } + template <> + void NIFStream::read(NiQuatTransform* dest, size_t size) + { + readRange(*this, dest, size); + } + template <> void NIFStream::read(bool* dest, size_t size) { diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 9c9a097662..c1f7cfdf12 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -175,6 +175,8 @@ namespace Nif template <> void NIFStream::read(NiTransform& transform); template <> + void NIFStream::read(NiQuatTransform& transform); + template <> void NIFStream::read(bool& data); template <> void NIFStream::read(std::string& str); @@ -194,6 +196,8 @@ namespace Nif template <> void NIFStream::read(NiTransform* dest, size_t size); template <> + void NIFStream::read(NiQuatTransform* dest, size_t size); + template <> void NIFStream::read(bool* dest, size_t size); template <> void NIFStream::read(std::string* dest, size_t size); diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index 3b2a4c1578..b294756f69 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -25,6 +25,7 @@ #define OPENMW_COMPONENTS_NIF_NIFTYPES_HPP #include +#include #include // Common types used in NIF files @@ -80,5 +81,30 @@ namespace Nif } }; + struct NiQuatTransform + { + osg::Vec3f mTranslation; + osg::Quat mRotation; + float mScale; + + osg::Matrixf toMatrix() const + { + osg::Matrixf transform(mRotation); + transform.setTrans(mTranslation); + for (int i = 0; i < 3; i++) + transform(i, i) *= mScale; + + return transform; + } + + bool isIdentity() const { return mTranslation == osg::Vec3f() && mRotation == osg::Quat() && mScale == 1.f; } + + static const NiQuatTransform& getIdentity() + { + static const NiQuatTransform identity = { osg::Vec3f(), osg::Quat(), 1.f }; + return identity; + } + }; + } // Namespace #endif From 208bfa9e21fd97ccd9c7f767d126888fcc353d61 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Sep 2023 23:30:13 +0300 Subject: [PATCH 0124/2167] Refactor NiMaterialColorController --- components/nif/controller.cpp | 12 +++++------- components/nif/controller.hpp | 10 +++++++++- components/nifosg/controller.cpp | 11 ++++++----- components/nifosg/controller.hpp | 9 +-------- components/nifosg/nifloader.cpp | 11 +++++------ 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index c900f90282..ee219fe5b5 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -224,15 +224,12 @@ namespace Nif void NiMaterialColorController::read(NIFStream* nif) { NiPoint3InterpController::read(nif); - // Two bits that correspond to the controlled material color. - // 00: Ambient - // 01: Diffuse - // 10: Specular - // 11: Emissive + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mTargetColor = nif->getUShort() & 3; + mTargetColor = static_cast(nif->get() & 3); else - mTargetColor = (flags >> 4) & 3; + mTargetColor = static_cast((flags >> 4) & 3); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); } @@ -240,6 +237,7 @@ namespace Nif void NiMaterialColorController::post(Reader& nif) { NiPoint3InterpController::post(nif); + mData.post(nif); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index e21b04bb72..4b46840d4a 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -157,8 +157,16 @@ namespace Nif struct NiMaterialColorController : public NiPoint3InterpController { + enum class TargetColor + { + Ambient = 0, + Diffuse = 1, + Specular = 2, + Emissive = 3, + }; + NiPosDataPtr mData; - unsigned int mTargetColor; + TargetColor mTargetColor; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 20a3ee92e4..8e9efba8a9 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -444,7 +444,7 @@ namespace NifOsg MaterialColorController::MaterialColorController( const Nif::NiMaterialColorController* ctrl, const osg::Material* baseMaterial) - : mTargetColor(static_cast(ctrl->mTargetColor)) + : mTargetColor(ctrl->mTargetColor) , mBaseMaterial(baseMaterial) { if (!ctrl->mInterpolator.empty()) @@ -477,30 +477,31 @@ namespace NifOsg { osg::Vec3f value = mData.interpKey(getInputValue(nv)); osg::Material* mat = static_cast(stateset->getAttribute(osg::StateAttribute::MATERIAL)); + using TargetColor = Nif::NiMaterialColorController::TargetColor; switch (mTargetColor) { - case Diffuse: + case TargetColor::Diffuse: { osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); diffuse.set(value.x(), value.y(), value.z(), diffuse.a()); mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); break; } - case Specular: + case TargetColor::Specular: { osg::Vec4f specular = mat->getSpecular(osg::Material::FRONT_AND_BACK); specular.set(value.x(), value.y(), value.z(), specular.a()); mat->setSpecular(osg::Material::FRONT_AND_BACK, specular); break; } - case Emissive: + case TargetColor::Emissive: { osg::Vec4f emissive = mat->getEmission(osg::Material::FRONT_AND_BACK); emissive.set(value.x(), value.y(), value.z(), emissive.a()); mat->setEmission(osg::Material::FRONT_AND_BACK, emissive); break; } - case Ambient: + case TargetColor::Ambient: default: { osg::Vec4f ambient = mat->getAmbient(osg::Material::FRONT_AND_BACK); diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 90e87366e1..1025f1988e 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -336,13 +336,6 @@ namespace NifOsg class MaterialColorController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { public: - enum TargetColor - { - Ambient = 0, - Diffuse = 1, - Specular = 2, - Emissive = 3 - }; MaterialColorController(const Nif::NiMaterialColorController* ctrl, const osg::Material* baseMaterial); MaterialColorController(); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); @@ -355,7 +348,7 @@ namespace NifOsg private: Vec3Interpolator mData; - TargetColor mTargetColor = Ambient; + Nif::NiMaterialColorController::TargetColor mTargetColor; osg::ref_ptr mBaseMaterial; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9683de9a23..8a4a62b017 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -988,18 +988,17 @@ namespace NifOsg { const Nif::NiMaterialColorController* matctrl = static_cast(ctrl.getPtr()); - if (matctrl->mData.empty() && matctrl->mInterpolator.empty()) + Nif::NiInterpolatorPtr interp = matctrl->mInterpolator; + if (matctrl->mData.empty() && interp.empty()) continue; - auto targetColor = static_cast(matctrl->mTargetColor); if (mVersion <= Nif::NIFFile::VER_MW - && targetColor == MaterialColorController::TargetColor::Specular) + && matctrl->mTargetColor == Nif::NiMaterialColorController::TargetColor::Specular) continue; - if (!matctrl->mInterpolator.empty() - && matctrl->mInterpolator->recType != Nif::RC_NiPoint3Interpolator) + if (!interp.empty() && interp->recType != Nif::RC_NiPoint3Interpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex - << " in " << mFilename << ": " << matctrl->mInterpolator->recName; + << " in " << mFilename << ": " << interp->recName; continue; } osg::ref_ptr osgctrl = new MaterialColorController(matctrl, baseMaterial); From 735a948452b2c23f1ba9491fc333c0774220707c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Sep 2023 23:43:24 +0300 Subject: [PATCH 0125/2167] Refactor NiMultiTargetTransformController --- components/nif/controller.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index ee219fe5b5..0e08c4d255 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -305,17 +305,16 @@ namespace Nif void NiMultiTargetTransformController::read(NIFStream* nif) { NiInterpController::read(nif); - size_t numTargets = nif->getUShort(); - std::vector targets; - targets.resize(numTargets); - for (size_t i = 0; i < targets.size(); i++) - targets[i].read(nif); - mExtraTargets = targets; + + mExtraTargets.resize(nif->get()); + for (NiAVObjectPtr& extraTarget : mExtraTargets) + extraTarget.read(nif); } void NiMultiTargetTransformController::post(Reader& nif) { NiInterpController::post(nif); + postRecordList(nif, mExtraTargets); } From 6d82f8b00d006d693ae7b6531ff90c07f1a1d354 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Sep 2023 23:55:26 +0300 Subject: [PATCH 0126/2167] Refactor NiKeyframeController and NiTransformInterpolator --- components/nif/controller.cpp | 19 +++++-------------- components/nif/controller.hpp | 6 ++---- components/nifosg/controller.cpp | 30 +++++++++++++++++------------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 0e08c4d255..7d6706aeae 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -292,6 +292,7 @@ namespace Nif void NiKeyframeController::read(NIFStream* nif) { NiSingleInterpController::read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); } @@ -299,6 +300,7 @@ namespace Nif void NiKeyframeController::post(Reader& nif) { NiSingleInterpController::post(nif); + mData.post(nif); } @@ -503,24 +505,13 @@ namespace Nif void NiTransformInterpolator::read(NIFStream* nif) { - defaultPos = nif->getVector3(); - nif->read(defaultRot); - defaultScale = nif->getFloat(); - if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 109)) - { - if (!nif->getBoolean()) - defaultPos = osg::Vec3f(); - if (!nif->getBoolean()) - defaultRot = osg::Quat(); - if (!nif->getBoolean()) - defaultScale = 1.f; - } - data.read(nif); + nif->read(mDefaultTransform); + mData.read(nif); } void NiTransformInterpolator::post(Reader& nif) { - data.post(nif); + mData.post(nif); } void NiColorInterpolator::read(NIFStream* nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 4b46840d4a..24d5b17465 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -344,10 +344,8 @@ namespace Nif struct NiTransformInterpolator : public NiInterpolator { - osg::Vec3f defaultPos; - osg::Quat defaultRot; - float defaultScale; - NiKeyframeDataPtr data; + NiQuatTransform mDefaultTransform; + NiKeyframeDataPtr mData; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 8e9efba8a9..88a88f46be 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -90,21 +90,23 @@ namespace NifOsg { const Nif::NiTransformInterpolator* interp = static_cast(keyctrl->mInterpolator.getPtr()); - if (!interp->data.empty()) + const Nif::NiQuatTransform& defaultTransform = interp->mDefaultTransform; + if (!interp->mData.empty()) { - mRotations = QuaternionInterpolator(interp->data->mRotations, interp->defaultRot); - mXRotations = FloatInterpolator(interp->data->mXRotations); - mYRotations = FloatInterpolator(interp->data->mYRotations); - mZRotations = FloatInterpolator(interp->data->mZRotations); - mTranslations = Vec3Interpolator(interp->data->mTranslations, interp->defaultPos); - mScales = FloatInterpolator(interp->data->mScales, interp->defaultScale); - mAxisOrder = interp->data->mAxisOrder; + mRotations = QuaternionInterpolator(interp->mData->mRotations, defaultTransform.mRotation); + mXRotations = FloatInterpolator(interp->mData->mXRotations); + mYRotations = FloatInterpolator(interp->mData->mYRotations); + mZRotations = FloatInterpolator(interp->mData->mZRotations); + mTranslations = Vec3Interpolator(interp->mData->mTranslations, defaultTransform.mTranslation); + mScales = FloatInterpolator(interp->mData->mScales, defaultTransform.mScale); + + mAxisOrder = interp->mData->mAxisOrder; } else { - mRotations = QuaternionInterpolator(Nif::QuaternionKeyMapPtr(), interp->defaultRot); - mTranslations = Vec3Interpolator(Nif::Vector3KeyMapPtr(), interp->defaultPos); - mScales = FloatInterpolator(Nif::FloatKeyMapPtr(), interp->defaultScale); + mRotations = QuaternionInterpolator(Nif::QuaternionKeyMapPtr(), defaultTransform.mRotation); + mTranslations = Vec3Interpolator(Nif::Vector3KeyMapPtr(), defaultTransform.mTranslation); + mScales = FloatInterpolator(Nif::FloatKeyMapPtr(), defaultTransform.mScale); } } } @@ -117,6 +119,7 @@ namespace NifOsg mZRotations = FloatInterpolator(keydata->mZRotations); mTranslations = Vec3Interpolator(keydata->mTranslations); mScales = FloatInterpolator(keydata->mScales, 1.f); + mAxisOrder = keydata->mAxisOrder; } } @@ -177,11 +180,12 @@ namespace NifOsg else node->setRotation(node->mRotationScale); + if (!mTranslations.empty()) + node->setTranslation(mTranslations.interpKey(time)); + if (!mScales.empty()) node->setScale(mScales.interpKey(time)); - if (!mTranslations.empty()) - node->setTranslation(mTranslations.interpKey(time)); } traverse(node, nv); From 19d1f6f3f5a6b7610fed2843ebbd735921035c59 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 00:15:02 +0300 Subject: [PATCH 0127/2167] Rewrite typed NiInterpolator loading --- components/nif/controller.cpp | 55 ------------------------------ components/nif/controller.hpp | 57 +++++++++++--------------------- components/nif/data.cpp | 2 +- components/nif/data.hpp | 5 +-- components/nif/nifkey.hpp | 4 +-- components/nifosg/controller.cpp | 9 ++--- components/nifosg/controller.hpp | 14 ++++---- 7 files changed, 38 insertions(+), 108 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 7d6706aeae..7a8bb31152 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -470,61 +470,6 @@ namespace Nif mObjectPalette.post(nif); } - void NiPoint3Interpolator::read(NIFStream* nif) - { - defaultVal = nif->getVector3(); - data.read(nif); - } - - void NiPoint3Interpolator::post(Reader& nif) - { - data.post(nif); - } - - void NiBoolInterpolator::read(NIFStream* nif) - { - defaultVal = nif->getBoolean(); - data.read(nif); - } - - void NiBoolInterpolator::post(Reader& nif) - { - data.post(nif); - } - - void NiFloatInterpolator::read(NIFStream* nif) - { - defaultVal = nif->getFloat(); - data.read(nif); - } - - void NiFloatInterpolator::post(Reader& nif) - { - data.post(nif); - } - - void NiTransformInterpolator::read(NIFStream* nif) - { - nif->read(mDefaultTransform); - mData.read(nif); - } - - void NiTransformInterpolator::post(Reader& nif) - { - mData.post(nif); - } - - void NiColorInterpolator::read(NIFStream* nif) - { - defaultVal = nif->getVector4(); - data.read(nif); - } - - void NiColorInterpolator::post(Reader& nif) - { - data.post(nif); - } - void NiBlendInterpolator::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 24d5b17465..1a6847a5b7 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -318,46 +318,29 @@ namespace Nif { }; - struct NiPoint3Interpolator : public NiInterpolator + template + struct TypedNiInterpolator : public NiInterpolator { - osg::Vec3f defaultVal; - NiPosDataPtr data; - void read(NIFStream* nif) override; - void post(Reader& nif) override; + T mDefaultValue; + DataPtr mData; + + void read(NIFStream* nif) override + { + nif->read(mDefaultValue); + mData.read(nif); + } + + void post(Reader& nif) override + { + mData.post(nif); + } }; - struct NiBoolInterpolator : public NiInterpolator - { - char defaultVal; - NiBoolDataPtr data; - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiFloatInterpolator : public NiInterpolator - { - float defaultVal; - NiFloatDataPtr data; - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiTransformInterpolator : public NiInterpolator - { - NiQuatTransform mDefaultTransform; - NiKeyframeDataPtr mData; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiColorInterpolator : public NiInterpolator - { - osg::Vec4f defaultVal; - NiColorDataPtr data; - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; + using NiPoint3Interpolator = TypedNiInterpolator; + using NiBoolInterpolator = TypedNiInterpolator; + using NiFloatInterpolator = TypedNiInterpolator; + using NiTransformInterpolator = TypedNiInterpolator; + using NiColorInterpolator = TypedNiInterpolator; // Abstract struct NiBlendInterpolator : public NiInterpolator diff --git a/components/nif/data.cpp b/components/nif/data.cpp index a07f66d43f..528256c49b 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -528,7 +528,7 @@ namespace Nif void NiBoolData::read(NIFStream* nif) { - mKeyList = std::make_shared(); + mKeyList = std::make_shared(); mKeyList->read(nif); } diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 849cbde7bd..6ec09c2f42 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -219,7 +219,7 @@ namespace Nif struct NiVisData : public Record { - // TODO: investigate possible use of ByteKeyMap + // TODO: investigate possible use of BoolKeyMap std::shared_ptr> mKeys; void read(NIFStream* nif) override; @@ -357,7 +357,8 @@ namespace Nif struct NiBoolData : public Record { - ByteKeyMapPtr mKeyList; + BoolKeyMapPtr mKeyList; + void read(NIFStream* nif) override; }; diff --git a/components/nif/nifkey.hpp b/components/nif/nifkey.hpp index bbd06cb048..604cf92c33 100644 --- a/components/nif/nifkey.hpp +++ b/components/nif/nifkey.hpp @@ -152,13 +152,13 @@ namespace Nif using Vector3KeyMap = KeyMapT>; using Vector4KeyMap = KeyMapT>; using QuaternionKeyMap = KeyMapT>; - using ByteKeyMap = KeyMapT>; + using BoolKeyMap = KeyMapT>; using FloatKeyMapPtr = std::shared_ptr; using Vector3KeyMapPtr = std::shared_ptr; using Vector4KeyMapPtr = std::shared_ptr; using QuaternionKeyMapPtr = std::shared_ptr; - using ByteKeyMapPtr = std::shared_ptr; + using BoolKeyMapPtr = std::shared_ptr; } // Namespace #endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 88a88f46be..018b650dbd 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -90,7 +90,7 @@ namespace NifOsg { const Nif::NiTransformInterpolator* interp = static_cast(keyctrl->mInterpolator.getPtr()); - const Nif::NiQuatTransform& defaultTransform = interp->mDefaultTransform; + const Nif::NiQuatTransform& defaultTransform = interp->mDefaultValue; if (!interp->mData.empty()) { mRotations = QuaternionInterpolator(interp->mData->mRotations, defaultTransform.mRotation); @@ -318,9 +318,10 @@ namespace NifOsg { if (!ctrl->mInterpolator.empty()) { - if (ctrl->mInterpolator->recType == Nif::RC_NiBoolInterpolator) - mInterpolator - = ByteInterpolator(static_cast(ctrl->mInterpolator.getPtr())); + if (ctrl->mInterpolator->recType != Nif::RC_NiBoolInterpolator) + return; + + mInterpolator = { static_cast(ctrl->mInterpolator.getPtr()) }; } else if (!ctrl->mData.empty()) mData = ctrl->mData->mKeys; diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 1025f1988e..613aa7dc16 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -66,15 +66,15 @@ namespace NifOsg typename = std::enable_if_t, std::is_same, std::is_same, - std::is_same, std::is_same>, - std::is_same>, + std::is_same>, + std::is_same>, T>> ValueInterpolator(const T* interpolator) - : mDefaultVal(interpolator->defaultVal) + : mDefaultVal(interpolator->mDefaultValue) { - if (interpolator->data.empty()) + if (interpolator->mData.empty()) return; - mKeys = interpolator->data->mKeyList; + mKeys = interpolator->mData->mKeyList; if (mKeys) { mLastLowKey = mKeys->mKeys.end(); @@ -182,7 +182,7 @@ namespace NifOsg using FloatInterpolator = ValueInterpolator; using Vec3Interpolator = ValueInterpolator; using Vec4Interpolator = ValueInterpolator; - using ByteInterpolator = ValueInterpolator; + using BoolInterpolator = ValueInterpolator; class ControllerFunction : public SceneUtil::ControllerFunction { @@ -283,7 +283,7 @@ namespace NifOsg { private: std::shared_ptr> mData; - ByteInterpolator mInterpolator; + BoolInterpolator mInterpolator; unsigned int mMask{ 0u }; bool calculate(float time) const; From 949dc587417696e606e422f2963d66bd88cae4da Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 13 Sep 2023 22:57:16 +0100 Subject: [PATCH 0128/2167] Improve Store-Symbols.ps1 * Handle other things also using CMake's file API. * Ensure the right version of symstore is used. * Upgrade to symstore 0.3.4 to fix incorrect IDs. --- CI/Store-Symbols.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CI/Store-Symbols.ps1 b/CI/Store-Symbols.ps1 index 6328a2a2f6..8634181432 100644 --- a/CI/Store-Symbols.ps1 +++ b/CI/Store-Symbols.ps1 @@ -3,10 +3,12 @@ if (-Not (Test-Path CMakeCache.txt)) Write-Error "This script must be run from the build directory." } -if (-Not (Test-Path .cmake\api\v1\reply)) +if (-Not (Test-Path .cmake\api\v1\reply\index-*.json) -Or -Not ((Get-Content -Raw .cmake\api\v1\reply\index-*.json | ConvertFrom-Json).reply.PSObject.Properties.Name -contains "codemodel-v2")) { + Write-Output "Running CMake query..." New-Item -Type File -Force .cmake\api\v1\query\codemodel-v2 cmake . + Write-Output "Done." } try @@ -44,9 +46,12 @@ if (-not (Test-Path symstore-venv)) { python -m venv symstore-venv } -if (-not (Test-Path symstore-venv\Scripts\symstore.exe)) +$symstoreVersion = "0.3.4" +if (-not (Test-Path symstore-venv\Scripts\symstore.exe) -or -not ((symstore-venv\Scripts\pip show symstore | Select-String '(?<=Version: ).*').Matches.Value -eq $symstoreVersion)) { - symstore-venv\Scripts\pip install symstore==0.3.3 + symstore-venv\Scripts\pip install symstore==$symstoreVersion } $artifacts = $artifacts | Where-Object { Test-Path $_ } -symstore-venv\Scripts\symstore --compress .\SymStore @artifacts +Write-Output "Storing symbols..." +symstore-venv\Scripts\symstore --compress --skip-published .\SymStore @artifacts +Write-Output "Done." From 05d8975ed1a78dcf858c5a8c2c03b3552656119a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 01:21:21 +0300 Subject: [PATCH 0129/2167] Revise various NIF controller records Mostly those that don't require external changes --- apps/openmw_test_suite/nif/node.hpp | 2 +- components/nif/base.hpp | 2 +- components/nif/controller.cpp | 164 ++++++++++++++++------------ components/nif/controller.hpp | 39 ++++--- components/nif/property.hpp | 2 +- 5 files changed, 122 insertions(+), 87 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index b39729c876..c606fe67e5 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -63,7 +63,7 @@ namespace Nif::Testing value.phase = 0; value.timeStart = 0; value.timeStop = 0; - value.target = NiObjectNETPtr(nullptr); + value.mTarget = NiObjectNETPtr(nullptr); } } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 6b4d5710b4..a24359772d 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -40,7 +40,7 @@ namespace Nif int flags; float frequency, phase; float timeStart, timeStop; - NiObjectNETPtr target; + NiObjectNETPtr mTarget; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 7a8bb31152..a3c81f96da 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -1,6 +1,7 @@ #include "controller.hpp" #include "data.hpp" +#include "exception.hpp" #include "node.hpp" #include "particle.hpp" #include "texture.hpp" @@ -13,20 +14,17 @@ namespace Nif next.read(nif); flags = nif->getUShort(); - frequency = nif->getFloat(); phase = nif->getFloat(); timeStart = nif->getFloat(); timeStop = nif->getFloat(); - - target.read(nif); + mTarget.read(nif); } void Controller::post(Reader& nif) { - Record::post(nif); next.post(nif); - target.post(nif); + mTarget.post(nif); } void ControlledBlock::read(NIFStream* nif) @@ -44,28 +42,28 @@ namespace Nif if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 110)) { mBlendInterpolator.read(nif); - mBlendIndex = nif->getUShort(); + nif->read(mBlendIndex); } if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106) && nif->getBethVersion() > 0) - mPriority = nif->getChar(); + nif->read(mPriority); if (nif->getVersion() >= NIFStream::generateVersion(10, 2, 0, 0) && nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 0)) { mStringPalette.read(nif); - mNodeNameOffset = nif->getUInt(); - mPropertyTypeOffset = nif->getUInt(); - mControllerTypeOffset = nif->getUInt(); - mControllerIdOffset = nif->getUInt(); - mInterpolatorIdOffset = nif->getUInt(); + nif->read(mNodeNameOffset); + nif->read(mPropertyTypeOffset); + nif->read(mControllerTypeOffset); + nif->read(mControllerIdOffset); + nif->read(mInterpolatorIdOffset); } else { - mNodeName = nif->getString(); - mPropertyType = nif->getString(); - mControllerType = nif->getString(); - mControllerId = nif->getString(); - mInterpolatorId = nif->getString(); + nif->read(mNodeName); + nif->read(mPropertyType); + nif->read(mControllerType); + nif->read(mControllerId); + nif->read(mInterpolatorId); } } @@ -80,16 +78,17 @@ namespace Nif void NiSequence::read(NIFStream* nif) { - mName = nif->getString(); + nif->read(mName); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) { - mAccumRootName = nif->getString(); + nif->read(mAccumRootName); mTextKeys.read(nif); } - size_t numControlledBlocks = nif->getUInt(); + uint32_t numBlocks; + nif->read(numBlocks); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106)) - mArrayGrowBy = nif->getUInt(); - mControlledBlocks.resize(numControlledBlocks); + nif->read(mArrayGrowBy); + mControlledBlocks.resize(numBlocks); for (ControlledBlock& block : mControlledBlocks) block.read(nif); } @@ -104,28 +103,30 @@ namespace Nif void NiControllerSequence::read(NIFStream* nif) { NiSequence::read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) return; - mWeight = nif->getFloat(); + nif->read(mWeight); mTextKeys.read(nif); mExtrapolationMode = static_cast(nif->getUInt()); mFrequency = nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) - mPhase = nif->getFloat(); - mStartTime = nif->getFloat(); - mStopTime = nif->getFloat(); - mPlayBackwards = nif->getVersion() == NIFStream::generateVersion(10, 1, 0, 106) && nif->getBoolean(); + nif->read(mPhase); + nif->read(mStartTime); + nif->read(mStopTime); + if (nif->getVersion() == NIFStream::generateVersion(10, 1, 0, 106)) + nif->read(mPlayBackwards); mManager.read(nif); - mAccumRootName = nif->getString(); + nif->read(mAccumRootName); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 113) && nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 0)) mStringPalette.read(nif); else if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() >= 24) { - size_t numAnimNotes = 1; + uint16_t numAnimNotes = 1; if (nif->getBethVersion() >= 29) - numAnimNotes = nif->getUShort(); + nif->read(numAnimNotes); nif->skip(4 * numAnimNotes); // BSAnimNotes links } @@ -134,6 +135,7 @@ namespace Nif void NiControllerSequence::post(Reader& nif) { NiSequence::post(nif); + mManager.post(nif); mStringPalette.post(nif); } @@ -141,14 +143,16 @@ namespace Nif void NiInterpController::read(NIFStream* nif) { Controller::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104) && nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 108)) - mManagerControlled = nif->getBoolean(); + nif->read(mManagerControlled); } void NiSingleInterpController::read(NIFStream* nif) { NiInterpController::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104)) mInterpolator.read(nif); } @@ -156,6 +160,7 @@ namespace Nif void NiSingleInterpController::post(Reader& nif) { NiInterpController::post(nif); + mInterpolator.post(nif); } @@ -216,6 +221,7 @@ namespace Nif void NiParticleSystemController::post(Reader& nif) { Controller::post(nif); + emitter.post(nif); affectors.post(nif); colliders.post(nif); @@ -244,15 +250,17 @@ namespace Nif void NiLookAtController::read(NIFStream* nif) { Controller::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - lookAtFlags = nif->getUShort(); - target.read(nif); + nif->read(mLookAtFlags); + mLookAt.read(nif); } void NiLookAtController::post(Reader& nif) { Controller::post(nif); - target.post(nif); + + mLookAt.post(nif); } void NiPathController::read(NIFStream* nif) @@ -286,6 +294,7 @@ namespace Nif void NiUVController::post(Reader& nif) { Controller::post(nif); + data.post(nif); } @@ -323,6 +332,7 @@ namespace Nif void NiAlphaController::read(NIFStream* nif) { NiFloatInterpController::read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); } @@ -330,12 +340,14 @@ namespace Nif void NiAlphaController::post(Reader& nif) { NiFloatInterpController::post(nif); + mData.post(nif); } void NiRollController::read(NIFStream* nif) { NiSingleInterpController::read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); } @@ -343,49 +355,47 @@ namespace Nif void NiRollController::post(Reader& nif) { NiSingleInterpController::post(nif); + mData.post(nif); } void NiGeomMorpherController::read(NIFStream* nif) { NiInterpController::read(nif); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_OB_OLD) - mUpdateNormals = nif->getUShort() & 1; + mUpdateNormals = nif->get() & 1; mData.read(nif); - if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) + + if (nif->getVersion() < NIFFile::NIFVersion::VER_MW) + return; + + mAlwaysActive = nif->get() != 0; + + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 105)) + return; + + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { - mAlwaysActive = nif->getChar(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106)) - { - if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - { - readRecordList(nif, mInterpolators); - if (nif->getVersion() >= NIFStream::generateVersion(10, 2, 0, 0) && nif->getBethVersion() > 9) - { - unsigned int numUnknown = nif->getUInt(); - nif->skip(4 * numUnknown); - } - } - else - { - std::vector interpolators; - size_t numInterps = nif->getUInt(); - interpolators.resize(numInterps); - mWeights.resize(numInterps); - for (size_t i = 0; i < numInterps; i++) - { - interpolators[i].read(nif); - mWeights[i] = nif->getFloat(); - } - mInterpolators = interpolators; - } - } + readRecordList(nif, mInterpolators); + if (nif->getVersion() >= NIFStream::generateVersion(10, 2, 0, 0) && nif->getBethVersion() >= 10) + nif->skip(4 * nif->get()); // Unknown + return; + } + + mInterpolators.resize(nif->get()); + mWeights.resize(mInterpolators.size()); + for (size_t i = 0; i < mInterpolators.size(); i++) + { + mInterpolators[i].read(nif); + nif->read(mWeights[i]); } } void NiGeomMorpherController::post(Reader& nif) { NiInterpController::post(nif); + mData.post(nif); postRecordList(nif, mInterpolators); } @@ -393,6 +403,7 @@ namespace Nif void NiVisController::read(NIFStream* nif) { NiBoolInterpController::read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); } @@ -400,17 +411,19 @@ namespace Nif void NiVisController::post(Reader& nif) { NiBoolInterpController::post(nif); + mData.post(nif); } void NiFlipController::read(NIFStream* nif) { NiFloatInterpController::read(nif); - mTexSlot = nif->getUInt(); + + mTexSlot = static_cast(nif->get()); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) { - timeStart = nif->getFloat(); - mDelta = nif->getFloat(); + nif->read(timeStart); + nif->read(mDelta); } readRecordList(nif, mSources); } @@ -418,14 +431,16 @@ namespace Nif void NiFlipController::post(Reader& nif) { NiFloatInterpController::post(nif); + postRecordList(nif, mSources); } void NiTextureTransformController::read(NIFStream* nif) { NiFloatInterpController::read(nif); - mShaderMap = nif->getBoolean(); - nif->read(mTexSlot); + + nif->read(mShaderMap); + mTexSlot = static_cast(nif->get()); nif->read(mTransformMember); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); @@ -434,31 +449,41 @@ namespace Nif void NiTextureTransformController::post(Reader& nif) { NiFloatInterpController::post(nif); + mData.post(nif); } void bhkBlendController::read(NIFStream* nif) { Controller::read(nif); - nif->getUInt(); // Zero + + uint32_t numKeys; + nif->read(numKeys); + // Is this possible? + if (numKeys != 0) + throw Nif::Exception( + "Unsupported keys in bhkBlendController " + std::to_string(recIndex), nif->getFile().getFilename()); } void BSEffectShaderPropertyFloatController::read(NIFStream* nif) { NiFloatInterpController::read(nif); + nif->read(mControlledVariable); } void BSEffectShaderPropertyColorController::read(NIFStream* nif) { NiPoint3InterpController::read(nif); + nif->read(mControlledColor); } void NiControllerManager::read(NIFStream* nif) { Controller::read(nif); - mCumulative = nif->getBoolean(); + + nif->read(mCumulative); readRecordList(nif, mSequences); mObjectPalette.read(nif); } @@ -466,6 +491,7 @@ namespace Nif void NiControllerManager::post(Reader& nif) { Controller::post(nif); + postRecordList(nif, mSequences); mObjectPalette.post(nif); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 1a6847a5b7..730cda5bd6 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -14,14 +14,14 @@ namespace Nif NiInterpolatorPtr mInterpolator; ControllerPtr mController; NiBlendInterpolatorPtr mBlendInterpolator; - unsigned short mBlendIndex; - unsigned char mPriority; + uint16_t mBlendIndex; + uint8_t mPriority; NiStringPalettePtr mStringPalette; - size_t mNodeNameOffset; - size_t mPropertyTypeOffset; - size_t mControllerTypeOffset; - size_t mControllerIdOffset; - size_t mInterpolatorIdOffset; + uint32_t mNodeNameOffset; + uint32_t mPropertyTypeOffset; + uint32_t mControllerTypeOffset; + uint32_t mControllerIdOffset; + uint32_t mInterpolatorIdOffset; std::string mNodeName; std::string mPropertyType; std::string mControllerType; @@ -38,7 +38,7 @@ namespace Nif std::string mName; std::string mAccumRootName; ExtraPtr mTextKeys; - unsigned int mArrayGrowBy; + uint32_t mArrayGrowBy; std::vector mControlledBlocks; void read(NIFStream* nif) override; @@ -197,8 +197,15 @@ namespace Nif struct NiLookAtController : public Controller { - NiAVObjectPtr target; - unsigned short lookAtFlags{ 0 }; + enum Flags + { + Flag_Flip = 0x1, + Flag_LookYAxis = 0x2, + Flag_LookZAxis = 0x4, + }; + + uint16_t mLookAtFlags{ 0 }; + NiAVObjectPtr mLookAt; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -267,7 +274,7 @@ namespace Nif struct NiFlipController : public NiFloatInterpController { - int mTexSlot; // NiTexturingProperty::TextureType + NiTexturingProperty::TextureType mTexSlot; float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; @@ -278,8 +285,8 @@ namespace Nif struct NiTextureTransformController : public NiFloatInterpController { bool mShaderMap; - int mTexSlot; // NiTexturingProperty::TextureType - unsigned int mTransformMember; + NiTexturingProperty::TextureType mTexSlot; + uint32_t mTransformMember; NiFloatDataPtr mData; void read(NIFStream* nif) override; @@ -293,14 +300,14 @@ namespace Nif struct BSEffectShaderPropertyFloatController : public NiFloatInterpController { - unsigned int mControlledVariable; + uint32_t mControlledVariable; void read(NIFStream* nif) override; }; struct BSEffectShaderPropertyColorController : public NiPoint3InterpController { - unsigned int mControlledColor; + uint32_t mControlledColor; void read(NIFStream* nif) override; }; @@ -310,10 +317,12 @@ namespace Nif bool mCumulative; NiControllerSequenceList mSequences; NiDefaultAVObjectPalettePtr mObjectPalette; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; + // Abstract struct NiInterpolator : public Record { }; diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 90f1e9ffb5..d9574f31d6 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -79,7 +79,7 @@ namespace Nif * 5 - Bump map texture * 6 - Decal texture */ - enum TextureType + enum TextureType : uint32_t { BaseTexture = 0, DarkTexture = 1, From b0dfd7456210ea52e98fc9c809c8865fa5eaa045 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 01:54:27 +0300 Subject: [PATCH 0130/2167] NIFStream: remove getBoolean, getString --- components/nif/nifstream.hpp | 2 -- components/nif/node.cpp | 8 ++++---- components/nif/property.cpp | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index c1f7cfdf12..8a4ec847fc 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -156,8 +156,6 @@ namespace Nif osg::Vec2f getVector2() { return get(); } osg::Vec3f getVector3() { return get(); } osg::Vec4f getVector4() { return get(); } - bool getBoolean() { return get(); } - std::string getString() { return get(); } }; template <> diff --git a/components/nif/node.cpp b/components/nif/node.cpp index ad966097b0..a9e4f183b2 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -153,8 +153,8 @@ namespace Nif if (nif->getVersion() < NIFStream::generateVersion(10, 0, 1, 0)) return; unsigned int num = 0; - if (nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 3)) - num = nif->getBoolean(); // Has Shader + if (nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 3) && nif->get()) + num = 1; else if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) num = nif->getUInt(); @@ -163,7 +163,7 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) active = nif->getUInt(); if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - needsUpdate = nif->getBoolean(); + nif->read(needsUpdate); } void NiGeometry::read(NIFStream* nif) @@ -210,7 +210,7 @@ namespace Nif nearDist = nif->getFloat(); farDist = nif->getFloat(); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - orthographic = nif->getBoolean(); + nif->read(orthographic); vleft = nif->getFloat(); vright = nif->getFloat(); vtop = nif->getFloat(); diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 39a7cbff23..3569fd55cc 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -8,7 +8,7 @@ namespace Nif void NiTexturingProperty::Texture::read(NIFStream* nif) { - inUse = nif->getBoolean(); + nif->read(inUse); if (!inUse) return; @@ -36,7 +36,7 @@ namespace Nif nif->skip(2); // Unknown short else if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { - if (nif->getBoolean()) // Has texture transform + if (nif->get()) // Has texture transform { nif->getVector2(); // UV translation nif->getVector2(); // UV scale From ca8582043169a960a61909ae56204d2a6b980f40 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 02:00:05 +0300 Subject: [PATCH 0131/2167] Refactor NiUVController --- components/nif/controller.cpp | 6 +++--- components/nif/controller.hpp | 4 ++-- components/nifosg/controller.cpp | 8 +++----- components/nifosg/controller.hpp | 6 +++--- components/nifosg/nifloader.cpp | 11 +++++------ 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index a3c81f96da..84c43bafcf 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -287,15 +287,15 @@ namespace Nif { Controller::read(nif); - uvSet = nif->getUShort(); - data.read(nif); + nif->read(mUvSet); + mData.read(nif); } void NiUVController::post(Reader& nif) { Controller::post(nif); - data.post(nif); + mData.post(nif); } void NiKeyframeController::read(NIFStream* nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 730cda5bd6..87bfe615a0 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -213,8 +213,8 @@ namespace Nif struct NiUVController : public Controller { - NiUVDataPtr data; - unsigned int uvSet; + NiUVDataPtr mData; + uint16_t mUvSet; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 018b650dbd..d424d7f56b 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -255,9 +255,7 @@ namespace NifOsg } } - UVController::UVController() {} - - UVController::UVController(const Nif::NiUVData* data, const std::set& textureUnits) + UVController::UVController(const Nif::NiUVData* data, const std::set& textureUnits) : mUTrans(data->mKeyList[0], 0.f) , mVTrans(data->mKeyList[1], 0.f) , mUScale(data->mKeyList[2], 1.f) @@ -281,8 +279,8 @@ namespace NifOsg void UVController::setDefaults(osg::StateSet* stateset) { osg::ref_ptr texMat(new osg::TexMat); - for (std::set::const_iterator it = mTextureUnits.begin(); it != mTextureUnits.end(); ++it) - stateset->setTextureAttributeAndModes(*it, texMat, osg::StateAttribute::ON); + for (unsigned int unit : mTextureUnits) + stateset->setTextureAttributeAndModes(unit, texMat, osg::StateAttribute::ON); } void UVController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 613aa7dc16..f830cf1c4c 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -262,9 +262,9 @@ namespace NifOsg class UVController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller { public: - UVController(); + UVController() = default; UVController(const UVController&, const osg::CopyOp&); - UVController(const Nif::NiUVData* data, const std::set& textureUnits); + UVController(const Nif::NiUVData* data, const std::set& textureUnits); META_Object(NifOsg, UVController) @@ -276,7 +276,7 @@ namespace NifOsg FloatInterpolator mVTrans; FloatInterpolator mUScale; FloatInterpolator mVScale; - std::set mTextureUnits; + std::set mTextureUnits; }; class VisController : public SceneUtil::NodeCallback, public SceneUtil::Controller diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8a4a62b017..76dfd7a315 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -865,18 +865,17 @@ namespace NifOsg if (ctrl->recType == Nif::RC_NiUVController) { const Nif::NiUVController* niuvctrl = static_cast(ctrl.getPtr()); - if (niuvctrl->data.empty()) + if (niuvctrl->mData.empty()) continue; - const unsigned int uvSet = niuvctrl->uvSet; - std::set texUnits; - // UVController should work only for textures which use a given UV Set, usually 0. + std::set texUnits; + // UVController should only work for textures which use the given UV Set. for (unsigned int i = 0; i < boundTextures.size(); ++i) { - if (boundTextures[i] == uvSet) + if (boundTextures[i] == niuvctrl->mUvSet) texUnits.insert(i); } - osg::ref_ptr uvctrl = new UVController(niuvctrl->data.getPtr(), texUnits); + osg::ref_ptr uvctrl = new UVController(niuvctrl->mData.getPtr(), texUnits); setupController(niuvctrl, uvctrl, animflags); composite->addController(uvctrl); } From 04d3f6a42d13a600fe62bd08b9b92dcd8eea2a76 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 02:20:18 +0300 Subject: [PATCH 0132/2167] Update NiPathController Support 10.1.0.0+ NiPathController loading --- components/nif/controller.cpp | 21 +++++++++++++-------- components/nif/controller.hpp | 26 ++++++++++++++------------ components/nifosg/controller.cpp | 6 +++--- components/nifosg/nifloader.cpp | 2 +- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 84c43bafcf..d9e6d657d7 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -267,20 +267,25 @@ namespace Nif { Controller::read(nif); - bankDir = nif->getInt(); - maxBankAngle = nif->getFloat(); - smoothing = nif->getFloat(); - nif->read(followAxis); - posData.read(nif); - floatData.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mPathFlags); + else + mPathFlags = (flags >> 16); + + nif->read(mBankDirection); + nif->read(mMaxBankAngle); + nif->read(mSmoothing); + nif->read(mFollowAxis); + mPathData.read(nif); + mPercentData.read(nif); } void NiPathController::post(Reader& nif) { Controller::post(nif); - posData.post(nif); - floatData.post(nif); + mPathData.post(nif); + mPercentData.post(nif); } void NiUVController::read(NIFStream* nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 87bfe615a0..046ddc2e5c 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -174,22 +174,24 @@ namespace Nif struct NiPathController : public Controller { - NiPosDataPtr posData; - NiFloatDataPtr floatData; - enum Flags { - Flag_OpenCurve = 0x020, - Flag_AllowFlip = 0x040, - Flag_Bank = 0x080, - Flag_ConstVelocity = 0x100, - Flag_Follow = 0x200, - Flag_FlipFollowAxis = 0x400 + Flag_CVDataNeedsUpdate = 0x01, + Flag_OpenCurve = 0x02, + Flag_AllowFlip = 0x04, + Flag_Bank = 0x08, + Flag_ConstVelocity = 0x10, + Flag_Follow = 0x20, + Flag_FlipFollowAxis = 0x40, }; - int bankDir; - float maxBankAngle, smoothing; - uint16_t followAxis; + uint16_t mPathFlags; + int32_t mBankDirection; + float mMaxBankAngle; + float mSmoothing; + uint16_t mFollowAxis; + NiPosDataPtr mPathData; + NiFloatDataPtr mPercentData; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index d424d7f56b..3d83c63721 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -598,9 +598,9 @@ namespace NifOsg } PathController::PathController(const Nif::NiPathController* ctrl) - : mPath(ctrl->posData->mKeyList, osg::Vec3f()) - , mPercent(ctrl->floatData->mKeyList, 1.f) - , mFlags(ctrl->flags) + : mPath(ctrl->mPathData->mKeyList, osg::Vec3f()) + , mPercent(ctrl->mPercentData->mKeyList, 1.f) + , mFlags(ctrl->mPathFlags) { } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 76dfd7a315..96740b8dd3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -907,7 +907,7 @@ namespace NifOsg else if (ctrl->recType == Nif::RC_NiPathController) { const Nif::NiPathController* path = static_cast(ctrl.getPtr()); - if (path->posData.empty() || path->floatData.empty()) + if (path->mPathData.empty() || path->mPercentData.empty()) continue; osg::ref_ptr callback(new PathController(path)); setupController(path, callback, animflags); From 0fe095303fed16d3ea91172988e79243bc3370db Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 02:46:20 +0300 Subject: [PATCH 0133/2167] Rename Controller->NiTimeController, update NiTimeController and related code --- apps/openmw_test_suite/nif/node.hpp | 16 +++--- .../nifloader/testbulletnifloader.cpp | 10 ++-- components/nif/base.hpp | 16 +++--- components/nif/controller.cpp | 54 +++++++++---------- components/nif/controller.hpp | 20 +++---- components/nif/particle.hpp | 2 +- components/nif/recordptr.hpp | 4 +- components/nifbullet/bulletnifloader.cpp | 2 +- components/nifosg/controller.cpp | 16 +++--- components/nifosg/controller.hpp | 4 +- components/nifosg/nifloader.cpp | 22 ++++---- 11 files changed, 83 insertions(+), 83 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index c606fe67e5..e05a056b79 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -20,7 +20,7 @@ namespace Nif::Testing { value.mExtra = ExtraPtr(nullptr); value.mExtraList = ExtraList(); - value.mController = ControllerPtr(nullptr); + value.mController = NiTimeControllerPtr(nullptr); } inline void init(NiAVObject& value) @@ -55,14 +55,14 @@ namespace Nif::Testing value.mRoot = NiAVObjectPtr(nullptr); } - inline void init(Controller& value) + inline void init(NiTimeController& value) { - value.next = ControllerPtr(nullptr); - value.flags = 0; - value.frequency = 0; - value.phase = 0; - value.timeStart = 0; - value.timeStop = 0; + value.mNext = NiTimeControllerPtr(nullptr); + value.mFlags = 0; + value.mFrequency = 0; + value.mPhase = 0; + value.mTimeStart = 0; + value.mTimeStop = 0; value.mTarget = NiObjectNETPtr(nullptr); } } diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index fc6a21f62c..1971895936 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -300,7 +300,7 @@ namespace Nif::NiStringExtraData mNiStringExtraData; Nif::NiStringExtraData mNiStringExtraData2; Nif::NiIntegerExtraData mNiIntegerExtraData; - Nif::Controller mController; + Nif::NiTimeController mController; btTransform mTransform{ btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3) }; btTransform mTransformScale2{ btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(2, 4, 6) }; btTransform mTransformScale3{ btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(3, 6, 9) }; @@ -817,11 +817,11 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_controller_should_return_animated_shape) { mController.recType = Nif::RC_NiKeyframeController; - mController.flags |= Nif::Controller::Flag_Active; + mController.mFlags |= Nif::NiTimeController::Flag_Active; copy(mTransform, mNiTriShape.mTransform); mNiTriShape.mTransform.mScale = 3; mNiTriShape.mParents.push_back(&mNiNode); - mNiTriShape.mController = Nif::ControllerPtr(&mController); + mNiTriShape.mController = Nif::NiTimeControllerPtr(&mController); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode.mTransform.mScale = 4; @@ -847,14 +847,14 @@ namespace TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_where_one_with_controller_should_return_animated_shape) { mController.recType = Nif::RC_NiKeyframeController; - mController.flags |= Nif::Controller::Flag_Active; + mController.mFlags |= Nif::NiTimeController::Flag_Active; copy(mTransform, mNiTriShape.mTransform); mNiTriShape.mTransform.mScale = 3; mNiTriShape.mParents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.mTransform); mNiTriShape2.mTransform.mScale = 3; mNiTriShape2.mParents.push_back(&mNiNode); - mNiTriShape2.mController = Nif::ControllerPtr(&mController); + mNiTriShape2.mController = Nif::NiTimeControllerPtr(&mController); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape), Nif::NiAVObjectPtr(&mNiTriShape2), diff --git a/components/nif/base.hpp b/components/nif/base.hpp index a24359772d..ae4bda7753 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -21,7 +21,7 @@ namespace Nif void post(Reader& nif) override { mNext.post(nif); } }; - struct Controller : public Record + struct NiTimeController : public Record { enum Flags { @@ -36,17 +36,17 @@ namespace Nif Mask = 6 }; - ControllerPtr next; - int flags; - float frequency, phase; - float timeStart, timeStop; + NiTimeControllerPtr mNext; + uint16_t mFlags; + float mFrequency, mPhase; + float mTimeStart, mTimeStop; NiObjectNETPtr mTarget; void read(NIFStream* nif) override; void post(Reader& nif) override; - bool isActive() const { return flags & Flag_Active; } - ExtrapolationMode extrapolationMode() const { return static_cast(flags & Mask); } + bool isActive() const { return mFlags & Flag_Active; } + ExtrapolationMode extrapolationMode() const { return static_cast(mFlags & Mask); } }; /// Abstract object that has a name, extra data and controllers @@ -55,7 +55,7 @@ namespace Nif std::string mName; ExtraPtr mExtra; ExtraList mExtraList; - ControllerPtr mController; + NiTimeControllerPtr mController; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index d9e6d657d7..17937b61af 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -9,21 +9,21 @@ namespace Nif { - void Controller::read(NIFStream* nif) + void NiTimeController::read(NIFStream* nif) { - next.read(nif); - - flags = nif->getUShort(); - frequency = nif->getFloat(); - phase = nif->getFloat(); - timeStart = nif->getFloat(); - timeStop = nif->getFloat(); - mTarget.read(nif); + mNext.read(nif); + nif->read(mFlags); + nif->read(mFrequency); + nif->read(mPhase); + nif->read(mTimeStart); + nif->read(mTimeStop); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mTarget.read(nif); } - void Controller::post(Reader& nif) + void NiTimeController::post(Reader& nif) { - next.post(nif); + mNext.post(nif); mTarget.post(nif); } @@ -109,7 +109,7 @@ namespace Nif nif->read(mWeight); mTextKeys.read(nif); - mExtrapolationMode = static_cast(nif->getUInt()); + mExtrapolationMode = static_cast(nif->getUInt()); mFrequency = nif->getFloat(); if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) nif->read(mPhase); @@ -142,7 +142,7 @@ namespace Nif void NiInterpController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104) && nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 108)) @@ -166,7 +166,7 @@ namespace Nif void NiParticleSystemController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); velocity = nif->getFloat(); velocityRandom = nif->getFloat(); @@ -220,7 +220,7 @@ namespace Nif void NiParticleSystemController::post(Reader& nif) { - Controller::post(nif); + NiTimeController::post(nif); emitter.post(nif); affectors.post(nif); @@ -234,7 +234,7 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) mTargetColor = static_cast(nif->get() & 3); else - mTargetColor = static_cast((flags >> 4) & 3); + mTargetColor = static_cast((mFlags >> 4) & 3); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) mData.read(nif); @@ -249,7 +249,7 @@ namespace Nif void NiLookAtController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) nif->read(mLookAtFlags); @@ -258,19 +258,19 @@ namespace Nif void NiLookAtController::post(Reader& nif) { - Controller::post(nif); + NiTimeController::post(nif); mLookAt.post(nif); } void NiPathController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) nif->read(mPathFlags); else - mPathFlags = (flags >> 16); + mPathFlags = (mFlags >> 16); nif->read(mBankDirection); nif->read(mMaxBankAngle); @@ -282,7 +282,7 @@ namespace Nif void NiPathController::post(Reader& nif) { - Controller::post(nif); + NiTimeController::post(nif); mPathData.post(nif); mPercentData.post(nif); @@ -290,7 +290,7 @@ namespace Nif void NiUVController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); nif->read(mUvSet); mData.read(nif); @@ -298,7 +298,7 @@ namespace Nif void NiUVController::post(Reader& nif) { - Controller::post(nif); + NiTimeController::post(nif); mData.post(nif); } @@ -427,7 +427,7 @@ namespace Nif mTexSlot = static_cast(nif->get()); if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) { - nif->read(timeStart); + nif->read(mTimeStart); nif->read(mDelta); } readRecordList(nif, mSources); @@ -460,7 +460,7 @@ namespace Nif void bhkBlendController::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); uint32_t numKeys; nif->read(numKeys); @@ -486,7 +486,7 @@ namespace Nif void NiControllerManager::read(NIFStream* nif) { - Controller::read(nif); + NiTimeController::read(nif); nif->read(mCumulative); readRecordList(nif, mSequences); @@ -495,7 +495,7 @@ namespace Nif void NiControllerManager::post(Reader& nif) { - Controller::post(nif); + NiTimeController::post(nif); postRecordList(nif, mSequences); mObjectPalette.post(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 046ddc2e5c..5449e19f8d 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -12,7 +12,7 @@ namespace Nif { std::string mTargetName; NiInterpolatorPtr mInterpolator; - ControllerPtr mController; + NiTimeControllerPtr mController; NiBlendInterpolatorPtr mBlendInterpolator; uint16_t mBlendIndex; uint8_t mPriority; @@ -49,7 +49,7 @@ namespace Nif struct NiControllerSequence : public NiSequence { float mWeight{ 1.f }; - Controller::ExtrapolationMode mExtrapolationMode{ Controller::ExtrapolationMode::Constant }; + NiTimeController::ExtrapolationMode mExtrapolationMode{ NiTimeController::ExtrapolationMode::Constant }; float mFrequency{ 1.f }; float mPhase{ 1.f }; float mStartTime, mStopTime; @@ -62,7 +62,7 @@ namespace Nif }; // Base class for controllers that use NiInterpolators to animate objects. - struct NiInterpController : public Controller + struct NiInterpController : public NiTimeController { // Usually one of the flags. bool mManagerControlled{ false }; @@ -94,7 +94,7 @@ namespace Nif { }; - struct NiParticleSystemController : public Controller + struct NiParticleSystemController : public NiTimeController { enum BSPArrayController { @@ -151,7 +151,7 @@ namespace Nif void post(Reader& nif) override; bool noAutoAdjust() const { return emitFlags & EmitFlag_NoAutoAdjust; } - bool emitAtVertex() const { return flags & BSPArrayController_AtVertex; } + bool emitAtVertex() const { return mFlags & BSPArrayController_AtVertex; } }; using NiBSPArrayController = NiParticleSystemController; @@ -172,7 +172,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPathController : public Controller + struct NiPathController : public NiTimeController { enum Flags { @@ -197,7 +197,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiLookAtController : public Controller + struct NiLookAtController : public NiTimeController { enum Flags { @@ -213,7 +213,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiUVController : public Controller + struct NiUVController : public NiTimeController { NiUVDataPtr mData; uint16_t mUvSet; @@ -295,7 +295,7 @@ namespace Nif void post(Reader& nif) override; }; - struct bhkBlendController : public Controller + struct bhkBlendController : public NiTimeController { void read(NIFStream* nif) override; }; @@ -314,7 +314,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiControllerManager : public Controller + struct NiControllerManager : public NiTimeController { bool mCumulative; NiControllerSequenceList mSequences; diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 5590877294..328498210a 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -9,7 +9,7 @@ namespace Nif struct NiParticleModifier : public Record { NiParticleModifierPtr mNext; - ControllerPtr mController; + NiTimeControllerPtr mController; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 9f75b31d74..627e59cf0d 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -116,7 +116,7 @@ namespace Nif struct NiUVData; struct NiPosData; struct NiVisData; - struct Controller; + struct NiTimeController; struct NiObjectNET; struct NiSkinData; struct NiFloatData; @@ -157,7 +157,7 @@ namespace Nif using NiUVDataPtr = RecordPtrT; using NiPosDataPtr = RecordPtrT; using NiVisDataPtr = RecordPtrT; - using ControllerPtr = RecordPtrT; + using NiTimeControllerPtr = RecordPtrT; using NiObjectNETPtr = RecordPtrT; using NiSkinDataPtr = RecordPtrT; using NiMorphDataPtr = RecordPtrT; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index bc207e52bf..59d5cd61d8 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -255,7 +255,7 @@ namespace NifBullet if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) return; - for (Nif::ControllerPtr ctrl = node.mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = node.mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (args.mAnimated) break; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 3d83c63721..45082d969a 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -15,11 +15,11 @@ namespace NifOsg { - ControllerFunction::ControllerFunction(const Nif::Controller* ctrl) - : mFrequency(ctrl->frequency) - , mPhase(ctrl->phase) - , mStartTime(ctrl->timeStart) - , mStopTime(ctrl->timeStop) + ControllerFunction::ControllerFunction(const Nif::NiTimeController* ctrl) + : mFrequency(ctrl->mFrequency) + , mPhase(ctrl->mPhase) + , mStartTime(ctrl->mTimeStart) + , mStopTime(ctrl->mTimeStop) , mExtrapolationMode(ctrl->extrapolationMode()) { } @@ -31,7 +31,7 @@ namespace NifOsg return time; switch (mExtrapolationMode) { - case Nif::Controller::ExtrapolationMode::Cycle: + case Nif::NiTimeController::ExtrapolationMode::Cycle: { float delta = mStopTime - mStartTime; if (delta <= 0) @@ -40,7 +40,7 @@ namespace NifOsg float remainder = (cycles - std::floor(cycles)) * delta; return mStartTime + remainder; } - case Nif::Controller::ExtrapolationMode::Reverse: + case Nif::NiTimeController::ExtrapolationMode::Reverse: { float delta = mStopTime - mStartTime; if (delta <= 0) @@ -55,7 +55,7 @@ namespace NifOsg return mStopTime - remainder; } - case Nif::Controller::ExtrapolationMode::Constant: + case Nif::NiTimeController::ExtrapolationMode::Constant: default: return std::clamp(time, mStartTime, mStopTime); } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index f830cf1c4c..2f9d58c6b1 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -191,10 +191,10 @@ namespace NifOsg float mPhase; float mStartTime; float mStopTime; - Nif::Controller::ExtrapolationMode mExtrapolationMode; + Nif::NiTimeController::ExtrapolationMode mExtrapolationMode; public: - ControllerFunction(const Nif::Controller* ctrl); + ControllerFunction(const Nif::NiTimeController* ctrl); float calculate(float value) const override; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 96740b8dd3..f1492b9d0b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -293,8 +293,8 @@ namespace NifOsg auto textKeyExtraData = static_cast(extraList[0].getPtr()); extractTextKeys(textKeyExtraData, target.mTextKeys); - Nif::ControllerPtr ctrl = seq->mController; - for (size_t i = 1; i < extraList.size() && !ctrl.empty(); i++, (ctrl = ctrl->next)) + Nif::NiTimeControllerPtr ctrl = seq->mController; + for (size_t i = 1; i < extraList.size() && !ctrl.empty(); i++, (ctrl = ctrl->mNext)) { Nif::ExtraPtr extra = extraList[i]; if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) @@ -449,7 +449,7 @@ namespace NifOsg animflags, hasStencilProperty); } - static void setupController(const Nif::Controller* ctrl, SceneUtil::Controller* toSetup, int animflags) + static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) { bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; if (autoPlay) @@ -725,7 +725,7 @@ namespace NifOsg if (nifNode->isHidden()) { bool hasVisController = false; - for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { hasVisController |= (ctrl->recType == Nif::RC_NiVisController); if (hasVisController) @@ -858,7 +858,7 @@ namespace NifOsg SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; @@ -884,7 +884,7 @@ namespace NifOsg void handleNodeControllers(const Nif::NiAVObject* nifNode, osg::Node* node, int animflags, bool& isAnimated) { - for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; @@ -962,7 +962,7 @@ namespace NifOsg void handleMaterialControllers(const Nif::Property* materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { - for (Nif::ControllerPtr ctrl = materialProperty->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = materialProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; @@ -1012,7 +1012,7 @@ namespace NifOsg void handleTextureControllers(const Nif::Property* texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet* stateset, int animflags) { - for (Nif::ControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; @@ -1200,7 +1200,7 @@ namespace NifOsg partctrl->horizontalAngle, partctrl->verticalDir, partctrl->verticalAngle, partctrl->lifetime, partctrl->lifetimeRandom); emitter->setShooter(shooter); - emitter->setFlags(partctrl->flags); + emitter->setFlags(partctrl->mFlags); if (partctrl->recType == Nif::RC_NiBSPArrayController && partctrl->emitAtVertex()) { @@ -1252,7 +1252,7 @@ namespace NifOsg partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; - for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; @@ -1483,7 +1483,7 @@ namespace NifOsg if (geom->empty()) return; osg::ref_ptr drawable; - for (Nif::ControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->next) + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; From d55ba0cfa2638953cee4dc6edd1bd9105db16bfd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 03:08:07 +0300 Subject: [PATCH 0134/2167] Cleanup --- components/nif/controller.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 17937b61af..f909d94da5 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -84,11 +84,9 @@ namespace Nif nif->read(mAccumRootName); mTextKeys.read(nif); } - uint32_t numBlocks; - nif->read(numBlocks); + mControlledBlocks.resize(nif->get()); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 106)) nif->read(mArrayGrowBy); - mControlledBlocks.resize(numBlocks); for (ControlledBlock& block : mControlledBlocks) block.read(nif); } @@ -109,8 +107,8 @@ namespace Nif nif->read(mWeight); mTextKeys.read(nif); - mExtrapolationMode = static_cast(nif->getUInt()); - mFrequency = nif->getFloat(); + mExtrapolationMode = static_cast(nif->get()); + nif->read(mFrequency); if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) nif->read(mPhase); nif->read(mStartTime); From 8856dff3db838e8806ea0f766b81944731614f37 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 04:24:37 +0300 Subject: [PATCH 0135/2167] Refactor NiParticleSystemController and update definitions --- components/nif/controller.cpp | 102 ++++++++++++++++--------------- components/nif/controller.hpp | 81 ++++++++++++------------ components/nifosg/controller.cpp | 10 +-- components/nifosg/controller.hpp | 6 +- components/nifosg/nifloader.cpp | 94 ++++++++++++++-------------- 5 files changed, 148 insertions(+), 145 deletions(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index f909d94da5..1875488143 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -162,67 +162,71 @@ namespace Nif mInterpolator.post(nif); } + void NiParticleInfo::read(NIFStream* nif) + { + nif->read(mVelocity); + if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) + nif->read(mRotationAxis); + nif->read(mAge); + nif->read(mLifespan); + nif->read(mLastUpdate); + nif->read(mSpawnGeneration); + nif->read(mCode); + } + void NiParticleSystemController::read(NIFStream* nif) { NiTimeController::read(nif); - velocity = nif->getFloat(); - velocityRandom = nif->getFloat(); - verticalDir = nif->getFloat(); - verticalAngle = nif->getFloat(); - horizontalDir = nif->getFloat(); - horizontalAngle = nif->getFloat(); - /*normal?*/ nif->getVector3(); - color = nif->getVector4(); - size = nif->getFloat(); - startTime = nif->getFloat(); - stopTime = nif->getFloat(); - nif->getChar(); - emitRate = nif->getFloat(); - lifetime = nif->getFloat(); - lifetimeRandom = nif->getFloat(); - - emitFlags = nif->getUShort(); - offsetRandom = nif->getVector3(); - - emitter.read(nif); - - /* Unknown Short, 0? - * Unknown Float, 1.0? - * Unknown Int, 1? - * Unknown Int, 0? - * Unknown Short, 0? - */ - nif->skip(16); - - numParticles = nif->getUShort(); - activeCount = nif->getUShort(); - - particles.resize(numParticles); - for (size_t i = 0; i < particles.size(); i++) + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mSpeed); + nif->read(mSpeedVariation); + nif->read(mDeclination); + nif->read(mDeclinationVariation); + nif->read(mPlanarAngle); + nif->read(mPlanarAngleVariation); + nif->read(mInitialNormal); + nif->read(mInitialColor); + nif->read(mInitialSize); + nif->read(mEmitStartTime); + nif->read(mEmitStopTime); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) { - particles[i].velocity = nif->getVector3(); - nif->getVector3(); /* unknown */ - particles[i].lifetime = nif->getFloat(); - particles[i].lifespan = nif->getFloat(); - particles[i].timestamp = nif->getFloat(); - nif->getUShort(); /* unknown */ - particles[i].vertex = nif->getUShort(); + mResetParticleSystem = nif->get() != 0; + nif->read(mBirthRate); } - - nif->getUInt(); /* -1? */ - affectors.read(nif); - colliders.read(nif); - nif->getChar(); + nif->read(mLifetime); + nif->read(mLifetimeVariation); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mEmitFlags); + nif->read(mEmitterDimensions); + mEmitter.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + { + nif->read(mNumSpawnGenerations); + nif->read(mPercentageSpawned); + nif->read(mSpawnMultiplier); + nif->read(mSpawnSpeedChaos); + nif->read(mSpawnDirChaos); + mParticles.resize(nif->get()); + nif->read(mNumValid); + for (NiParticleInfo& particle : mParticles) + particle.read(nif); + nif->skip(4); // NiEmitterModifier link + } + mModifier.read(nif); + mCollider.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 15)) + nif->read(mStaticTargetBound); } void NiParticleSystemController::post(Reader& nif) { NiTimeController::post(nif); - emitter.post(nif); - affectors.post(nif); - colliders.post(nif); + mEmitter.post(nif); + mModifier.post(nif); + mCollider.post(nif); } void NiMaterialColorController::read(NIFStream* nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 5449e19f8d..a05ff247c5 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -94,6 +94,19 @@ namespace Nif { }; + struct NiParticleInfo + { + osg::Vec3f mVelocity; + osg::Vec3f mRotationAxis; + float mAge; + float mLifespan; + float mLastUpdate; + uint16_t mSpawnGeneration; + uint16_t mCode; + + void read(NIFStream* nif); + }; + struct NiParticleSystemController : public NiTimeController { enum BSPArrayController @@ -102,55 +115,47 @@ namespace Nif BSPArrayController_AtVertex = 0x10 }; - struct Particle - { - osg::Vec3f velocity; - float lifetime; - float lifespan; - float timestamp; - unsigned short vertex; - }; - - float velocity; - float velocityRandom; - - float verticalDir; // 0=up, pi/2=horizontal, pi=down - float verticalAngle; - float horizontalDir; - float horizontalAngle; - - osg::Vec4f color; - float size; - float startTime; - float stopTime; - - float emitRate; - float lifetime; - float lifetimeRandom; - enum EmitFlags { EmitFlag_NoAutoAdjust = 0x1 // If this flag is set, we use the emitRate value. Otherwise, // we calculate an emit rate so that the maximum number of particles // in the system (numParticles) is never exceeded. }; - int emitFlags; - osg::Vec3f offsetRandom; - - NiAVObjectPtr emitter; - - int numParticles; - int activeCount; - std::vector particles; - - NiParticleModifierPtr affectors; - NiParticleModifierPtr colliders; + float mSpeed; + float mSpeedVariation; + float mDeclination; + float mDeclinationVariation; + float mPlanarAngle; + float mPlanarAngleVariation; + osg::Vec3f mInitialNormal; + osg::Vec4f mInitialColor; + float mInitialSize; + float mEmitStartTime; + float mEmitStopTime; + bool mResetParticleSystem{ false }; + float mBirthRate; + float mLifetime; + float mLifetimeVariation; + uint16_t mEmitFlags{ 0 }; + osg::Vec3f mEmitterDimensions; + NiAVObjectPtr mEmitter; + uint16_t mNumSpawnGenerations; + float mPercentageSpawned; + uint16_t mSpawnMultiplier; + float mSpawnSpeedChaos; + float mSpawnDirChaos; + uint16_t mNumParticles; + uint16_t mNumValid; + std::vector mParticles; + NiParticleModifierPtr mModifier; + NiParticleModifierPtr mCollider; + uint8_t mStaticTargetBound; void read(NIFStream* nif) override; void post(Reader& nif) override; - bool noAutoAdjust() const { return emitFlags & EmitFlag_NoAutoAdjust; } + bool noAutoAdjust() const { return mEmitFlags & EmitFlag_NoAutoAdjust; } bool emitAtVertex() const { return mFlags & BSPArrayController_AtVertex; } }; using NiBSPArrayController = NiParticleSystemController; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 45082d969a..c5511141a2 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -556,14 +556,8 @@ namespace NifOsg } ParticleSystemController::ParticleSystemController(const Nif::NiParticleSystemController* ctrl) - : mEmitStart(ctrl->startTime) - , mEmitStop(ctrl->stopTime) - { - } - - ParticleSystemController::ParticleSystemController() - : mEmitStart(0.f) - , mEmitStop(0.f) + : mEmitStart(ctrl->mEmitStartTime) + , mEmitStop(ctrl->mEmitStopTime) { } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 2f9d58c6b1..7ac34d61ed 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -379,7 +379,7 @@ namespace NifOsg { public: ParticleSystemController(const Nif::NiParticleSystemController* ctrl); - ParticleSystemController(); + ParticleSystemController() = default; ParticleSystemController(const ParticleSystemController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, ParticleSystemController) @@ -387,8 +387,8 @@ namespace NifOsg void operator()(osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv); private: - float mEmitStart; - float mEmitStop; + float mEmitStart{ 0.f }; + float mEmitStop{ 0.f }; }; class PathController : public SceneUtil::NodeCallback, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f1492b9d0b..0e1d4091a9 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1061,7 +1061,7 @@ namespace NifOsg } } - void handleParticlePrograms(Nif::NiParticleModifierPtr affectors, Nif::NiParticleModifierPtr colliders, + void handleParticlePrograms(Nif::NiParticleModifierPtr modifier, Nif::NiParticleModifierPtr collider, osg::Group* attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) { @@ -1069,50 +1069,50 @@ namespace NifOsg attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); - for (; !affectors.empty(); affectors = affectors->mNext) + for (; !modifier.empty(); modifier = modifier->mNext) { - if (affectors->recType == Nif::RC_NiParticleGrowFade) + if (modifier->recType == Nif::RC_NiParticleGrowFade) { - const Nif::NiParticleGrowFade* gf = static_cast(affectors.getPtr()); + const Nif::NiParticleGrowFade* gf = static_cast(modifier.getPtr()); program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime)); } - else if (affectors->recType == Nif::RC_NiGravity) + else if (modifier->recType == Nif::RC_NiGravity) { - const Nif::NiGravity* gr = static_cast(affectors.getPtr()); + const Nif::NiGravity* gr = static_cast(modifier.getPtr()); program->addOperator(new GravityAffector(gr)); } - else if (affectors->recType == Nif::RC_NiParticleColorModifier) + else if (modifier->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier* cl - = static_cast(affectors.getPtr()); + = static_cast(modifier.getPtr()); if (cl->mData.empty()) continue; const Nif::NiColorData* clrdata = cl->mData.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } - else if (affectors->recType == Nif::RC_NiParticleRotation) + else if (modifier->recType == Nif::RC_NiParticleRotation) { // unused } else - Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; + Log(Debug::Info) << "Unhandled particle modifier " << modifier->recName << " in " << mFilename; } - for (; !colliders.empty(); colliders = colliders->mNext) + for (; !collider.empty(); collider = collider->mNext) { - if (colliders->recType == Nif::RC_NiPlanarCollider) + if (collider->recType == Nif::RC_NiPlanarCollider) { const Nif::NiPlanarCollider* planarcollider - = static_cast(colliders.getPtr()); + = static_cast(collider.getPtr()); program->addOperator(new PlanarCollider(planarcollider)); } - else if (colliders->recType == Nif::RC_NiSphericalCollider) + else if (collider->recType == Nif::RC_NiSphericalCollider) { const Nif::NiSphericalCollider* sphericalcollider - = static_cast(colliders.getPtr()); + = static_cast(collider.getPtr()); program->addOperator(new SphericalCollider(sphericalcollider)); } else - Log(Debug::Info) << "Unhandled particle collider " << colliders->recName << " in " << mFilename; + Log(Debug::Info) << "Unhandled particle collider " << collider->recName << " in " << mFilename; } } @@ -1124,7 +1124,7 @@ namespace NifOsg auto particleNode = static_cast(nifNode); if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) { - partsys->setQuota(partctrl->numParticles); + partsys->setQuota(partctrl->mParticles.size()); return; } @@ -1134,35 +1134,35 @@ namespace NifOsg osg::BoundingBox box; int i = 0; - for (const auto& particle : partctrl->particles) + for (const auto& particle : partctrl->mParticles) { if (i++ >= particledata->mActiveCount) break; - if (particle.lifespan <= 0) + if (particle.mLifespan <= 0) continue; - if (particle.vertex >= particledata->mVertices.size()) + if (particle.mCode >= particledata->mVertices.size()) continue; - ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); + ParticleAgeSetter particletemplate(std::max(0.f, particle.mAge)); osgParticle::Particle* created = partsys->createParticle(&particletemplate); - created->setLifeTime(particle.lifespan); + created->setLifeTime(particle.mLifespan); // Note this position and velocity is not correct for a particle system with absolute reference frame, // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up // post-load in the SceneManager. - created->setVelocity(particle.velocity); - const osg::Vec3f& position = particledata->mVertices[particle.vertex]; + created->setVelocity(particle.mVelocity); + const osg::Vec3f& position = particledata->mVertices[particle.mCode]; created->setPosition(position); - created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); + created->setColorRange(osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); created->setAlphaRange(osgParticle::rangef(1.f, 1.f)); - float size = partctrl->size; - if (particle.vertex < particledata->mSizes.size()) - size *= particledata->mSizes[particle.vertex]; + float size = partctrl->mInitialSize; + if (particle.mCode < particledata->mSizes.size()) + size *= particledata->mSizes[particle.mCode]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); @@ -1179,39 +1179,39 @@ namespace NifOsg std::vector targets; if (partctrl->recType == Nif::RC_NiBSPArrayController && !partctrl->emitAtVertex()) { - getAllNiNodes(partctrl->emitter.getPtr(), targets); + getAllNiNodes(partctrl->mEmitter.getPtr(), targets); } osg::ref_ptr emitter = new Emitter(targets); osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter; if (partctrl->noAutoAdjust()) - counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate); - else if (partctrl->lifetime == 0 && partctrl->lifetimeRandom == 0) + counter->setNumberOfParticlesPerSecondToCreate(partctrl->mBirthRate); + else if (partctrl->mLifetime == 0 && partctrl->mLifetimeVariation == 0) counter->setNumberOfParticlesPerSecondToCreate(0); else counter->setNumberOfParticlesPerSecondToCreate( - partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom / 2)); + partctrl->mParticles.size() / (partctrl->mLifetime + partctrl->mLifetimeVariation / 2)); emitter->setCounter(counter); - ParticleShooter* shooter = new ParticleShooter(partctrl->velocity - partctrl->velocityRandom * 0.5f, - partctrl->velocity + partctrl->velocityRandom * 0.5f, partctrl->horizontalDir, - partctrl->horizontalAngle, partctrl->verticalDir, partctrl->verticalAngle, partctrl->lifetime, - partctrl->lifetimeRandom); + ParticleShooter* shooter = new ParticleShooter(partctrl->mSpeed - partctrl->mSpeedVariation * 0.5f, + partctrl->mSpeed + partctrl->mSpeedVariation * 0.5f, partctrl->mPlanarAngle, + partctrl->mPlanarAngleVariation, partctrl->mDeclination, partctrl->mDeclinationVariation, + partctrl->mLifetime, partctrl->mLifetimeVariation); emitter->setShooter(shooter); emitter->setFlags(partctrl->mFlags); if (partctrl->recType == Nif::RC_NiBSPArrayController && partctrl->emitAtVertex()) { - emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); + emitter->setGeometryEmitterTarget(partctrl->mEmitter->recIndex); } else { osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; - placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); - placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); - placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); + placer->setXRange(-partctrl->mEmitterDimensions.x() / 2.f, partctrl->mEmitterDimensions.x() / 2.f); + placer->setYRange(-partctrl->mEmitterDimensions.y() / 2.f, partctrl->mEmitterDimensions.y() / 2.f); + placer->setZRange(-partctrl->mEmitterDimensions.z() / 2.f, partctrl->mEmitterDimensions.z() / 2.f); emitter->setPlacer(placer); } @@ -1280,11 +1280,11 @@ namespace NifOsg handleParticleInitialState(nifNode, partsys, partctrl); - partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size)); - partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); + partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->mInitialSize, partctrl->mInitialSize)); + partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); - if (!partctrl->emitter.empty()) + if (!partctrl->mEmitter.empty()) { osg::ref_ptr emitter = handleParticleEmitter(partctrl); emitter->setParticleSystem(partsys); @@ -1293,7 +1293,7 @@ namespace NifOsg // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later // moment. If the emitter node is placed later than the particle node, it'll have a single frame delay // in particle processing. But that shouldn't be a game-breaking issue. - mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); + mEmitterQueue.emplace_back(partctrl->mEmitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); setupController(partctrl, callback, animflags); @@ -1310,16 +1310,16 @@ namespace NifOsg partsys->update(0.0, nv); } - // affectors should be attached *after* the emitter in the scene graph for correct update order + // modifiers should be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct // localToWorldMatrix for transforming to particle space - handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); + handleParticlePrograms(partctrl->mModifier, partctrl->mCollider, parentNode, partsys.get(), rf); std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); - // particle system updater (after the emitters and affectors in the scene graph) + // particle system updater (after the emitters and modifiers in the scene graph) // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other // way osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater; From f271c4305ade0f117b31ccfb536b1ebb7a857947 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 05:11:40 +0300 Subject: [PATCH 0136/2167] Fix formatting --- components/nif/controller.hpp | 7 ++----- components/nifosg/controller.cpp | 1 - components/nifosg/controller.hpp | 9 ++++----- components/nifosg/nifloader.cpp | 11 ++++++----- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index a05ff247c5..afca97d885 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -334,7 +334,7 @@ namespace Nif { }; - template + template struct TypedNiInterpolator : public NiInterpolator { T mDefaultValue; @@ -346,10 +346,7 @@ namespace Nif mData.read(nif); } - void post(Reader& nif) override - { - mData.post(nif); - } + void post(Reader& nif) override { mData.post(nif); } }; using NiPoint3Interpolator = TypedNiInterpolator; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index c5511141a2..54e9e2bb16 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -185,7 +185,6 @@ namespace NifOsg if (!mScales.empty()) node->setScale(mScales.interpKey(time)); - } traverse(node, nv); diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 7ac34d61ed..df6fdb2a24 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -63,11 +63,10 @@ namespace NifOsg ValueInterpolator() = default; template , - std::is_same, std::is_same, - std::is_same>, - std::is_same>, + typename = std::enable_if_t< + std::conjunction_v, std::is_same, + std::is_same, std::is_same>, + std::is_same>, T>> ValueInterpolator(const T* interpolator) : mDefaultVal(interpolator->mDefaultValue) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0e1d4091a9..fc8da57dbd 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -995,9 +995,8 @@ namespace NifOsg continue; if (!interp.empty() && interp->recType != Nif::RC_NiPoint3Interpolator) { - Log(Debug::Error) - << "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex - << " in " << mFilename << ": " << interp->recName; + Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController " + << matctrl->recIndex << " in " << mFilename << ": " << interp->recName; continue; } osg::ref_ptr osgctrl = new MaterialColorController(matctrl, baseMaterial); @@ -1280,8 +1279,10 @@ namespace NifOsg handleParticleInitialState(nifNode, partsys, partctrl); - partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->mInitialSize, partctrl->mInitialSize)); - partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); + partsys->getDefaultParticleTemplate().setSizeRange( + osgParticle::rangef(partctrl->mInitialSize, partctrl->mInitialSize)); + partsys->getDefaultParticleTemplate().setColorRange( + osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); if (!partctrl->mEmitter.empty()) From 2f8229a54d2cee921fc86cca5f31dfbf23a7ad6e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 07:01:12 +0300 Subject: [PATCH 0137/2167] Fix bit shift --- components/nif/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 1875488143..99317cda57 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -272,7 +272,7 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) nif->read(mPathFlags); else - mPathFlags = (mFlags >> 16); + mPathFlags = (mFlags >> 4); nif->read(mBankDirection); nif->read(mMaxBankAngle); From ebb75008f8552ad3e4a1ee7d46d28efa586b691a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 14 Sep 2023 09:17:59 +0400 Subject: [PATCH 0138/2167] Do not use playlist for title music --- apps/openmw/engine.cpp | 8 +++++++- apps/openmw/mwsound/soundmanagerimp.cpp | 17 ++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2966800014..df89cd3eb6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -913,7 +913,13 @@ void OMW::Engine::go() { // start in main menu mWindowManager->pushGuiMode(MWGui::GM_MainMenu); - mSoundManager->playPlaylist("Title"); + + std::string titlefile = "music/special/morrowind title.mp3"; + if (mVFS->exists(titlefile)) + mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special); + else + Log(Debug::Warning) << "Title music not found"; + std::string_view logo = Fallback::Map::getString("Movies_Morrowind_Logo"); if (!logo.empty()) mWindowManager->playVideo(logo, /*allowSkipping*/ true, /*overrideSounds*/ false); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 9cc67f5af4..be8bae20a6 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -132,15 +132,6 @@ namespace MWSound Log(Debug::Info) << stream.str(); } - - // TODO: dehardcode this - std::vector titleMusic; - std::string_view titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - titleMusic.emplace_back(titlefile); - else - Log(Debug::Warning) << "Title music not found"; - mMusicFiles["Title"] = titleMusic; } SoundManager::~SoundManager() @@ -1143,6 +1134,14 @@ namespace MWSound if (!mOutput->isInitialized() || mPlaybackPaused) return; + MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); + if (state == MWBase::StateManager::State_NoGame && !isMusicPlaying()) + { + std::string titlefile = "music/special/morrowind title.mp3"; + if (mVFS->exists(titlefile)) + streamMusic(titlefile, MWSound::MusicType::Special); + } + updateSounds(duration); if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) { From db72a9118094acc17734af22266cc78c8af6da00 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 14 Sep 2023 12:54:20 +0400 Subject: [PATCH 0139/2167] Add a way to check if sound system is enabled --- apps/openmw/mwbase/soundmanager.hpp | 3 +++ apps/openmw/mwlua/soundbindings.cpp | 2 ++ apps/openmw/mwsound/soundmanagerimp.hpp | 3 +++ files/lua_api/openmw/core.lua | 7 +++++++ 4 files changed, 15 insertions(+) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 85062ace8d..1f0337869b 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -109,6 +109,9 @@ namespace MWBase virtual void processChangedSettings(const std::set>& settings) = 0; + virtual bool isEnabled() const = 0; + ///< Returns true if sound system is enabled + virtual void stopMusic() = 0; ///< Stops music if it's playing diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index 102f88ce61..dc45a672b4 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -112,6 +112,8 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table api(lua, sol::create); + api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); }; + api["playSound3d"] = [](std::string_view soundId, const Object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 87fb109791..94d407c11b 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -173,6 +173,9 @@ namespace MWSound void processChangedSettings(const Settings::CategorySettingVector& settings) override; + bool isEnabled() const override { return mOutput->isInitialized(); } + ///< Returns true if sound system is enabled + void stopMusic() override; ///< Stops music if it's playing diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index af09274981..a6e9cb361c 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -749,6 +749,13 @@ --- @{#Sound}: Sounds and Speech -- @field [parent=#core] #Sound sound +--- +-- Checks if sound system is enabled (any functions to play sounds are no-ops when it is disabled). +-- It can not be enabled or disabled during runtime. +-- @function [parent=#Sound] isEnabled +-- @return #boolean +-- @usage local enabled = core.sound.isEnabled(); + --- -- Play a 3D sound, attached to object -- @function [parent=#Sound] playSound3d From 4fa8756791c8f0c2974eb2185ae21c185d4f1df2 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 12 Sep 2023 11:34:22 +0200 Subject: [PATCH 0140/2167] Silent coverity warning --- apps/openmw/mwworld/cellref.cpp | 2 +- apps/openmw/mwworld/cellref.hpp | 2 +- apps/openmw/mwworld/ptrregistry.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 37e1be0ddf..35a04574ee 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -30,7 +30,7 @@ namespace MWWorld { } - const ESM::RefNum& CellRef::getRefNum() const + const ESM::RefNum& CellRef::getRefNum() const noexcept { return std::visit(ESM::VisitOverload{ [&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mId; }, diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 73e721278e..1943fd9b54 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -27,7 +27,7 @@ namespace MWWorld explicit CellRef(const ESM4::ActorCharacter& ref); // Note: Currently unused for items in containers - const ESM::RefNum& getRefNum() const; + const ESM::RefNum& getRefNum() const noexcept; // Returns RefNum. // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. diff --git a/apps/openmw/mwworld/ptrregistry.hpp b/apps/openmw/mwworld/ptrregistry.hpp index 3b535a220c..b640a22bfe 100644 --- a/apps/openmw/mwworld/ptrregistry.hpp +++ b/apps/openmw/mwworld/ptrregistry.hpp @@ -49,7 +49,7 @@ namespace MWWorld if (!refNum.isSet()) return; auto it = mIndex.find(refNum); - if (it != mIndex.end() && it->second.getBase() == &ref) + if (it != mIndex.end() && it->second.mRef == &ref) { mIndex.erase(it); ++mRevision; From 56ea3e3879491582e539317d3c726f87b980ebac Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Sep 2023 18:49:46 +0400 Subject: [PATCH 0141/2167] Add a way to toggle AI to Lua debug package --- apps/openmw/mwlua/debugbindings.cpp | 5 +++++ files/lua_api/openmw/debug.lua | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 7f64188ff5..41b8092e37 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -3,7 +3,9 @@ #include "luamanagerimp.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" + #include "../mwrender/postprocessor.hpp" #include "../mwrender/renderingmanager.hpp" @@ -38,6 +40,9 @@ namespace MWLua api["toggleGodMode"] = []() { MWBase::Environment::get().getWorld()->toggleGodMode(); }; api["isGodMode"] = []() { return MWBase::Environment::get().getWorld()->getGodModeState(); }; + api["toggleAI"] = []() { MWBase::Environment::get().getMechanicsManager()->toggleAI(); }; + api["isAIEnabled"] = []() { return MWBase::Environment::get().getMechanicsManager()->isAIActive(); }; + api["toggleCollision"] = []() { MWBase::Environment::get().getWorld()->toggleCollisionMode(); }; api["isCollisionEnabled"] = []() { auto world = MWBase::Environment::get().getWorld(); diff --git a/files/lua_api/openmw/debug.lua b/files/lua_api/openmw/debug.lua index fba649c16e..fdce2dedf8 100644 --- a/files/lua_api/openmw/debug.lua +++ b/files/lua_api/openmw/debug.lua @@ -35,6 +35,15 @@ -- @function [parent=#Debug] isGodMode -- @return #boolean +--- +-- Toggles AI +-- @function [parent=#Debug] toggleAI + +--- +-- Is AI enabled +-- @function [parent=#Debug] isAIEnabled +-- @return #boolean + --- -- Toggles collisions -- @function [parent=#Debug] toggleCollision From 6769d102036bac74a81072edda64701732258cff Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Sep 2023 19:22:24 +0400 Subject: [PATCH 0142/2167] Fix documentation error --- files/lua_api/openmw/core.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index a6e9cb361c..b7a3b65ba6 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -827,7 +827,7 @@ --- -- Play an animated voiceover. Has two overloads: -- --- * With an "object" argument: play sound for given object, with speaking animation if possible equipment slots. +-- * With an "object" argument: play sound for given object, with speaking animation if possible -- * Without an "object" argument: play sound globally, without object -- @function [parent=#Sound] say -- @param #string fileName Path to sound file in VFS From 72b8ff82ffb16ff009521fb09a0bae6f44840e58 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 9 Sep 2023 11:42:55 +0200 Subject: [PATCH 0143/2167] !3362 with safe reloadlua --- apps/openmw/mwlua/debugbindings.cpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.cpp | 9 ++++++++- apps/openmw/mwlua/luamanagerimp.hpp | 7 +++++-- files/lua_api/openmw/debug.lua | 13 +++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 7f64188ff5..aa5ececc22 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -44,6 +44,11 @@ namespace MWLua return world->isActorCollisionEnabled(world->getPlayerPtr()); }; + api["toggleMWScript"] = []() { MWBase::Environment::get().getWorld()->toggleScripts(); }; + api["isMWScriptEnabled"] = []() { return MWBase::Environment::get().getWorld()->getScriptsEnabled(); }; + + api["reloadLua"] = []() { MWBase::Environment::get().getLuaManager()->reloadAllScripts(); }; + api["NAV_MESH_RENDER_MODE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "AreaType", MWRender::NavMeshMode::AreaType }, diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 23aca1fd46..fc686dcbb3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -239,6 +239,13 @@ namespace MWLua mInGameConsoleMessages.clear(); applyDelayedActions(); + + if (mReloadAllScriptsRequested) + { + // Reloading right after `applyDelayedActions` to guarantee that no delayed actions are currently queued. + reloadAllScriptsImpl(); + mReloadAllScriptsRequested = false; + } } void LuaManager::applyDelayedActions() @@ -477,7 +484,7 @@ namespace MWLua scripts->load(data); } - void LuaManager::reloadAllScripts() + void LuaManager::reloadAllScriptsImpl() { Log(Debug::Info) << "Reload Lua"; diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 3cbef6d945..9f4c0096b5 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -116,8 +116,9 @@ namespace MWLua void loadLocalScripts(const MWWorld::Ptr& ptr, const ESM::LuaScripts& data) override; void setContentFileMapping(const std::map& mapping) override { mContentFileMapping = mapping; } - // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. - void reloadAllScripts() override; + // At the end of the next `synchronizedUpdate` drops script cache and reloads all scripts. + // Calls `onSave` and `onLoad` for every script. + void reloadAllScripts() override { mReloadAllScriptsRequested = true; } void handleConsoleCommand( const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr) override; @@ -149,12 +150,14 @@ namespace MWLua void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, std::optional autoStartConf = std::nullopt); + void reloadAllScriptsImpl(); bool mInitialized = false; bool mGlobalScriptsStarted = false; bool mProcessingInputEvents = false; bool mApplyingDelayedActions = false; bool mNewGameStarted = false; + bool mReloadAllScriptsRequested = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; diff --git a/files/lua_api/openmw/debug.lua b/files/lua_api/openmw/debug.lua index fba649c16e..52ed79aa00 100644 --- a/files/lua_api/openmw/debug.lua +++ b/files/lua_api/openmw/debug.lua @@ -44,6 +44,19 @@ -- @function [parent=#Debug] isCollisionEnabled -- @return #boolean +--- +-- Toggles MWScripts +-- @function [parent=#Debug] toggleMWScript + +--- +-- Is MWScripts enabled +-- @function [parent=#Debug] isMWScriptEnabled +-- @return #boolean + +--- +-- Reloads all Lua scripts +-- @function [parent=#Debug] reloadLua + --- -- Navigation mesh rendering modes -- @type NAV_MESH_RENDER_MODE From 8bef84c16bdb99c4b91f50b690a857fe97c7070d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 15 Sep 2023 19:29:38 +0200 Subject: [PATCH 0144/2167] util.color (0, 0, 0) -> util.color (`rgb(0, 0, 0)`) --- docs/source/reference/lua-scripting/widgets/image.rst | 2 +- docs/source/reference/lua-scripting/widgets/text.rst | 4 ++-- docs/source/reference/lua-scripting/widgets/textedit.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/lua-scripting/widgets/image.rst b/docs/source/reference/lua-scripting/widgets/image.rst index c461a69a65..49eaec79e0 100644 --- a/docs/source/reference/lua-scripting/widgets/image.rst +++ b/docs/source/reference/lua-scripting/widgets/image.rst @@ -21,5 +21,5 @@ Properties - boolean (false) - Tile the texture vertically * - color - - util.color (1, 1, 1) + - util.color (``rgb(1, 1, 1)``) - Modulate constant color with the color of the image texture. diff --git a/docs/source/reference/lua-scripting/widgets/text.rst b/docs/source/reference/lua-scripting/widgets/text.rst index 074100577a..2970724c74 100644 --- a/docs/source/reference/lua-scripting/widgets/text.rst +++ b/docs/source/reference/lua-scripting/widgets/text.rst @@ -22,7 +22,7 @@ Properties - number (10) - The size of the text. * - textColor - - util.color (0, 0, 0, 1) + - util.color (``rgb(0, 0, 0)``) - The color of the text. * - multiline - boolean (false) @@ -40,5 +40,5 @@ Properties - boolean (false) - Whether to render a shadow behind the text. * - textShadowColor - - util.color (0, 0, 0, 1) + - util.color (``rgb(0, 0, 0)``) - The color of the text shadow. diff --git a/docs/source/reference/lua-scripting/widgets/textedit.rst b/docs/source/reference/lua-scripting/widgets/textedit.rst index 4e80c18197..6cb18fc685 100644 --- a/docs/source/reference/lua-scripting/widgets/textedit.rst +++ b/docs/source/reference/lua-scripting/widgets/textedit.rst @@ -18,7 +18,7 @@ Properties - number (10) - The size of the text. * - textColor - - util.color (0, 0, 0, 1) + - util.color (``rgb(0, 0, 0)``) - The color of the text. * - multiline - boolean (false) From c67b866a11806d8e02ee5857e273f4ffb7265de3 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 15 Sep 2023 19:38:09 +0200 Subject: [PATCH 0145/2167] Replace `const ESM::RefNum&` -> `ESM::RefNum` --- apps/navmeshtool/worldspacedata.cpp | 3 +-- apps/openmw/mwlua/engineevents.cpp | 4 ++-- apps/openmw/mwlua/luaevents.cpp | 2 +- apps/openmw/mwlua/object.hpp | 2 +- apps/openmw/mwlua/objectlists.cpp | 2 +- apps/openmw/mwlua/userdataserializer.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 7 +++---- apps/openmw/mwrender/objectpaging.hpp | 5 ++--- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwworld/cellref.cpp | 10 +++++----- apps/openmw/mwworld/cellref.hpp | 4 ++-- apps/openmw/mwworld/cellreflist.hpp | 2 +- apps/openmw/mwworld/cellstore.cpp | 4 ++-- apps/openmw/mwworld/ptrregistry.hpp | 2 +- apps/openmw/mwworld/scene.cpp | 6 +++--- components/resource/foreachbulletobject.cpp | 3 +-- 16 files changed, 28 insertions(+), 32 deletions(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 8d2ac7382b..7f579f8fde 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -103,8 +103,7 @@ namespace NavMeshTool Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; - const auto getKey - = [](const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + const auto getKey = [](const EsmLoader::Record& v) -> ESM::RefNum { return v.mValue.mRefNum; }; std::vector result = prepareRecords(cellRefs, getKey); Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index c838ccfcba..0fbb13f1cf 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -87,7 +87,7 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } private: - MWWorld::Ptr getPtr(const ESM::RefNum& id) const + MWWorld::Ptr getPtr(ESM::RefNum id) const { MWWorld::Ptr res = mWorldModel->getPtr(id); if (res.isEmpty() && Settings::lua().mLuaDebug) @@ -103,7 +103,7 @@ namespace MWLua return ptr.getRefData().getLuaScripts(); } - LocalScripts* getLocalScripts(const ESM::RefNum& id) const { return getLocalScripts(getPtr(id)); } + LocalScripts* getLocalScripts(ESM::RefNum id) const { return getLocalScripts(getPtr(id)); } GlobalScripts& mGlobalScripts; MWWorld::WorldModel* mWorldModel = MWBase::Environment::get().getWorldModel(); diff --git a/apps/openmw/mwlua/luaevents.cpp b/apps/openmw/mwlua/luaevents.cpp index b036fea3b6..02ea3415d2 100644 --- a/apps/openmw/mwlua/luaevents.cpp +++ b/apps/openmw/mwlua/luaevents.cpp @@ -52,7 +52,7 @@ namespace MWLua } template - static void saveEvent(ESM::ESMWriter& esm, const ESM::RefNum& dest, const Event& event) + static void saveEvent(ESM::ESMWriter& esm, ESM::RefNum dest, const Event& event) { esm.writeHNString("LUAE", event.mEventName); esm.writeFormId(dest, true); diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index fcf225de85..b42b092302 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -16,7 +16,7 @@ namespace MWLua // ObjectId is a unique identifier of a game object. // It can change only if the order of content files was change. using ObjectId = ESM::RefNum; - inline const ObjectId& getId(const MWWorld::Ptr& ptr) + inline ObjectId getId(const MWWorld::Ptr& ptr) { return ptr.getCellRef().getRefNum(); } diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index 40b1ab93fa..e7b3bb2e06 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -75,7 +75,7 @@ namespace MWLua if (mChanged) { mList->clear(); - for (const ObjectId& id : mSet) + for (ObjectId id : mSet) mList->push_back(id); mChanged = false; } diff --git a/apps/openmw/mwlua/userdataserializer.cpp b/apps/openmw/mwlua/userdataserializer.cpp index 6565ee0bde..478f725d7c 100644 --- a/apps/openmw/mwlua/userdataserializer.cpp +++ b/apps/openmw/mwlua/userdataserializer.cpp @@ -52,7 +52,7 @@ namespace MWLua { std::vector buf; buf.reserve(objList->size()); - for (const ESM::RefNum& v : *objList) + for (ESM::RefNum v : *objList) buf.push_back({ Misc::toLittleEndian(v.mIndex), Misc::toLittleEndian(v.mContentFile) }); append(out, sObjListTypeName, buf.data(), buf.size() * sizeof(ESM::RefNum)); } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 3579badee2..385ce46fff 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -437,7 +437,7 @@ namespace MWRender class AddRefnumMarkerVisitor : public osg::NodeVisitor { public: - AddRefnumMarkerVisitor(const ESM::RefNum& refnum) + AddRefnumMarkerVisitor(ESM::RefNum refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mRefnum(refnum) { @@ -896,7 +896,7 @@ namespace MWRender }; bool ObjectPaging::enableObject( - int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) + int type, ESM::RefNum refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled) { if (!typeFilter(type, false)) return false; @@ -923,8 +923,7 @@ namespace MWRender return true; } - bool ObjectPaging::blacklistObject( - int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) + bool ObjectPaging::blacklistObject(int type, ESM::RefNum refnum, const osg::Vec3f& pos, const osg::Vec2i& cell) { if (!typeFilter(type, false)) return false; diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index d677c172f2..6085887b9c 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -41,11 +41,10 @@ namespace MWRender unsigned int getNodeMask() override; /// @return true if view needs rebuild - bool enableObject( - int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled); + bool enableObject(int type, ESM::RefNum refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled); /// @return true if view needs rebuild - bool blacklistObject(int type, const ESM::RefNum& refnum, const osg::Vec3f& pos, const osg::Vec2i& cell); + bool blacklistObject(int type, ESM::RefNum refnum, const osg::Vec3f& pos, const osg::Vec2i& cell); void clear(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c9750ec0b9..6155325410 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1644,7 +1644,7 @@ namespace MWRender { if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging) return; - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile()) return; if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3(), diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 35a04574ee..d9a017fe68 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -30,17 +30,17 @@ namespace MWWorld { } - const ESM::RefNum& CellRef::getRefNum() const noexcept + ESM::RefNum CellRef::getRefNum() const noexcept { return std::visit(ESM::VisitOverload{ - [&](const ESM4::Reference& ref) -> const ESM::RefNum& { return ref.mId; }, - [&](const ESM4::ActorCharacter& ref) -> const ESM::RefNum& { return ref.mId; }, - [&](const ESM::CellRef& ref) -> const ESM::RefNum& { return ref.mRefNum; }, + [&](const ESM4::Reference& ref) -> ESM::RefNum { return ref.mId; }, + [&](const ESM4::ActorCharacter& ref) -> ESM::RefNum { return ref.mId; }, + [&](const ESM::CellRef& ref) -> ESM::RefNum { return ref.mRefNum; }, }, mCellRef.mVariant); } - const ESM::RefNum& CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) + ESM::RefNum CellRef::getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum) { ESM::RefNum& refNum = std::visit(ESM::VisitOverload{ [&](ESM4::Reference& ref) -> ESM::RefNum& { return ref.mId; }, diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 1943fd9b54..954babe481 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -27,11 +27,11 @@ namespace MWWorld explicit CellRef(const ESM4::ActorCharacter& ref); // Note: Currently unused for items in containers - const ESM::RefNum& getRefNum() const noexcept; + ESM::RefNum getRefNum() const noexcept; // Returns RefNum. // If RefNum is not set, assigns a generated one and changes the "lastAssignedRefNum" counter. - const ESM::RefNum& getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum); + ESM::RefNum getOrAssignRefNum(ESM::RefNum& lastAssignedRefNum); void setRefNum(ESM::RefNum refNum); diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 12f086e15d..187fff4287 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -38,7 +38,7 @@ namespace MWWorld } /// Remove all references with the given refNum from this list. - void remove(const ESM::RefNum& refNum) + void remove(ESM::RefNum refNum) { for (typename List::iterator it = mList.begin(); it != mList.end();) { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 61de2ab90d..f7ec3ddcba 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -660,10 +660,10 @@ namespace MWWorld class RefNumSearchVisitor { - const ESM::RefNum& mRefNum; + ESM::RefNum mRefNum; public: - RefNumSearchVisitor(const ESM::RefNum& refNum) + RefNumSearchVisitor(ESM::RefNum refNum) : mRefNum(refNum) { } diff --git a/apps/openmw/mwworld/ptrregistry.hpp b/apps/openmw/mwworld/ptrregistry.hpp index b640a22bfe..c5ce371538 100644 --- a/apps/openmw/mwworld/ptrregistry.hpp +++ b/apps/openmw/mwworld/ptrregistry.hpp @@ -45,7 +45,7 @@ namespace MWWorld void remove(const LiveCellRefBase& ref) noexcept { - const ESM::RefNum& refNum = ref.mRef.getRefNum(); + ESM::RefNum refNum = ref.mRef.getRefNum(); if (!refNum.isSet()) return; auto it = mIndex.find(refNum); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b5aff875bd..3d96de6749 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,7 +111,7 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else @@ -259,7 +259,7 @@ namespace return false; } - bool removeFromSorted(const ESM::RefNum& refNum, std::vector& pagedRefs) + bool removeFromSorted(ESM::RefNum refNum, std::vector& pagedRefs) { const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum); if (it == pagedRefs.end() || *it != refNum) @@ -274,7 +274,7 @@ namespace MWWorld void Scene::removeFromPagedRefs(const Ptr& ptr) { - const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); + ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs)) { if (!ptr.getRefData().getBaseNode()) diff --git a/components/resource/foreachbulletobject.cpp b/components/resource/foreachbulletobject.cpp index dbad2aea62..b936e39a2a 100644 --- a/components/resource/foreachbulletobject.cpp +++ b/components/resource/foreachbulletobject.cpp @@ -79,8 +79,7 @@ namespace Resource Log(Debug::Debug) << "Loaded " << cellRefs.size() << " cell refs"; - const auto getKey - = [](const EsmLoader::Record& v) -> const ESM::RefNum& { return v.mValue.mRefNum; }; + const auto getKey = [](const EsmLoader::Record& v) -> ESM::RefNum { return v.mValue.mRefNum; }; std::vector result = prepareRecords(cellRefs, getKey); Log(Debug::Debug) << "Prepared " << result.size() << " unique cell refs"; From c9300cac318adc27734f0ad624fa2dd532dde3a0 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 12:06:52 +0300 Subject: [PATCH 0146/2167] Update NIF node records, first pass --- components/nif/node.cpp | 209 +++++++++++++++----------------- components/nif/node.hpp | 160 ++++++++++++------------ components/nifosg/nifloader.cpp | 12 +- 3 files changed, 187 insertions(+), 194 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index a9e4f183b2..b515f5038f 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -1,8 +1,9 @@ #include "node.hpp" -#include #include +#include + #include "data.hpp" #include "exception.hpp" #include "physics.hpp" @@ -10,6 +11,7 @@ namespace Nif { + void NiBoundingVolume::read(NIFStream* nif) { nif->read(type); @@ -19,52 +21,49 @@ namespace Nif break; case SPHERE_BV: { - nif->read(sphere); + nif->read(mSphere); break; } case BOX_BV: { - box.center = nif->getVector3(); + nif->read(box.center); nif->read(box.axes); - box.extents = nif->getVector3(); + nif->read(box.extents); break; } case CAPSULE_BV: { - capsule.center = nif->getVector3(); - capsule.axis = nif->getVector3(); - capsule.extent = nif->getFloat(); - capsule.radius = nif->getFloat(); + nif->read(mCapsule.mCenter); + nif->read(mCapsule.mAxis); + nif->read(mCapsule.mExtent); + nif->read(mCapsule.mRadius); break; } case LOZENGE_BV: { - lozenge.radius = nif->getFloat(); + nif->read(mLozenge.mRadius); if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 1, 0)) { - lozenge.extent0 = nif->getFloat(); - lozenge.extent1 = nif->getFloat(); + nif->read(mLozenge.mExtent0); + nif->read(mLozenge.mExtent1); } - lozenge.center = nif->getVector3(); - lozenge.axis0 = nif->getVector3(); - lozenge.axis1 = nif->getVector3(); + nif->read(mLozenge.mCenter); + nif->read(mLozenge.mAxis0); + nif->read(mLozenge.mAxis1); break; } case UNION_BV: { - unsigned int numChildren = nif->getUInt(); - if (numChildren == 0) - break; - children.resize(numChildren); - for (NiBoundingVolume& child : children) + mChildren.resize(nif->get()); + for (NiBoundingVolume& child : mChildren) child.read(nif); break; } case HALFSPACE_BV: { - halfSpace.plane = osg::Plane(nif->getVector4()); + mHalfSpace.mPlane = osg::Plane(nif->get()); if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 1, 0)) - halfSpace.origin = nif->getVector3(); + nif->read(mHalfSpace.mOrigin); break; } default: @@ -152,26 +151,25 @@ namespace Nif { if (nif->getVersion() < NIFStream::generateVersion(10, 0, 1, 0)) return; - unsigned int num = 0; - if (nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 3) && nif->get()) - num = 1; - else if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) - num = nif->getUInt(); - - nif->readVector(names, num); - nif->readVector(extra, num); if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) - active = nif->getUInt(); + mNames.resize(nif->get()); + else if (nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 3)) + mNames.resize(nif->get()); + nif->readVector(mNames, mNames.size()); + nif->readVector(mExtra, mNames.size()); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) + nif->read(mActive); if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - nif->read(needsUpdate); + nif->read(mNeedsUpdate); } void NiGeometry::read(NIFStream* nif) { NiAVObject::read(nif); + data.read(nif); skin.read(nif); - material.read(nif); + mMaterial.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { @@ -183,6 +181,7 @@ namespace Nif void NiGeometry::post(Reader& nif) { NiAVObject::post(nif); + data.post(nif); skin.post(nif); shaderprop.post(nif); @@ -194,83 +193,84 @@ namespace Nif void BSLODTriShape::read(NIFStream* nif) { NiTriShape::read(nif); - lod0 = nif->getUInt(); - lod1 = nif->getUInt(); - lod2 = nif->getUInt(); - } - void NiCamera::Camera::read(NIFStream* nif) - { - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - cameraFlags = nif->getUShort(); - left = nif->getFloat(); - right = nif->getFloat(); - top = nif->getFloat(); - bottom = nif->getFloat(); - nearDist = nif->getFloat(); - farDist = nif->getFloat(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(orthographic); - vleft = nif->getFloat(); - vright = nif->getFloat(); - vtop = nif->getFloat(); - vbottom = nif->getFloat(); - - LOD = nif->getFloat(); + nif->readArray(mLOD); } void NiCamera::read(NIFStream* nif) { NiAVObject::read(nif); - cam.read(nif); - - nif->getInt(); // -1 - nif->getInt(); // 0 + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mCameraFlags); + nif->read(mLeft); + nif->read(mRight); + nif->read(mTop); + nif->read(mBottom); + nif->read(mNearDist); + nif->read(mFarDist); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mOrthographic); + nif->read(mVLeft); + nif->read(mVRight); + nif->read(mVTop); + nif->read(mVBottom); + nif->read(mLODAdjust); + mScene.read(nif); + nif->skip(4); // Unused if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 1, 0)) - nif->getInt(); // 0 + nif->skip(4); // Unused + } + + void NiCamera::post(Reader& nif) + { + NiAVObject::post(nif); + + mScene.post(nif); } void NiSwitchNode::read(NIFStream* nif) { NiNode::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - switchFlags = nif->getUShort(); - initialIndex = nif->getUInt(); + nif->read(switchFlags); + nif->read(initialIndex); } void NiLODNode::read(NIFStream* nif) { NiSwitchNode::read(nif); - if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW - && nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 0)) - lodCenter = nif->getVector3(); - else if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) + + if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) { nif->skip(4); // NiLODData, unsupported at the moment return; } - unsigned int numLodLevels = nif->getUInt(); - for (unsigned int i = 0; i < numLodLevels; ++i) + if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) + nif->read(lodCenter); + + lodLevels.resize(nif->get()); + for (LODRange& level : lodLevels) { - LODRange r; - r.minRange = nif->getFloat(); - r.maxRange = nif->getFloat(); - lodLevels.push_back(r); + nif->read(level.minRange); + nif->read(level.maxRange); } } void NiFltAnimationNode::read(NIFStream* nif) { NiSwitchNode::read(nif); - mDuration = nif->getFloat(); + + nif->read(mDuration); } void NiSortAdjustNode::read(NIFStream* nif) { NiNode::read(nif); - mMode = nif->getInt(); + + mMode = static_cast(nif->get()); if (nif->getVersion() <= NIFStream::generateVersion(20, 0, 0, 3)) mSubSorter.read(nif); } @@ -278,14 +278,16 @@ namespace Nif void NiSortAdjustNode::post(Reader& nif) { NiNode::post(nif); + mSubSorter.post(nif); } void NiBillboardNode::read(NIFStream* nif) { NiNode::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mMode = nif->getUShort() & 0x7; + mMode = nif->get() & 0x7; else mMode = (mFlags >> 5) & 0x3; } @@ -293,8 +295,9 @@ namespace Nif void NiDefaultAVObjectPalette::read(NIFStream* nif) { mScene.read(nif); - size_t numObjects = nif->getUInt(); - for (size_t i = 0; i < numObjects; i++) + uint32_t numObjects; + nif->read(numObjects); + for (uint32_t i = 0; i < numObjects; i++) mObjects[nif->getSizedString()].read(nif); } @@ -308,6 +311,7 @@ namespace Nif void BSTreeNode::read(NIFStream* nif) { NiNode::read(nif); + readRecordList(nif, mBones1); readRecordList(nif, mBones2); } @@ -315,6 +319,7 @@ namespace Nif void BSTreeNode::post(Reader& nif) { NiNode::post(nif); + postRecordList(nif, mBones1); postRecordList(nif, mBones2); } @@ -322,67 +327,53 @@ namespace Nif void BSMultiBoundNode::read(NIFStream* nif) { NiNode::read(nif); + mMultiBound.read(nif); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SKY) - mType = nif->getUInt(); + nif->read(mType); } void BSMultiBoundNode::post(Reader& nif) { NiNode::post(nif); + mMultiBound.post(nif); } void BSTriShape::read(NIFStream* nif) { NiAVObject::read(nif); - nif->read(mBoundingSphere); - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) - { + nif->read(mBoundingSphere); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) nif->readArray(mBoundMinMax); - } mSkin.read(nif); mShaderProperty.read(nif); mAlphaProperty.read(nif); - mVertDesc.read(nif); - unsigned int triNum; - if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) - { - triNum = nif->get(); - } + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + mTriangles.resize(nif->get() * 3); else - { - nif->read(triNum); - } - - unsigned short vertNum; - nif->read(vertNum); + mTriangles.resize(nif->get() * 3); + mVertData.resize(nif->get()); nif->read(mDataSize); - - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) + if (mDataSize > 0) { - mVertData.resize(vertNum); for (auto& vertex : mVertData) vertex.read(nif, mVertDesc.mFlags); + nif->readVector(mTriangles, mTriangles.size()); } - else if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) - { - throw Nif::Exception("FO4 BSTriShape is not supported yet: ", nif->getFile().getFilename()); - } - - if (mDataSize > 0) - nif->readVector(mTriangles, triNum * 3); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) { nif->read(mParticleDataSize); if (mParticleDataSize > 0) { - throw Nif::Exception("Unhandled Particle Data in BSTriShape: ", nif->getFile().getFilename()); + nif->readVector(mParticleVerts, mVertData.size() * 3); + nif->readVector(mParticleNormals, mVertData.size() * 3); + nif->readVector(mParticleTriangles, mTriangles.size()); } } } @@ -390,6 +381,7 @@ namespace Nif void BSTriShape::post(Reader& nif) { NiAVObject::post(nif); + mSkin.post(nif); mShaderProperty.post(nif); mAlphaProperty.post(nif); @@ -443,7 +435,6 @@ namespace Nif if (normalsFlag) { nif->readArray(mNormal); - nif->read(mBitangentY); } @@ -451,7 +442,6 @@ namespace Nif == (BSVertexDesc::VertexAttribute::Normals | BSVertexDesc::VertexAttribute::Tangents)) { nif->readArray(mTangent); - nif->read(mBitangentZ); } @@ -468,14 +458,14 @@ namespace Nif if (flags & BSVertexDesc::VertexAttribute::Eye_Data) { - throw Nif::Exception("Unhandled Eye Data in BSTriShape: ", nif->getFile().getFilename()); - // nif->read(mEyeData); + nif->read(mEyeData); } } void BSValueNode::read(NIFStream* nif) { NiNode::read(nif); + nif->read(mValue); nif->read(mValueFlags); } @@ -483,6 +473,7 @@ namespace Nif void BSOrderedNode::read(NIFStream* nif) { NiNode::read(nif); + nif->read(mAlphaSortBound); nif->read(mStaticBound); } @@ -490,8 +481,10 @@ namespace Nif void BSRangeNode::read(NIFStream* nif) { NiNode::read(nif); + nif->read(mMin); nif->read(mMax); nif->read(mCurrent); } + } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 51781a5290..d837cbe3da 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -35,29 +35,29 @@ namespace Nif struct NiCapsuleBV { - osg::Vec3f center, axis; - float extent{ 0.f }, radius{ 0.f }; + osg::Vec3f mCenter, mAxis; + float mExtent{ 0.f }, mRadius{ 0.f }; }; struct NiLozengeBV { - float radius{ 0.f }, extent0{ 0.f }, extent1{ 0.f }; - osg::Vec3f center, axis0, axis1; + float mRadius{ 0.f }, mExtent0{ 0.f }, mExtent1{ 0.f }; + osg::Vec3f mCenter, mAxis0, mAxis1; }; struct NiHalfSpaceBV { - osg::Plane plane; - osg::Vec3f origin; + osg::Plane mPlane; + osg::Vec3f mOrigin; }; uint32_t type{ BASE_BV }; - osg::BoundingSpheref sphere; + osg::BoundingSpheref mSphere; NiBoxBV box; - NiCapsuleBV capsule; - NiLozengeBV lozenge; - std::vector children; - NiHalfSpaceBV halfSpace; + NiCapsuleBV mCapsule; + NiLozengeBV mLozenge; + std::vector mChildren; + NiHalfSpaceBV mHalfSpace; void read(NIFStream* nif); }; @@ -68,7 +68,7 @@ namespace Nif // NiAVObject is an object that is a part of the main NIF tree. It has // a parent node (unless it's the root) and transformation relative to its parent. - struct NiAVObject : public NiObjectNET + struct NiAVObject : NiObjectNET { enum Flags { @@ -130,16 +130,17 @@ namespace Nif struct MaterialData { - std::vector names; - std::vector extra; - unsigned int active{ 0 }; - bool needsUpdate{ false }; + std::vector mNames; + std::vector mExtra; + int32_t mActive{ -1 }; + bool mNeedsUpdate{ false }; + void read(NIFStream* nif); }; NiGeometryDataPtr data; NiSkinInstancePtr skin; - MaterialData material; + MaterialData mMaterial; BSShaderPropertyPtr shaderprop; NiAlphaPropertyPtr alphaprop; @@ -147,80 +148,76 @@ namespace Nif void post(Reader& nif) override; }; + // TODO: consider checking the data record type here struct NiTriShape : NiGeometry { }; - struct BSLODTriShape : NiTriShape - { - unsigned int lod0, lod1, lod2; - void read(NIFStream* nif) override; - }; + struct NiTriStrips : NiGeometry { }; + struct NiLines : NiGeometry { }; + struct NiParticles : NiGeometry { }; + struct BSLODTriShape : NiTriShape + { + std::array mLOD; + void read(NIFStream* nif) override; + }; + struct NiCamera : NiAVObject { - struct Camera - { - unsigned short cameraFlags{ 0 }; - - // Camera frustrum - float left, right, top, bottom, nearDist, farDist; - - // Viewport - float vleft, vright, vtop, vbottom; - - // Level of detail modifier - float LOD; - - // Orthographic projection usage flag - bool orthographic{ false }; - - void read(NIFStream* nif); - }; - Camera cam; + uint16_t mCameraFlags{ 0 }; + // Camera frustum + float mLeft, mRight, mTop, mBottom, mNearDist, mFarDist; + bool mOrthographic{ false }; + // Viewport + float mVLeft, mVRight, mVTop, mVBottom; + float mLODAdjust; + NiAVObjectPtr mScene; void read(NIFStream* nif) override; + void post(Reader& nif) override; }; // A node used as the base to switch between child nodes, such as for LOD levels. - struct NiSwitchNode : public NiNode + struct NiSwitchNode : NiNode { - unsigned int switchFlags{ 0 }; - unsigned int initialIndex{ 0 }; + uint16_t switchFlags; + uint32_t initialIndex; void read(NIFStream* nif) override; }; - struct NiLODNode : public NiSwitchNode + struct NiLODNode : NiSwitchNode { - osg::Vec3f lodCenter; - struct LODRange { float minRange; float maxRange; }; + + osg::Vec3f lodCenter; std::vector lodLevels; void read(NIFStream* nif) override; }; - struct NiFltAnimationNode : public NiSwitchNode + struct NiFltAnimationNode : NiSwitchNode { - float mDuration; enum Flags { Flag_Swing = 0x40 }; + float mDuration; + void read(NIFStream* nif) override; bool swing() const { return mFlags & Flag_Swing; } @@ -236,20 +233,21 @@ namespace Nif struct NiClusterAccumulator : NiAccumulator { }; + struct NiAlphaAccumulator : NiClusterAccumulator { }; struct NiSortAdjustNode : NiNode { - enum SortingMode + enum class SortingMode : uint32_t { - SortingMode_Inherit, - SortingMode_Off, - SortingMode_Subsort + Inherit, + Off, + Subsort, }; - int mMode; + SortingMode mMode; NiAccumulatorPtr mSubSorter; void read(NIFStream* nif) override; @@ -258,7 +256,7 @@ namespace Nif struct NiBillboardNode : NiNode { - int mMode{ 0 }; + int mMode; void read(NIFStream* nif) override; }; @@ -275,6 +273,7 @@ namespace Nif struct BSTreeNode : NiNode { NiAVObjectList mBones1, mBones2; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -282,7 +281,7 @@ namespace Nif struct BSMultiBoundNode : NiNode { BSMultiBoundPtr mMultiBound; - unsigned int mType{ 0 }; + uint32_t mType{ 0 }; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -290,17 +289,17 @@ namespace Nif struct BSVertexDesc { - unsigned char mVertexDataSize; - unsigned char mDynamicVertexSize; - unsigned char mUV1Offset; - unsigned char mUV2Offset; - unsigned char mNormalOffset; - unsigned char mTangentOffset; - unsigned char mColorOffset; - unsigned char mSkinningDataOffset; - unsigned char mLandscapeDataOffset; - unsigned char mEyeDataOffset; - unsigned short mFlags; + uint8_t mVertexDataSize; + uint8_t mDynamicVertexSize; + uint8_t mUV1Offset; + uint8_t mUV2Offset; + uint8_t mNormalOffset; + uint8_t mTangentOffset; + uint8_t mColorOffset; + uint8_t mSkinningDataOffset; + uint8_t mLandscapeDataOffset; + uint8_t mEyeDataOffset; + uint16_t mFlags; enum VertexAttribute { @@ -324,9 +323,8 @@ namespace Nif { osg::Vec3f mVertex; float mBitangentX; - unsigned int mUnusedW; + uint32_t mUnusedW; std::array mUV; - std::array mNormal; char mBitangentY; std::array mTangent; @@ -343,21 +341,17 @@ namespace Nif { osg::BoundingSpheref mBoundingSphere; std::array mBoundMinMax; - NiSkinInstancePtr mSkin; BSShaderPropertyPtr mShaderProperty; NiAlphaPropertyPtr mAlphaProperty; - BSVertexDesc mVertDesc; - - unsigned int mDataSize; - unsigned int mParticleDataSize; - + uint32_t mDataSize; std::vector mVertData; std::vector mTriangles; + uint32_t mParticleDataSize; + std::vector mParticleVerts; + std::vector mParticleNormals; std::vector mParticleTriangles; - std::vector mParticleVerts; - std::vector mParticleNormals; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -365,8 +359,14 @@ namespace Nif struct BSValueNode : NiNode { - unsigned int mValue; - char mValueFlags; + enum Flags + { + Flag_BillboardWorldZ = 0x1, + Flag_UsePlayerAdjust = 0x2, + }; + + uint32_t mValue; + uint8_t mValueFlags; void read(NIFStream* nif) override; }; @@ -374,7 +374,7 @@ namespace Nif struct BSOrderedNode : NiNode { osg::Vec4f mAlphaSortBound; - char mStaticBound; + bool mStaticBound; void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index fc8da57dbd..e815dbd13c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -706,7 +706,7 @@ namespace NifOsg else { if (mPushedSorter && !mPushedSorter->mSubSorter.empty() - && mPushedSorter->mMode != Nif::NiSortAdjustNode::SortingMode_Inherit) + && mPushedSorter->mMode != Nif::NiSortAdjustNode::SortingMode::Inherit) mLastAppliedNoInheritSorter = mPushedSorter; mPushedSorter = sortNode; } @@ -2627,8 +2627,8 @@ namespace NifOsg if (!mPushedSorter) return; - auto assignBin = [&](int mode, int type) { - if (mode == Nif::NiSortAdjustNode::SortingMode_Off) + auto assignBin = [&](Nif::NiSortAdjustNode::SortingMode mode, int type) { + if (mode == Nif::NiSortAdjustNode::SortingMode::Off) { setBin_Traversal(stateset); return; @@ -2649,7 +2649,7 @@ namespace NifOsg switch (mPushedSorter->mMode) { - case Nif::NiSortAdjustNode::SortingMode_Inherit: + case Nif::NiSortAdjustNode::SortingMode::Inherit: { if (mLastAppliedNoInheritSorter) assignBin(mLastAppliedNoInheritSorter->mMode, mLastAppliedNoInheritSorter->mSubSorter->recType); @@ -2657,12 +2657,12 @@ namespace NifOsg assignBin(mPushedSorter->mMode, Nif::RC_NiAlphaAccumulator); break; } - case Nif::NiSortAdjustNode::SortingMode_Off: + case Nif::NiSortAdjustNode::SortingMode::Off: { setBin_Traversal(stateset); break; } - case Nif::NiSortAdjustNode::SortingMode_Subsort: + case Nif::NiSortAdjustNode::SortingMode::Subsort: { assignBin(mPushedSorter->mMode, mPushedSorter->mSubSorter->recType); break; From 5b0bc97db084ac759e9f2f791a3439a82b4e6342 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 12:29:49 +0300 Subject: [PATCH 0147/2167] Refactor NiSwitchNode and NiLODNode --- components/nif/node.cpp | 14 +++++++------- components/nif/node.hpp | 12 ++++++------ components/nifosg/nifloader.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index b515f5038f..ef0ff18599 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -234,8 +234,8 @@ namespace Nif NiNode::read(nif); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - nif->read(switchFlags); - nif->read(initialIndex); + nif->read(mSwitchFlags); + nif->read(mInitialIndex); } void NiLODNode::read(NIFStream* nif) @@ -249,13 +249,13 @@ namespace Nif } if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) - nif->read(lodCenter); + nif->read(mLODCenter); - lodLevels.resize(nif->get()); - for (LODRange& level : lodLevels) + mLODLevels.resize(nif->get()); + for (LODRange& level : mLODLevels) { - nif->read(level.minRange); - nif->read(level.maxRange); + nif->read(level.mMinRange); + nif->read(level.mMaxRange); } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index d837cbe3da..790a85d876 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -189,8 +189,8 @@ namespace Nif // A node used as the base to switch between child nodes, such as for LOD levels. struct NiSwitchNode : NiNode { - uint16_t switchFlags; - uint32_t initialIndex; + uint16_t mSwitchFlags; + uint32_t mInitialIndex; void read(NIFStream* nif) override; }; @@ -199,12 +199,12 @@ namespace Nif { struct LODRange { - float minRange; - float maxRange; + float mMinRange; + float mMaxRange; }; - osg::Vec3f lodCenter; - std::vector lodLevels; + osg::Vec3f mLODCenter; + std::vector mLODLevels; void read(NIFStream* nif) override; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e815dbd13c..23a86ba983 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -463,11 +463,11 @@ namespace NifOsg osg::ref_ptr lod(new osg::LOD); lod->setName(niLodNode->mName); lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER); - lod->setCenter(niLodNode->lodCenter); - for (unsigned int i = 0; i < niLodNode->lodLevels.size(); ++i) + lod->setCenter(niLodNode->mLODCenter); + for (unsigned int i = 0; i < niLodNode->mLODLevels.size(); ++i) { - const Nif::NiLODNode::LODRange& range = niLodNode->lodLevels[i]; - lod->setRange(i, range.minRange, range.maxRange); + const Nif::NiLODNode::LODRange& range = niLodNode->mLODLevels[i]; + lod->setRange(i, range.mMinRange, range.mMaxRange); } lod->setRangeMode(osg::LOD::DISTANCE_FROM_EYE_POINT); return lod; @@ -478,7 +478,7 @@ namespace NifOsg osg::ref_ptr switchNode(new osg::Switch); switchNode->setName(niSwitchNode->mName); switchNode->setNewChildDefaultValue(false); - switchNode->setSingleChildOn(niSwitchNode->initialIndex); + switchNode->setSingleChildOn(niSwitchNode->mInitialIndex); return switchNode; } From deb051639e644e24c61c486f27c6bcaea2ed9488 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 12:39:44 +0300 Subject: [PATCH 0148/2167] Refactor NiBoundingVolume --- .../nifloader/testbulletnifloader.cpp | 72 +++++++++---------- components/nif/node.cpp | 12 ++-- components/nif/node.hpp | 10 +-- components/nifbullet/bulletnifloader.cpp | 10 +-- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 1971895936..2c2b60db36 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -415,9 +415,9 @@ namespace TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNode); @@ -439,9 +439,9 @@ namespace TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode) }; @@ -466,14 +466,14 @@ namespace for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); - mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.mBounds.box.extents = osg::Vec3f(4, 5, 6); - mNiNode.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); + mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode) }; Nif::NIFFile file("test.nif"); @@ -497,19 +497,19 @@ namespace for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); - mNode2.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode2.mBounds.box.extents = osg::Vec3f(4, 5, 6); - mNode2.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); + mNode2.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNode2.mParents.push_back(&mNiNode); - mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.mBounds.box.extents = osg::Vec3f(7, 8, 9); - mNiNode.mBounds.box.center = osg::Vec3f(-7, -8, -9); + mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mBox.mExtents = osg::Vec3f(7, 8, 9); + mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-7, -8, -9); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; Nif::NIFFile file("test.nif"); @@ -532,20 +532,20 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); mNode2.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode2.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode2.mBounds.box.extents = osg::Vec3f(4, 5, 6); - mNode2.mBounds.box.center = osg::Vec3f(-4, -5, -6); + mNode2.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); + mNode2.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNode2.mParents.push_back(&mNiNode); - mNiNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiNode.mBounds.box.extents = osg::Vec3f(7, 8, 9); - mNiNode.mBounds.box.center = osg::Vec3f(-7, -8, -9); + mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mBox.mExtents = osg::Vec3f(7, 8, 9); + mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-7, -8, -9); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; Nif::NIFFile file("test.nif"); @@ -568,9 +568,9 @@ namespace TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { - mNode.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNode.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNode.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNode); @@ -608,9 +608,9 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_static_shape_with_bounds_but_with_null_collision_shape) { - mNiTriShape.mBounds.type = Nif::NiBoundingVolume::Type::BOX_BV; - mNiTriShape.mBounds.box.extents = osg::Vec3f(1, 2, 3); - mNiTriShape.mBounds.box.center = osg::Vec3f(-1, -2, -3); + mNiTriShape.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiTriShape.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); + mNiTriShape.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriShape); diff --git a/components/nif/node.cpp b/components/nif/node.cpp index ef0ff18599..cf4df3c733 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -14,8 +14,8 @@ namespace Nif void NiBoundingVolume::read(NIFStream* nif) { - nif->read(type); - switch (type) + nif->read(mType); + switch (mType) { case BASE_BV: break; @@ -26,9 +26,9 @@ namespace Nif } case BOX_BV: { - nif->read(box.center); - nif->read(box.axes); - nif->read(box.extents); + nif->read(mBox.mCenter); + nif->read(mBox.mAxes); + nif->read(mBox.mExtents); break; } case CAPSULE_BV: @@ -69,7 +69,7 @@ namespace Nif default: { throw Nif::Exception( - "Unhandled NiBoundingVolume type: " + std::to_string(type), nif->getFile().getFilename()); + "Unhandled NiBoundingVolume type: " + std::to_string(mType), nif->getFile().getFilename()); } } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 790a85d876..d539298995 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -28,9 +28,9 @@ namespace Nif struct NiBoxBV { - osg::Vec3f center; - Matrix3 axes; - osg::Vec3f extents; + osg::Vec3f mCenter; + Matrix3 mAxes; + osg::Vec3f mExtents; }; struct NiCapsuleBV @@ -51,9 +51,9 @@ namespace Nif osg::Vec3f mOrigin; }; - uint32_t type{ BASE_BV }; + uint32_t mType{ BASE_BV }; osg::BoundingSpheref mSphere; - NiBoxBV box; + NiBoxBV mBox; NiCapsuleBV mCapsule; NiLozengeBV mLozenge; std::vector mChildren; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 59d5cd61d8..7d1ccc99ff 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -181,7 +181,7 @@ namespace NifBullet bool hasCollisionShape = false; if (colNode != nullptr) { - if (colNode->mBounds.type == Nif::NiBoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) + if (colNode->mBounds.mType == Nif::NiBoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) hasCollisionShape = true; else mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; @@ -205,14 +205,14 @@ namespace NifBullet // Return: use bounding box for collision? bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node, const std::string& filename) { - unsigned int type = node.mBounds.type; + unsigned int type = node.mBounds.mType; switch (type) { case Nif::NiBoundingVolume::Type::BASE_BV: break; case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBox.mExtents = node.mBounds.box.extents; - mShape->mCollisionBox.mCenter = node.mBounds.box.center; + mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents; + mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter; break; default: { @@ -335,7 +335,7 @@ namespace NifBullet // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if (node.mBounds.type == Nif::NiBoundingVolume::Type::BASE_BV + if (node.mBounds.mType == Nif::NiBoundingVolume::Type::BASE_BV && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { From 83ebaf27cc4552d35c597598920eae72202319ad Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 15 Sep 2023 23:26:58 +0800 Subject: [PATCH 0149/2167] take2 --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwlua/itemdata.cpp | 129 ++++++++++++++++++++++++++ apps/openmw/mwlua/itemdata.hpp | 13 +++ apps/openmw/mwlua/localscripts.hpp | 2 +- apps/openmw/mwlua/types/item.cpp | 25 ++--- apps/openmw/mwlua/types/itemstats.cpp | 33 ------- apps/openmw/mwlua/types/itemstats.hpp | 29 ------ 7 files changed, 153 insertions(+), 82 deletions(-) create mode 100644 apps/openmw/mwlua/itemdata.cpp create mode 100644 apps/openmw/mwlua/itemdata.hpp delete mode 100644 apps/openmw/mwlua/types/itemstats.cpp delete mode 100644 apps/openmw/mwlua/types/itemstats.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0b8ed449f9..27b0a9516d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,8 +61,8 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings - types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal types/itemstats + camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings itemdata + types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal worker magicbindings factionbindings ) diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp new file mode 100644 index 0000000000..33a1a21310 --- /dev/null +++ b/apps/openmw/mwlua/itemdata.cpp @@ -0,0 +1,129 @@ +#include "itemdata.hpp" + +#include "context.hpp" + +#include "luamanagerimp.hpp" + +#include "../mwworld/class.hpp" + +#include "objectvariant.hpp" + +namespace +{ + using SelfObject = MWLua::SelfObject; + using Index = const SelfObject::CachedStat::Index&; + + constexpr std::array properties = { "condition", /*"enchantmentCharge", "soul", "owner", etc..*/ }; + + void invalidPropErr(std::string_view prop, const MWWorld::Ptr& ptr) + { + throw std::runtime_error(std::string(prop) + " does not exist for item " + + std::string(ptr.getClass().getName(ptr)) + "(" + std::string(ptr.getTypeDescription()) + ")"); + } +} + +namespace MWLua +{ + static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) + { + if (!obj.mStatsCache.empty()) + return; // was already added before + manager->addAction( + [obj = Object(obj)] { + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + }, + "StatUpdateAction"); + } + + class ItemData + { + ObjectVariant mObject; + + public: + ItemData(ObjectVariant object) + : mObject(object) + { + } + + sol::object get(const Context& context, std::string_view prop) const + { + if (mObject.isSelfObject()) + { + SelfObject* self = mObject.asSelfObject(); + auto it = self->mStatsCache.find({ &ItemData::setValue, std::monostate{}, prop }); + if (it != self->mStatsCache.end()) + return it->second; + } + return sol::make_object(context.mLua->sol(), getValue(context, prop)); + } + + void set(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; + } + + sol::object getValue(const Context& context, std::string_view prop) const + { + if (prop == "condition") + { + MWWorld::Ptr o = mObject.ptr(); + if (o.getClass().isLight(o)) + return sol::make_object(context.mLua->sol(), o.getClass().getRemainingUsageTime(o)); + else if (o.getClass().hasItemHealth(o)) + return sol::make_object(context.mLua->sol(), o.getClass().getItemHealth(o)); + } + + return sol::lua_nil; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + if (prop == "condition") + { + double cond = value.as(); + if (ptr.getClass().isLight(ptr)) + ptr.getClass().setRemainingUsageTime(ptr, cond); + else if (ptr.getClass().hasItemHealth(ptr)) + ptr.getCellRef().setCharge(std::max(0, static_cast(cond))); + else /*ignore or error?*/ + invalidPropErr(prop, ptr); + } + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + void addItemDataBindings(sol::table& item, const Context& context) + { + item["itemData"] = [](const sol::object& object) -> sol::optional { + ObjectVariant o(object); + if (o.ptr().getClass().isItem(o.ptr()) || o.ptr().mRef->getType() == ESM::REC_LIGH) + return ItemData(std::move(o)); + return {}; + }; + + sol::usertype itemData = context.mLua->sol().new_usertype("ItemData"); + itemData[sol::meta_function::new_index] = [](const ItemData& stat, const sol::variadic_args args) { + throw std::runtime_error("Unknown ItemData property '" + args.get() + "'"); + }; + + for (std::string_view prop : properties) + { + itemData[prop] = sol::property([context, prop](const ItemData& stat) { return stat.get(context, prop); }, + [context, prop](const ItemData& stat, const sol::object& value) { stat.set(context, prop, value); }); + } + } +} diff --git a/apps/openmw/mwlua/itemdata.hpp b/apps/openmw/mwlua/itemdata.hpp new file mode 100644 index 0000000000..f70705fb6c --- /dev/null +++ b/apps/openmw/mwlua/itemdata.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_ITEMDATA_H +#define MWLUA_ITEMDATA_H + +#include + +namespace MWLua +{ + struct Context; + + void addItemDataBindings(sol::table& item, const Context& context); + +} +#endif // MWLUA_ITEMDATA_H diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 6b1555868d..b87b628a89 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -22,7 +22,7 @@ namespace MWLua class CachedStat { public: - using Index = std::variant; + using Index = std::variant; using Setter = void (*)(const Index&, std::string_view, const MWWorld::Ptr&, const sol::object&); CachedStat(Setter setter, Index index, std::string_view prop) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 789891e8a9..b616075496 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,31 +1,22 @@ -#include "itemstats.hpp" +#include -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; -} +#include "../../mwworld/class.hpp" + +#include "../itemdata.hpp" + +#include "types.hpp" namespace MWLua { - void addItemBindings(sol::table item, const Context& context) { - sol::usertype ItemStats = context.mLua->sol().new_usertype("ItemStat"); - ItemStats[sol::meta_function::new_index] = [](const ItemStat& i, const sol::variadic_args args) { - throw std::runtime_error("Unknown itemStat property '" + args.get() + "'"); - }; - ItemStats["condition"] = sol::property([](const ItemStat& i) { return i.getCondition(); }, - [](const ItemStat& i, float cond) { i.setCondition(cond); }); - item["getEnchantmentCharge"] = [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); }; item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; - item["itemStats"] = [](const sol::object& object) { return ItemStat(object); }; + + addItemDataBindings(item, context); } } diff --git a/apps/openmw/mwlua/types/itemstats.cpp b/apps/openmw/mwlua/types/itemstats.cpp deleted file mode 100644 index 4739475f6d..0000000000 --- a/apps/openmw/mwlua/types/itemstats.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "itemstats.hpp" - -namespace MWLua -{ - ItemStat::ItemStat(const sol::object& object) - : mObject(ObjectVariant(object)) - { - } - sol::optional ItemStat::getCondition() const - { - MWWorld::Ptr o = mObject.ptr(); - if (o.getClass().isLight(o)) - return o.getClass().getRemainingUsageTime(o); - else if (o.getClass().hasItemHealth(o)) - return o.getClass().getItemHealth(o); - else - return sol::nullopt; - } - void ItemStat::setCondition(float cond) const - { - if (!mObject.isGObject()) - throw std::runtime_error("This property can only be set in global scripts"); - - MWWorld::Ptr o = mObject.ptr(); - if (o.getClass().isLight(o)) - return o.getClass().setRemainingUsageTime(o, cond); - else if (o.getClass().hasItemHealth(o)) - o.getCellRef().setCharge(std::max(0, static_cast(cond))); - else - throw std::runtime_error("'condition' property does not exist for " + std::string(o.getClass().getName(o)) - + "(" + std::string(o.getTypeDescription()) + ")"); - }; -} diff --git a/apps/openmw/mwlua/types/itemstats.hpp b/apps/openmw/mwlua/types/itemstats.hpp deleted file mode 100644 index 55d55ed234..0000000000 --- a/apps/openmw/mwlua/types/itemstats.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef MWLUA_ITEMSTATS_H -#define MWLUA_ITEMSTATS_H - -#include - -#include "../../mwworld/class.hpp" - -#include "../objectvariant.hpp" -#include "types.hpp" - -namespace MWLua -{ - class ItemStat - { - public: - ItemStat(const sol::object& object); - - sol::optional getCondition() const; - - void setCondition(float cond) const; - - /* - * set,get, enchantmentCharge, soul? etc.. - */ - - ObjectVariant mObject; - }; -} -#endif // MWLUA_ITEMSTATS_H From cda5f126308d4607162cc0c3b6444778f2156d2a Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 16 Sep 2023 13:45:19 +0200 Subject: [PATCH 0150/2167] Fix Lua UI Layer bindings --- apps/openmw/mwlua/uibindings.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index eba761ed97..04914dd881 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -156,20 +156,16 @@ namespace MWLua uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; - sol::table layers = context.mLua->newTable(); - layers[sol::meta_function::length] = []() { return LuaUi::Layer::count(); }; - layers[sol::meta_function::index] = [](size_t index) { - index = fromLuaIndex(index); - return LuaUi::Layer(index); - }; - layers["indexOf"] = [](std::string_view name) -> sol::optional { + sol::table layersTable = context.mLua->newTable(); + layersTable["indexOf"] = [](std::string_view name) -> sol::optional { size_t index = LuaUi::Layer::indexOf(name); if (index == LuaUi::Layer::count()) return sol::nullopt; else return toLuaIndex(index); }; - layers["insertAfter"] = [context](std::string_view afterName, std::string_view name, const sol::object& opt) { + layersTable["insertAfter"] = [context]( + std::string_view afterName, std::string_view name, const sol::object& opt) { LuaUi::Layer::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); size_t index = LuaUi::Layer::indexOf(afterName); @@ -178,7 +174,8 @@ namespace MWLua index++; context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; - layers["insertBefore"] = [context](std::string_view beforename, std::string_view name, const sol::object& opt) { + layersTable["insertBefore"] = [context]( + std::string_view beforename, std::string_view name, const sol::object& opt) { LuaUi::Layer::Options options; options.mInteractive = LuaUtil::getValueOrDefault(LuaUtil::getFieldOrNil(opt, "interactive"), true); size_t index = LuaUi::Layer::indexOf(beforename); @@ -186,6 +183,16 @@ namespace MWLua throw std::logic_error(std::string("Layer not found")); context.mLuaManager->addAction([=]() { LuaUi::Layer::insert(index, name, options); }, "Insert UI layer"); }; + sol::table layers = LuaUtil::makeReadOnly(layersTable); + sol::table layersMeta = layers[sol::metatable_key]; + layersMeta[sol::meta_function::length] = []() { return LuaUi::Layer::count(); }; + layersMeta[sol::meta_function::index] = sol::overload( + [](const sol::object& self, size_t index) { + index = fromLuaIndex(index); + return LuaUi::Layer(index); + }, + [layersTable]( + const sol::object& self, std::string_view key) { return layersTable.raw_get(key); }); { auto pairs = [layers](const sol::object&) { auto next = [](const sol::table& l, size_t i) -> sol::optional> { @@ -196,10 +203,10 @@ namespace MWLua }; return std::make_tuple(next, layers, 0); }; - layers[sol::meta_function::pairs] = pairs; - layers[sol::meta_function::ipairs] = pairs; + layersMeta[sol::meta_function::pairs] = pairs; + layersMeta[sol::meta_function::ipairs] = pairs; } - api["layers"] = LuaUtil::makeReadOnly(layers); + api["layers"] = layers; sol::table typeTable = context.mLua->newTable(); for (const auto& it : LuaUi::widgetTypeToName()) From bff9231c3b2a587050ae9061386cb803576b495c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 13:07:20 +0300 Subject: [PATCH 0151/2167] Refactor NiGeometry/BSTriShape Don't pass invalid geometry data links to the loaders --- apps/openmw_test_suite/nif/node.hpp | 4 +- .../nifloader/testbulletnifloader.cpp | 44 +++---------------- components/nif/node.cpp | 44 +++++++++++++++---- components/nif/node.hpp | 20 ++++----- components/nifbullet/bulletnifloader.cpp | 16 +++---- components/nifosg/nifloader.cpp | 34 ++++++-------- 6 files changed, 73 insertions(+), 89 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index e05a056b79..76cc6ac687 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -33,8 +33,8 @@ namespace Nif::Testing inline void init(NiGeometry& value) { init(static_cast(value)); - value.data = NiGeometryDataPtr(nullptr); - value.skin = NiSkinInstancePtr(nullptr); + value.mData = NiGeometryDataPtr(nullptr); + value.mSkin = NiSkinInstancePtr(nullptr); } inline void init(NiTriShape& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 2c2b60db36..8d456fd7ed 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -326,20 +326,20 @@ namespace mNiTriShapeData.mVertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0) }; mNiTriShapeData.mNumTriangles = 1; mNiTriShapeData.mTriangles = { 0, 1, 2 }; - mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + mNiTriShape.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData); mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.mVertices = { osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1) }; mNiTriShapeData2.mNumTriangles = 1; mNiTriShapeData2.mTriangles = { 0, 1, 2 }; - mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); + mNiTriShape2.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData2); mNiTriStripsData.recType = Nif::RC_NiTriStripsData; mNiTriStripsData.mVertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0) }; mNiTriStripsData.mNumTriangles = 2; mNiTriStripsData.mStrips = { { 0, 1, 2, 3 } }; - mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + mNiTriStrips.mData = Nif::NiGeometryDataPtr(&mNiTriStripsData); } }; @@ -703,7 +703,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_static_shape) { - mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiTriShape.mSkin = Nif::NiSkinInstancePtr(&mNiSkinInstance); mNiTriShape.mParents.push_back(&mNiNode); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; @@ -943,7 +943,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { - mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); + mNiTriShape.mData = Nif::NiGeometryDataPtr(nullptr); mNiTriShape.mParents.push_back(&mNiNode); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; @@ -961,7 +961,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { - auto data = static_cast(mNiTriShape.data.getPtr()); + auto data = static_cast(mNiTriShape.mData.getPtr()); data->mTriangles.clear(); mNiTriShape.mParents.push_back(&mNiNode); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; @@ -1095,7 +1095,7 @@ namespace init(niTriShape); init(emptyCollisionNode); - niTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + niTriShape.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData); niTriShape.mParents.push_back(&mNiNode); emptyCollisionNode.recType = Nif::RC_RootCollisionNode; @@ -1192,21 +1192,6 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, should_ignore_tri_shape_data_with_mismatching_data_rec_type) - { - mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiTriShape); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - const Resource::BulletShape expected; - - EXPECT_EQ(*result, expected); - } - TEST_F(TestBulletNifLoader, for_tri_strips_root_node_should_return_static_shape) { Nif::NIFFile file("test.nif"); @@ -1227,21 +1212,6 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_mismatching_data_rec_type) - { - mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiTriStrips); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - const Resource::BulletShape expected; - - EXPECT_EQ(*result, expected); - } - TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_empty_strips) { mNiTriStripsData.mStrips.clear(); diff --git a/components/nif/node.cpp b/components/nif/node.cpp index cf4df3c733..18742f1ea1 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -167,14 +167,14 @@ namespace Nif { NiAVObject::read(nif); - data.read(nif); - skin.read(nif); + mData.read(nif); + mSkin.read(nif); mMaterial.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { - shaderprop.read(nif); - alphaprop.read(nif); + mShaderProperty.read(nif); + mAlphaProperty.read(nif); } } @@ -182,12 +182,38 @@ namespace Nif { NiAVObject::post(nif); - data.post(nif); - skin.post(nif); - shaderprop.post(nif); - alphaprop.post(nif); - if (recType != RC_NiParticles && !skin.empty()) + mData.post(nif); + mSkin.post(nif); + mShaderProperty.post(nif); + mAlphaProperty.post(nif); + if (recType != RC_NiParticles && !mSkin.empty()) nif.setUseSkinning(true); + + if (!mData.empty()) + { + switch (recType) + { + case RC_NiTriShape: + case RC_BSLODTriShape: + if (mData->recType != RC_NiTriShapeData) + mData = NiGeometryDataPtr(nullptr); + break; + case RC_NiTriStrips: + if (mData->recType != RC_NiTriStripsData) + mData = NiGeometryDataPtr(nullptr); + break; + case RC_NiParticles: + if (mData->recType != RC_NiParticlesData) + mData = NiGeometryDataPtr(nullptr); + break; + case RC_NiLines: + if (mData->recType != RC_NiLinesData) + mData = NiGeometryDataPtr(nullptr); + break; + default: + break; + } + } } void BSLODTriShape::read(NIFStream* nif) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index d539298995..6e28f3b647 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -119,7 +119,14 @@ namespace Nif void post(Reader& nif) override; }; - struct NiGeometry : NiAVObject + struct GeometryInterface + { + NiSkinInstancePtr mSkin; + BSShaderPropertyPtr mShaderProperty; + NiAlphaPropertyPtr mAlphaProperty; + }; + + struct NiGeometry : NiAVObject, GeometryInterface { /* Possible flags: 0x40 - mesh has no vertex normals ? @@ -138,17 +145,13 @@ namespace Nif void read(NIFStream* nif); }; - NiGeometryDataPtr data; - NiSkinInstancePtr skin; + NiGeometryDataPtr mData; MaterialData mMaterial; - BSShaderPropertyPtr shaderprop; - NiAlphaPropertyPtr alphaprop; void read(NIFStream* nif) override; void post(Reader& nif) override; }; - // TODO: consider checking the data record type here struct NiTriShape : NiGeometry { }; @@ -337,13 +340,10 @@ namespace Nif void read(NIFStream* nif, uint16_t flags); }; - struct BSTriShape : NiAVObject + struct BSTriShape : NiAVObject, GeometryInterface { osg::BoundingSpheref mBoundingSphere; std::array mBoundMinMax; - NiSkinInstancePtr mSkin; - BSShaderPropertyPtr mShaderProperty; - NiAlphaPropertyPtr mAlphaProperty; BSVertexDesc mVertDesc; uint32_t mDataSize; std::vector mVertData; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 7d1ccc99ff..1349c1dc97 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -79,14 +79,11 @@ namespace template auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) - -> decltype(function(static_cast(geometry.data.get()))) + -> decltype(function(static_cast(geometry.mData.get()))) { if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) { - if (geometry.data->recType != Nif::RC_NiTriShapeData) - return {}; - - auto data = static_cast(geometry.data.getPtr()); + auto data = static_cast(geometry.mData.getPtr()); if (data->mTriangles.empty()) return {}; @@ -95,10 +92,7 @@ namespace if (geometry.recType == Nif::RC_NiTriStrips) { - if (geometry.data->recType != Nif::RC_NiTriStripsData) - return {}; - - auto data = static_cast(geometry.data.getPtr()); + auto data = static_cast(geometry.mData.getPtr()); if (data->mStrips.empty()) return {}; @@ -366,10 +360,10 @@ namespace NifBullet if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "EditorMarker")) return; - if (niGeometry.data.empty() || niGeometry.data->mVertices.empty()) + if (niGeometry.mData.empty() || niGeometry.mData->mVertices.empty()) return; - if (!niGeometry.skin.empty()) + if (!niGeometry.mSkin.empty()) args.mAnimated = false; // TODO: handle NiSkinPartition diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 23a86ba983..05f42db05a 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -128,10 +128,10 @@ namespace auto geometry = dynamic_cast(nifNode); if (geometry) { - if (!geometry->shaderprop.empty()) - out.emplace_back(geometry->shaderprop.getPtr()); - if (!geometry->alphaprop.empty()) - out.emplace_back(geometry->alphaprop.getPtr()); + if (!geometry->mShaderProperty.empty()) + out.emplace_back(geometry->mShaderProperty.getPtr()); + if (!geometry->mAlphaProperty.empty()) + out.emplace_back(geometry->mAlphaProperty.getPtr()); } } @@ -444,8 +444,8 @@ namespace NifOsg auto geometry = dynamic_cast(nifNode); // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property - if (geometry && !geometry->shaderprop.empty()) - handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, + if (geometry && !geometry->mShaderProperty.empty()) + handleProperty(geometry->mShaderProperty.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } @@ -761,7 +761,7 @@ namespace NifOsg skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); if (!skip) { - Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; + Nif::NiSkinInstancePtr skin = static_cast(nifNode)->mSkin; if (skin.empty()) handleGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); @@ -1121,13 +1121,13 @@ namespace NifOsg const Nif::NiAVObject* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { auto particleNode = static_cast(nifNode); - if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) + if (particleNode->mData.empty()) { partsys->setQuota(partctrl->mParticles.size()); return; } - auto particledata = static_cast(particleNode->data.getPtr()); + auto particledata = static_cast(particleNode->mData.getPtr()); partsys->setQuota(particledata->mNumParticles); osg::BoundingBox box; @@ -1379,13 +1379,13 @@ namespace NifOsg const std::vector& boundTextures, int animflags) { const Nif::NiGeometry* niGeometry = static_cast(nifNode); - if (niGeometry->data.empty()) + if (niGeometry->mData.empty()) return; bool hasPartitions = false; - if (!niGeometry->skin.empty()) + if (!niGeometry->mSkin.empty()) { - const Nif::NiSkinInstance* skin = niGeometry->skin.getPtr(); + const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); const Nif::NiSkinData* data = nullptr; const Nif::NiSkinPartition* partitions = nullptr; if (!skin->mData.empty()) @@ -1419,13 +1419,11 @@ namespace NifOsg } } - const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); + const Nif::NiGeometryData* niGeometryData = niGeometry->mData.getPtr(); if (!hasPartitions) { if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape) { - if (niGeometryData->recType != Nif::RC_NiTriShapeData) - return; auto data = static_cast(niGeometryData); const std::vector& triangles = data->mTriangles; if (triangles.empty()) @@ -1435,8 +1433,6 @@ namespace NifOsg } else if (niGeometry->recType == Nif::RC_NiTriStrips) { - if (niGeometryData->recType != Nif::RC_NiTriStripsData) - return; auto data = static_cast(niGeometryData); bool hasGeometry = false; for (const std::vector& strip : data->mStrips) @@ -1452,8 +1448,6 @@ namespace NifOsg } else if (niGeometry->recType == Nif::RC_NiLines) { - if (niGeometryData->recType != Nif::RC_NiLinesData) - return; auto data = static_cast(niGeometryData); const auto& line = data->mLines; if (line.empty()) @@ -1545,7 +1539,7 @@ namespace NifOsg // Assign bone weights osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - const Nif::NiSkinInstance* skin = static_cast(nifNode)->skin.getPtr(); + const Nif::NiSkinInstance* skin = static_cast(nifNode)->mSkin.getPtr(); const Nif::NiSkinData* data = skin->mData.getPtr(); const Nif::NiAVObjectList& bones = skin->mBones; for (std::size_t i = 0; i < bones.size(); ++i) From eb8242946a5646a66f20004bf445699806f35723 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Sep 2023 14:03:50 +0300 Subject: [PATCH 0152/2167] Improve node record consistency with NifTools --- .../nifloader/testbulletnifloader.cpp | 24 +++++++-------- components/nif/node.cpp | 13 ++++---- components/nif/node.hpp | 30 ++++++++++++++----- components/nifbullet/bulletnifloader.cpp | 12 ++++---- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 8d456fd7ed..8e8d04d93d 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -415,7 +415,7 @@ namespace TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -439,7 +439,7 @@ namespace TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); @@ -466,12 +466,12 @@ namespace for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); - mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNiNode.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode) }; @@ -497,17 +497,17 @@ namespace for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) { mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); - mNode2.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode2.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); mNode2.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNode2.mParents.push_back(&mNiNode); - mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNiNode.mBounds.mBox.mExtents = osg::Vec3f(7, 8, 9); mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-7, -8, -9); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; @@ -532,18 +532,18 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); mNode2.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; - mNode2.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode2.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); mNode2.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); mNode2.mParents.push_back(&mNiNode); - mNiNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNiNode.mBounds.mBox.mExtents = osg::Vec3f(7, 8, 9); mNiNode.mBounds.mBox.mCenter = osg::Vec3f(-7, -8, -9); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNode), Nif::NiAVObjectPtr(&mNode2) }; @@ -568,7 +568,7 @@ namespace TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { - mNode.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -608,7 +608,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_static_shape_with_bounds_but_with_null_collision_shape) { - mNiTriShape.mBounds.mType = Nif::NiBoundingVolume::Type::BOX_BV; + mNiTriShape.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNiTriShape.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNiTriShape.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 18742f1ea1..328546b7c6 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -12,7 +12,7 @@ namespace Nif { - void NiBoundingVolume::read(NIFStream* nif) + void BoundingVolume::read(NIFStream* nif) { nif->read(mType); switch (mType) @@ -55,7 +55,7 @@ namespace Nif case UNION_BV: { mChildren.resize(nif->get()); - for (NiBoundingVolume& child : mChildren) + for (BoundingVolume& child : mChildren) child.read(nif); break; } @@ -69,7 +69,7 @@ namespace Nif default: { throw Nif::Exception( - "Unhandled NiBoundingVolume type: " + std::to_string(mType), nif->getFile().getFilename()); + "Unhandled BoundingVolume type: " + std::to_string(mType), nif->getFile().getFilename()); } } } @@ -168,7 +168,8 @@ namespace Nif NiAVObject::read(nif); mData.read(nif); - mSkin.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mSkin.read(nif); mMaterial.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) @@ -218,7 +219,7 @@ namespace Nif void BSLODTriShape::read(NIFStream* nif) { - NiTriShape::read(nif); + NiTriBasedGeom::read(nif); nif->readArray(mLOD); } @@ -356,7 +357,7 @@ namespace Nif mMultiBound.read(nif); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SKY) - nif->read(mType); + mCullingType = static_cast(nif->get()); } void BSMultiBoundNode::post(Reader& nif) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 6e28f3b647..7d851051e0 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -13,7 +13,7 @@ namespace Nif struct NiNode; - struct NiBoundingVolume + struct BoundingVolume { enum Type : uint32_t { @@ -56,7 +56,7 @@ namespace Nif NiBoxBV mBox; NiCapsuleBV mCapsule; NiLozengeBV mLozenge; - std::vector mChildren; + std::vector mChildren; NiHalfSpaceBV mHalfSpace; void read(NIFStream* nif); @@ -83,7 +83,7 @@ namespace Nif NiTransform mTransform; osg::Vec3f mVelocity; PropertyList mProperties; - NiBoundingVolume mBounds; + BoundingVolume mBounds; NiCollisionObjectPtr mCollision; // Parent nodes for the node. Only types derived from NiNode can be parents. std::vector mParents; @@ -152,15 +152,20 @@ namespace Nif void post(Reader& nif) override; }; - struct NiTriShape : NiGeometry + // Abstract triangle-based geometry + struct NiTriBasedGeom : NiGeometry { }; - struct NiTriStrips : NiGeometry + struct NiTriShape : NiTriBasedGeom { }; - struct NiLines : NiGeometry + struct NiTriStrips : NiTriBasedGeom + { + }; + + struct NiLines : NiTriBasedGeom { }; @@ -168,7 +173,7 @@ namespace Nif { }; - struct BSLODTriShape : NiTriShape + struct BSLODTriShape : NiTriBasedGeom { std::array mLOD; void read(NIFStream* nif) override; @@ -283,8 +288,17 @@ namespace Nif struct BSMultiBoundNode : NiNode { + enum class BSCPCullingType : uint32_t + { + Normal, + AllPass, + AllFail, + IgnoreMultiBounds, + ForceMultiBoundsNoUpdate, + }; + BSMultiBoundPtr mMultiBound; - uint32_t mType{ 0 }; + BSCPCullingType mCullingType; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 1349c1dc97..df4155db7d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -175,7 +175,7 @@ namespace NifBullet bool hasCollisionShape = false; if (colNode != nullptr) { - if (colNode->mBounds.mType == Nif::NiBoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) + if (colNode->mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) hasCollisionShape = true; else mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; @@ -202,22 +202,22 @@ namespace NifBullet unsigned int type = node.mBounds.mType; switch (type) { - case Nif::NiBoundingVolume::Type::BASE_BV: + case Nif::BoundingVolume::Type::BASE_BV: break; - case Nif::NiBoundingVolume::Type::BOX_BV: + case Nif::BoundingVolume::Type::BOX_BV: mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents; mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter; break; default: { std::stringstream warning; - warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; + warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex; warning << " in file " << filename; warn(warning.str()); } } - if (type != Nif::NiBoundingVolume::Type::BASE_BV && node.hasBBoxCollision()) + if (type != Nif::BoundingVolume::Type::BASE_BV && node.hasBBoxCollision()) return true; if (const Nif::NiNode* ninode = dynamic_cast(&node)) @@ -329,7 +329,7 @@ namespace NifBullet // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if (node.mBounds.mType == Nif::NiBoundingVolume::Type::BASE_BV + if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { From 9ae1077808ba8857428318937bfe3bda6abcd80a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 18:29:30 +0300 Subject: [PATCH 0153/2167] Update NIF property loading, first pass Revise all FO3+ shader properties (attempt reading FO4, FO76 and Starfield properties) Use constants for most instances of property flags Drop invalid usage of non-existent double-sided flag for BSShader Make formatting more consistent, drop unnecessary comments --- .../nifosg/testnifloader.cpp | 6 +- components/nif/property.cpp | 301 +++++++++++++++--- components/nif/property.hpp | 238 ++++++++++---- components/nifosg/nifloader.cpp | 22 +- 4 files changed, 432 insertions(+), 135 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index c8bc57bf4b..86a2df2135 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -187,9 +187,9 @@ osg::Group { init(node); Nif::BSShaderPPLightingProperty property; property.recType = Nif::RC_BSShaderPPLightingProperty; - property.textureSet = nullptr; + property.mTextureSet = nullptr; property.mController = nullptr; - property.type = GetParam().mShaderType; + property.mType = GetParam().mShaderType; node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); @@ -217,7 +217,7 @@ osg::Group { property.recType = Nif::RC_BSLightingShaderProperty; property.mTextureSet = nullptr; property.mController = nullptr; - property.type = GetParam().mShaderType; + property.mType = GetParam().mShaderType; node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 3569fd55cc..8dd2be40f4 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -55,6 +55,7 @@ namespace Nif void NiTexturingProperty::read(NIFStream* nif) { Property::read(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20, 1, 0, 2)) flags = nif->getUShort(); @@ -95,103 +96,247 @@ namespace Nif void NiTexturingProperty::post(Reader& nif) { Property::post(nif); + for (size_t i = 0; i < textures.size(); i++) textures[i].post(nif); for (size_t i = 0; i < shaderTextures.size(); i++) shaderTextures[i].post(nif); } + void BSSPParallaxParams::read(NIFStream* nif) + { + nif->read(mMaxPasses); + nif->read(mScale); + } + + void BSSPRefractionParams::read(NIFStream* nif) + { + nif->read(mStrength); + nif->read(mPeriod); + } + void BSShaderProperty::read(NIFStream* nif) { NiShadeProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { - type = nif->getUInt(); - flags1 = nif->getUInt(); - flags2 = nif->getUInt(); - envMapIntensity = nif->getFloat(); + nif->read(mType); + nif->read(mShaderFlags1); + nif->read(mShaderFlags2); + nif->read(mEnvMapScale); } } void BSShaderLightingProperty::read(NIFStream* nif) { BSShaderProperty::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) - clamp = nif->getUInt(); + nif->read(mClamp); } void BSShaderPPLightingProperty::read(NIFStream* nif) { BSShaderLightingProperty::read(nif); - textureSet.read(nif); - if (nif->getBethVersion() <= 14) - return; - refraction.strength = nif->getFloat(); - refraction.period = nif->getInt(); - if (nif->getBethVersion() <= 24) - return; - parallax.passes = nif->getFloat(); - parallax.scale = nif->getFloat(); + + mTextureSet.read(nif); + if (nif->getBethVersion() >= 15) + mRefraction.read(nif); + if (nif->getBethVersion() >= 25) + mParallax.read(nif); } void BSShaderPPLightingProperty::post(Reader& nif) { BSShaderLightingProperty::post(nif); - textureSet.post(nif); + + mTextureSet.post(nif); } void BSShaderNoLightingProperty::read(NIFStream* nif) { BSShaderLightingProperty::read(nif); - filename = nif->getSizedString(); + + mFilename = nif->getSizedString(); if (nif->getBethVersion() >= 27) - falloffParams = nif->getVector4(); + nif->read(mFalloffParams); } + void BSSPLuminanceParams::read(NIFStream* nif) + { + nif->read(mLumEmittance); + nif->read(mExposureOffset); + nif->read(mFinalExposureMin); + nif->read(mFinalExposureMax); + }; + + void BSSPWetnessParams::read(NIFStream* nif) + { + nif->read(mSpecScale); + nif->read(mSpecPower); + nif->read(mMinVar); + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) + nif->read(mEnvMapScale); + nif->read(mFresnelPower); + nif->read(mMetalness); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + nif->skip(4); // Unknown + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->skip(4); // Unknown + }; + + void BSSPMLParallaxParams::read(NIFStream* nif) + { + nif->read(mInnerLayerThickness); + nif->read(mRefractionScale); + nif->read(mInnerLayerTextureScale); + nif->read(mEnvMapScale); + }; + + void BSSPTranslucencyParams::read(NIFStream* nif) + { + nif->read(mSubsurfaceColor); + nif->read(mTransmissiveScale); + nif->read(mTurbulence); + nif->read(mThickObject); + nif->read(mMixAlbedo); + }; + void BSLightingShaderProperty::read(NIFStream* nif) { - type = nif->getUInt(); + if (nif->getBethVersion() <= 139) + nif->read(mType); + BSShaderProperty::read(nif); - flags1 = nif->getUInt(); - flags2 = nif->getUInt(); - nif->skip(8); // UV offset - nif->skip(8); // UV scale + + if (nif->getBethVersion() <= 130) + { + nif->read(mShaderFlags1); + nif->read(mShaderFlags2); + } + else if (nif->getBethVersion() >= 132) + { + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->read(mType); + + nif->read(mUVOffset); + nif->read(mUVScale); mTextureSet.read(nif); - mEmissive = nif->getVector3(); - mEmissiveMult = nif->getFloat(); - mClamp = nif->getUInt(); - mAlpha = nif->getFloat(); - nif->getFloat(); // Refraction strength - mGlossiness = nif->getFloat(); - mSpecular = nif->getVector3(); - mSpecStrength = nif->getFloat(); - nif->skip(8); // Lighting effects - switch (static_cast(type)) + nif->read(mEmissive); + nif->read(mEmissiveMult); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + nif->read(mRootMaterial); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + nif->skip(4); // Unknown float + + nif->read(mClamp); + nif->read(mAlpha); + nif->read(mRefractionStrength); + + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) + nif->read(mGlossiness); + else + nif->read(mSmoothness); + + nif->read(mSpecular); + nif->read(mSpecStrength); + + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) + nif->readArray(mLightingEffects); + else if (nif->getBethVersion() <= 139) + { + nif->read(mSubsurfaceRolloff); + nif->read(mRimlightPower); + if (mRimlightPower == std::numeric_limits::max()) + nif->read(mBacklightPower); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + { + nif->read(mGrayscaleToPaletteScale); + nif->read(mFresnelPower); + mWetness.read(nif); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + mLuminance.read(nif); + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + { + nif->read(mDoTranslucency); + if (mDoTranslucency) + mTranslucency.read(nif); + if (nif->get() != 0) + { + mTextureArrays.resize(nif->get()); + for (std::vector& textureArray : mTextureArrays) + nif->getSizedStrings(textureArray, nif->get()); + } + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + { + nif->skip(4); // Unknown + nif->skip(4); // Unknown + nif->skip(2); // Unknown + } + + // TODO: consider separating this switch for pre-FO76 and FO76+ + switch (static_cast(mType)) { case BSLightingShaderType::ShaderType_EnvMap: - nif->skip(4); // Environment map scale + if (nif->getBethVersion() <= 139) + nif->read(mEnvMapScale); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + { + nif->read(mUseSSR); + nif->read(mWetnessUseSSR); + } + break; + case BSLightingShaderType::ShaderType_FaceTint: + // Skin tint shader in FO76+ + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->read(mSkinTintColor); break; case BSLightingShaderType::ShaderType_SkinTint: + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->read(mHairTintColor); + else if (nif->getBethVersion() <= 130) + mSkinTintColor = { nif->get(), 1.f }; + else if (nif->getBethVersion() <= 139) + nif->read(mSkinTintColor); + // Hair tint shader in FO76+ + else if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->read(mHairTintColor); + break; case BSLightingShaderType::ShaderType_HairTint: - nif->skip(12); // Tint color + if (nif->getBethVersion() <= 139) + nif->read(mHairTintColor); break; case BSLightingShaderType::ShaderType_ParallaxOcc: - nif->skip(4); // Max passes - nif->skip(4); // Scale + mParallax.read(nif); break; case BSLightingShaderType::ShaderType_MultiLayerParallax: - nif->skip(4); // Inner layer thickness - nif->skip(4); // Refraction scale - nif->skip(8); // Inner layer texture scale - nif->skip(4); // Environment map strength + mMultiLayerParallax.read(nif); break; case BSLightingShaderType::ShaderType_SparkleSnow: - nif->skip(16); // Sparkle parameters + nif->read(mSparkle); break; case BSLightingShaderType::ShaderType_EyeEnvmap: - nif->skip(4); // Cube map scale - nif->skip(12); // Left eye cube map offset - nif->skip(12); // Right eye cube map offset + nif->read(mCubeMapScale); + nif->read(mLeftEyeReflectionCenter); + nif->read(mRightEyeReflectionCenter); break; default: break; @@ -201,31 +346,80 @@ namespace Nif void BSLightingShaderProperty::post(Reader& nif) { BSShaderProperty::post(nif); + mTextureSet.post(nif); } void BSEffectShaderProperty::read(NIFStream* nif) { BSShaderProperty::read(nif); - flags1 = nif->getUInt(); - flags2 = nif->getUInt(); - mUVOffset = nif->getVector2(); - mUVScale = nif->getVector2(); + + if (nif->getBethVersion() <= 130) + { + nif->read(mShaderFlags1); + nif->read(mShaderFlags2); + } + else if (nif->getBethVersion() >= 132) + { + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); + } + + nif->read(mUVOffset); + nif->read(mUVScale); mSourceTexture = nif->getSizedString(); - unsigned int miscParams = nif->getUInt(); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + nif->skip(4); // Unknown + + uint32_t miscParams = nif->get(); mClamp = miscParams & 0xFF; mLightingInfluence = (miscParams >> 8) & 0xFF; mEnvMapMinLOD = (miscParams >> 16) & 0xFF; - mFalloffParams = nif->getVector4(); - mBaseColor = nif->getVector4(); - mBaseColorScale = nif->getFloat(); - mFalloffDepth = nif->getFloat(); + nif->read(mFalloffParams); + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + nif->read(mRefractionPower); + + nif->read(mBaseColor); + nif->read(mBaseColorScale); + nif->read(mFalloffDepth); mGreyscaleTexture = nif->getSizedString(); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + { + mEnvMapTexture = nif->getSizedString(); + mNormalTexture = nif->getSizedString(); + mEnvMaskTexture = nif->getSizedString(); + nif->read(mEnvMapScale); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + { + nif->read(mRefractionPower); + mReflectanceTexture = nif->getSizedString(); + mLightingTexture = nif->getSizedString(); + nif->read(mEmittanceColor); + mEmitGradientTexture = nif->getSizedString(); + mLuminance.read(nif); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + { + nif->skip(7); // Unknown bytes + nif->skip(6 * sizeof(float)); // Unknown floats + nif->skip(1); // Unknown byte + } } void NiFogProperty::read(NIFStream* nif) { Property::read(nif); + mFlags = nif->getUShort(); mFogDepth = nif->getFloat(); mColour = nif->getVector3(); @@ -249,6 +443,7 @@ namespace Nif void NiVertexColorProperty::read(NIFStream* nif) { Property::read(nif); + mFlags = nif->getUShort(); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { diff --git a/components/nif/property.hpp b/components/nif/property.hpp index d9574f31d6..1e7d7a78da 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -1,26 +1,3 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (property.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_PROPERTY_HPP #define OPENMW_COMPONENTS_NIF_PROPERTY_HPP @@ -109,19 +86,18 @@ namespace Nif void read(NIFStream* nif) override; }; - // These contain no other data than the 'flags' field struct NiShadeProperty : public Property { - unsigned short flags{ 0u }; + uint16_t mFlags{ 0u }; void read(NIFStream* nif) override { Property::read(nif); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) - flags = nif->getUShort(); + nif->read(mFlags); } }; - enum class BSShaderType : unsigned int + enum class BSShaderType : uint32_t { ShaderType_TallGrass = 0, ShaderType_Default = 1, @@ -133,42 +109,55 @@ namespace Nif ShaderType_NoLighting = 33 }; + enum BSShaderFlags1 + { + BSSFlag1_Specular = 0x00000001, + BSSFlag1_Decal = 0x04000000, + }; + + struct BSSPParallaxParams + { + float mMaxPasses; + float mScale; + + void read(NIFStream* nif); + }; + + struct BSSPRefractionParams + { + float mStrength; + int32_t mPeriod; + + void read(NIFStream* nif); + }; + struct BSShaderProperty : public NiShadeProperty { - unsigned int type{ 0u }, flags1{ 0u }, flags2{ 0u }; - float envMapIntensity{ 0.f }; + uint32_t mType{ 0u }, mShaderFlags1{ 0u }, mShaderFlags2{ 0u }; + float mEnvMapScale{ 0.f }; + void read(NIFStream* nif) override; - bool specular() const { return flags1 & 1; } - bool doubleSided() const { return (flags2 >> 4) & 1; } - bool treeAnim() const { return (flags2 >> 29) & 1; } - bool decal() const { return (flags1 >> 26) & 1; } + // These flags are shared between BSShader and BSLightingShader + // Shader-specific flag methods must be handled on per-record basis + bool specular() const { return mShaderFlags1 & BSSFlag1_Specular; } + bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } }; struct BSShaderLightingProperty : public BSShaderProperty { - unsigned int clamp{ 0u }; + unsigned int mClamp{ 0u }; void read(NIFStream* nif) override; - bool wrapT() const { return clamp & 1; } - bool wrapS() const { return (clamp >> 1) & 1; } + bool wrapT() const { return mClamp & 1; } + bool wrapS() const { return mClamp & 2; } }; struct BSShaderPPLightingProperty : public BSShaderLightingProperty { - BSShaderTextureSetPtr textureSet; - struct RefractionSettings - { - float strength{ 0.f }; - int period{ 0 }; - }; - struct ParallaxSettings - { - float passes{ 0.f }; - float scale{ 0.f }; - }; - RefractionSettings refraction; - ParallaxSettings parallax; + BSShaderTextureSetPtr mTextureSet; + BSSPRefractionParams mRefraction; + BSSPParallaxParams mParallax; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -176,13 +165,13 @@ namespace Nif struct BSShaderNoLightingProperty : public BSShaderLightingProperty { - std::string filename; - osg::Vec4f falloffParams; + std::string mFilename; + osg::Vec4f mFalloffParams; void read(NIFStream* nif) override; }; - enum class BSLightingShaderType : unsigned int + enum class BSLightingShaderType : uint32_t { ShaderType_Default = 0, ShaderType_EnvMap = 1, @@ -207,43 +196,146 @@ namespace Nif ShaderType_Dismemberment = 20 }; + enum BSLightingShaderFlags1 + { + BSLSFlag1_Falloff = 0x00000040, + }; + + enum BSLightingShaderFlags2 + { + BSLSFlag2_DoubleSided = 0x00000010, + BSLSFlag2_TreeAnim = 0x20000000, + }; + + struct BSSPLuminanceParams + { + float mLumEmittance; + float mExposureOffset; + float mFinalExposureMin, mFinalExposureMax; + + void read(NIFStream* nif); + }; + + struct BSSPWetnessParams + { + float mSpecScale; + float mSpecPower; + float mMinVar; + float mEnvMapScale; + float mFresnelPower; + float mMetalness; + + void read(NIFStream* nif); + }; + + struct BSSPMLParallaxParams + { + float mInnerLayerThickness; + float mRefractionScale; + osg::Vec2f mInnerLayerTextureScale; + float mEnvMapScale; + + void read(NIFStream* nif); + }; + + struct BSSPTranslucencyParams + { + osg::Vec3f mSubsurfaceColor; + float mTransmissiveScale; + float mTurbulence; + bool mThickObject; + bool mMixAlbedo; + + void read(NIFStream* nif); + }; + struct BSLightingShaderProperty : public BSShaderProperty { + std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; + osg::Vec2f mUVOffset, mUVScale; BSShaderTextureSetPtr mTextureSet; - unsigned int mClamp{ 0u }; + osg::Vec3f mEmissive; + float mEmissiveMult; + std::string mRootMaterial; + uint32_t mClamp; float mAlpha; - float mGlossiness; - osg::Vec3f mEmissive, mSpecular; - float mEmissiveMult, mSpecStrength; + float mRefractionStrength; + float mGlossiness{ 80.f }; + float mSmoothness{ 1.f }; + osg::Vec3f mSpecular; + float mSpecStrength; + std::array mLightingEffects; + float mSubsurfaceRolloff; + float mRimlightPower; + float mBacklightPower; + float mGrayscaleToPaletteScale{ 1.f }; + float mFresnelPower{ 5.f }; + BSSPWetnessParams mWetness; + bool mDoTranslucency{ false }; + BSSPTranslucencyParams mTranslucency; + std::vector> mTextureArrays; + BSSPLuminanceParams mLuminance; + + bool mUseSSR; + bool mWetnessUseSSR; + + osg::Vec4f mSkinTintColor; + osg::Vec3f mHairTintColor; + + BSSPParallaxParams mParallax; + BSSPMLParallaxParams mMultiLayerParallax; + osg::Vec4f mSparkle; + + float mCubeMapScale; + osg::Vec3f mLeftEyeReflectionCenter; + osg::Vec3f mRightEyeReflectionCenter; void read(NIFStream* nif) override; void post(Reader& nif) override; + + bool doubleSided() const { return mShaderFlags2 & BSLSFlag2_DoubleSided; } + bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; struct BSEffectShaderProperty : public BSShaderProperty { + std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; osg::Vec2f mUVOffset, mUVScale; std::string mSourceTexture; - unsigned char mClamp; - unsigned char mLightingInfluence; - unsigned char mEnvMapMinLOD; + uint8_t mClamp; + uint8_t mLightingInfluence; + uint8_t mEnvMapMinLOD; osg::Vec4f mFalloffParams; + float mRefractionPower; osg::Vec4f mBaseColor; float mBaseColorScale; float mFalloffDepth; std::string mGreyscaleTexture; + std::string mEnvMapTexture; + std::string mNormalTexture; + std::string mEnvMaskTexture; + float mEnvMapScale; + std::string mReflectanceTexture; + std::string mLightingTexture; + osg::Vec3f mEmittanceColor; + std::string mEmitGradientTexture; + BSSPLuminanceParams mLuminance; void read(NIFStream* nif) override; - bool useFalloff() const { return (flags >> 6) & 1; } + bool useFalloff() const { return mShaderFlags1 & BSLSFlag1_Falloff; } + bool doubleSided() const { return mShaderFlags2 & BSLSFlag2_DoubleSided; } + bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; struct NiDitherProperty : public Property { unsigned short flags; + void read(NIFStream* nif) override { Property::read(nif); + flags = nif->getUShort(); } }; @@ -252,9 +344,11 @@ namespace Nif { unsigned short flags; unsigned int testFunction; + void read(NIFStream* nif) override { Property::read(nif); + flags = nif->getUShort(); testFunction = (flags >> 2) & 0x7; if (nif->getVersion() >= NIFStream::generateVersion(4, 1, 0, 12) @@ -264,15 +358,17 @@ namespace Nif bool depthTest() const { return flags & 1; } - bool depthWrite() const { return (flags >> 1) & 1; } + bool depthWrite() const { return flags & 2; } }; struct NiSpecularProperty : public Property { unsigned short flags; + void read(NIFStream* nif) override { Property::read(nif); + flags = nif->getUShort(); } @@ -282,9 +378,11 @@ namespace Nif struct NiWireframeProperty : public Property { unsigned short flags; + void read(NIFStream* nif) override { Property::read(nif); + flags = nif->getUShort(); } @@ -301,6 +399,7 @@ namespace Nif void read(NIFStream* nif) override { Property::read(nif); + flags = nif->getUShort(); data.read(nif); } @@ -400,12 +499,19 @@ namespace Nif struct NiAlphaProperty : public StructPropT { - bool useAlphaBlending() const { return flags & 1; } + enum Flags + { + Flag_Blending = 0x0001, + Flag_Testing = 0x0200, + Flag_NoSorter = 0x2000, + }; + + bool useAlphaBlending() const { return flags & Flag_Blending; } + bool useAlphaTesting() const { return flags & Flag_Testing; } + bool noSorter() const { return flags & Flag_NoSorter; } + int sourceBlendMode() const { return (flags >> 1) & 0xF; } int destinationBlendMode() const { return (flags >> 5) & 0xF; } - bool noSorter() const { return (flags >> 13) & 1; } - - bool useAlphaTesting() const { return (flags >> 9) & 1; } int alphaTestMode() const { return (flags >> 10) & 0x7; } }; @@ -460,5 +566,5 @@ namespace Nif } }; -} // Namespace +} #endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 05f42db05a..00001581d5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2202,18 +2202,16 @@ namespace NifOsg { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!texprop->textureSet.empty()) + if (!texprop->mTextureSet.empty()) { - auto textureSet = texprop->textureSet.getPtr(); + auto textureSet = texprop->mTextureSet.getPtr(); handleTextureSet( - textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures); + textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); - if (texprop->doubleSided()) - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2221,10 +2219,10 @@ namespace NifOsg auto texprop = static_cast(property); bool shaderRequired = true; bool useFalloff = false; - node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->type))); + node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!texprop->filename.empty()) + if (!texprop->mFilename.empty()) { if (!boundTextures.empty()) { @@ -2233,7 +2231,7 @@ namespace NifOsg boundTextures.clear(); } std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->filename, imageManager->getVFS()); + = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); @@ -2247,20 +2245,18 @@ namespace NifOsg if (mBethVersion >= 27) { useFalloff = true; - stateset->addUniform(new osg::Uniform("falloffParams", texprop->falloffParams)); + stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); } } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); - if (texprop->doubleSided()) - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); break; } case Nif::RC_BSLightingShaderProperty: { auto texprop = static_cast(property); bool shaderRequired = true; - node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->type))); + node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); if (!texprop->mTextureSet.empty()) From 1e3da5516a603b17e56e116319b2ce5bbac540ef Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 18:58:37 +0300 Subject: [PATCH 0154/2167] Fix BA2 handling in niftest --- apps/niftest/niftest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 06f2110e69..004e45765c 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -38,7 +38,7 @@ bool isNIF(const std::filesystem::path& filename) /// See if the file has the "bsa" extension. bool isBSA(const std::filesystem::path& filename) { - return hasExtension(filename, ".bsa"); + return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); } std::unique_ptr makeBsaArchive(const std::filesystem::path& path) @@ -216,7 +216,7 @@ int main(int argc, char** argv) else { std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path) - << "\" is not a nif file, bsa file, or directory!" << std::endl; + << "\" is not a nif file, bsa/ba2 file, or directory!" << std::endl; } } catch (std::exception& e) From 18e4af04b37b284ed9455570e244227c142ac6ad Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 20:04:51 +0300 Subject: [PATCH 0155/2167] Rewrite BSVertexData loading and read FO4 geometry properly --- components/nif/node.cpp | 75 +++++++++++++++++++++-------------------- components/nif/node.hpp | 5 +-- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 328546b7c6..ac99a06a1b 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -433,60 +433,63 @@ namespace Nif void BSVertexData::read(NIFStream* nif, uint16_t flags) { - uint16_t vertexFlag = flags & BSVertexDesc::VertexAttribute::Vertex; - uint16_t tangentsFlag = flags & BSVertexDesc::VertexAttribute::Tangents; - uint16_t UVsFlag = flags & BSVertexDesc::VertexAttribute::UVs; - uint16_t normalsFlag = flags & BSVertexDesc::VertexAttribute::Normals; + bool fullPrecision = true; + if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_SSE) + fullPrecision = flags & BSVertexDesc::VertexAttribute::Full_Precision; - if (vertexFlag == BSVertexDesc::VertexAttribute::Vertex) + bool hasVertex = flags & BSVertexDesc::VertexAttribute::Vertex; + bool hasTangent = flags & BSVertexDesc::VertexAttribute::Tangents; + bool hasUV = flags & BSVertexDesc::VertexAttribute::UVs; + bool hasNormal = flags & BSVertexDesc::VertexAttribute::Normals; + bool hasVertexColor = flags & BSVertexDesc::VertexAttribute::Vertex_Colors; + bool hasSkinData = flags & BSVertexDesc::VertexAttribute::Skinned; + bool hasEyeData = flags & BSVertexDesc::VertexAttribute::Eye_Data; + + if (hasVertex) { - nif->read(mVertex); + if (fullPrecision) + { + nif->read(mVertex); + if (hasTangent) + nif->read(mBitangentX); + else + nif->skip(4); // Unused + } + else + { + nif->readArray(mHalfVertex); + if (hasTangent) + nif->read(mHalfBitangentX); + else + nif->skip(2); // Unused + } } - if ((vertexFlag | tangentsFlag) - == (BSVertexDesc::VertexAttribute::Vertex | BSVertexDesc::VertexAttribute::Tangents)) - { - nif->read(mBitangentX); - } - - if ((vertexFlag | tangentsFlag) == BSVertexDesc::VertexAttribute::Vertex) - { - nif->read(mUnusedW); - } - - if (UVsFlag == BSVertexDesc::VertexAttribute::UVs) - { + if (hasUV) nif->readArray(mUV); - } - if (normalsFlag) + if (hasNormal) { nif->readArray(mNormal); nif->read(mBitangentY); + if (hasTangent) + { + nif->readArray(mTangent); + nif->read(mBitangentZ); + } } - if ((normalsFlag | tangentsFlag) - == (BSVertexDesc::VertexAttribute::Normals | BSVertexDesc::VertexAttribute::Tangents)) - { - nif->readArray(mTangent); - nif->read(mBitangentZ); - } + if (hasVertexColor) + nif->readArray(mVertColor); - if (flags & BSVertexDesc::VertexAttribute::Vertex_Colors) - { - nif->readArray(mVertColors); - } - - if (flags & BSVertexDesc::VertexAttribute::Skinned) + if (hasSkinData) { nif->readArray(mBoneWeights); nif->readArray(mBoneIndices); } - if (flags & BSVertexDesc::VertexAttribute::Eye_Data) - { + if (hasEyeData) nif->read(mEyeData); - } } void BSValueNode::read(NIFStream* nif) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 7d851051e0..c87c3f6a62 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -339,14 +339,15 @@ namespace Nif struct BSVertexData { osg::Vec3f mVertex; + std::array mHalfVertex; float mBitangentX; - uint32_t mUnusedW; + Misc::float16_t mHalfBitangentX; std::array mUV; std::array mNormal; char mBitangentY; std::array mTangent; char mBitangentZ; - std::array mVertColors; + std::array mVertColor; std::array mBoneWeights; std::array mBoneIndices; float mEyeData; From 080d7d2c135ca95c89121597d307da66ff6bd09e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 21:07:49 +0300 Subject: [PATCH 0156/2167] Refactor NiTexturingProperty, load everything --- components/nif/property.cpp | 103 +++++++++++++++++--------------- components/nif/property.hpp | 101 +++++++++++++++++-------------- components/nifosg/nifloader.cpp | 22 +++---- 3 files changed, 121 insertions(+), 105 deletions(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 8dd2be40f4..308c3a42e2 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -6,50 +6,61 @@ namespace Nif { + void NiTextureTransform::read(NIFStream* nif) + { + nif->read(mOffset); + nif->read(mScale); + nif->read(mRotation); + mTransformMethod = static_cast(nif->get()); + nif->read(mOrigin); + } + void NiTexturingProperty::Texture::read(NIFStream* nif) { - nif->read(inUse); - if (!inUse) + nif->read(mEnabled); + if (!mEnabled) return; - texture.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mSourceTexture.read(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { - clamp = nif->getInt(); - nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible + nif->read(mClamp); + nif->read(mFilter); } else { - clamp = nif->getUShort() & 0xF; + uint16_t flags; + nif->read(flags); + mClamp = flags & 0xF; + mFilter = (flags >> 4) & 0xF; } - // Max anisotropy. I assume we'll always only use the global anisotropy setting. + if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4)) - nif->getUShort(); + nif->read(mMaxAnisotropy); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - uvSet = nif->getUInt(); + nif->read(mUVSet); - // Two PS2-specific shorts. - if (nif->getVersion() < NIFStream::generateVersion(10, 4, 0, 2)) + // PS2 filtering settings + if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) nif->skip(4); - if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 18)) - nif->skip(2); // Unknown short - else if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + + if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 12)) + nif->skip(2); // Unknown + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { - if (nif->get()) // Has texture transform - { - nif->getVector2(); // UV translation - nif->getVector2(); // UV scale - nif->getFloat(); // W axis rotation - nif->getUInt(); // Transform method - nif->getVector2(); // Texture rotation origin - } + nif->read(mHasTransform); + if (mHasTransform) + mTransform.read(nif); } } void NiTexturingProperty::Texture::post(Reader& nif) { - texture.post(nif); + mSourceTexture.post(nif); } void NiTexturingProperty::read(NIFStream* nif) @@ -58,37 +69,33 @@ namespace Nif if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20, 1, 0, 2)) - flags = nif->getUShort(); + nif->read(mFlags); if (nif->getVersion() <= NIFStream::generateVersion(20, 1, 0, 1)) - apply = nif->getUInt(); + mApplyMode = static_cast(nif->get()); - unsigned int numTextures = nif->getUInt(); - - if (!numTextures) - return; - - textures.resize(numTextures); - for (unsigned int i = 0; i < numTextures; i++) + mTextures.resize(nif->get()); + for (size_t i = 0; i < mTextures.size(); i++) { - textures[i].read(nif); - if (i == 5 && textures[5].inUse) // Bump map settings + mTextures[i].read(nif); + + if (i == 5 && mTextures[5].mEnabled) { - envMapLumaBias = nif->getVector2(); - bumpMapMatrix = nif->getVector4(); + nif->read(mEnvMapLumaBias); + nif->read(mBumpMapMatrix); } - else if (i == 7 && textures[7].inUse && nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) - /*float parallaxOffset = */ nif->getFloat(); + else if (i == 7 && mTextures[7].mEnabled && nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) + nif->read(mParallaxOffset); } if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) { - unsigned int numShaderTextures = nif->getUInt(); - shaderTextures.resize(numShaderTextures); - for (unsigned int i = 0; i < numShaderTextures; i++) + mShaderTextures.resize(nif->get()); + mShaderIds.resize(mShaderTextures.size()); + for (size_t i = 0; i < mShaderTextures.size(); i++) { - shaderTextures[i].read(nif); - if (shaderTextures[i].inUse) - nif->getUInt(); // Unique identifier + mShaderTextures[i].read(nif); + if (mShaderTextures[i].mEnabled) + nif->read(mShaderIds[i]); } } } @@ -97,10 +104,10 @@ namespace Nif { Property::post(nif); - for (size_t i = 0; i < textures.size(); i++) - textures[i].post(nif); - for (size_t i = 0; i < shaderTextures.size(); i++) - shaderTextures[i].post(nif); + for (Texture& tex : mTextures) + tex.post(nif); + for (Texture& tex : mShaderTextures) + tex.post(nif); } void BSSPParallaxParams::read(NIFStream* nif) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 1e7d7a78da..c4cf947d4e 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -10,53 +10,38 @@ namespace Nif { }; - struct NiTexturingProperty : public Property + struct NiTextureTransform { - unsigned short flags{ 0u }; - - // A sub-texture - struct Texture + enum class Method : uint32_t { - /* Clamp mode - 0 - clampS clampT - 1 - clampS wrapT - 2 - wrapS clampT - 3 - wrapS wrapT - */ - - bool inUse; - NiSourceTexturePtr texture; - - unsigned int clamp, uvSet; - - void read(NIFStream* nif); - void post(Reader& nif); - - bool wrapT() const { return clamp & 1; } - bool wrapS() const { return (clamp >> 1) & 1; } + // Back = inverse of mOrigin. + // FromMaya = inverse of the V axis with a positive translation along V of 1 unit. + MayaLegacy = 0, // mOrigin * mRotation * Back * mOffset * mScale + Max = 1, // mOrigin * mScale * mRotation * mOffset * Back + Maya = 2, // mOrigin * mRotation * Back * FromMaya * mOffset * mScale }; - /* Apply mode: - 0 - replace - 1 - decal - 2 - modulate - 3 - hilight // These two are for PS2 only? - 4 - hilight2 - */ - unsigned int apply{ 0 }; + osg::Vec2f mOffset; + osg::Vec2f mScale; + float mRotation; + Method mTransformMethod; + osg::Vec2f mOrigin; - /* - * The textures in this list are as follows: - * - * 0 - Base texture - * 1 - Dark texture - * 2 - Detail texture - * 3 - Gloss texture - * 4 - Glow texture - * 5 - Bump map texture - * 6 - Decal texture - */ - enum TextureType : uint32_t + void read(NIFStream* nif); + }; + + struct NiTexturingProperty : public Property + { + enum class ApplyMode : uint32_t + { + Replace = 0, + Decal = 1, + Modulate = 2, + Hilight = 3, // PS2-specific? + Hilight2 = 4, // Used for Oblivion parallax + }; + + enum TextureType { BaseTexture = 0, DarkTexture = 1, @@ -67,11 +52,35 @@ namespace Nif DecalTexture = 6, }; - std::vector textures; - std::vector shaderTextures; + // A sub-texture + struct Texture + { + bool mEnabled; + NiSourceTexturePtr mSourceTexture; + uint32_t mClamp; + uint32_t mFilter; + uint16_t mMaxAnisotropy; + uint32_t mUVSet; + bool mHasTransform; + NiTextureTransform mTransform; - osg::Vec2f envMapLumaBias; - osg::Vec4f bumpMapMatrix; + void read(NIFStream* nif); + void post(Reader& nif); + + bool wrapT() const { return mClamp & 1; } + bool wrapS() const { return mClamp & 2; } + }; + + uint16_t mFlags{ 0u }; + ApplyMode mApplyMode{ ApplyMode::Modulate }; + + std::vector mTextures; + std::vector mShaderTextures; + std::vector mShaderIds; + + osg::Vec2f mEnvMapLumaBias; + osg::Vec4f mBumpMapMatrix; + float mParallaxOffset; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 00001581d5..10997c8558 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1827,9 +1827,9 @@ namespace NifOsg // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the // shadow casting shader will need to be updated accordingly. - for (size_t i = 0; i < texprop->textures.size(); ++i) + for (size_t i = 0; i < texprop->mTextures.size(); ++i) { - if (texprop->textures[i].inUse + if (texprop->mTextures[i].mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) { switch (i) @@ -1854,10 +1854,10 @@ namespace NifOsg unsigned int uvSet = 0; // create a new texture, will later attempt to share using the SharedStateManager osg::ref_ptr texture2d; - if (texprop->textures[i].inUse) + if (texprop->mTextures[i].mEnabled) { - const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; - if (tex.texture.empty() && texprop->mController.empty()) + const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; + if (tex.mSourceTexture.empty() && texprop->mController.empty()) { if (i == 0) Log(Debug::Warning) << "Base texture is in use but empty on shape \"" << nodeName @@ -1865,9 +1865,9 @@ namespace NifOsg continue; } - if (!tex.texture.empty()) + if (!tex.mSourceTexture.empty()) { - const Nif::NiSourceTexture* st = tex.texture.getPtr(); + const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); osg::ref_ptr image = handleSourceTexture(st, imageManager); texture2d = new osg::Texture2D(image); if (image) @@ -1878,7 +1878,7 @@ namespace NifOsg handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT()); - uvSet = tex.uvSet; + uvSet = tex.mUVSet; } else { @@ -1926,10 +1926,10 @@ namespace NifOsg // Bump maps offset the environment map. // Set this texture to Off by default since we can't render it with the fixed-function pipeline stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); - osg::Matrix2 bumpMapMatrix(texprop->bumpMapMatrix.x(), texprop->bumpMapMatrix.y(), - texprop->bumpMapMatrix.z(), texprop->bumpMapMatrix.w()); + osg::Matrix2 bumpMapMatrix(texprop->mBumpMapMatrix.x(), texprop->mBumpMapMatrix.y(), + texprop->mBumpMapMatrix.z(), texprop->mBumpMapMatrix.w()); stateset->addUniform(new osg::Uniform("bumpMapMatrix", bumpMapMatrix)); - stateset->addUniform(new osg::Uniform("envMapLumaBias", texprop->envMapLumaBias)); + stateset->addUniform(new osg::Uniform("envMapLumaBias", texprop->mEnvMapLumaBias)); } else if (i == Nif::NiTexturingProperty::GlossTexture) { From c551f23667af67e3c3c805440c11524b0f1ccacc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 21:18:14 +0300 Subject: [PATCH 0157/2167] Update NIF property loading, second pass Refactor all remaining NIF properties --- components/nif/property.cpp | 130 +++++++++----- components/nif/property.hpp | 301 ++++++++++++-------------------- components/nifosg/nifloader.cpp | 101 ++++++----- 3 files changed, 257 insertions(+), 275 deletions(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 308c3a42e2..a0d6675778 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -423,39 +423,104 @@ namespace Nif } } + void NiAlphaProperty::read(NIFStream* nif) + { + Property::read(nif); + + nif->read(mFlags); + nif->read(mThreshold); + } + + void NiDitherProperty::read(NIFStream* nif) + { + Property::read(nif); + + nif->read(mFlags); + } + void NiFogProperty::read(NIFStream* nif) { Property::read(nif); - mFlags = nif->getUShort(); - mFogDepth = nif->getFloat(); - mColour = nif->getVector3(); + nif->read(mFlags); + nif->read(mFogDepth); + nif->read(mColour); } - void S_MaterialProperty::read(NIFStream* nif) + void NiMaterialProperty::read(NIFStream* nif) { + Property::read(nif); + + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + nif->read(mFlags); if (nif->getBethVersion() < 26) { - ambient = nif->getVector3(); - diffuse = nif->getVector3(); + nif->read(mAmbient); + nif->read(mDiffuse); } - specular = nif->getVector3(); - emissive = nif->getVector3(); - glossiness = nif->getFloat(); - alpha = nif->getFloat(); + nif->read(mSpecular); + nif->read(mEmissive); + nif->read(mGlossiness); + nif->read(mAlpha); if (nif->getBethVersion() >= 22) - emissiveMult = nif->getFloat(); + nif->read(mEmissiveMult); + } + + void NiShadeProperty::read(NIFStream* nif) + { + Property::read(nif); + + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + nif->read(mFlags); + } + + void NiSpecularProperty::read(NIFStream* nif) + { + Property::read(nif); + + mEnable = nif->get() & 1; + } + + void NiStencilProperty::read(NIFStream* nif) + { + Property::read(nif); + + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + nif->read(mFlags); + mEnabled = nif->get() != 0; + mTestFunction = static_cast(nif->get()); + nif->read(mStencilRef); + nif->read(mStencilMask); + mFailAction = static_cast(nif->get()); + mZFailAction = static_cast(nif->get()); + mPassAction = static_cast(nif->get()); + mDrawMode = static_cast(nif->get()); + } + else + { + nif->read(mFlags); + mEnabled = mFlags & 0x1; + mFailAction = static_cast((mFlags>> 1) & 0x7); + mZFailAction = static_cast((mFlags >> 4) & 0x7); + mPassAction = static_cast((mFlags >> 7) & 0x7); + mDrawMode = static_cast((mFlags >> 10) & 0x3); + mTestFunction = static_cast((mFlags >> 12) & 0x7); + nif->read(mStencilRef); + nif->read(mStencilMask); + } } void NiVertexColorProperty::read(NIFStream* nif) { Property::read(nif); - mFlags = nif->getUShort(); + nif->read(mFlags); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { - mVertexMode = static_cast(nif->getUInt()); - mLightingMode = static_cast(nif->getUInt()); + mVertexMode = static_cast(nif->get()); + mLightingMode = static_cast(nif->get()); } else { @@ -464,36 +529,23 @@ namespace Nif } } - void S_AlphaProperty::read(NIFStream* nif) + void NiWireframeProperty::read(NIFStream* nif) { - threshold = nif->getChar(); + Property::read(nif); + + mEnable = nif->get() & 1; } - void S_StencilProperty::read(NIFStream* nif) + void NiZBufferProperty::read(NIFStream* nif) { - if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - { - enabled = nif->getChar(); - compareFunc = nif->getInt(); - stencilRef = nif->getUInt(); - stencilMask = nif->getUInt(); - failAction = nif->getInt(); - zFailAction = nif->getInt(); - zPassAction = nif->getInt(); - drawMode = nif->getInt(); - } + Property::read(nif); + + nif->read(mFlags); + if (nif->getVersion() >= NIFStream::generateVersion(4, 1, 0, 12) + && nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + nif->read(mTestFunction); else - { - unsigned short flags = nif->getUShort(); - enabled = flags & 0x1; - failAction = (flags >> 1) & 0x7; - zFailAction = (flags >> 4) & 0x7; - zPassAction = (flags >> 7) & 0x7; - drawMode = (flags >> 10) & 0x3; - compareFunc = (flags >> 12) & 0x7; - stencilRef = nif->getUInt(); - stencilMask = nif->getUInt(); - } + mTestFunction = (mFlags >> 2) & 0x7; } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index c4cf947d4e..54da082b5c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -6,7 +6,7 @@ namespace Nif { - struct Property : public NiObjectNET + struct Property : NiObjectNET { }; @@ -30,7 +30,7 @@ namespace Nif void read(NIFStream* nif); }; - struct NiTexturingProperty : public Property + struct NiTexturingProperty : Property { enum class ApplyMode : uint32_t { @@ -86,24 +86,11 @@ namespace Nif void post(Reader& nif) override; }; - struct NiFogProperty : public Property - { - unsigned short mFlags; - float mFogDepth; - osg::Vec3f mColour; - - void read(NIFStream* nif) override; - }; - - struct NiShadeProperty : public Property + struct NiShadeProperty : Property { uint16_t mFlags{ 0u }; - void read(NIFStream* nif) override - { - Property::read(nif); - if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) - nif->read(mFlags); - } + + void read(NIFStream* nif) override; }; enum class BSShaderType : uint32_t @@ -140,7 +127,7 @@ namespace Nif void read(NIFStream* nif); }; - struct BSShaderProperty : public NiShadeProperty + struct BSShaderProperty : NiShadeProperty { uint32_t mType{ 0u }, mShaderFlags1{ 0u }, mShaderFlags2{ 0u }; float mEnvMapScale{ 0.f }; @@ -153,16 +140,17 @@ namespace Nif bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } }; - struct BSShaderLightingProperty : public BSShaderProperty + struct BSShaderLightingProperty : BSShaderProperty { unsigned int mClamp{ 0u }; + void read(NIFStream* nif) override; bool wrapT() const { return mClamp & 1; } bool wrapS() const { return mClamp & 2; } }; - struct BSShaderPPLightingProperty : public BSShaderLightingProperty + struct BSShaderPPLightingProperty : BSShaderLightingProperty { BSShaderTextureSetPtr mTextureSet; BSSPRefractionParams mRefraction; @@ -172,7 +160,7 @@ namespace Nif void post(Reader& nif) override; }; - struct BSShaderNoLightingProperty : public BSShaderLightingProperty + struct BSShaderNoLightingProperty : BSShaderLightingProperty { std::string mFilename; osg::Vec4f mFalloffParams; @@ -258,7 +246,7 @@ namespace Nif void read(NIFStream* nif); }; - struct BSLightingShaderProperty : public BSShaderProperty + struct BSLightingShaderProperty : BSShaderProperty { std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; osg::Vec2f mUVOffset, mUVScale; @@ -306,7 +294,7 @@ namespace Nif bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; - struct BSEffectShaderProperty : public BSShaderProperty + struct BSEffectShaderProperty : BSShaderProperty { std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; osg::Vec2f mUVOffset, mUVScale; @@ -337,95 +325,24 @@ namespace Nif bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; - struct NiDitherProperty : public Property + struct NiAlphaProperty : Property { - unsigned short flags; - - void read(NIFStream* nif) override + enum Flags { - Property::read(nif); + Flag_Blending = 0x0001, + Flag_Testing = 0x0200, + Flag_NoSorter = 0x2000, + }; - flags = nif->getUShort(); - } - }; + uint16_t mFlags; + uint8_t mThreshold; - struct NiZBufferProperty : public Property - { - unsigned short flags; - unsigned int testFunction; + void read(NIFStream* nif) override; - void read(NIFStream* nif) override - { - Property::read(nif); + bool useAlphaBlending() const { return mFlags & Flag_Blending; } + bool useAlphaTesting() const { return mFlags & Flag_Testing; } + bool noSorter() const { return mFlags & Flag_NoSorter; } - flags = nif->getUShort(); - testFunction = (flags >> 2) & 0x7; - if (nif->getVersion() >= NIFStream::generateVersion(4, 1, 0, 12) - && nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - testFunction = nif->getUInt(); - } - - bool depthTest() const { return flags & 1; } - - bool depthWrite() const { return flags & 2; } - }; - - struct NiSpecularProperty : public Property - { - unsigned short flags; - - void read(NIFStream* nif) override - { - Property::read(nif); - - flags = nif->getUShort(); - } - - bool isEnabled() const { return flags & 1; } - }; - - struct NiWireframeProperty : public Property - { - unsigned short flags; - - void read(NIFStream* nif) override - { - Property::read(nif); - - flags = nif->getUShort(); - } - - bool isEnabled() const { return flags & 1; } - }; - - // The rest are all struct-based - template - struct StructPropT : Property - { - T data; - unsigned short flags; - - void read(NIFStream* nif) override - { - Property::read(nif); - - flags = nif->getUShort(); - data.read(nif); - } - }; - - struct S_MaterialProperty - { - // The vector components are R,G,B - osg::Vec3f ambient{ 1.f, 1.f, 1.f }, diffuse{ 1.f, 1.f, 1.f }; - osg::Vec3f specular, emissive; - float glossiness{ 0.f }, alpha{ 0.f }, emissiveMult{ 1.f }; - - void read(NIFStream* nif); - }; - - struct S_AlphaProperty - { /* NiAlphaProperty blend modes (glBlendFunc): 0000 GL_ONE @@ -454,125 +371,131 @@ namespace Nif http://niftools.sourceforge.net/doc/nif/NiAlphaProperty.html */ - // Tested against when certain flags are set (see above.) - unsigned char threshold; - - void read(NIFStream* nif); + int sourceBlendMode() const { return (mFlags >> 1) & 0xF; } + int destinationBlendMode() const { return (mFlags >> 5) & 0xF; } + int alphaTestMode() const { return (mFlags >> 10) & 0x7; } }; - /* - Docs taken from: - http://niftools.sourceforge.net/doc/nif/NiStencilProperty.html - */ - struct S_StencilProperty + struct NiDitherProperty : Property { - // Is stencil test enabled? - unsigned char enabled; + uint16_t mFlags; - /* - 0 TEST_NEVER - 1 TEST_LESS - 2 TEST_EQUAL - 3 TEST_LESS_EQUAL - 4 TEST_GREATER - 5 TEST_NOT_EQUAL - 6 TEST_GREATER_EQUAL - 7 TEST_NEVER (though nifskope comment says TEST_ALWAYS, but ingame it is TEST_NEVER) - */ - int compareFunc; - unsigned stencilRef; - unsigned stencilMask; - /* - Stencil test fail action, depth test fail action and depth test pass action: - 0 ACTION_KEEP - 1 ACTION_ZERO - 2 ACTION_REPLACE - 3 ACTION_INCREMENT - 4 ACTION_DECREMENT - 5 ACTION_INVERT - */ - int failAction; - int zFailAction; - int zPassAction; - /* - Face draw mode: - 0 DRAW_CCW_OR_BOTH - 1 DRAW_CCW [default] - 2 DRAW_CW - 3 DRAW_BOTH - */ - int drawMode; - - void read(NIFStream* nif); + void read(NIFStream* nif) override; }; - struct NiAlphaProperty : public StructPropT + struct NiFogProperty : Property { - enum Flags + uint16_t mFlags; + float mFogDepth; + osg::Vec3f mColour; + + void read(NIFStream* nif) override; + }; + + struct NiMaterialProperty : Property + { + uint16_t mFlags{ 0u }; + osg::Vec3f mAmbient{ 1.f, 1.f, 1.f }; + osg::Vec3f mDiffuse{ 1.f, 1.f, 1.f }; + osg::Vec3f mSpecular; + osg::Vec3f mEmissive; + float mGlossiness{ 0.f }; + float mAlpha{ 0.f }; + float mEmissiveMult{ 1.f }; + + void read(NIFStream* nif) override; + }; + + struct NiSpecularProperty : Property + { + bool mEnable; + + void read(NIFStream* nif) override; + }; + + struct NiStencilProperty : Property + { + enum class TestFunc : uint32_t { - Flag_Blending = 0x0001, - Flag_Testing = 0x0200, - Flag_NoSorter = 0x2000, + Never = 0, + Less = 1, + Equal = 2, + LessEqual = 3, + Greater = 4, + NotEqual = 5, + GreaterEqual = 6, + Always = 7, }; - bool useAlphaBlending() const { return flags & Flag_Blending; } - bool useAlphaTesting() const { return flags & Flag_Testing; } - bool noSorter() const { return flags & Flag_NoSorter; } + enum class Action : uint32_t + { + Keep = 0, + Zero = 1, + Replace = 2, + Increment = 3, + Decrement = 4, + Invert = 5, + }; - int sourceBlendMode() const { return (flags >> 1) & 0xF; } - int destinationBlendMode() const { return (flags >> 5) & 0xF; } - int alphaTestMode() const { return (flags >> 10) & 0x7; } + enum class DrawMode : uint32_t + { + Default = 0, + CounterClockwise = 1, + Clockwise = 2, + Both = 3, + }; + + uint16_t mFlags{ 0u }; + bool mEnabled; + TestFunc mTestFunction; + uint32_t mStencilRef; + uint32_t mStencilMask; + Action mFailAction; + Action mZFailAction; + Action mPassAction; + DrawMode mDrawMode; + + void read(NIFStream* nif) override; }; - struct NiVertexColorProperty : public Property + struct NiVertexColorProperty : Property { - enum class VertexMode : unsigned int + enum class VertexMode : uint32_t { VertMode_SrcIgnore = 0, VertMode_SrcEmissive = 1, VertMode_SrcAmbDif = 2 }; - enum class LightMode : unsigned int + enum class LightMode : uint32_t { LightMode_Emissive = 0, LightMode_EmiAmbDif = 1 }; - unsigned short mFlags; + uint16_t mFlags; VertexMode mVertexMode; LightMode mLightingMode; void read(NIFStream* nif) override; }; - struct NiStencilProperty : public Property + struct NiWireframeProperty : Property { - S_StencilProperty data; - unsigned short flags{ 0u }; + bool mEnable; - void read(NIFStream* nif) override - { - Property::read(nif); - if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) - flags = nif->getUShort(); - data.read(nif); - } + void read(NIFStream* nif) override; }; - struct NiMaterialProperty : public Property + struct NiZBufferProperty : Property { - S_MaterialProperty data; - unsigned short flags{ 0u }; + uint16_t mFlags; + uint32_t mTestFunction; - void read(NIFStream* nif) override - { - Property::read(nif); - if (nif->getVersion() >= NIFStream::generateVersion(3, 0, 0, 0) - && nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) - flags = nif->getUShort(); - data.read(nif); - } + void read(NIFStream* nif) override; + + bool depthTest() const { return mFlags & 1; } + bool depthWrite() const { return mFlags & 2; } }; } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 10997c8558..ef2b97dff8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -412,7 +412,7 @@ namespace NifOsg { const Nif::NiStencilProperty* stencilprop = static_cast(property.getPtr()); - if (stencilprop->data.enabled != 0) + if (stencilprop->mEnabled) { hasStencilProperty = true; break; @@ -1616,50 +1616,52 @@ namespace NifOsg } } - osg::Stencil::Function getStencilFunction(int func) + osg::Stencil::Function getStencilFunction(Nif::NiStencilProperty::TestFunc func) { + using TestFunc = Nif::NiStencilProperty::TestFunc; switch (func) { - case 0: + case TestFunc::Never: return osg::Stencil::NEVER; - case 1: + case TestFunc::Less: return osg::Stencil::LESS; - case 2: + case TestFunc::Equal: return osg::Stencil::EQUAL; - case 3: + case TestFunc::LessEqual: return osg::Stencil::LEQUAL; - case 4: + case TestFunc::Greater: return osg::Stencil::GREATER; - case 5: + case TestFunc::NotEqual: return osg::Stencil::NOTEQUAL; - case 6: + case TestFunc::GreaterEqual: return osg::Stencil::GEQUAL; - case 7: + case TestFunc::Always: return osg::Stencil::ALWAYS; default: - Log(Debug::Info) << "Unexpected stencil function: " << func << " in " << mFilename; + Log(Debug::Info) << "Unexpected stencil function: " << static_cast(func) << " in " << mFilename; return osg::Stencil::NEVER; } } - osg::Stencil::Operation getStencilOperation(int op) + osg::Stencil::Operation getStencilOperation(Nif::NiStencilProperty::Action op) { + using Action = Nif::NiStencilProperty::Action; switch (op) { - case 0: + case Action::Keep: return osg::Stencil::KEEP; - case 1: + case Action::Zero: return osg::Stencil::ZERO; - case 2: + case Action::Replace: return osg::Stencil::REPLACE; - case 3: + case Action::Increment: return osg::Stencil::INCR; - case 4: + case Action::Decrement: return osg::Stencil::DECR; - case 5: + case Action::Invert: return osg::Stencil::INVERT; default: - Log(Debug::Info) << "Unexpected stencil operation: " << op << " in " << mFilename; + Log(Debug::Info) << "Unexpected stencil operation: " << static_cast(op) << " in " << mFilename; return osg::Stencil::KEEP; } } @@ -2114,14 +2116,17 @@ namespace NifOsg case Nif::RC_NiStencilProperty: { const Nif::NiStencilProperty* stencilprop = static_cast(property); + osg::ref_ptr frontFace = new osg::FrontFace; - switch (stencilprop->data.drawMode) + using DrawMode = Nif::NiStencilProperty::DrawMode; + switch (stencilprop->mDrawMode) { - case 2: + case DrawMode::Clockwise: frontFace->setMode(osg::FrontFace::CLOCKWISE); break; - case 0: - case 1: + case DrawMode::Default: + case DrawMode::CounterClockwise: + case DrawMode::Both: default: frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE); break; @@ -2130,20 +2135,20 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setAttribute(frontFace, osg::StateAttribute::ON); - stateset->setMode(GL_CULL_FACE, - stencilprop->data.drawMode == 3 ? osg::StateAttribute::OFF : osg::StateAttribute::ON); + if (stencilprop->mDrawMode == DrawMode::Both) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + else + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - if (stencilprop->data.enabled != 0) + if (stencilprop->mEnabled) { mHasStencilProperty = true; osg::ref_ptr stencil = new osg::Stencil; - stencil->setFunction(getStencilFunction(stencilprop->data.compareFunc), - stencilprop->data.stencilRef, stencilprop->data.stencilMask); - stencil->setStencilFailOperation(getStencilOperation(stencilprop->data.failAction)); - stencil->setStencilPassAndDepthFailOperation( - getStencilOperation(stencilprop->data.zFailAction)); - stencil->setStencilPassAndDepthPassOperation( - getStencilOperation(stencilprop->data.zPassAction)); + stencil->setFunction(getStencilFunction(stencilprop->mTestFunction), + stencilprop->mStencilRef, stencilprop->mStencilMask); + stencil->setStencilFailOperation(getStencilOperation(stencilprop->mFailAction)); + stencil->setStencilPassAndDepthFailOperation(getStencilOperation(stencilprop->mZFailAction)); + stencil->setStencilPassAndDepthPassOperation(getStencilOperation(stencilprop->mPassAction)); stencil = shareAttribute(stencil); stateset->setAttributeAndModes(stencil, osg::StateAttribute::ON); @@ -2155,7 +2160,7 @@ namespace NifOsg const Nif::NiWireframeProperty* wireprop = static_cast(property); osg::ref_ptr mode = new osg::PolygonMode; mode->setMode(osg::PolygonMode::FRONT_AND_BACK, - wireprop->isEnabled() ? osg::PolygonMode::LINE : osg::PolygonMode::FILL); + wireprop->mEnable ? osg::PolygonMode::LINE : osg::PolygonMode::FILL); mode = shareAttribute(mode); node->getOrCreateStateSet()->setAttributeAndModes(mode, osg::StateAttribute::ON); break; @@ -2395,7 +2400,7 @@ namespace NifOsg // Specular property can turn specular lighting off. // FIXME: NiMaterialColorController doesn't care about this. auto specprop = static_cast(property); - specEnabled = specprop->isEnabled(); + specEnabled = specprop->mEnable; break; } case Nif::RC_NiMaterialProperty: @@ -2403,13 +2408,13 @@ namespace NifOsg const Nif::NiMaterialProperty* matprop = static_cast(property); mat->setDiffuse( - osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); - mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); - emissiveMult = matprop->data.emissiveMult; + osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mDiffuse, matprop->mAlpha)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mAmbient, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mEmissive, 1.f)); + emissiveMult = matprop->mEmissiveMult; - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); - mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mSpecular, 1.f)); + mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->mGlossiness); if (!matprop->mController.empty()) { @@ -2424,29 +2429,31 @@ namespace NifOsg const Nif::NiVertexColorProperty* vertprop = static_cast(property); + using VertexMode = Nif::NiVertexColorProperty::VertexMode; switch (vertprop->mVertexMode) { - case Nif::NiVertexColorProperty::VertexMode::VertMode_SrcIgnore: + case VertexMode::VertMode_SrcIgnore: { mat->setColorMode(osg::Material::OFF); break; } - case Nif::NiVertexColorProperty::VertexMode::VertMode_SrcEmissive: + case VertexMode::VertMode_SrcEmissive: { mat->setColorMode(osg::Material::EMISSION); break; } - case Nif::NiVertexColorProperty::VertexMode::VertMode_SrcAmbDif: + case VertexMode::VertMode_SrcAmbDif: { lightmode = vertprop->mLightingMode; + using LightMode = Nif::NiVertexColorProperty::LightMode; switch (lightmode) { - case Nif::NiVertexColorProperty::LightMode::LightMode_Emissive: + case LightMode::LightMode_Emissive: { mat->setColorMode(osg::Material::OFF); break; } - case Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif: + case LightMode::LightMode_EmiAmbDif: default: { mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); @@ -2499,7 +2506,7 @@ namespace NifOsg if (alphaprop->useAlphaTesting()) { osg::ref_ptr alphaFunc(new osg::AlphaFunc( - getTestMode(alphaprop->alphaTestMode()), alphaprop->data.threshold / 255.f)); + getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f)); alphaFunc = shareAttribute(alphaFunc); node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } From defe5ba5e748c34c041483ccdc2c0ee9df037bdf Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 22:43:02 +0300 Subject: [PATCH 0158/2167] Remove last remnants of deprecated NIFStream methods --- components/nif/effect.cpp | 18 +++++++++--------- components/nif/nifstream.hpp | 10 ---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 8db6de06d4..ae4c7947cb 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -26,27 +26,27 @@ namespace Nif { NiDynamicEffect::read(nif); - mDimmer = nif->getFloat(); - mAmbient = nif->getVector3(); - mDiffuse = nif->getVector3(); - mSpecular = nif->getVector3(); + nif->read(mDimmer); + nif->read(mAmbient); + nif->read(mDiffuse); + nif->read(mSpecular); } void NiPointLight::read(NIFStream* nif) { NiLight::read(nif); - mConstantAttenuation = nif->getFloat(); - mLinearAttenuation = nif->getFloat(); - mQuadraticAttenuation = nif->getFloat(); + nif->read(mConstantAttenuation); + nif->read(mLinearAttenuation); + nif->read(mQuadraticAttenuation); } void NiSpotLight::read(NIFStream* nif) { NiPointLight::read(nif); - mCutoff = nif->getFloat(); - mExponent = nif->getFloat(); + nif->read(mCutoff); + nif->read(mExponent); } void NiTextureEffect::read(NIFStream* nif) diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 8a4ec847fc..95205c4fda 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -146,16 +146,6 @@ namespace Nif /// Read a sequence of null-terminated strings std::string getStringPalette(); - - /// DEPRECATED: Use read() or get() - char getChar() { return get(); } - unsigned short getUShort() { return get(); } - int getInt() { return get(); } - unsigned int getUInt() { return get(); } - float getFloat() { return get(); } - osg::Vec2f getVector2() { return get(); } - osg::Vec3f getVector3() { return get(); } - osg::Vec4f getVector4() { return get(); } }; template <> From 1b93e646b84bb58f711f11607ee01848273b694a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 22:49:04 +0300 Subject: [PATCH 0159/2167] Rename Property->NiProperty --- .../nifosg/testnifloader.cpp | 4 ++-- components/nif/node.hpp | 2 +- components/nif/property.cpp | 24 +++++++++---------- components/nif/property.hpp | 24 +++++++++---------- components/nif/recordptr.hpp | 4 ++-- components/nifosg/nifloader.cpp | 16 ++++++------- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 86a2df2135..a82fba15ca 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -190,7 +190,7 @@ osg::Group { property.mTextureSet = nullptr; property.mController = nullptr; property.mType = GetParam().mShaderType; - node.mProperties.push_back(Nif::RecordPtrT(&property)); + node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); @@ -218,7 +218,7 @@ osg::Group { property.mTextureSet = nullptr; property.mController = nullptr; property.mType = GetParam().mShaderType; - node.mProperties.push_back(Nif::RecordPtrT(&property)); + node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index c87c3f6a62..0d2edad9a6 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -82,7 +82,7 @@ namespace Nif uint32_t mFlags; NiTransform mTransform; osg::Vec3f mVelocity; - PropertyList mProperties; + NiPropertyList mProperties; BoundingVolume mBounds; NiCollisionObjectPtr mCollision; // Parent nodes for the node. Only types derived from NiNode can be parents. diff --git a/components/nif/property.cpp b/components/nif/property.cpp index a0d6675778..2dd47e2e01 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -65,7 +65,7 @@ namespace Nif void NiTexturingProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20, 1, 0, 2)) @@ -102,7 +102,7 @@ namespace Nif void NiTexturingProperty::post(Reader& nif) { - Property::post(nif); + NiProperty::post(nif); for (Texture& tex : mTextures) tex.post(nif); @@ -425,7 +425,7 @@ namespace Nif void NiAlphaProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); nif->read(mFlags); nif->read(mThreshold); @@ -433,14 +433,14 @@ namespace Nif void NiDitherProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); nif->read(mFlags); } void NiFogProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); nif->read(mFlags); nif->read(mFogDepth); @@ -449,7 +449,7 @@ namespace Nif void NiMaterialProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->read(mFlags); @@ -468,7 +468,7 @@ namespace Nif void NiShadeProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) nif->read(mFlags); @@ -476,14 +476,14 @@ namespace Nif void NiSpecularProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); mEnable = nif->get() & 1; } void NiStencilProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { @@ -514,7 +514,7 @@ namespace Nif void NiVertexColorProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); nif->read(mFlags); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) @@ -531,14 +531,14 @@ namespace Nif void NiWireframeProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); mEnable = nif->get() & 1; } void NiZBufferProperty::read(NIFStream* nif) { - Property::read(nif); + NiProperty::read(nif); nif->read(mFlags); if (nif->getVersion() >= NIFStream::generateVersion(4, 1, 0, 12) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 54da082b5c..6b91d8d3d5 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -6,7 +6,7 @@ namespace Nif { - struct Property : NiObjectNET + struct NiProperty : NiObjectNET { }; @@ -30,7 +30,7 @@ namespace Nif void read(NIFStream* nif); }; - struct NiTexturingProperty : Property + struct NiTexturingProperty : NiProperty { enum class ApplyMode : uint32_t { @@ -86,7 +86,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiShadeProperty : Property + struct NiShadeProperty : NiProperty { uint16_t mFlags{ 0u }; @@ -325,7 +325,7 @@ namespace Nif bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; - struct NiAlphaProperty : Property + struct NiAlphaProperty : NiProperty { enum Flags { @@ -376,14 +376,14 @@ namespace Nif int alphaTestMode() const { return (mFlags >> 10) & 0x7; } }; - struct NiDitherProperty : Property + struct NiDitherProperty : NiProperty { uint16_t mFlags; void read(NIFStream* nif) override; }; - struct NiFogProperty : Property + struct NiFogProperty : NiProperty { uint16_t mFlags; float mFogDepth; @@ -392,7 +392,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiMaterialProperty : Property + struct NiMaterialProperty : NiProperty { uint16_t mFlags{ 0u }; osg::Vec3f mAmbient{ 1.f, 1.f, 1.f }; @@ -406,14 +406,14 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiSpecularProperty : Property + struct NiSpecularProperty : NiProperty { bool mEnable; void read(NIFStream* nif) override; }; - struct NiStencilProperty : Property + struct NiStencilProperty : NiProperty { enum class TestFunc : uint32_t { @@ -458,7 +458,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiVertexColorProperty : Property + struct NiVertexColorProperty : NiProperty { enum class VertexMode : uint32_t { @@ -480,14 +480,14 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiWireframeProperty : Property + struct NiWireframeProperty : NiProperty { bool mEnable; void read(NIFStream* nif) override; }; - struct NiZBufferProperty : Property + struct NiZBufferProperty : NiProperty { uint16_t mFlags; uint32_t mTestFunction; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 627e59cf0d..54ebe0e9a2 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -112,7 +112,7 @@ namespace Nif struct NiAVObject; struct Extra; - struct Property; + struct NiProperty; struct NiUVData; struct NiPosData; struct NiVisData; @@ -191,7 +191,7 @@ namespace Nif using BSMultiBoundDataPtr = RecordPtrT; using NiAVObjectList = RecordListT; - using PropertyList = RecordListT; + using NiPropertyList = RecordListT; using ExtraList = RecordListT; using NiSourceTextureList = RecordListT; using NiInterpolatorList = RecordListT; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index ef2b97dff8..f184f49625 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -103,7 +103,7 @@ namespace // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the // node hierarchy above it. void collectDrawableProperties( - const Nif::NiAVObject* nifNode, const Nif::Parent* parent, std::vector& out) + const Nif::NiAVObject* nifNode, const Nif::Parent* parent, std::vector& out) { if (parent != nullptr) collectDrawableProperties(&parent->mNiNode, parent->mParent, out); @@ -959,7 +959,7 @@ namespace NifOsg } } - void handleMaterialControllers(const Nif::Property* materialProperty, + void handleMaterialControllers(const Nif::NiProperty* materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial) { for (Nif::NiTimeControllerPtr ctrl = materialProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) @@ -1008,7 +1008,7 @@ namespace NifOsg } } - void handleTextureControllers(const Nif::Property* texProperty, SceneUtil::CompositeStateSetUpdater* composite, + void handleTextureControllers(const Nif::NiProperty* texProperty, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, osg::StateSet* stateset, int animflags) { for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) @@ -1316,7 +1316,7 @@ namespace NifOsg // localToWorldMatrix for transforming to particle space handleParticlePrograms(partctrl->mModifier, partctrl->mCollider, parentNode, partsys.get(), rf); - std::vector drawableProps; + std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); @@ -1462,7 +1462,7 @@ namespace NifOsg // - if there are no vertex colors, we need to disable colorMode. // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. - std::vector drawableProps; + std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->mColors.empty(), animflags); } @@ -2107,7 +2107,7 @@ namespace NifOsg return "bs/default"; } - void handleProperty(const Nif::Property* property, osg::Node* node, + void handleProperty(const Nif::NiProperty* property, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags, bool hasStencilProperty) { @@ -2366,7 +2366,7 @@ namespace NifOsg return *found; } - void applyDrawableProperties(osg::Node* node, const std::vector& properties, + void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { // Specular lighting is enabled by default, but there's a quirk... @@ -2391,7 +2391,7 @@ namespace NifOsg float emissiveMult = 1.f; float specStrength = 1.f; - for (const Nif::Property* property : properties) + for (const Nif::NiProperty* property : properties) { switch (property->recType) { From 81441bc96354586621b2345544d0fb4458fbe6a6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 17 Sep 2023 22:52:17 +0300 Subject: [PATCH 0160/2167] Fix formatting and typos --- components/nif/property.cpp | 10 +++++----- components/nifosg/nifloader.cpp | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 2dd47e2e01..bb2a01e2dc 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -176,7 +176,7 @@ namespace Nif nif->read(mExposureOffset); nif->read(mFinalExposureMin); nif->read(mFinalExposureMax); - }; + } void BSSPWetnessParams::read(NIFStream* nif) { @@ -191,7 +191,7 @@ namespace Nif nif->skip(4); // Unknown if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) nif->skip(4); // Unknown - }; + } void BSSPMLParallaxParams::read(NIFStream* nif) { @@ -199,7 +199,7 @@ namespace Nif nif->read(mRefractionScale); nif->read(mInnerLayerTextureScale); nif->read(mEnvMapScale); - }; + } void BSSPTranslucencyParams::read(NIFStream* nif) { @@ -208,7 +208,7 @@ namespace Nif nif->read(mTurbulence); nif->read(mThickObject); nif->read(mMixAlbedo); - }; + } void BSLightingShaderProperty::read(NIFStream* nif) { @@ -502,7 +502,7 @@ namespace Nif { nif->read(mFlags); mEnabled = mFlags & 0x1; - mFailAction = static_cast((mFlags>> 1) & 0x7); + mFailAction = static_cast((mFlags >> 1) & 0x7); mZFailAction = static_cast((mFlags >> 4) & 0x7); mPassAction = static_cast((mFlags >> 7) & 0x7); mDrawMode = static_cast((mFlags >> 10) & 0x3); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f184f49625..7498c1cf46 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1008,8 +1008,9 @@ namespace NifOsg } } - void handleTextureControllers(const Nif::NiProperty* texProperty, SceneUtil::CompositeStateSetUpdater* composite, - Resource::ImageManager* imageManager, osg::StateSet* stateset, int animflags) + void handleTextureControllers(const Nif::NiProperty* texProperty, + SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, + osg::StateSet* stateset, int animflags) { for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { @@ -1638,7 +1639,8 @@ namespace NifOsg case TestFunc::Always: return osg::Stencil::ALWAYS; default: - Log(Debug::Info) << "Unexpected stencil function: " << static_cast(func) << " in " << mFilename; + Log(Debug::Info) << "Unexpected stencil function: " << static_cast(func) << " in " + << mFilename; return osg::Stencil::NEVER; } } @@ -1661,7 +1663,8 @@ namespace NifOsg case Action::Invert: return osg::Stencil::INVERT; default: - Log(Debug::Info) << "Unexpected stencil operation: " << static_cast(op) << " in " << mFilename; + Log(Debug::Info) << "Unexpected stencil operation: " << static_cast(op) << " in " + << mFilename; return osg::Stencil::KEEP; } } @@ -2144,8 +2147,8 @@ namespace NifOsg { mHasStencilProperty = true; osg::ref_ptr stencil = new osg::Stencil; - stencil->setFunction(getStencilFunction(stencilprop->mTestFunction), - stencilprop->mStencilRef, stencilprop->mStencilMask); + stencil->setFunction(getStencilFunction(stencilprop->mTestFunction), stencilprop->mStencilRef, + stencilprop->mStencilMask); stencil->setStencilFailOperation(getStencilOperation(stencilprop->mFailAction)); stencil->setStencilPassAndDepthFailOperation(getStencilOperation(stencilprop->mZFailAction)); stencil->setStencilPassAndDepthPassOperation(getStencilOperation(stencilprop->mPassAction)); @@ -2407,8 +2410,7 @@ namespace NifOsg { const Nif::NiMaterialProperty* matprop = static_cast(property); - mat->setDiffuse( - osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mDiffuse, matprop->mAlpha)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mDiffuse, matprop->mAlpha)); mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mAmbient, 1.f)); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mEmissive, 1.f)); emissiveMult = matprop->mEmissiveMult; From 4db994cda9992a3ae68e3723973a8c095c7af483 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 18 Sep 2023 06:37:36 +0300 Subject: [PATCH 0161/2167] Remap FO76 shader types --- components/nif/property.cpp | 44 ++++++++++++++++++++++----------- components/nif/property.hpp | 3 ++- components/nifosg/nifloader.cpp | 1 + 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index bb2a01e2dc..c5fc7a88b1 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -299,7 +299,32 @@ namespace Nif nif->skip(2); // Unknown } - // TODO: consider separating this switch for pre-FO76 and FO76+ + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + { + // Remap FO76+ shader types to FO4 system so that we can actually use them + // TODO: NifTools spec doesn't do anything about the misplaced EyeEnvmap. Bug or feature? + switch (static_cast(mType)) + { + case BSLightingShaderType::ShaderType_Parallax: + mType = static_cast(BSLightingShaderType::ShaderType_FaceTint); + break; + case BSLightingShaderType::ShaderType_FaceTint: + mType = static_cast(BSLightingShaderType::ShaderType_SkinTint); + break; + case BSLightingShaderType::ShaderType_SkinTint: + mType = static_cast(BSLightingShaderType::ShaderType_HairTint); + break; + case BSLightingShaderType::ShaderType_TreeAnim: + mType = static_cast(BSLightingShaderType::ShaderType_EyeEnvmap); + break; + case BSLightingShaderType::ShaderType_Cloud: + mType = static_cast(BSLightingShaderType::ShaderType_Terrain); + break; + default: + break; + } + } + switch (static_cast(mType)) { case BSLightingShaderType::ShaderType_EnvMap: @@ -311,25 +336,14 @@ namespace Nif nif->read(mWetnessUseSSR); } break; - case BSLightingShaderType::ShaderType_FaceTint: - // Skin tint shader in FO76+ - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->read(mSkinTintColor); - break; case BSLightingShaderType::ShaderType_SkinTint: - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->read(mHairTintColor); - else if (nif->getBethVersion() <= 130) + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO4) mSkinTintColor = { nif->get(), 1.f }; - else if (nif->getBethVersion() <= 139) + else nif->read(mSkinTintColor); - // Hair tint shader in FO76+ - else if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->read(mHairTintColor); break; case BSLightingShaderType::ShaderType_HairTint: - if (nif->getBethVersion() <= 139) - nif->read(mHairTintColor); + nif->read(mHairTintColor); break; case BSLightingShaderType::ShaderType_ParallaxOcc: mParallax.read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 6b91d8d3d5..f87384703c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -190,7 +190,8 @@ namespace Nif ShaderType_Cloud = 17, ShaderType_LODNoise = 18, ShaderType_MultitexLandLODBlend = 19, - ShaderType_Dismemberment = 20 + ShaderType_Dismemberment = 20, + ShaderType_Terrain = 21, // FO76+, technically 17 }; enum BSLightingShaderFlags1 diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7498c1cf46..7a8fd6afb2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2103,6 +2103,7 @@ namespace NifOsg case Nif::BSLightingShaderType::ShaderType_LODNoise: case Nif::BSLightingShaderType::ShaderType_MultitexLandLODBlend: case Nif::BSLightingShaderType::ShaderType_Dismemberment: + case Nif::BSLightingShaderType::ShaderType_Terrain: Log(Debug::Warning) << "Unhandled BSLightingShaderType " << type << " in " << mFilename; return "bs/default"; } From 3ef9b850d7559268eadfbf632d0c4b6831a7cc50 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 18 Sep 2023 10:11:35 +0400 Subject: [PATCH 0162/2167] Repeat title music only in main menu --- apps/openmw/mwsound/soundmanagerimp.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index be8bae20a6..73cfeba3db 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -16,6 +16,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" @@ -1135,7 +1136,10 @@ namespace MWSound return; MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); - if (state == MWBase::StateManager::State_NoGame && !isMusicPlaying()) + bool isMainMenu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) + && state == MWBase::StateManager::State_NoGame; + + if (isMainMenu && !isMusicPlaying()) { std::string titlefile = "music/special/morrowind title.mp3"; if (mVFS->exists(titlefile)) @@ -1143,7 +1147,7 @@ namespace MWSound } updateSounds(duration); - if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame) + if (state != MWBase::StateManager::State_NoGame) { updateRegionSound(duration); updateWaterSound(); From 82eed09604dc61df4d8d0e08461ce0997b2384c6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 18 Sep 2023 23:56:38 +0300 Subject: [PATCH 0163/2167] Further adjust FO3+ shader properties based on nifly definitions --- components/nif/property.cpp | 144 ++++++++++++++++++------------------ components/nif/property.hpp | 20 ++--- 2 files changed, 80 insertions(+), 84 deletions(-) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index c5fc7a88b1..fbdb273cfd 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -124,22 +124,74 @@ namespace Nif void BSShaderProperty::read(NIFStream* nif) { + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_F76 && recType == RC_BSLightingShaderProperty) + nif->read(mType); + NiShadeProperty::read(nif); - if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + if (nif->getUserVersion() <= 11) { nif->read(mType); nif->read(mShaderFlags1); nif->read(mShaderFlags2); nif->read(mEnvMapScale); + return; } + + if (!mName.empty() && nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + return; + + if (nif->getBethVersion() <= 131) + { + nif->read(mShaderFlags1); + nif->read(mShaderFlags2); + } + else + { + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76 && recType == RC_BSLightingShaderProperty) + { + nif->read(mType); + + // Remap FO76+ shader types to FO4 system so that we can actually use them + // TODO: NifTools spec doesn't do anything about the misplaced EyeEnvmap. Bug or feature? + switch (mType) + { + case 3: + mType = static_cast(BSLightingShaderType::ShaderType_FaceTint); + break; + case 4: + mType = static_cast(BSLightingShaderType::ShaderType_SkinTint); + break; + case 5: + mType = static_cast(BSLightingShaderType::ShaderType_HairTint); + break; + case 12: + mType = static_cast(BSLightingShaderType::ShaderType_EyeEnvmap); + break; + case 17: + mType = static_cast(BSLightingShaderType::ShaderType_Terrain); + break; + default: + break; + } + } + } + + nif->read(mUVOffset); + nif->read(mUVScale); } void BSShaderLightingProperty::read(NIFStream* nif) { BSShaderProperty::read(nif); - if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + if (nif->getUserVersion() <= 11) nif->read(mClamp); } @@ -148,10 +200,15 @@ namespace Nif BSShaderLightingProperty::read(nif); mTextureSet.read(nif); - if (nif->getBethVersion() >= 15) - mRefraction.read(nif); - if (nif->getBethVersion() >= 25) - mParallax.read(nif); + if (nif->getUserVersion() == 11) + { + if (nif->getBethVersion() >= 15) + mRefraction.read(nif); + if (nif->getBethVersion() >= 25) + mParallax.read(nif); + } + else if (nif->getUserVersion() >= 12) + nif->read(mEmissiveColor); } void BSShaderPPLightingProperty::post(Reader& nif) @@ -212,31 +269,11 @@ namespace Nif void BSLightingShaderProperty::read(NIFStream* nif) { - if (nif->getBethVersion() <= 139) - nif->read(mType); - BSShaderProperty::read(nif); - if (nif->getBethVersion() <= 130) - { - nif->read(mShaderFlags1); - nif->read(mShaderFlags2); - } - else if (nif->getBethVersion() >= 132) - { - uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; - nif->read(numShaderFlags1); - if (nif->getBethVersion() >= 152) - nif->read(numShaderFlags2); - nif->readVector(mShaderFlags1Hashes, numShaderFlags1); - nif->readVector(mShaderFlags2Hashes, numShaderFlags2); - } + if (!mName.empty() && nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + return; - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->read(mType); - - nif->read(mUVOffset); - nif->read(mUVScale); mTextureSet.read(nif); nif->read(mEmissive); nif->read(mEmissiveMult); @@ -299,32 +336,6 @@ namespace Nif nif->skip(2); // Unknown } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - { - // Remap FO76+ shader types to FO4 system so that we can actually use them - // TODO: NifTools spec doesn't do anything about the misplaced EyeEnvmap. Bug or feature? - switch (static_cast(mType)) - { - case BSLightingShaderType::ShaderType_Parallax: - mType = static_cast(BSLightingShaderType::ShaderType_FaceTint); - break; - case BSLightingShaderType::ShaderType_FaceTint: - mType = static_cast(BSLightingShaderType::ShaderType_SkinTint); - break; - case BSLightingShaderType::ShaderType_SkinTint: - mType = static_cast(BSLightingShaderType::ShaderType_HairTint); - break; - case BSLightingShaderType::ShaderType_TreeAnim: - mType = static_cast(BSLightingShaderType::ShaderType_EyeEnvmap); - break; - case BSLightingShaderType::ShaderType_Cloud: - mType = static_cast(BSLightingShaderType::ShaderType_Terrain); - break; - default: - break; - } - } - switch (static_cast(mType)) { case BSLightingShaderType::ShaderType_EnvMap: @@ -337,10 +348,9 @@ namespace Nif } break; case BSLightingShaderType::ShaderType_SkinTint: - if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO4) - mSkinTintColor = { nif->get(), 1.f }; - else - nif->read(mSkinTintColor); + nif->read(mSkinTintColor); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + nif->read(mSkinTintAlpha); break; case BSLightingShaderType::ShaderType_HairTint: nif->read(mHairTintColor); @@ -375,23 +385,9 @@ namespace Nif { BSShaderProperty::read(nif); - if (nif->getBethVersion() <= 130) - { - nif->read(mShaderFlags1); - nif->read(mShaderFlags2); - } - else if (nif->getBethVersion() >= 132) - { - uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; - nif->read(numShaderFlags1); - if (nif->getBethVersion() >= 152) - nif->read(numShaderFlags2); - nif->readVector(mShaderFlags1Hashes, numShaderFlags1); - nif->readVector(mShaderFlags2Hashes, numShaderFlags2); - } + if (!mName.empty() && nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + return; - nif->read(mUVOffset); - nif->read(mUVScale); mSourceTexture = nif->getSizedString(); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index f87384703c..797e02c40d 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -113,16 +113,16 @@ namespace Nif struct BSSPParallaxParams { - float mMaxPasses; - float mScale; + float mMaxPasses{ 4.f }; + float mScale{ 1.f }; void read(NIFStream* nif); }; struct BSSPRefractionParams { - float mStrength; - int32_t mPeriod; + float mStrength{ 0.f }; + int32_t mPeriod{ 0 }; void read(NIFStream* nif); }; @@ -131,6 +131,8 @@ namespace Nif { uint32_t mType{ 0u }, mShaderFlags1{ 0u }, mShaderFlags2{ 0u }; float mEnvMapScale{ 0.f }; + std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; + osg::Vec2f mUVOffset, mUVScale; void read(NIFStream* nif) override; @@ -142,7 +144,7 @@ namespace Nif struct BSShaderLightingProperty : BSShaderProperty { - unsigned int mClamp{ 0u }; + uint32_t mClamp{ 3 }; void read(NIFStream* nif) override; @@ -155,6 +157,7 @@ namespace Nif BSShaderTextureSetPtr mTextureSet; BSSPRefractionParams mRefraction; BSSPParallaxParams mParallax; + osg::Vec4f mEmissiveColor; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -249,8 +252,6 @@ namespace Nif struct BSLightingShaderProperty : BSShaderProperty { - std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; - osg::Vec2f mUVOffset, mUVScale; BSShaderTextureSetPtr mTextureSet; osg::Vec3f mEmissive; float mEmissiveMult; @@ -277,7 +278,8 @@ namespace Nif bool mUseSSR; bool mWetnessUseSSR; - osg::Vec4f mSkinTintColor; + osg::Vec3f mSkinTintColor; + float mSkinTintAlpha{ 1.f }; osg::Vec3f mHairTintColor; BSSPParallaxParams mParallax; @@ -297,8 +299,6 @@ namespace Nif struct BSEffectShaderProperty : BSShaderProperty { - std::vector mShaderFlags1Hashes, mShaderFlags2Hashes; - osg::Vec2f mUVOffset, mUVScale; std::string mSourceTexture; uint8_t mClamp; uint8_t mLightingInfluence; From 95906a34b37dbd18507c0c5e1bc7611ab2734090 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 19 Sep 2023 10:59:20 +0200 Subject: [PATCH 0164/2167] Fix #6146 (`actor:setEquipment` doesn't trigger mwscripts) --- apps/openmw/mwlua/types/actor.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 08fd98c41c..6edb363cd0 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -66,11 +66,12 @@ namespace MWLua static void setEquipment(const MWWorld::Ptr& actor, const Equipment& equipment) { + bool isPlayer = actor == MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); std::array usedSlots; std::fill(usedSlots.begin(), usedSlots.end(), false); - auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool { + auto tryEquipToSlot = [&store, &usedSlots, isPlayer](int slot, const EquipmentItem& item) -> bool { auto [it, alreadyEquipped] = findInInventory(store, item, slot); if (alreadyEquipped) return true; @@ -93,7 +94,22 @@ namespace MWLua slot = *firstAllowed; } - store.equip(slot, it); + bool skipEquip = false; + + if (isPlayer) + { + const ESM::RefId& script = itemPtr.getClass().getScript(itemPtr); + if (!script.empty()) + { + MWScript::Locals& locals = itemPtr.getRefData().getLocals(); + locals.setVarByInt(script, "onpcequip", 1); + skipEquip = locals.getIntVar(script, "pcskipequip") == 1; + } + } + + if (!skipEquip) + store.equip(slot, it); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed }; From 38f56cfcddcab6f4a42ac264a33a12fd3c511a73 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Sep 2023 18:16:34 +0200 Subject: [PATCH 0165/2167] Fix #7453: wrong position of dynamically placed CreatureLevList --- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index dd346306e9..461cf07276 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -148,7 +148,7 @@ namespace MWClass manualRef.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); manualRef.getPtr().getCellRef().setScale(ptr.getCellRef().getScale()); MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject( - manualRef.getPtr(), ptr.getCell(), ptr.getCellRef().getPosition()); + manualRef.getPtr(), ptr.getCell(), ptr.getRefData().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } From 440851ff48a1b80fbc32a6c1b08647e7a8051d21 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 24 Jul 2023 11:52:19 +0300 Subject: [PATCH 0166/2167] Rewrite melee hit target selection (bug #3438) --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 10 --- apps/openmw/mwclass/creature.cpp | 16 +--- apps/openmw/mwclass/npc.cpp | 17 +---- apps/openmw/mwmechanics/aicombat.cpp | 3 +- apps/openmw/mwmechanics/combat.cpp | 98 +++++++++++++++++++++++++ apps/openmw/mwmechanics/combat.hpp | 7 ++ apps/openmw/mwphysics/physicssystem.cpp | 87 ---------------------- apps/openmw/mwphysics/physicssystem.hpp | 9 --- apps/openmw/mwphysics/raycasting.hpp | 6 -- apps/openmw/mwworld/worldimp.cpp | 51 +------------ apps/openmw/mwworld/worldimp.hpp | 9 --- 12 files changed, 112 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83892500dd..abb10007ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #2623: Snowy Granius doesn't prioritize conjuration spells + Bug #3438: NPCs can't hit bull netch with melee weapons Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 40d57cbb6e..e5499f6680 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -252,13 +252,6 @@ namespace MWBase virtual float getMaxActivationDistance() const = 0; - /// Returns a pointer to the object the provided object would hit (if within the - /// specified distance), and the point where the hit occurs. This will attempt to - /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. - virtual std::pair getHitContact( - const MWWorld::ConstPtr& ptr, float distance, std::vector& targets) - = 0; - virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. /// @param force do this even if the ptr is flying @@ -546,9 +539,6 @@ namespace MWBase const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) = 0; - /// Return the distance between actor's weapon and target's collision box. - virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0; - virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0; virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index df1ada96f4..36241b02d2 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -242,24 +242,10 @@ namespace MWClass if (!weapon.isEmpty()) dist *= weapon.get()->mBase->mData.mReach; - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit - // result. - std::vector targetActors; - getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); - - std::pair result - = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); + const std::pair result = MWMechanics::getHitContact(ptr, dist); if (result.first.isEmpty()) // Didn't hit anything return true; - const MWWorld::Class& othercls = result.first.getClass(); - if (!othercls.isActor()) // Can't hit non-actors - return true; - - MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(result.first); - if (otherstats.isDead()) // Can't hit dead actors - return true; - // Note that earlier we returned true in spite of an apparent failure to hit anything alive. // This is because hitting nothing is not a "miss" and should be handled as such character controller-side. victim = result.first; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1e7dae3600..91601513a8 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -569,25 +569,10 @@ namespace MWClass * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : store.find("fHandToHandReach")->mValue.getFloat()); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit - // result. - std::vector targetActors; - if (ptr != MWMechanics::getPlayer()) - getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); - - // TODO: Use second to work out the hit angle - std::pair result = world->getHitContact(ptr, dist, targetActors); + const std::pair result = MWMechanics::getHitContact(ptr, dist); if (result.first.isEmpty()) // Didn't hit anything return true; - const MWWorld::Class& othercls = result.first.getClass(); - if (!othercls.isActor()) // Can't hit non-actors - return true; - - MWMechanics::CreatureStats& otherstats = othercls.getCreatureStats(result.first); - if (otherstats.isDead()) // Can't hit dead actors - return true; - // Note that earlier we returned true in spite of an apparent failure to hit anything alive. // This is because hitting nothing is not a "miss" and should be handled as such character controller-side. victim = result.first; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index dbe83eab42..4c6ea42d36 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -23,6 +23,7 @@ #include "actorutil.hpp" #include "aicombataction.hpp" #include "character.hpp" +#include "combat.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include "pathgrid.hpp" @@ -242,7 +243,7 @@ namespace MWMechanics const osg::Vec3f vActorPos(pos.asVec3()); const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); - float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); + float distToTarget = getDistanceToBounds(actor, target); storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 1c9c0c1dee..02279859b5 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -551,4 +551,102 @@ namespace MWMechanics return distanceIgnoreZ(lhs, rhs); return distance(lhs, rhs); } + + float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + { + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + float dist = (targetPos - actorPos).length(); + dist -= world->getHalfExtents(actor).y(); + dist -= world->getHalfExtents(target).y(); + return dist; + } + + std::pair getHitContact(const MWWorld::Ptr& actor, float reach) + { + // Lasciate ogne speranza, voi ch'entrate + MWWorld::Ptr result; + osg::Vec3f hitPos; + float minDist = std::numeric_limits::max(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& store = world->getStore().get(); + + const ESM::Position& posdata = actor.getRefData().getPosition(); + const osg::Vec3f actorPos(posdata.asVec3()); + + // Morrowind uses body orientation or camera orientation if available + // The difference between that and this is subtle + osg::Quat actorRot + = osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); + + const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat(); + const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat(); + const float combatAngleXYcos = std::cos(osg::DegreesToRadians(fCombatAngleXY)); + const float combatAngleZcos = std::cos(osg::DegreesToRadians(fCombatAngleZ)); + + // The player can target any active actor, non-playable actors only target their targets + std::vector targets; + if (actor != getPlayer()) + actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targets); + else + MWBase::Environment::get().getMechanicsManager()->getActorsInRange( + actorPos, Settings::game().mActorsProcessingRange, targets); + + for (MWWorld::Ptr& target : targets) + { + if (actor == target || target.getClass().getCreatureStats(target).isDead()) + continue; + float dist = getDistanceToBounds(actor, target); + osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + osg::Vec3f dirToTarget = targetPos - actorPos; + if (dist >= reach || dist >= minDist || std::abs(dirToTarget.z()) >= reach) + continue; + + dirToTarget.normalize(); + + // The idea is to use fCombatAngleXY and fCombatAngleZ as tolerance angles + // in XY and YZ planes of the coordinate system where the actor's orientation + // corresponds to (0, 1, 0) vector. This is not exactly what Morrowind does + // but Morrowind does something (even more) stupid here + osg::Vec3f hitDir = actorRot.inverse() * dirToTarget; + if (combatAngleXYcos * std::abs(hitDir.x()) > hitDir.y()) + continue; + + // Nice cliff racer hack Todd + if (combatAngleZcos * std::abs(hitDir.z()) > hitDir.y() && !MWMechanics::canActorMoveByZAxis(target)) + continue; + + // Gotta use physics somehow! + if (!world->getLOS(actor, target)) + continue; + + minDist = dist; + result = target; + } + + // This hit position is currently used for spawning the blood effect. + // Morrowind does this elsewhere, but roughly at the same time + // and it would be hard to track the original hit results outside of this function + // without code duplication + // The idea is to use a random point on a plane in front of the target + // that is defined by its width and height + if (!result.isEmpty()) + { + osg::Vec3f resultPos(result.getRefData().getPosition().asVec3()); + osg::Vec3f dirToActor = actorPos - resultPos; + dirToActor.normalize(); + + hitPos = resultPos + dirToActor * world->getHalfExtents(result).y(); + // -25% to 25% of width + float xOffset = Misc::Rng::deviate(0.f, 0.25f, world->getPrng()); + // 20% to 100% of height + float zOffset = Misc::Rng::deviate(0.6f, 0.4f, world->getPrng()); + hitPos.x() += world->getHalfExtents(result).x() * 2.f * xOffset; + hitPos.z() += world->getHalfExtents(result).z() * 2.f * zOffset; + } + + return std::make_pair(result, hitPos); + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 2e7caf6189..515d2e406c 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MECHANICS_COMBAT_H #define OPENMW_MECHANICS_COMBAT_H +#include + namespace osg { class Vec3f; @@ -59,6 +61,11 @@ namespace MWMechanics float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); + // Cursed distance calculation used for combat proximity and hit checks in Morrowind + float getDistanceToBounds(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + + // Similarly cursed hit target selection + std::pair getHitContact(const MWWorld::Ptr& actor, float reach); } #endif diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2d756fedc8..2196834a50 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -192,93 +192,6 @@ namespace MWPhysics return true; } - std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, - const osg::Vec3f& origin, const osg::Quat& orient, float queryDistance, std::vector& targets) - { - // First of all, try to hit where you aim to - int hitmask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; - RayCastingResult result = castRay(origin, origin + (orient * osg::Vec3f(0.0f, queryDistance, 0.0f)), actor, - targets, hitmask, CollisionType_Actor); - - if (result.mHit) - { - reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal)); - return std::make_pair(result.mHitObject, result.mHitPos); - } - - // Use cone shape as fallback - const MWWorld::Store& store - = MWBase::Environment::get().getESMStore()->get(); - - btConeShape shape(osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat() / 2.0f), queryDistance); - shape.setLocalScaling(btVector3( - 1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat() / 2.0f) / shape.getRadius())); - - // The shape origin is its center, so we have to move it forward by half the length. The - // real origin will be provided to getFilteredContact to find the closest. - osg::Vec3f center = origin + (orient * osg::Vec3f(0.0f, queryDistance * 0.5f, 0.0f)); - - btCollisionObject object; - object.setCollisionShape(&shape); - object.setWorldTransform(btTransform(Misc::Convert::toBullet(orient), Misc::Convert::toBullet(center))); - - const btCollisionObject* me = nullptr; - std::vector targetCollisionObjects; - - const Actor* physactor = getActor(actor); - if (physactor) - me = physactor->getCollisionObject(); - - if (!targets.empty()) - { - for (MWWorld::Ptr& target : targets) - { - const Actor* targetActor = getActor(target); - if (targetActor) - targetCollisionObjects.push_back(targetActor->getCollisionObject()); - } - } - - DeepestNotMeContactTestResultCallback resultCallback( - me, targetCollisionObjects, Misc::Convert::toBullet(origin)); - resultCallback.m_collisionFilterGroup = CollisionType_Actor; - resultCallback.m_collisionFilterMask - = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; - mTaskScheduler->contactTest(&object, resultCallback); - - if (resultCallback.mObject) - { - PtrHolder* holder = static_cast(resultCallback.mObject->getUserPointer()); - if (holder) - { - reportCollision(resultCallback.mContactPoint, resultCallback.mContactNormal); - return std::make_pair(holder->getPtr(), Misc::Convert::toOsg(resultCallback.mContactPoint)); - } - } - return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); - } - - float PhysicsSystem::getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const - { - btCollisionObject* targetCollisionObj = nullptr; - const Actor* actor = getActor(target); - if (actor) - targetCollisionObj = actor->getCollisionObject(); - if (!targetCollisionObj) - return 0.f; - - btTransform rayFrom; - rayFrom.setIdentity(); - rayFrom.setOrigin(Misc::Convert::toBullet(point)); - - auto hitpoint = mTaskScheduler->getHitPoint(rayFrom, targetCollisionObj); - if (hitpoint) - return (point - Misc::Convert::toOsg(*hitpoint)).length(); - - // didn't hit the target. this could happen if point is already inside the collision box - return 0.f; - } - RayCastingResult PhysicsSystem::castRay(const osg::Vec3f& from, const osg::Vec3f& to, const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const { diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index e4c1b63776..ad56581eb3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -207,15 +207,6 @@ namespace MWPhysics const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const; osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight); - std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f& origin, - const osg::Quat& orientation, float queryDistance, std::vector& targets); - - /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the - /// target vector hits the collision shape and then calculates distance from the intersection point. - /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be - /// successful. \note Only Actor targets are supported at the moment. - float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const override; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all /// other actors. RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 4a56e9bf33..6b1a743d54 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -23,12 +23,6 @@ namespace MWPhysics public: virtual ~RayCastingInterface() = default; - /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the - /// target vector hits the collision shape and then calculates distance from the intersection point. - /// This can be used to find out how much nearer we need to move to the target for a "getHitContact" to be - /// successful. \note Only Actor targets are supported at the moment. - virtual float getHitDistance(const osg::Vec3f& point, const MWWorld::ConstPtr& target) const = 0; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all /// other actors. virtual RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 748187d868..02e4d066bc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1037,44 +1037,6 @@ namespace MWWorld return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); } - std::pair World::getHitContact( - const MWWorld::ConstPtr& ptr, float distance, std::vector& targets) - { - const ESM::Position& posdata = ptr.getRefData().getPosition(); - - osg::Quat rot - = osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); - - osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr); - - // the origin of hitbox is an actor's front, not center - distance += halfExtents.y(); - - // special cased for better aiming with the camera - // if we do not hit anything, will use the default approach as fallback - if (ptr == getPlayerPtr()) - { - osg::Vec3f pos = getActorHeadTransform(ptr).getTrans(); - - std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); - if (!result.first.isEmpty()) - return std::make_pair(result.first, result.second); - } - - osg::Vec3f pos = ptr.getRefData().getPosition().asVec3(); - - // general case, compatible with all types of different creatures - // note: we intentionally do *not* use the collision box offset here, this is required to make - // some flying creatures work that have their collision box offset in the air - pos.z() += halfExtents.z(); - - std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); - if (result.first.isEmpty()) - return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); - - return std::make_pair(result.first, result.second); - } - void World::deleteObject(const Ptr& ptr) { if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr) @@ -3010,12 +2972,12 @@ namespace MWWorld } else { - // For actor targets, we want to use hit contact with bounding boxes. + // For actor targets, we want to use melee hit contact. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would // be very hard to aim at otherwise. For object targets, we want the detailed shapes (rendering // raycast). If we used the bounding boxes for static objects, then we would not be able to target e.g. // objects lying on a shelf. - std::pair result1 = getHitContact(actor, fCombatDistance, targetActors); + const std::pair result1 = MWMechanics::getHitContact(actor, fCombatDistance); // Get the target to use for "on touch" effects, using the facing direction from Head node osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); @@ -3728,15 +3690,6 @@ namespace MWWorld return (targetPos - weaponPos); } - float World::getHitDistance(const ConstPtr& actor, const ConstPtr& target) - { - osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3(); - osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor); - weaponPos.z() += halfExtents.z(); - - return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y(); - } - void preload(MWWorld::Scene* scene, const ESMStore& store, const ESM::RefId& obj) { if (obj.empty()) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6bf256b083..46043afe46 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -345,12 +345,6 @@ namespace MWWorld float getDistanceToFacedObject() override; - /// Returns a pointer to the object the provided object would hit (if within the - /// specified distance), and the point where the hit occurs. This will attempt to - /// use the "Head" node as a basis. - std::pair getHitContact( - const MWWorld::ConstPtr& ptr, float distance, std::vector& targets) override; - /// @note No-op for items in containers. Use ContainerStore::removeItem instead. void deleteObject(const Ptr& ptr) override; @@ -627,9 +621,6 @@ namespace MWWorld osg::Vec3f aimToTarget( const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target, bool isRangedCombat) override; - /// Return the distance between actor's weapon and target's collision box. - float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) override; - bool isPlayerInJail() const override; void setPlayerTraveling(bool traveling) override; From a39182f7de8a04666e5d5b5214c27a98526a4a04 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 00:59:23 +0300 Subject: [PATCH 0167/2167] Read a bunch more extra data records NiStringsExtraData, BSExtraData, BSBoneLODExtraData, BSClothExtraData, BSDecalPlacementVectorExtraData, BSDistantObjectExtraData, BSDistantObjectLargeRefExtraData, BSWArray --- components/nif/extra.cpp | 42 +++++++++++++++++++++++++++++ components/nif/extra.hpp | 55 ++++++++++++++++++++++++++++++++++++++ components/nif/niffile.cpp | 10 +++++++ components/nif/record.hpp | 8 ++++++ 4 files changed, 115 insertions(+) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index e289ae626e..69f66014d6 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -10,6 +10,13 @@ namespace Nif nif->readVector(mData, mRecordSize); } + void NiStringsExtraData::read(NIFStream* nif) + { + Extra::read(nif); + + nif->getSizedStrings(mData, nif->get()); + } + void NiTextKeyExtraData::read(NIFStream* nif) { Extra::read(nif); @@ -94,4 +101,39 @@ namespace Nif nif->read(mControlsBaseSkeleton); } + void BSBoneLODExtraData::read(NIFStream* nif) + { + Extra::read(nif); + + mData.resize(nif->get()); + for (BoneLOD& lod : mData) + lod.read(nif); + } + + void BSBoneLODExtraData::BoneLOD::read(NIFStream* nif) + { + nif->read(mDistance); + nif->read(mBone); + } + + void BSDecalPlacementVectorExtraData::read(NIFStream* nif) + { + NiFloatExtraData::read(nif); + + mBlocks.resize(nif->get()); + for (Block& block : mBlocks) + block.read(nif); + } + + void BSDecalPlacementVectorExtraData::Block::read(NIFStream* nif) + { + nif->readVector(mPoints, nif->get()); + nif->readVector(mNormals, mPoints.size()); + } + + void BSClothExtraData::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index dfe4539138..bb3cc3f73e 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -38,10 +38,15 @@ namespace Nif using NiStringExtraData = TypedExtra; using NiVectorExtraData = TypedExtra; + using BSDistantObjectExtraData = TypedExtra; + using BSDistantObjectLargeRefExtraData = TypedExtra; + using NiBinaryExtraData = TypedVectorExtra; using NiFloatsExtraData = TypedVectorExtra; using NiIntegersExtraData = TypedVectorExtra; + using BSWArray = TypedVectorExtra; + // Distinct from NiBinaryExtraData, uses mRecordSize as its size struct NiExtraData : public Extra { @@ -50,6 +55,14 @@ namespace Nif void read(NIFStream* nif) override; }; + // != TypedVectorExtra, doesn't use the string table + struct NiStringsExtraData : public Extra + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + struct NiVertWeightsExtraData : public Extra { void read(NIFStream* nif) override; @@ -115,5 +128,47 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSBoneLODExtraData : public Extra + { + struct BoneLOD + { + uint32_t mDistance; + std::string mBone; + + void read(NIFStream* nif); + }; + + std::vector mData; + + void read(NIFStream* nif) override; + }; + + struct BSDecalPlacementVectorExtraData : public NiFloatExtraData + { + struct Block + { + std::vector mPoints; + std::vector mNormals; + + void read(NIFStream* nif); + }; + + std::vector mBlocks; + + void read(NIFStream* nif) override; + }; + + struct BSExtraData : NiExtraData + { + void read(NIFStream* nif) override { } + }; + + struct BSClothExtraData : BSExtraData + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + } #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index ae871002c3..748c8bea42 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -200,6 +200,7 @@ namespace Nif { "NiIntegerExtraData", &construct }, { "NiIntegersExtraData", &construct }, { "NiVectorExtraData", &construct }, + { "NiStringsExtraData", &construct }, { "NiStringPalette", &construct }, // Bethesda bounds @@ -214,7 +215,16 @@ namespace Nif { "BSInvMarker", &construct }, // Other Bethesda records + { "BSExtraData", &construct }, { "BSBehaviorGraphExtraData", &construct }, + { "BSBoneLODExtraData", &construct }, + { "BSClothExtraData", &construct }, + { "BSDecalPlacementVectorExtraData", + &construct }, + { "BSDistantObjectExtraData", &construct }, + { "BSDistantObjectLargeRefExtraData", + &construct }, + { "BSWArray", &construct }, { "BSXFlags", &construct }, // GEOMETRY diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 3cb000e90e..ba13554571 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -57,10 +57,16 @@ namespace Nif RC_bhkSphereShape, RC_BSBehaviorGraphExtraData, RC_BSBound, + RC_BSBoneLODExtraData, + RC_BSClothExtraData, + RC_BSDecalPlacementVectorExtraData, RC_BSDismemberSkinInstance, + RC_BSDistantObjectExtraData, + RC_BSDistantObjectLargeRefExtraData, RC_BSEffectShaderProperty, RC_BSEffectShaderPropertyColorController, RC_BSEffectShaderPropertyFloatController, + RC_BSExtraData, RC_BSFurnitureMarker, RC_BSInvMarker, RC_BSLightingShaderProperty, @@ -78,6 +84,7 @@ namespace Nif RC_BSShaderProperty, RC_BSShaderTextureSet, RC_BSTriShape, + RC_BSWArray, RC_BSXFlags, RC_hkPackedNiTriStripsData, RC_NiAlphaAccumulator, @@ -156,6 +163,7 @@ namespace Nif RC_NiSphericalCollider, RC_NiStencilProperty, RC_NiStringExtraData, + RC_NiStringsExtraData, RC_NiStringPalette, RC_NiSwitchNode, RC_NiTextKeyExtraData, From 30b842dd1edc0d699c0b98bb11a76c58b93e9fdb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 01:54:26 +0300 Subject: [PATCH 0168/2167] Read BSMeshLODTriShape and BSDynamicTriShape Slightly clean up BSVertexData loading Fix skin tint alpha loading in BSLightingShaderProperty --- components/nif/niffile.cpp | 2 ++ components/nif/node.cpp | 33 +++++++++++++++++---------------- components/nif/node.hpp | 27 +++++++++++++++++++-------- components/nif/property.cpp | 2 +- components/nif/record.hpp | 2 ++ 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 748c8bea42..dcce055984 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -249,7 +249,9 @@ namespace Nif // Bethesda { "BSDismemberSkinInstance", &construct }, { "BSTriShape", &construct }, + { "BSDynamicTriShape", &construct }, { "BSLODTriShape", &construct }, + { "BSMeshLODTriShape", &construct }, // PARTICLES diff --git a/components/nif/node.cpp b/components/nif/node.cpp index ac99a06a1b..9a40acc99a 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -414,6 +414,23 @@ namespace Nif mAlphaProperty.post(nif); } + void BSDynamicTriShape::read(NIFStream* nif) + { + BSTriShape::read(nif); + + nif->read(mDynamicDataSize); + // nifly style. + // Consider complaining if mDynamicDataSize * 16 != mVertData.size()? + nif->readVector(mDynamicData, mVertData.size()); + } + + void BSMeshLODTriShape::read(NIFStream* nif) + { + BSTriShape::read(nif); + + nif->readArray(mLOD); + } + void BSVertexDesc::read(NIFStream* nif) { uint64_t data; @@ -448,21 +465,9 @@ namespace Nif if (hasVertex) { if (fullPrecision) - { nif->read(mVertex); - if (hasTangent) - nif->read(mBitangentX); - else - nif->skip(4); // Unused - } else - { nif->readArray(mHalfVertex); - if (hasTangent) - nif->read(mHalfBitangentX); - else - nif->skip(2); // Unused - } } if (hasUV) @@ -471,12 +476,8 @@ namespace Nif if (hasNormal) { nif->readArray(mNormal); - nif->read(mBitangentY); if (hasTangent) - { nif->readArray(mTangent); - nif->read(mBitangentZ); - } } if (hasVertexColor) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 0d2edad9a6..d2857752c3 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -338,15 +338,11 @@ namespace Nif struct BSVertexData { - osg::Vec3f mVertex; - std::array mHalfVertex; - float mBitangentX; - Misc::float16_t mHalfBitangentX; + osg::Vec4f mVertex; // Bitangent X is stored in the fourth component + std::array mHalfVertex; // Ditto std::array mUV; - std::array mNormal; - char mBitangentY; - std::array mTangent; - char mBitangentZ; + std::array mNormal; // Bitangent Y is stored in the fourth component + std::array mTangent; // Bitangent Z is stored in the fourth component std::array mVertColor; std::array mBoneWeights; std::array mBoneIndices; @@ -372,6 +368,21 @@ namespace Nif void post(Reader& nif) override; }; + struct BSDynamicTriShape : BSTriShape + { + uint32_t mDynamicDataSize; + std::vector mDynamicData; + + void read(NIFStream* nif) override; + }; + + struct BSMeshLODTriShape : BSTriShape + { + std::array mLOD; + + void read(NIFStream* nif) override; + }; + struct BSValueNode : NiNode { enum Flags diff --git a/components/nif/property.cpp b/components/nif/property.cpp index fbdb273cfd..3fc726132a 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -349,7 +349,7 @@ namespace Nif break; case BSLightingShaderType::ShaderType_SkinTint: nif->read(mSkinTintColor); - if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) nif->read(mSkinTintAlpha); break; case BSLightingShaderType::ShaderType_HairTint: diff --git a/components/nif/record.hpp b/components/nif/record.hpp index ba13554571..0505f0a30f 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -60,6 +60,7 @@ namespace Nif RC_BSBoneLODExtraData, RC_BSClothExtraData, RC_BSDecalPlacementVectorExtraData, + RC_BSDynamicTriShape, RC_BSDismemberSkinInstance, RC_BSDistantObjectExtraData, RC_BSDistantObjectLargeRefExtraData, @@ -74,6 +75,7 @@ namespace Nif RC_BSLightingShaderPropertyFloatController, RC_BSLODTriShape, RC_BSMaterialEmittanceMultController, + RC_BSMeshLODTriShape, RC_BSMultiBound, RC_BSMultiBoundOBB, RC_BSMultiBoundSphere, From 77c58826228f1c1a9934a9d0c5e1c2772a5f097d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 03:27:44 +0300 Subject: [PATCH 0169/2167] Read various FO3 shader properties BSDistantTreeShaderProperty, DistantLODShaderProperty, HairShaderProperty, Lighting30ShaderProperty, SkyShaderProperty, TallGrassShaderProperty, TileShaderProperty, VolumetricFogShaderProperty, WaterShaderProperty --- components/nif/niffile.cpp | 9 +++++++++ components/nif/property.cpp | 22 ++++++++++++++++++++++ components/nif/property.hpp | 32 ++++++++++++++++++++++++++++++++ components/nif/record.hpp | 8 ++++++++ 4 files changed, 71 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index dcce055984..9a43261616 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -319,8 +319,17 @@ namespace Nif { "BSShaderProperty", &construct }, { "BSShaderPPLightingProperty", &construct }, { "BSShaderNoLightingProperty", &construct }, + { "BSDistantTreeShaderProperty", &construct }, { "BSLightingShaderProperty", &construct }, { "BSEffectShaderProperty", &construct }, + { "DistantLODShaderProperty", &construct }, + { "HairShaderProperty", &construct }, + { "Lighting30ShaderProperty", &construct }, + { "SkyShaderProperty", &construct }, + { "TallGrassShaderProperty", &construct }, + { "TileShaderProperty", &construct }, + { "VolumetricFogShaderProperty", &construct }, + { "WaterShaderProperty", &construct }, }; } diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 3fc726132a..fa6fa1b4cf 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -227,6 +227,28 @@ namespace Nif nif->read(mFalloffParams); } + void SkyShaderProperty::read(NIFStream* nif) + { + BSShaderLightingProperty::read(nif); + + mFilename = nif->getSizedString(); + mSkyObjectType = static_cast(nif->get()); + } + + void TallGrassShaderProperty::read(NIFStream* nif) + { + BSShaderProperty::read(nif); + + mFilename = nif->getSizedString(); + } + + void TileShaderProperty::read(NIFStream* nif) + { + BSShaderLightingProperty::read(nif); + + mFilename = nif->getSizedString(); + } + void BSSPLuminanceParams::read(NIFStream* nif) { nif->read(mLumEmittance); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 797e02c40d..37d17989b6 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -171,6 +171,38 @@ namespace Nif void read(NIFStream* nif) override; }; + struct SkyShaderProperty : BSShaderLightingProperty + { + enum class ObjectType : uint32_t + { + SkyTexture = 0, + SkySunglare = 1, + Sky = 2, + SkyClouds = 3, + SkyStars = 5, + SkyMoonStarsMask = 7, + }; + + std::string mFilename; + ObjectType mSkyObjectType; + + void read(NIFStream* nif) override; + }; + + struct TallGrassShaderProperty : BSShaderProperty + { + std::string mFilename; + + void read(NIFStream* nif) override; + }; + + struct TileShaderProperty : BSShaderLightingProperty + { + std::string mFilename; + + void read(NIFStream* nif) override; + }; + enum class BSLightingShaderType : uint32_t { ShaderType_Default = 0, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0505f0a30f..a66c82fd32 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -60,6 +60,7 @@ namespace Nif RC_BSBoneLODExtraData, RC_BSClothExtraData, RC_BSDecalPlacementVectorExtraData, + RC_BSDistantTreeShaderProperty, RC_BSDynamicTriShape, RC_BSDismemberSkinInstance, RC_BSDistantObjectExtraData, @@ -88,6 +89,8 @@ namespace Nif RC_BSTriShape, RC_BSWArray, RC_BSXFlags, + RC_DistantLODShaderProperty, + RC_HairShaderProperty, RC_hkPackedNiTriStripsData, RC_NiAlphaAccumulator, RC_NiAlphaController, @@ -187,6 +190,11 @@ namespace Nif RC_NiWireframeProperty, RC_NiZBufferProperty, RC_RootCollisionNode, + RC_SkyShaderProperty, + RC_TallGrassShaderProperty, + RC_TileShaderProperty, + RC_VolumetricFogShaderProperty, + RC_WaterShaderProperty, }; /// Base class for all records From 5f504688ad4814d04c62ccb2d4054a401aadd0f0 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 03:42:59 +0300 Subject: [PATCH 0170/2167] Read BSMultiBoundAABB --- components/nif/data.cpp | 6 ++++++ components/nif/data.hpp | 8 ++++++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 1 + 4 files changed, 16 insertions(+) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 528256c49b..2023735252 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -542,6 +542,12 @@ namespace Nif mData.post(nif); } + void BSMultiBoundAABB::read(NIFStream* nif) + { + nif->read(mPosition); + nif->read(mExtents); + } + void BSMultiBoundOBB::read(NIFStream* nif) { nif->read(mCenter); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 6ec09c2f42..0eb6387f89 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -375,6 +375,14 @@ namespace Nif { }; + struct BSMultiBoundAABB : public BSMultiBoundData + { + osg::Vec3f mPosition; + osg::Vec3f mExtents; + + void read(NIFStream* nif) override; + }; + struct BSMultiBoundOBB : public BSMultiBoundData { osg::Vec3f mCenter; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 9a43261616..cf877c7cd9 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -206,6 +206,7 @@ namespace Nif // Bethesda bounds { "BSBound", &construct }, { "BSMultiBound", &construct }, + { "BSMultiBoundAABB", &construct }, { "BSMultiBoundOBB", &construct }, { "BSMultiBoundSphere", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index a66c82fd32..46aa3ce5d8 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -78,6 +78,7 @@ namespace Nif RC_BSMaterialEmittanceMultController, RC_BSMeshLODTriShape, RC_BSMultiBound, + RC_BSMultiBoundAABB, RC_BSMultiBoundOBB, RC_BSMultiBoundSphere, RC_BSRefractionFirePeriodController, From e2072853235610b31868e927d821036d7837172a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 03:57:44 +0300 Subject: [PATCH 0171/2167] Read BSFrustumFOVController and BSKeyframeController --- components/nif/controller.cpp | 14 ++++++++++++++ components/nif/controller.hpp | 8 ++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 2 ++ 4 files changed, 26 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 99317cda57..89cb066c62 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -486,6 +486,20 @@ namespace Nif nif->read(mControlledColor); } + void BSKeyframeController::read(NIFStream* nif) + { + NiKeyframeController::read(nif); + + mData2.read(nif); + } + + void BSKeyframeController::post(Reader& nif) + { + NiKeyframeController::post(nif); + + mData2.post(nif); + } + void NiControllerManager::read(NIFStream* nif) { NiTimeController::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index afca97d885..e0ac2d29ae 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -319,6 +319,14 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSKeyframeController : NiKeyframeController + { + NiKeyframeDataPtr mData2; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiControllerManager : public NiTimeController { bool mCumulative; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index cf877c7cd9..4b77fe471d 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -123,6 +123,8 @@ namespace Nif &construct }, // Bethesda + { "BSFrustumFOVController", &construct }, + { "BSKeyframeController", &construct }, { "BSMaterialEmittanceMultController", &construct }, { "BSRefractionFirePeriodController", diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 46aa3ce5d8..115d6827c2 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -69,8 +69,10 @@ namespace Nif RC_BSEffectShaderPropertyColorController, RC_BSEffectShaderPropertyFloatController, RC_BSExtraData, + RC_BSFrustumFOVController, RC_BSFurnitureMarker, RC_BSInvMarker, + RC_BSKeyframeController, RC_BSLightingShaderProperty, RC_BSLightingShaderPropertyColorController, RC_BSLightingShaderPropertyFloatController, From ecf644bda5869efa414690966df4349d62353c2f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 04:08:11 +0300 Subject: [PATCH 0172/2167] Read bhkCylinderShape --- components/nif/niffile.cpp | 1 + components/nif/physics.cpp | 11 +++++++++++ components/nif/physics.hpp | 9 +++++++++ components/nif/record.hpp | 1 + 4 files changed, 22 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4b77fe471d..6201c4df44 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -290,6 +290,7 @@ namespace Nif // Physics geometry records, Bethesda { "bhkBoxShape", &construct }, { "bhkCapsuleShape", &construct }, + { "bhkCylinderShape", &construct }, { "bhkCompressedMeshShape", &construct }, { "bhkCompressedMeshShapeData", &construct }, { "bhkConvexTransformShape", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 95786cb247..03f196bd53 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -512,6 +512,17 @@ namespace Nif nif->read(mRadius2); } + void bhkCylinderShape::read(NIFStream* nif) + { + bhkConvexShape::read(nif); + + nif->skip(8); // Unused + nif->read(mVertexA); + nif->read(mVertexB); + nif->read(mCylinderRadius); + nif->skip(12); // Unused + } + void bhkListShape::read(NIFStream* nif) { readRecordList(nif, mSubshapes); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 7c7c3df21e..18d9d23249 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -524,6 +524,15 @@ namespace Nif void read(NIFStream* nif) override; }; + // A cylinder + struct bhkCylinderShape : public bhkConvexShape + { + osg::Vec4f mVertexA, mVertexB; + float mCylinderRadius; + + void read(NIFStream* nif) override; + }; + // A sphere using bhkSphereShape = bhkConvexShape; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 115d6827c2..23bdebb7f7 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -39,6 +39,7 @@ namespace Nif RC_bhkBlendController, RC_bhkBoxShape, RC_bhkCapsuleShape, + RC_bhkCylinderShape, RC_bhkCollisionObject, RC_bhkCompressedMeshShape, RC_bhkCompressedMeshShapeData, From 9a2d385d870b34471bb4628ec44833205816a424 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 04:57:10 +0300 Subject: [PATCH 0173/2167] Read NiAdditionalGeometryData records --- components/nif/data.cpp | 38 ++++++++++++++++++++++++++++++++++++++ components/nif/data.hpp | 35 +++++++++++++++++++++++++++++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 2 ++ 4 files changed, 77 insertions(+) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 2023735252..c7f9e71fda 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -532,6 +532,44 @@ namespace Nif mKeyList->read(nif); } + void NiAdditionalGeometryData::read(NIFStream* nif) + { + nif->read(mNumVertices); + mBlockInfos.resize(nif->get()); + for (DataStream& info : mBlockInfos) + info.read(nif); + mBlocks.resize(nif->get()); + for (DataBlock& block : mBlocks) + block.read(nif, recType == RC_BSPackedAdditionalGeometryData); + } + + void NiAdditionalGeometryData::DataStream::read(NIFStream* nif) + { + nif->read(mType); + nif->read(mUnitSize); + nif->read(mTotalSize); + nif->read(mStride); + nif->read(mBlockIndex); + nif->read(mBlockOffset); + nif->read(mFlags); + } + + void NiAdditionalGeometryData::DataBlock::read(NIFStream* nif, bool bsPacked) + { + nif->read(mValid); + if (!mValid) + return; + nif->read(mBlockSize); + nif->readVector(mBlockOffsets, nif->get()); + nif->readVector(mDataSizes, nif->get()); + nif->readVector(mData, mDataSizes.size() * mBlockSize); + if (bsPacked) + { + nif->read(mShaderIndex); + nif->read(mTotalSize); + } + } + void BSMultiBound::read(NIFStream* nif) { mData.read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 0eb6387f89..1596579fdd 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -362,6 +362,41 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiAdditionalGeometryData : public Record + { + struct DataStream + { + uint32_t mType; + uint32_t mUnitSize; + uint32_t mTotalSize; + uint32_t mStride; + uint32_t mBlockIndex; + uint32_t mBlockOffset; + uint8_t mFlags; + + void read(NIFStream* nif); + }; + + struct DataBlock + { + bool mValid; + uint32_t mBlockSize; + std::vector mBlockOffsets; + std::vector mDataSizes; + std::vector mData; + uint32_t mShaderIndex; + uint32_t mTotalSize; + + void read(NIFStream* nif, bool bsPacked); + }; + + uint16_t mNumVertices; + std::vector mBlockInfos; + std::vector mBlocks; + + void read(NIFStream* nif); + }; + struct BSMultiBound : public Record { BSMultiBoundDataPtr mData; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6201c4df44..4f1eaae257 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -169,11 +169,13 @@ namespace Nif { "NiVisData", &construct }, // Gamebryo + { "NiAdditionalGeometryData", &construct }, { "NiBoolData", &construct }, { "NiDefaultAVObjectPalette", &construct }, { "NiTransformData", &construct }, // Bethesda + { "BSPackedAdditionalGeometryData", &construct }, { "BSShaderTextureSet", &construct }, // DYNAMIC EFFECTS diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 23bdebb7f7..8240bf4026 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -84,6 +84,7 @@ namespace Nif RC_BSMultiBoundAABB, RC_BSMultiBoundOBB, RC_BSMultiBoundSphere, + RC_BSPackedAdditionalGeometryData, RC_BSRefractionFirePeriodController, RC_BSRefractionStrengthController, RC_BSShaderNoLightingProperty, @@ -96,6 +97,7 @@ namespace Nif RC_DistantLODShaderProperty, RC_HairShaderProperty, RC_hkPackedNiTriStripsData, + RC_NiAdditionalGeometryData, RC_NiAlphaAccumulator, RC_NiAlphaController, RC_NiAlphaProperty, From 70877c94bc27cc750293f0a19295be55579f7a09 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 05:26:41 +0300 Subject: [PATCH 0174/2167] Read BSNiAlphaPropertyTestRefController --- components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4f1eaae257..52472caaeb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -127,6 +127,8 @@ namespace Nif { "BSKeyframeController", &construct }, { "BSMaterialEmittanceMultController", &construct }, + { "BSNiAlphaPropertyTestRefController", + &construct }, { "BSRefractionFirePeriodController", &construct }, { "BSRefractionStrengthController", diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 8240bf4026..2551fa836c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -84,6 +84,7 @@ namespace Nif RC_BSMultiBoundAABB, RC_BSMultiBoundOBB, RC_BSMultiBoundSphere, + RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, RC_BSRefractionFirePeriodController, RC_BSRefractionStrengthController, From 0eb8d28e81f91976f9b37f4fd81e2cb8bba73796 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 05:37:52 +0300 Subject: [PATCH 0175/2167] Read bhkBlendCollisionObject --- components/nif/niffile.cpp | 1 + components/nif/physics.cpp | 11 +++++++++++ components/nif/physics.hpp | 8 ++++++++ components/nif/record.hpp | 1 + 4 files changed, 21 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 52472caaeb..6d86455e49 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -281,6 +281,7 @@ namespace Nif { "bhkCollisionObject", &construct }, { "bhkPCollisionObject", &construct }, { "bhkSPCollisionObject", &construct }, + { "bhkBlendCollisionObject", &construct }, // Constraint records, Bethesda { "bhkHingeConstraint", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 03f196bd53..62fcd4b6b3 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -342,6 +342,17 @@ namespace Nif mBody.read(nif); } + void bhkBlendCollisionObject::read(NIFStream* nif) + { + bhkCollisionObject::read(nif); + + nif->read(mHeirGain); + nif->read(mVelGain); + + if (nif->getBethVersion() <= 8) + nif->skip(8); // Unknown + } + void bhkWorldObject::read(NIFStream* nif) { mShape.read(nif); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 18d9d23249..fb0ab6694c 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -394,6 +394,14 @@ namespace Nif } }; + struct bhkBlendCollisionObject : bhkCollisionObject + { + float mHeirGain; + float mVelGain; + + void read(NIFStream* nif) override; + }; + // Abstract Havok shape info record struct bhkWorldObject : public bhkSerializable { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 2551fa836c..9d02fc2c85 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,7 @@ namespace Nif { RC_MISSING = 0, RC_AvoidNode, + RC_bhkBlendCollisionObject, RC_bhkBlendController, RC_bhkBoxShape, RC_bhkCapsuleShape, From 312e32717ce251678199910c6bc42fa43ba43ac5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 05:57:14 +0300 Subject: [PATCH 0176/2167] Read bhkConvexListShape and bhkConvexSweepShape --- components/nif/niffile.cpp | 2 ++ components/nif/physics.cpp | 29 +++++++++++++++++++++++++++++ components/nif/physics.hpp | 24 ++++++++++++++++++++++++ components/nif/record.hpp | 2 ++ 4 files changed, 57 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6d86455e49..870731b586 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -298,6 +298,8 @@ namespace Nif { "bhkCylinderShape", &construct }, { "bhkCompressedMeshShape", &construct }, { "bhkCompressedMeshShapeData", &construct }, + { "bhkConvexListShape", &construct }, + { "bhkConvexSweepShape", &construct }, { "bhkConvexTransformShape", &construct }, { "bhkConvexVerticesShape", &construct }, { "bhkListShape", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 62fcd4b6b3..120e56e2dd 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -477,6 +477,35 @@ namespace Nif nif->read(mRadius); } + void bhkConvexListShape::read(NIFStream* nif) + { + readRecordList(nif, mSubShapes); + mMaterial.read(nif); + nif->read(mRadius); + nif->skip(8); // Unknown + mChildShapeProperty.read(nif); + nif->read(mUseCachedAABB); + nif->read(mClosestPointMinDistance); + } + + void bhkConvexListShape::post(Reader& nif) + { + postRecordList(nif, mSubShapes); + } + + void bhkConvexSweepShape::read(NIFStream* nif) + { + mShape.read(nif); + mMaterial.read(nif); + nif->read(mRadius); + nif->skip(12); // Unknown + } + + void bhkConvexSweepShape::post(Reader& nif) + { + mShape.post(nif); + } + void bhkConvexVerticesShape::read(NIFStream* nif) { bhkConvexShape::read(nif); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index fb0ab6694c..d4addeaad6 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -493,6 +493,30 @@ namespace Nif void read(NIFStream* nif) override; }; + // A list of convex shapes sharing the same properties + struct bhkConvexListShape : public bhkShape + { + bhkShapeList mSubShapes; + HavokMaterial mMaterial; + float mRadius; + bhkWorldObjCInfoProperty mChildShapeProperty; + bool mUseCachedAABB; + float mClosestPointMinDistance; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct bhkConvexSweepShape : bhkShape + { + bhkConvexShape mShape; + HavokMaterial mMaterial; + float mRadius; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + // A convex shape built from vertices struct bhkConvexVerticesShape : public bhkConvexShape { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 9d02fc2c85..ac0901ed87 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -44,6 +44,8 @@ namespace Nif RC_bhkCollisionObject, RC_bhkCompressedMeshShape, RC_bhkCompressedMeshShapeData, + RC_bhkConvexListShape, + RC_bhkConvexSweepShape, RC_bhkConvexTransformShape, RC_bhkConvexVerticesShape, RC_bhkHingeConstraint, From ad2038475015d5b542fe9ff75a3d07d892cef22f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 06:12:56 +0300 Subject: [PATCH 0177/2167] Read NiLightDimmerController --- components/nif/niffile.cpp | 1 + components/nif/record.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 870731b586..33a29e2c07 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -116,6 +116,7 @@ namespace Nif // Gamebryo { "NiControllerManager", &construct }, + { "NiLightDimmerController", &construct }, { "NiTransformController", &construct }, { "NiTextureTransformController", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index ac0901ed87..1699820cdc 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -144,6 +144,7 @@ namespace Nif RC_NiKeyframeController, RC_NiKeyframeData, RC_NiLight, + RC_NiLightDimmerController, RC_NiLines, RC_NiLinesData, RC_NiLODNode, From b5f1d0a91b36f159e43fe5fb8de4ca2ac18bf250 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 07:24:52 +0300 Subject: [PATCH 0178/2167] Fix formatting --- components/nif/extra.hpp | 2 +- components/nif/niffile.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index bb3cc3f73e..49c77723fb 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -160,7 +160,7 @@ namespace Nif struct BSExtraData : NiExtraData { - void read(NIFStream* nif) override { } + void read(NIFStream* nif) override {} }; struct BSClothExtraData : BSExtraData diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 33a29e2c07..6f588a7cdb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -178,7 +178,8 @@ namespace Nif { "NiTransformData", &construct }, // Bethesda - { "BSPackedAdditionalGeometryData", &construct }, + { "BSPackedAdditionalGeometryData", + &construct }, { "BSShaderTextureSet", &construct }, // DYNAMIC EFFECTS @@ -226,7 +227,7 @@ namespace Nif { "BSExtraData", &construct }, { "BSBehaviorGraphExtraData", &construct }, { "BSBoneLODExtraData", &construct }, - { "BSClothExtraData", &construct }, + { "BSClothExtraData", &construct }, { "BSDecalPlacementVectorExtraData", &construct }, { "BSDistantObjectExtraData", &construct }, From 1aabc9aee5c9182f0dde0c08dc7c9a84ba95c73f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 08:11:57 +0300 Subject: [PATCH 0179/2167] Read bhkNPCollisionObject, bhkPhysicsSystem and bhkRagdollSystem --- components/nif/niffile.cpp | 5 +++++ components/nif/physics.cpp | 26 ++++++++++++++++++++++++++ components/nif/physics.hpp | 29 +++++++++++++++++++++++++++++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 2 ++ 5 files changed, 64 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6f588a7cdb..efdd51a909 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -283,6 +283,7 @@ namespace Nif { "bhkCollisionObject", &construct }, { "bhkPCollisionObject", &construct }, { "bhkSPCollisionObject", &construct }, + { "bhkNPCollisionObject", &construct }, { "bhkBlendCollisionObject", &construct }, // Constraint records, Bethesda @@ -313,6 +314,10 @@ namespace Nif { "bhkSphereShape", &construct }, { "bhkTransformShape", &construct }, + // Physics system records, Bethesda + { "bhkPhysicsSystem", &construct }, + { "bhkRagdollSystem", &construct }, + // PROPERTIES // 4.0.0.2 diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 120e56e2dd..a359f86066 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -342,6 +342,22 @@ namespace Nif mBody.read(nif); } + void bhkNPCollisionObject::read(NIFStream* nif) + { + NiCollisionObject::read(nif); + + nif->read(mFlags); + mData.read(nif); + nif->read(mBodyID); + } + + void bhkNPCollisionObject::post(Reader& nif) + { + NiCollisionObject::post(nif); + + mData.post(nif); + } + void bhkBlendCollisionObject::read(NIFStream* nif) { bhkCollisionObject::read(nif); @@ -353,6 +369,16 @@ namespace Nif nif->skip(8); // Unknown } + void bhkPhysicsSystem::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + + void bhkRagdollSystem::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + void bhkWorldObject::read(NIFStream* nif) { mShape.read(nif); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index d4addeaad6..91d55c1f80 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -370,6 +370,11 @@ namespace Nif { }; + // Abstract physics system + struct bhkSystem : public Record + { + }; + // Generic collision object struct NiCollisionObject : public Record { @@ -394,6 +399,16 @@ namespace Nif } }; + struct bhkNPCollisionObject : NiCollisionObject + { + uint16_t mFlags; + bhkSystemPtr mData; + uint32_t mBodyID; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct bhkBlendCollisionObject : bhkCollisionObject { float mHeirGain; @@ -402,6 +417,20 @@ namespace Nif void read(NIFStream* nif) override; }; + struct bhkPhysicsSystem : public bhkSystem + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + + struct bhkRagdollSystem : public bhkSystem + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + // Abstract Havok shape info record struct bhkWorldObject : public bhkSerializable { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 1699820cdc..2c641b2aff 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -54,7 +54,9 @@ namespace Nif RC_bhkMoppBvTreeShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, + RC_bhkPhysicsSystem, RC_bhkRagdollConstraint, + RC_bhkRagdollSystem, RC_bhkRigidBody, RC_bhkRigidBodyT, RC_bhkSimpleShapePhantom, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 54ebe0e9a2..8598a88f0e 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -136,6 +136,7 @@ namespace Nif struct BSShaderProperty; struct NiAlphaProperty; struct NiCollisionObject; + struct bhkSystem; struct bhkWorldObject; struct bhkShape; struct bhkSerializable; @@ -176,6 +177,7 @@ namespace Nif using BSShaderPropertyPtr = RecordPtrT; using NiAlphaPropertyPtr = RecordPtrT; using NiCollisionObjectPtr = RecordPtrT; + using bhkSystemPtr = RecordPtrT; using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; using bhkEntityPtr = RecordPtrT; From 73a12eb74a2aa668448f913e3cb558727d1b19ce Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 20 Sep 2023 08:53:35 +0300 Subject: [PATCH 0180/2167] Read BSLagBoneController --- components/nif/controller.cpp | 9 +++++++++ components/nif/controller.hpp | 9 +++++++++ components/nif/niffile.cpp | 1 + components/nif/record.hpp | 1 + 4 files changed, 20 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 89cb066c62..73644f1541 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -500,6 +500,15 @@ namespace Nif mData2.post(nif); } + void BSLagBoneController::read(NIFStream* nif) + { + NiTimeController::read(nif); + + nif->read(mLinearVelocity); + nif->read(mLinearRotation); + nif->read(mMaximumDistance); + } + void NiControllerManager::read(NIFStream* nif) { NiTimeController::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index e0ac2d29ae..3104c29f94 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -327,6 +327,15 @@ namespace Nif void post(Reader& nif) override; }; + struct BSLagBoneController : NiTimeController + { + float mLinearVelocity; + float mLinearRotation; + float mMaximumDistance; + + void read(NIFStream* nif) override; + }; + struct NiControllerManager : public NiTimeController { bool mCumulative; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index efdd51a909..66c4aaac7b 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -126,6 +126,7 @@ namespace Nif // Bethesda { "BSFrustumFOVController", &construct }, { "BSKeyframeController", &construct }, + { "BSLagBoneController", &construct }, { "BSMaterialEmittanceMultController", &construct }, { "BSNiAlphaPropertyTestRefController", diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 2c641b2aff..cecb9631d6 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -79,6 +79,7 @@ namespace Nif RC_BSFurnitureMarker, RC_BSInvMarker, RC_BSKeyframeController, + RC_BSLagBoneController, RC_BSLightingShaderProperty, RC_BSLightingShaderPropertyColorController, RC_BSLightingShaderPropertyFloatController, From 6ed5cbb7d03016221f5c36c7b9782226ba9b93e4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Sep 2023 19:51:29 +0200 Subject: [PATCH 0181/2167] Remove numeric magic school ids and deprecate the enum --- CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 4 ++-- apps/openmw/mwlua/magicbindings.cpp | 17 +++++++++-------- files/lua_api/openmw/core.lua | 28 ++++++++++++++-------------- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d3a16c285f..43bbcfc2e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 48) +set(OPENMW_LUA_API_REVISION 49) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 8b62c5480d..cb0dcfff97 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -184,7 +184,7 @@ namespace MWLua return sol::nil; }; - // TODO: deprecate this and provide access to the store instead + // deprecated, use core.stats.Skill sol::table skills(context.mLua->sol(), sol::create); api["SKILL"] = LuaUtil::makeStrictReadOnly(skills); for (int i = 0; i < ESM::Skill::Length; ++i) @@ -197,7 +197,7 @@ namespace MWLua skills[key] = id; } - // TODO: deprecate this and provide access to the store instead + // deprecated, use core.stats.Attribute sol::table attributes(context.mLua->sol(), sol::create); api["ATTRIBUTE"] = LuaUtil::makeStrictReadOnly(attributes); for (int i = 0; i < ESM::Attribute::Length; ++i) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index afcc5bc54a..c0596a9478 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -203,13 +203,14 @@ namespace MWLua { "Touch", ESM::RT_Touch }, { "Target", ESM::RT_Target }, })); - magicApi["SCHOOL"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ - { "Alteration", 0 }, - { "Conjuration", 1 }, - { "Destruction", 2 }, - { "Illusion", 3 }, - { "Mysticism", 4 }, - { "Restoration", 5 }, + // deprecated, use core.stats.Skill + magicApi["SCHOOL"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Alteration", ESM::RefId(ESM::Skill::Alteration).serializeText() }, + { "Conjuration", ESM::RefId(ESM::Skill::Conjuration).serializeText() }, + { "Destruction", ESM::RefId(ESM::Skill::Destruction).serializeText() }, + { "Illusion", ESM::RefId(ESM::Skill::Illusion).serializeText() }, + { "Mysticism", ESM::RefId(ESM::Skill::Mysticism).serializeText() }, + { "Restoration", ESM::RefId(ESM::Skill::Restoration).serializeText() }, })); magicApi["SPELL_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ @@ -406,7 +407,7 @@ namespace MWLua ->mValue.getString(); }); magicEffectT["school"] = sol::readonly_property( - [](const ESM::MagicEffect& rec) -> int { return ESM::MagicSchool::skillRefIdToIndex(rec.mData.mSchool); }); + [](const ESM::MagicEffect& rec) -> std::string { return rec.mData.mSchool.serializeText(); }); magicEffectT["baseCost"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> float { return rec.mData.mBaseCost; }); magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index b7a3b65ba6..efa88a0cbd 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -333,8 +333,8 @@ --- -- @type ActiveSpellEffect --- @field #string affectedSkill @{#SKILL} or nil --- @field #string affectedAttribute @{#ATTRIBUTE} or nil +-- @field #string affectedSkill Optional skill ID +-- @field #string affectedAttribute Optional attribute ID -- @field #string id Magic effect id -- @field #string name Localized name of the effect -- @field #number magnitudeThisFrame The magnitude of the effect in the current frame. This will be a new random number between minMagnitude and maxMagnitude every frame. Or nil if the effect has no magnitude. @@ -498,17 +498,17 @@ -- @field #number Target Ranged spell ---- Possible @{#MagicSchool} values +--- Possible @{#MagicSchool} values (DEPRECATED use @{#Skill}) -- @field [parent=#Magic] #MagicSchool SCHOOL --- `core.magic.SCHOOL` -- @type MagicSchool --- @field #number Alteration Alteration --- @field #number Conjuration Conjuration --- @field #number Destruction Destruction --- @field #number Illusion Illusion --- @field #number Mysticism Mysticism --- @field #number Restoration Restoration +-- @field #string Alteration alteration +-- @field #string Conjuration conjuration +-- @field #string Destruction destruction +-- @field #string Illusion illusion +-- @field #string Mysticism mysticism +-- @field #string Restoration restoration --- Possible @{#MagicEffectId} values @@ -720,7 +720,7 @@ -- @field #string id Effect ID -- @field #string icon Effect Icon Path -- @field #string name Localized name of the effect --- @field #number school @{#MagicSchool} +-- @field #string school Skill ID -- @field #number baseCost -- @field openmw.util#Color color -- @field #boolean harmful @@ -728,8 +728,8 @@ --- -- @type MagicEffectWithParams -- @field #MagicEffect effect @{#MagicEffect} --- @field #string affectedSkill @{#SKILL} or nil --- @field #string affectedAttribute @{#ATTRIBUTE} or nil +-- @field #string affectedSkill Optional skill ID +-- @field #string affectedAttribute Optional attribute ID -- @field #number range -- @field #number area -- @field #number magnitudeMin @@ -738,8 +738,8 @@ --- -- @type ActiveEffect --- @field #string affectedSkill @{#SKILL} or nil --- @field #string affectedAttribute @{#ATTRIBUTE} or nil +-- @field #string affectedSkill Optional skill ID +-- @field #string affectedAttribute Optional attribute ID -- @field #string id Effect id string -- @field #string name Localized name of the effect -- @field #number magnitude From dddfbf806b9913b24cc38a70fb6477428a5dec6c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 12:07:48 +0300 Subject: [PATCH 0182/2167] Move particle geometry, add NiPSysData and NiPSysEmitterCtlrData These record types are currently unreachable, might get tweaks later --- components/nif/data.cpp | 81 ++++++-------------------------- components/nif/data.hpp | 26 ----------- components/nif/niffile.cpp | 20 +++++--- components/nif/particle.cpp | 93 +++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 45 ++++++++++++++++++ components/nif/record.hpp | 2 + 6 files changed, 167 insertions(+), 100 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index c7f9e71fda..34ec3b2831 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -13,8 +13,8 @@ namespace Nif if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 114)) nif->read(mGroupId); - // Note: has special meaning for NiPSysData (unimplemented) nif->read(mNumVertices); + bool hasData = recType != RC_NiPSysData || nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO3; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { @@ -22,7 +22,7 @@ namespace Nif nif->read(mCompressFlags); } - if (nif->get()) + if (nif->get() && hasData) nif->readVector(mVertices, mNumVertices); if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) @@ -34,7 +34,7 @@ namespace Nif nif->read(mMaterialHash); } - if (nif->get()) + if (nif->get() && hasData) { nif->readVector(mNormals, mNumVertices); if (mDataFlags & DataFlag_HasTangents) @@ -46,7 +46,7 @@ namespace Nif nif->read(mBoundingSphere); - if (nif->get()) + if (nif->get() && hasData) nif->readVector(mColors, mNumVertices); if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) @@ -64,13 +64,16 @@ namespace Nif else if (!nif->get()) numUVs = 0; - mUVList.resize(numUVs); - for (std::vector& list : mUVList) + if (hasData) { - nif->readVector(list, mNumVertices); - // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin - for (osg::Vec2f& uv : list) - uv.y() = 1.f - uv.y(); + mUVList.resize(numUVs); + for (std::vector& list : mUVList) + { + nif->readVector(list, mNumVertices); + // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin + for (osg::Vec2f& uv : list) + uv.y() = 1.f - uv.y(); + } } if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) @@ -146,64 +149,6 @@ namespace Nif mLines.shrink_to_fit(); } - void NiParticlesData::read(NIFStream* nif) - { - NiGeometryData::read(nif); - - // Should always match the number of vertices in theory, but doesn't: - // see mist.nif in Mistify mod (https://www.nexusmods.com/morrowind/mods/48112). - if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - nif->read(mNumParticles); - bool isBs202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() != 0; - - bool numRadii = 1; - if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) - numRadii = (nif->get() && !isBs202) ? mNumVertices : 0; - nif->readVector(mRadii, numRadii); - nif->read(mActiveCount); - if (nif->get() && !isBs202) - nif->readVector(mSizes, mNumVertices); - - if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - { - if (nif->get() && !isBs202) - nif->readVector(mRotations, mNumVertices); - if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) - { - if (nif->get() && !isBs202) - nif->readVector(mRotationAngles, mNumVertices); - if (nif->get() && !isBs202) - nif->readVector(mRotationAxes, mNumVertices); - if (isBs202) - { - nif->read(mHasTextureIndices); - uint32_t numSubtextureOffsets; - if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) - numSubtextureOffsets = nif->get(); - else - nif->read(numSubtextureOffsets); - nif->readVector(mSubtextureOffsets, numSubtextureOffsets); - if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - { - nif->read(mAspectRatio); - nif->read(mAspectFlags); - nif->read(mAspectRatio2); - nif->read(mAspectSpeed); - nif->read(mAspectSpeed2); - } - } - } - } - } - - void NiRotatingParticlesData::read(NIFStream* nif) - { - NiParticlesData::read(nif); - - if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->get()) - nif->readVector(mRotations, mNumVertices); - } - void NiPosData::read(NIFStream* nif) { mKeyList = std::make_shared(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 1596579fdd..efab514223 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -70,32 +70,6 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticlesData : public NiGeometryData - { - uint16_t mNumParticles{ 0 }; - uint16_t mActiveCount; - - std::vector mRadii; - std::vector mSizes; - std::vector mRotations; - std::vector mRotationAngles; - std::vector mRotationAxes; - - bool mHasTextureIndices{ false }; - std::vector mSubtextureOffsets; - float mAspectRatio{ 1.f }; - uint16_t mAspectFlags{ 0 }; - float mAspectRatio2; - float mAspectSpeed, mAspectSpeed2; - - void read(NIFStream* nif) override; - }; - - struct NiRotatingParticlesData : public NiParticlesData - { - void read(NIFStream* nif) override; - }; - struct NiPosData : public Record { Vector3KeyMapPtr mKeyList; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 66c4aaac7b..fe59bb8b9d 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -240,14 +240,8 @@ namespace Nif // GEOMETRY // 4.0.0.2 - { "NiAutoNormalParticles", &construct }, - { "NiAutoNormalParticlesData", &construct }, { "NiLines", &construct }, { "NiLinesData", &construct }, - { "NiParticles", &construct }, - { "NiParticlesData", &construct }, - { "NiRotatingParticles", &construct }, - { "NiRotatingParticlesData", &construct }, { "NiSkinData", &construct }, { "NiSkinInstance", &construct }, { "NiSkinPartition", &construct }, @@ -265,12 +259,26 @@ namespace Nif // PARTICLES + // Geometry, 4.0.0.2 + { "NiAutoNormalParticles", &construct }, + { "NiAutoNormalParticlesData", &construct }, + { "NiParticles", &construct }, + { "NiParticlesData", &construct }, + { "NiRotatingParticles", &construct }, + { "NiRotatingParticlesData", &construct }, + + // Geometry, Gamebryo + { "NiPSysData", &construct }, + // Modifiers, 4.0.0.2 { "NiGravity", &construct }, { "NiParticleColorModifier", &construct }, { "NiParticleGrowFade", &construct }, { "NiParticleRotation", &construct }, + // Modifier data, Gamebryo + { "NiPSysEmitterCtlrData", &construct }, + // Colliders, 4.0.0.2 { "NiPlanarCollider", &construct }, { "NiSphericalCollider", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index ae391c59e4..14898abf94 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -92,4 +92,97 @@ namespace Nif nif->read(mRotationSpeed); } + void NiParticlesData::read(NIFStream* nif) + { + NiGeometryData::read(nif); + + // Should always match the number of vertices in theory, but doesn't: + // see mist.nif in Mistify mod (https://www.nexusmods.com/morrowind/mods/48112). + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + nif->read(mNumParticles); + bool isBs202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() != 0; + + bool numRadii = 1; + if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) + numRadii = (nif->get() && !isBs202) ? mNumVertices : 0; + nif->readVector(mRadii, numRadii); + nif->read(mActiveCount); + if (nif->get() && !isBs202) + nif->readVector(mSizes, mNumVertices); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) + { + if (nif->get() && !isBs202) + nif->readVector(mRotations, mNumVertices); + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) + { + if (nif->get() && !isBs202) + nif->readVector(mRotationAngles, mNumVertices); + if (nif->get() && !isBs202) + nif->readVector(mRotationAxes, mNumVertices); + if (isBs202) + { + nif->read(mHasTextureIndices); + uint32_t numSubtextureOffsets; + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + numSubtextureOffsets = nif->get(); + else + nif->read(numSubtextureOffsets); + nif->readVector(mSubtextureOffsets, numSubtextureOffsets); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + { + nif->read(mAspectRatio); + nif->read(mAspectFlags); + nif->read(mAspectRatio2); + nif->read(mAspectSpeed); + nif->read(mAspectSpeed2); + } + } + } + } + } + + void NiRotatingParticlesData::read(NIFStream* nif) + { + NiParticlesData::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->get()) + nif->readVector(mRotations, mNumVertices); + } + + void NiPSysData::read(NIFStream* nif) + { + NiParticlesData::read(nif); + + bool hasData = nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO3; + if (hasData) + { + mParticles.resize(mNumVertices); + for (NiParticleInfo& info : mParticles) + info.read(nif); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->skip(12); // Unknown + + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2) && nif->get() && hasData) + nif->readVector(mRotationSpeeds, mNumVertices); + + if (nif->getVersion() != NIFStream::generateVersion(20, 2, 0, 7) || nif->getBethVersion() == 0) + { + nif->read(mNumAddedParticles); + nif->read(mAddedParticlesBase); + } + } + + void NiPSysEmitterCtlrData::read(NIFStream* nif) + { + mFloatKeyList = std::make_shared(); + mVisKeyList = std::make_shared(); + uint32_t numVisKeys; + nif->read(numVisKeys); + for (size_t i = 0; i < numVisKeys; i++) + mVisKeyList->mKeys[nif->get()].mValue = nif->get() != 0; + } + } diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 328498210a..084735a472 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_NIF_PARTICLE_HPP #include "base.hpp" +#include "data.hpp" namespace Nif { @@ -86,5 +87,49 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiParticlesData : NiGeometryData + { + uint16_t mNumParticles{ 0 }; + uint16_t mActiveCount; + + std::vector mRadii; + std::vector mSizes; + std::vector mRotations; + std::vector mRotationAngles; + std::vector mRotationAxes; + + bool mHasTextureIndices{ false }; + std::vector mSubtextureOffsets; + float mAspectRatio{ 1.f }; + uint16_t mAspectFlags{ 0 }; + float mAspectRatio2; + float mAspectSpeed, mAspectSpeed2; + + void read(NIFStream* nif) override; + }; + + struct NiRotatingParticlesData : NiParticlesData + { + void read(NIFStream* nif) override; + }; + + struct NiPSysData : NiParticlesData + { + std::vector mParticles; + std::vector mRotationSpeeds; + uint16_t mNumAddedParticles; + uint16_t mAddedParticlesBase; + + void read(NIFStream* nif) override; + }; + + struct NiPSysEmitterCtlrData : Record + { + FloatKeyMapPtr mFloatKeyList; + BoolKeyMapPtr mVisKeyList; + + void read(NIFStream* nif) override; + }; + } #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index cecb9631d6..3013bef8de 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -169,6 +169,8 @@ namespace Nif RC_NiPlanarCollider, RC_NiPoint3Interpolator, RC_NiPosData, + RC_NiPSysEmitterCtlrData, + RC_NiPSysData, RC_NiRollController, RC_NiSequence, RC_NiSequenceStreamHelper, From c8307ad397413b82f08cdad20ec42a656e773e81 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 13:09:33 +0300 Subject: [PATCH 0183/2167] Read particle system modifier controllers --- components/nif/niffile.cpp | 28 +++++++++++++++++++++++- components/nif/particle.cpp | 25 +++++++++++++++++++++ components/nif/particle.hpp | 42 ++++++++++++++++++++++++++++++++++++ components/nif/record.hpp | 22 ++++++++++++++++++- components/nif/recordptr.hpp | 2 ++ 5 files changed, 117 insertions(+), 2 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index fe59bb8b9d..62516c1df7 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -276,7 +276,33 @@ namespace Nif { "NiParticleGrowFade", &construct }, { "NiParticleRotation", &construct }, - // Modifier data, Gamebryo + // Modifier controllers, Gamebryo + { "NiPSysAirFieldAirFrictionCtlr", &construct }, + { "NiPSysAirFieldInheritVelocityCtlr", + &construct }, + { "NiPSysAirFieldSpreadCtlr", &construct }, + { "NiPSysEmitterCtlr", &construct }, + { "NiPSysEmitterDeclinationCtlr", &construct }, + { "NiPSysEmitterDeclinationVarCtlr", + &construct }, + { "NiPSysEmitterInitialRadiusCtlr", + &construct }, + { "NiPSysEmitterLifeSpanCtlr", &construct }, + { "NiPSysEmitterPlanarAngleCtlr", &construct }, + { "NiPSysEmitterPlanarAngleVarCtlr", + &construct }, + { "NiPSysEmitterSpeedCtlr", &construct }, + { "NiPSysFieldAttenuationCtlr", &construct }, + { "NiPSysFieldMagnitudeCtlr", &construct }, + { "NiPSysFieldMaxDistanceCtlr", &construct }, + { "NiPSysGravityStrengthCtlr", &construct }, + { "NiPSysInitialRotSpeedCtlr", &construct }, + { "NiPSysInitialRotSpeedVarCtlr", &construct }, + { "NiPSysInitialRotAngleCtlr", &construct }, + { "NiPSysInitialRotAngleVarCtlr", &construct }, + { "NiPSysModifierActiveCtlr", &construct }, + + // Modifier controller data, Gamebryo { "NiPSysEmitterCtlrData", &construct }, // Colliders, 4.0.0.2 diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 14898abf94..389449ed77 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -175,6 +175,31 @@ namespace Nif } } + void NiPSysModifierCtlr::read(NIFStream* nif) + { + NiSingleInterpController::read(nif); + + nif->read(mModifierName); + } + + void NiPSysEmitterCtlr::read(NIFStream* nif) + { + NiPSysModifierCtlr::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) + mData.read(nif); + else + mVisInterpolator.read(nif); + } + + void NiPSysEmitterCtlr::post(Reader& nif) + { + NiPSysModifierCtlr::post(nif); + + mData.post(nif); + mVisInterpolator.post(nif); + } + void NiPSysEmitterCtlrData::read(NIFStream* nif) { mFloatKeyList = std::make_shared(); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 084735a472..ffdb307bab 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_NIF_PARTICLE_HPP #include "base.hpp" +#include "controller.hpp" #include "data.hpp" namespace Nif @@ -123,6 +124,47 @@ namespace Nif void read(NIFStream* nif) override; }; + // Abstract + struct NiPSysModifierCtlr : NiSingleInterpController + { + std::string mModifierName; + + void read(NIFStream* nif) override; + }; + + template + struct TypedNiPSysModifierCtlr : NiPSysModifierCtlr + { + DataPtr mData; + + void read(NIFStream* nif) override + { + NiPSysModifierCtlr::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) + mData.read(nif); + } + + void post(Reader& nif) override + { + NiPSysModifierCtlr::post(nif); + + mData.post(nif); + } + }; + + using NiPSysModifierBoolCtlr = TypedNiPSysModifierCtlr; + using NiPSysModifierFloatCtlr = TypedNiPSysModifierCtlr; + + struct NiPSysEmitterCtlr : NiPSysModifierCtlr + { + NiPSysEmitterCtlrDataPtr mData; + NiInterpolatorPtr mVisInterpolator; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysEmitterCtlrData : Record { FloatKeyMapPtr mFloatKeyList; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 3013bef8de..aa0dbf9f37 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -169,8 +169,28 @@ namespace Nif RC_NiPlanarCollider, RC_NiPoint3Interpolator, RC_NiPosData, - RC_NiPSysEmitterCtlrData, + RC_NiPSysAirFieldAirFrictionCtlr, + RC_NiPSysAirFieldInheritVelocityCtlr, + RC_NiPSysAirFieldSpreadCtlr, RC_NiPSysData, + RC_NiPSysEmitterCtlr, + RC_NiPSysEmitterCtlrData, + RC_NiPSysEmitterDeclinationCtlr, + RC_NiPSysEmitterDeclinationVarCtlr, + RC_NiPSysEmitterInitialRadiusCtlr, + RC_NiPSysEmitterLifeSpanCtlr, + RC_NiPSysEmitterPlanarAngleCtlr, + RC_NiPSysEmitterPlanarAngleVarCtlr, + RC_NiPSysEmitterSpeedCtlr, + RC_NiPSysFieldAttenuationCtlr, + RC_NiPSysFieldMagnitudeCtlr, + RC_NiPSysFieldMaxDistanceCtlr, + RC_NiPSysGravityStrengthCtlr, + RC_NiPSysInitialRotSpeedCtlr, + RC_NiPSysInitialRotSpeedVarCtlr, + RC_NiPSysInitialRotAngleCtlr, + RC_NiPSysInitialRotAngleVarCtlr, + RC_NiPSysModifierActiveCtlr, RC_NiRollController, RC_NiSequence, RC_NiSequenceStreamHelper, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 8598a88f0e..53b9c02a95 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -129,6 +129,7 @@ namespace Nif struct NiSourceTexture; struct NiPalette; struct NiParticleModifier; + struct NiPSysEmitterCtlrData; struct NiBoolData; struct NiSkinPartition; struct BSShaderTextureSet; @@ -170,6 +171,7 @@ namespace Nif using NiSourceTexturePtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; + using NiPSysEmitterCtlrDataPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; From a90c848349a3586e248641e4246637d0454398d1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 13:54:28 +0300 Subject: [PATCH 0184/2167] Reject files with non-zero preceding separators This sign is typically bad news on some 10.0.1.2 files used in Oblivion. Technically our fault, but NifTools research is incomplete, will need to check if cc9cii found anything --- components/nif/niffile.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 62516c1df7..d761d52b33 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -539,10 +539,8 @@ namespace Nif } // Record separator. Some Havok records in Oblivion do not have it. - if (hasRecordSeparators && !rec.starts_with("bhk")) - if (nif.get()) - Log(Debug::Warning) << "NIFFile Warning: Record of type " << rec << ", index " << i - << " is preceded by a non-zero separator. File: " << mFilename; + if (hasRecordSeparators && !rec.starts_with("bhk") && nif.get()) + throw Nif::Exception("Non-zero separator precedes " + rec + ", index " + std::to_string(i), mFilename); const auto entry = factories.find(rec); From c34faaf336e3f3ebe842c70fec5af7cc2580a9f1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 14:01:43 +0300 Subject: [PATCH 0185/2167] Read NiPSysUpdateCtlr --- components/nif/niffile.cpp | 7 ++++++- components/nif/record.hpp | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d761d52b33..85ec92f2fa 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -108,7 +108,6 @@ namespace Nif { "NiKeyframeController", &construct }, { "NiLookAtController", &construct }, { "NiMaterialColorController", &construct }, - { "NiParticleSystemController", &construct }, { "NiPathController", &construct }, { "NiRollController", &construct }, { "NiUVController", &construct }, @@ -309,6 +308,12 @@ namespace Nif { "NiPlanarCollider", &construct }, { "NiSphericalCollider", &construct }, + // Particle system controllers, 4.0.0.2 + { "NiParticleSystemController", &construct }, + + // Particle system controllers, Gamebryo + { "NiPSysUpdateCtlr", &construct }, + // PHYSICS // Collision objects, Gamebryo diff --git a/components/nif/record.hpp b/components/nif/record.hpp index aa0dbf9f37..b8cbbfe804 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -191,6 +191,7 @@ namespace Nif RC_NiPSysInitialRotAngleCtlr, RC_NiPSysInitialRotAngleVarCtlr, RC_NiPSysModifierActiveCtlr, + RC_NiPSysUpdateCtlr, RC_NiRollController, RC_NiSequence, RC_NiSequenceStreamHelper, From 812b0cf246aaf1f46e9e897f71d805edf5c35f45 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 14:59:14 +0300 Subject: [PATCH 0186/2167] Read NiFloatExtraDataController records --- components/nif/controller.cpp | 57 +++++++++++++++++++++++++++++++++++ components/nif/controller.hpp | 32 ++++++++++++++++++++ components/nif/niffile.cpp | 7 +++++ components/nif/record.hpp | 4 +++ 4 files changed, 100 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 73644f1541..5dcf6ef80d 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -526,6 +526,63 @@ namespace Nif mObjectPalette.post(nif); } + void NiExtraDataController::read(NIFStream* nif) + { + NiSingleInterpController::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 2, 0, 0)) + nif->read(mExtraDataName); + } + + void NiFloatExtraDataController::read(NIFStream* nif) + { + NiExtraDataController::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 104)) + return; + + // Unknown + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 0)) + { + uint8_t numExtraBytes; + nif->read(numExtraBytes); + nif->skip(7); + nif->skip(numExtraBytes); + } + + mData.read(nif); + } + + void NiFloatExtraDataController::post(Reader& nif) + { + NiExtraDataController::post(nif); + + mData.post(nif); + } + + void NiFloatsExtraDataController::read(NIFStream* nif) + { + NiExtraDataController::read(nif); + + nif->read(mFloatsExtraDataIndex); + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) + mData.read(nif); + } + + void NiFloatsExtraDataController::post(Reader& nif) + { + NiExtraDataController::post(nif); + + mData.post(nif); + } + + void NiFloatsExtraDataPoint3Controller::read(NIFStream* nif) + { + NiExtraDataController::read(nif); + + nif->read(mFloatsExtraDataIndex); + } + void NiBlendInterpolator::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 3104c29f94..31f3d19f91 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -346,6 +346,38 @@ namespace Nif void post(Reader& nif) override; }; + // Abstract + struct NiExtraDataController : NiSingleInterpController + { + std::string mExtraDataName; + + void read(NIFStream* nif) override; + }; + + struct NiFloatExtraDataController : NiExtraDataController + { + NiFloatDataPtr mData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiFloatsExtraDataController : NiExtraDataController + { + int32_t mFloatsExtraDataIndex; + NiFloatDataPtr mData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiFloatsExtraDataPoint3Controller : NiExtraDataController + { + int32_t mFloatsExtraDataIndex; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiInterpolator : public Record { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 85ec92f2fa..3609c42fe6 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -122,6 +122,13 @@ namespace Nif { "NiMultiTargetTransformController", &construct }, + // Extra data controllers, Gamebryo + { "NiColorExtraDataController", &construct }, + { "NiFloatExtraDataController", &construct }, + { "NiFloatsExtraDataController", &construct }, + { "NiFloatsExtraDataPoint3Controller", + &construct }, + // Bethesda { "BSFrustumFOVController", &construct }, { "BSKeyframeController", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index b8cbbfe804..0b7c239442 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -127,6 +127,7 @@ namespace Nif RC_NiCollisionSwitch, RC_NiColorData, RC_NiColorExtraData, + RC_NiColorExtraDataController, RC_NiColorInterpolator, RC_NiControllerManager, RC_NiControllerSequence, @@ -136,8 +137,11 @@ namespace Nif RC_NiFlipController, RC_NiFloatData, RC_NiFloatExtraData, + RC_NiFloatExtraDataController, RC_NiFloatInterpolator, RC_NiFloatsExtraData, + RC_NiFloatsExtraDataController, + RC_NiFloatsExtraDataPoint3Controller, RC_NiFltAnimationNode, RC_NiFogProperty, RC_NiGeomMorpherController, From 2d9c70053088c2337c3a56a024903f7a0a20f712 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 15:17:45 +0300 Subject: [PATCH 0187/2167] Read BSDebrisNode --- components/nif/niffile.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 3609c42fe6..0e4ef39b60 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -69,6 +69,7 @@ namespace Nif // NiNode-like nodes, Bethesda { "BSBlastNode", &construct }, { "BSDamageStage", &construct }, + { "BSDebrisNode", &construct }, { "BSFadeNode", &construct }, { "BSLeafAnimNode", &construct }, { "BSMultiBoundNode", &construct }, From e2efc9dd2fe0fec375a3655b8a7657b92a899525 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 15:42:13 +0300 Subject: [PATCH 0188/2167] Read NiLightColorController and NiPathInterpolator --- components/nif/controller.cpp | 17 +++++++++++++++++ components/nif/controller.hpp | 15 +++++++++++++++ components/nif/niffile.cpp | 3 +++ components/nif/record.hpp | 2 ++ 4 files changed, 37 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 5dcf6ef80d..a3033357ec 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -583,6 +583,23 @@ namespace Nif nif->read(mFloatsExtraDataIndex); } + void NiPathInterpolator::read(NIFStream* nif) + { + nif->read(mFlags); + nif->read(mBankDirection); + nif->read(mMaxBankAngle); + nif->read(mSmoothing); + nif->read(mFollowAxis); + mPathData.read(nif); + mPercentData.read(nif); + } + + void NiPathInterpolator::post(Reader& nif) + { + mPathData.post(nif); + mPercentData.post(nif); + } + void NiBlendInterpolator::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 31f3d19f91..8a7d306a85 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -404,6 +404,21 @@ namespace Nif using NiTransformInterpolator = TypedNiInterpolator; using NiColorInterpolator = TypedNiInterpolator; + struct NiPathInterpolator : public NiInterpolator + { + // Uses the same flags as NiPathController + uint16_t mFlags; + int32_t mBankDirection; + float mMaxBankAngle; + float mSmoothing; + uint16_t mFollowAxis; + NiPosDataPtr mPathData; + NiFloatDataPtr mPercentData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + // Abstract struct NiBlendInterpolator : public NiInterpolator { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 0e4ef39b60..bad2482432 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -108,6 +108,8 @@ namespace Nif { "NiGeomMorpherController", &construct }, { "NiKeyframeController", &construct }, { "NiLookAtController", &construct }, + // FIXME: NiLightColorController should have its own struct + { "NiLightColorController", &construct }, { "NiMaterialColorController", &construct }, { "NiPathController", &construct }, { "NiRollController", &construct }, @@ -162,6 +164,7 @@ namespace Nif { "NiBoolTimelineInterpolator", &construct }, { "NiColorInterpolator", &construct }, { "NiFloatInterpolator", &construct }, + { "NiPathInterpolator", &construct }, { "NiPoint3Interpolator", &construct }, { "NiTransformInterpolator", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0b7c239442..b26233259a 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -151,6 +151,7 @@ namespace Nif RC_NiKeyframeController, RC_NiKeyframeData, RC_NiLight, + RC_NiLightColorController, RC_NiLightDimmerController, RC_NiLines, RC_NiLinesData, @@ -169,6 +170,7 @@ namespace Nif RC_NiParticlesData, RC_NiParticleSystemController, RC_NiPathController, + RC_NiPathInterpolator, RC_NiPixelData, RC_NiPlanarCollider, RC_NiPoint3Interpolator, From 90ca8a2f4e903977d63fef944bbbcb2aa68a801f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 21 Sep 2023 19:34:13 +0200 Subject: [PATCH 0189/2167] Remove ATTRIBUTE, SKILL, and SCHOOL enums --- apps/openmw/mwlua/luabindings.cpp | 26 ------------ apps/openmw/mwlua/magicbindings.cpp | 9 ---- files/lua_api/openmw/core.lua | 64 ----------------------------- 3 files changed, 99 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index cb0dcfff97..a50459502b 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -184,32 +184,6 @@ namespace MWLua return sol::nil; }; - // deprecated, use core.stats.Skill - sol::table skills(context.mLua->sol(), sol::create); - api["SKILL"] = LuaUtil::makeStrictReadOnly(skills); - for (int i = 0; i < ESM::Skill::Length; ++i) - { - ESM::RefId skill = ESM::Skill::indexToRefId(i); - std::string id = skill.serializeText(); - std::string key = Misc::StringUtils::lowerCase(skill.getRefIdString()); - // force first character to uppercase for backwards compatability - key[0] += 'A' - 'a'; - skills[key] = id; - } - - // deprecated, use core.stats.Attribute - sol::table attributes(context.mLua->sol(), sol::create); - api["ATTRIBUTE"] = LuaUtil::makeStrictReadOnly(attributes); - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - ESM::RefId attribute = ESM::Attribute::indexToRefId(i); - std::string id = attribute.serializeText(); - std::string key = Misc::StringUtils::lowerCase(attribute.getRefIdString()); - // force first character to uppercase for backwards compatability - key[0] += 'A' - 'a'; - attributes[key] = id; - } - return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index c0596a9478..0e99a57c97 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -203,15 +203,6 @@ namespace MWLua { "Touch", ESM::RT_Touch }, { "Target", ESM::RT_Target }, })); - // deprecated, use core.stats.Skill - magicApi["SCHOOL"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ - { "Alteration", ESM::RefId(ESM::Skill::Alteration).serializeText() }, - { "Conjuration", ESM::RefId(ESM::Skill::Conjuration).serializeText() }, - { "Destruction", ESM::RefId(ESM::Skill::Destruction).serializeText() }, - { "Illusion", ESM::RefId(ESM::Skill::Illusion).serializeText() }, - { "Mysticism", ESM::RefId(ESM::Skill::Mysticism).serializeText() }, - { "Restoration", ESM::RefId(ESM::Skill::Restoration).serializeText() }, - })); magicApi["SPELL_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "Spell", ESM::Spell::ST_Spell }, diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index efa88a0cbd..601e8ea27c 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -433,57 +433,6 @@ -- @usage for _, item in ipairs(inventory:findAll('common_shirt_01')) do ... end ---- Possible @{#ATTRIBUTE} values (DEPRECATED use @{#Attribute}) --- @field [parent=#core] #ATTRIBUTE ATTRIBUTE - ---- DEPRECATED, use @{#Attribute} ---- `core.ATTRIBUTE` --- @type ATTRIBUTE --- @field #string Strength "strength" --- @field #string Intelligence "intelligence" --- @field #string Willpower "willpower" --- @field #string Agility "agility" --- @field #string Speed "speed" --- @field #string Endurance "endurance" --- @field #string Personality "personality" --- @field #string Luck "luck" - - ---- Possible @{#SKILL} values (DEPRECATED use @{#Skill}) --- @field [parent=#core] #SKILL SKILL - ---- DEPRECATED, use @{#Skill} ---- `core.SKILL` --- @type SKILL --- @field #string Acrobatics "acrobatics" --- @field #string Alchemy "alchemy" --- @field #string Alteration "alteration" --- @field #string Armorer "armorer" --- @field #string Athletics "athletics" --- @field #string Axe "axe" --- @field #string Block "block" --- @field #string BluntWeapon "bluntweapon" --- @field #string Conjuration "conjuration" --- @field #string Destruction "destruction" --- @field #string Enchant "enchant" --- @field #string HandToHand "handtohand" --- @field #string HeavyArmor "heavyarmor" --- @field #string Illusion "illusion" --- @field #string LightArmor "lightarmor" --- @field #string LongBlade "longblade" --- @field #string Marksman "marksman" --- @field #string MediumArmor "mediumarmor" --- @field #string Mercantile "mercantile" --- @field #string Mysticism "mysticism" --- @field #string Restoration "restoration" --- @field #string Security "security" --- @field #string ShortBlade "shortblade" --- @field #string Sneak "sneak" --- @field #string Spear "spear" --- @field #string Speechcraft "speechcraft" --- @field #string Unarmored "unarmored" - - --- @{#Magic}: spells and spell effects -- @field [parent=#core] #Magic magic @@ -498,19 +447,6 @@ -- @field #number Target Ranged spell ---- Possible @{#MagicSchool} values (DEPRECATED use @{#Skill}) --- @field [parent=#Magic] #MagicSchool SCHOOL - ---- `core.magic.SCHOOL` --- @type MagicSchool --- @field #string Alteration alteration --- @field #string Conjuration conjuration --- @field #string Destruction destruction --- @field #string Illusion illusion --- @field #string Mysticism mysticism --- @field #string Restoration restoration - - --- Possible @{#MagicEffectId} values -- @field [parent=#Magic] #MagicEffectId EFFECT_TYPE From 6204a83a2b238cae3ee538b064e70291b81e5465 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Sep 2023 22:59:55 +0300 Subject: [PATCH 0190/2167] Read bhkBallAndSocketConstraint and bhkStiffSpringConstraint --- components/nif/niffile.cpp | 2 ++ components/nif/physics.cpp | 27 +++++++++++++++++++++++++++ components/nif/physics.hpp | 29 +++++++++++++++++++++++++++++ components/nif/record.hpp | 2 ++ 4 files changed, 60 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index bad2482432..8f0925103a 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -338,9 +338,11 @@ namespace Nif { "bhkBlendCollisionObject", &construct }, // Constraint records, Bethesda + { "bhkBallAndSocketConstraint", &construct }, { "bhkHingeConstraint", &construct }, { "bhkLimitedHingeConstraint", &construct }, { "bhkRagdollConstraint", &construct }, + { "bhkStiffSpringConstraint", &construct }, // Physics body records, Bethesda { "bhkRigidBody", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index a359f86066..f97b4b6169 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -332,6 +332,19 @@ namespace Nif mMotor.read(nif); } + void bhkBallAndSocketConstraintCInfo::read(NIFStream* nif) + { + nif->read(mPivotA); + nif->read(mPivotB); + } + + void bhkStiffSpringConstraintCInfo::read(NIFStream* nif) + { + nif->read(mPivotA); + nif->read(mPivotB); + nif->read(mLength); + } + /// Record types void bhkCollisionObject::read(NIFStream* nif) @@ -719,4 +732,18 @@ namespace Nif mConstraint.read(nif); } + void bhkBallAndSocketConstraint::read(NIFStream* nif) + { + bhkConstraint::read(nif); + + mConstraint.read(nif); + } + + void bhkStiffSpringConstraint::read(NIFStream* nif) + { + bhkConstraint::read(nif); + + mConstraint.read(nif); + } + } // Namespace diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 91d55c1f80..d9656510c3 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -348,6 +348,21 @@ namespace Nif void read(NIFStream* nif); }; + struct bhkBallAndSocketConstraintCInfo + { + osg::Vec4f mPivotA, mPivotB; + + void read(NIFStream* nif); + }; + + struct bhkStiffSpringConstraintCInfo + { + osg::Vec4f mPivotA, mPivotB; + float mLength; + + void read(NIFStream* nif); + }; + /// Record types // Abstract Bethesda Havok object @@ -684,5 +699,19 @@ namespace Nif void read(NIFStream* nif) override; }; + struct bhkBallAndSocketConstraint : bhkConstraint + { + bhkBallAndSocketConstraintCInfo mConstraint; + + void read(NIFStream* nif) override; + }; + + struct bhkStiffSpringConstraint : bhkConstraint + { + bhkStiffSpringConstraintCInfo mConstraint; + + void read(NIFStream* nif) override; + }; + } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index b26233259a..d399660432 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,7 @@ namespace Nif { RC_MISSING = 0, RC_AvoidNode, + RC_bhkBallAndSocketConstraint, RC_bhkBlendCollisionObject, RC_bhkBlendController, RC_bhkBoxShape, @@ -61,6 +62,7 @@ namespace Nif RC_bhkRigidBodyT, RC_bhkSimpleShapePhantom, RC_bhkSphereShape, + RC_bhkStiffSpringConstraint, RC_BSBehaviorGraphExtraData, RC_BSBound, RC_BSBoneLODExtraData, From f507e17807d1b7e9f1546a99e10be10a4a18dd44 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 00:18:35 +0300 Subject: [PATCH 0191/2167] Read BSWaterShaderProperty and BSSkyShaderProperty --- components/nif/niffile.cpp | 2 ++ components/nif/property.cpp | 17 +++++++++++- components/nif/property.hpp | 55 +++++++++++++++++++++++++++++-------- components/nif/record.hpp | 2 ++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 8f0925103a..8dbf65d070 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -393,6 +393,8 @@ namespace Nif { "BSDistantTreeShaderProperty", &construct }, { "BSLightingShaderProperty", &construct }, { "BSEffectShaderProperty", &construct }, + { "BSSkyShaderProperty", &construct }, + { "BSWaterShaderProperty", &construct }, { "DistantLODShaderProperty", &construct }, { "HairShaderProperty", &construct }, { "Lighting30ShaderProperty", &construct }, diff --git a/components/nif/property.cpp b/components/nif/property.cpp index fa6fa1b4cf..bcc70540c8 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -232,7 +232,7 @@ namespace Nif BSShaderLightingProperty::read(nif); mFilename = nif->getSizedString(); - mSkyObjectType = static_cast(nif->get()); + mSkyObjectType = static_cast(nif->get()); } void TallGrassShaderProperty::read(NIFStream* nif) @@ -455,6 +455,21 @@ namespace Nif } } + void BSSkyShaderProperty::read(NIFStream* nif) + { + BSShaderProperty::read(nif); + + mFilename = nif->getSizedString(); + mSkyObjectType = static_cast(nif->get()); + } + + void BSWaterShaderProperty::read(NIFStream* nif) + { + BSShaderProperty::read(nif); + + nif->read(mFlags); + } + void NiAlphaProperty::read(NIFStream* nif) { NiProperty::read(nif); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 37d17989b6..0d4a5b33c6 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -171,20 +171,20 @@ namespace Nif void read(NIFStream* nif) override; }; + enum class SkyObjectType : uint32_t + { + SkyTexture = 0, + SkySunglare = 1, + Sky = 2, + SkyClouds = 3, + SkyStars = 5, + SkyMoonStarsMask = 7, + }; + struct SkyShaderProperty : BSShaderLightingProperty { - enum class ObjectType : uint32_t - { - SkyTexture = 0, - SkySunglare = 1, - Sky = 2, - SkyClouds = 3, - SkyStars = 5, - SkyMoonStarsMask = 7, - }; - std::string mFilename; - ObjectType mSkyObjectType; + SkyObjectType mSkyObjectType; void read(NIFStream* nif) override; }; @@ -358,6 +358,39 @@ namespace Nif bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; + struct BSSkyShaderProperty : BSShaderProperty + { + std::string mFilename; + SkyObjectType mSkyObjectType; + + void read(NIFStream* nif) override; + }; + + struct BSWaterShaderProperty : BSShaderProperty + { + enum Flags + { + Flag_Displacement = 0x0001, + Flag_LOD = 0x0002, + Flag_Depth = 0x0004, + Flag_ActorInWater = 0x0008, + Flag_ActorInWaterIsMoving = 0x0010, + Flag_Underwater = 0x0020, + Flag_Reflections = 0x0040, + Flag_Refractions = 0x0080, + Flag_VertexUV = 0x0100, + Flag_VertexAlphaDepth = 0x0200, + Flag_Procedural = 0x0400, + Flag_Fog = 0x0800, + Flag_UpdateConstants = 0x1000, + Flag_CubeMap = 0x2000, + }; + + uint32_t mFlags; + + void read(NIFStream* nif) override; + }; + struct NiAlphaProperty : NiProperty { enum Flags diff --git a/components/nif/record.hpp b/components/nif/record.hpp index d399660432..fd780e52eb 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -100,8 +100,10 @@ namespace Nif RC_BSShaderPPLightingProperty, RC_BSShaderProperty, RC_BSShaderTextureSet, + RC_BSSkyShaderProperty, RC_BSTriShape, RC_BSWArray, + RC_BSWaterShaderProperty, RC_BSXFlags, RC_DistantLODShaderProperty, RC_HairShaderProperty, From c800152ca25c2e6abed79f87f7f158b54b9a6781 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 01:00:32 +0300 Subject: [PATCH 0192/2167] Diminish error marker prominence Don't render ESM4 actors or SpeedTree objects --- apps/openmw/mwclass/esm4base.hpp | 7 ++----- components/resource/scenemanager.cpp | 5 +++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 3d2184e5fe..c59e8e1dc2 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -150,11 +150,8 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr& ptr) const override { - // TODO: Not clear where to get something renderable: - // ESM4::Npc::mModel is usually an empty string - // ESM4::Race::mModelMale is only a skeleton - // For now show error marker as a dummy model. - return "meshes/marker_error.nif"; + // TODO: Implement actor rendering. This function will typically return the skeleton. + return {}; } }; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index dde39c5d65..66fee5256d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -726,6 +726,11 @@ namespace Resource auto ext = Misc::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); + else if (ext == "spt") + { + Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename; + return new osg::Node(); + } else return loadNonNif(normalizedFilename, *vfs->get(normalizedFilename), imageManager); } From a8946e06f61814de0e3bbf986acf45dd7c5340e7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 01:52:30 +0300 Subject: [PATCH 0193/2167] Read NiParticleSystem and its twins Looks like NiPSysData is loading correctly --- components/nif/niffile.cpp | 5 ++++ components/nif/particle.cpp | 53 ++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 43 +++++++++++++++++++++++++++++ components/nif/record.hpp | 1 + components/nif/recordptr.hpp | 4 +++ 5 files changed, 106 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 8dbf65d070..f3a03b097f 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -278,8 +278,13 @@ namespace Nif { "NiRotatingParticlesData", &construct }, // Geometry, Gamebryo + { "NiParticleSystem", &construct }, + { "NiMeshParticleSystem", &construct }, { "NiPSysData", &construct }, + // Geometry, Bethesda + { "BSStripParticleSystem", &construct }, + // Modifiers, 4.0.0.2 { "NiGravity", &construct }, { "NiParticleColorModifier", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 389449ed77..8dee873c0b 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -150,6 +150,46 @@ namespace Nif nif->readVector(mRotations, mNumVertices); } + void NiParticleSystem::read(NIFStream* nif) + { + // Weird loading to account for inheritance differences starting from SSE + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_SSE) + NiParticles::read(nif); + else + { + NiAVObject::read(nif); + + nif->read(mBoundingSphere); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->readArray(mBoundMinMax); + + mSkin.read(nif); + mShaderProperty.read(nif); + mAlphaProperty.read(nif); + mVertDesc.read(nif); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SKY) + { + nif->readArray(mNearFar); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE) + mData.read(nif); + } + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + { + nif->read(mWorldSpace); + readRecordList(nif, mModifiers); + } + } + + void NiParticleSystem::post(Reader& nif) + { + NiParticles::post(nif); + + postRecordList(nif, mModifiers); + } + void NiPSysData::read(NIFStream* nif) { NiParticlesData::read(nif); @@ -175,6 +215,19 @@ namespace Nif } } + void NiPSysModifier::read(NIFStream* nif) + { + nif->read(mName); + mOrder = static_cast(nif->get()); + mTarget.read(nif); + nif->read(mActive); + } + + void NiPSysModifier::post(Reader& nif) + { + mTarget.post(nif); + } + void NiPSysModifierCtlr::read(NIFStream* nif) { NiSingleInterpController::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index ffdb307bab..f800ddefb7 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -4,6 +4,7 @@ #include "base.hpp" #include "controller.hpp" #include "data.hpp" +#include "node.hpp" namespace Nif { @@ -114,6 +115,19 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiParticleSystem : NiParticles + { + osg::BoundingSpheref mBoundingSphere; + std::array mBoundMinMax; + BSVertexDesc mVertDesc; + std::array mNearFar; + bool mWorldSpace{ true }; + NiPSysModifierList mModifiers; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysData : NiParticlesData { std::vector mParticles; @@ -124,6 +138,35 @@ namespace Nif void read(NIFStream* nif) override; }; + // Abstract + struct NiPSysModifier : Record + { + enum class NiPSysModifierOrder : uint32_t + { + KillOldParticles = 0, + BSLOD = 1, + Emitter = 1000, + Spawn = 2000, + BSStripUpdateFO3 = 2500, + General = 3000, + Force = 4000, + Collider = 5000, + PosUpdate = 6000, + PostPosUpdate = 6500, + WorldshiftPartspawn = 6600, + BoundUpdate = 7000, + BSStripUpdateSK = 8000, + }; + + std::string mName; + NiPSysModifierOrder mOrder; + NiParticleSystemPtr mTarget; + bool mActive; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + // Abstract struct NiPSysModifierCtlr : NiSingleInterpController { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index fd780e52eb..a4598ebe3c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -172,6 +172,7 @@ namespace Nif RC_NiParticleRotation, RC_NiParticles, RC_NiParticlesData, + RC_NiParticleSystem, RC_NiParticleSystemController, RC_NiPathController, RC_NiPathInterpolator, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 53b9c02a95..2355879ac6 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -129,7 +129,9 @@ namespace Nif struct NiSourceTexture; struct NiPalette; struct NiParticleModifier; + struct NiParticleSystem; struct NiPSysEmitterCtlrData; + struct NiPSysModifier; struct NiBoolData; struct NiSkinPartition; struct BSShaderTextureSet; @@ -171,6 +173,7 @@ namespace Nif using NiSourceTexturePtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; + using NiParticleSystemPtr = RecordPtrT; using NiPSysEmitterCtlrDataPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; @@ -204,6 +207,7 @@ namespace Nif using bhkSerializableList = RecordListT; using bhkEntityList = RecordListT; using NiControllerSequenceList = RecordListT; + using NiPSysModifierList = RecordListT; } // Namespace #endif From eaa82ba5c6f677ce1bcfebeb0e13ba9c0ed2c283 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 02:19:32 +0300 Subject: [PATCH 0194/2167] Read BSStripPSysData --- components/nif/data.cpp | 14 +++++++++++++- components/nif/niffile.cpp | 3 ++- components/nif/particle.cpp | 10 ++++++++++ components/nif/particle.hpp | 10 ++++++++++ components/nif/record.hpp | 2 ++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 34ec3b2831..9aa61b4db7 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -14,7 +14,19 @@ namespace Nif nif->read(mGroupId); nif->read(mNumVertices); - bool hasData = recType != RC_NiPSysData || nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO3; + + bool isPSysData = false; + switch (recType) + { + case RC_NiPSysData: + // case RC_NiMeshPSysData: + case RC_BSStripPSysData: + isPSysData = true; + break; + default: + break; + } + bool hasData = !isPSysData || nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO3; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f3a03b097f..196fe780cc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -283,7 +283,8 @@ namespace Nif { "NiPSysData", &construct }, // Geometry, Bethesda - { "BSStripParticleSystem", &construct }, + { "BSStripParticleSystem", &construct }, + { "BSStripPSysData", &construct }, // Modifiers, 4.0.0.2 { "NiGravity", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 8dee873c0b..bd3b4dcddb 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -215,6 +215,16 @@ namespace Nif } } + void BSStripPSysData::read(NIFStream* nif) + { + NiPSysData::read(nif); + + nif->read(mMaxPointCount); + nif->read(mStartCapSize); + nif->read(mEndCapSize); + nif->read(mDoZPrepass); + } + void NiPSysModifier::read(NIFStream* nif) { nif->read(mName); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index f800ddefb7..4428347105 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -138,6 +138,16 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSStripPSysData : NiPSysData + { + uint16_t mMaxPointCount; + float mStartCapSize; + float mEndCapSize; + bool mDoZPrepass; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysModifier : Record { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index a4598ebe3c..2073723ece 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -94,6 +94,8 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSStripParticleSystem, + RC_BSStripPSysData, RC_BSRefractionFirePeriodController, RC_BSRefractionStrengthController, RC_BSShaderNoLightingProperty, From b45923ac39f5f921039365c36b35ff249aadfb60 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 02:54:03 +0300 Subject: [PATCH 0195/2167] Read NiPSysSpawnModifier, NiPSysAgeDeathModifier --- components/nif/niffile.cpp | 4 ++++ components/nif/particle.cpp | 29 +++++++++++++++++++++++++++++ components/nif/particle.hpp | 23 +++++++++++++++++++++++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 2 ++ 5 files changed, 60 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 196fe780cc..a89540ac15 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -292,6 +292,10 @@ namespace Nif { "NiParticleGrowFade", &construct }, { "NiParticleRotation", &construct }, + // Modifiers, Gamebryo + { "NiPSysAgeDeathModifier", &construct }, + { "NiPSysSpawnModifier", &construct }, + // Modifier controllers, Gamebryo { "NiPSysAirFieldAirFrictionCtlr", &construct }, { "NiPSysAirFieldInheritVelocityCtlr", diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index bd3b4dcddb..04da656e9d 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -238,6 +238,35 @@ namespace Nif mTarget.post(nif); } + void NiPSysAgeDeathModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mSpawnOnDeath); + mSpawnModifier.read(nif); + } + + void NiPSysAgeDeathModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mSpawnModifier.post(nif); + } + + void NiPSysSpawnModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mNumSpawnGenerations); + nif->read(mPercentageSpawned); + nif->read(mMinNumToSpawn); + nif->read(mMaxNumToSpawn); + nif->read(mSpawnSpeedVariation); + nif->read(mSpawnDirVariation); + nif->read(mLifespan); + nif->read(mLifespanVariation); + } + void NiPSysModifierCtlr::read(NIFStream* nif) { NiSingleInterpController::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 4428347105..b556b0b285 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -177,6 +177,29 @@ namespace Nif void post(Reader& nif) override; }; + struct NiPSysAgeDeathModifier : NiPSysModifier + { + bool mSpawnOnDeath; + NiPSysSpawnModifierPtr mSpawnModifier; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysSpawnModifier : NiPSysModifier + { + uint16_t mNumSpawnGenerations; + float mPercentageSpawned; + uint16_t mMinNumToSpawn; + uint16_t mMaxNumToSpawn; + float mSpawnSpeedVariation; + float mSpawnDirVariation; + float mLifespan; + float mLifespanVariation; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysModifierCtlr : NiSingleInterpController { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 2073723ece..78bbb0f062 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -182,6 +182,7 @@ namespace Nif RC_NiPlanarCollider, RC_NiPoint3Interpolator, RC_NiPosData, + RC_NiPSysAgeDeathModifier, RC_NiPSysAirFieldAirFrictionCtlr, RC_NiPSysAirFieldInheritVelocityCtlr, RC_NiPSysAirFieldSpreadCtlr, @@ -204,6 +205,7 @@ namespace Nif RC_NiPSysInitialRotAngleCtlr, RC_NiPSysInitialRotAngleVarCtlr, RC_NiPSysModifierActiveCtlr, + RC_NiPSysSpawnModifier, RC_NiPSysUpdateCtlr, RC_NiRollController, RC_NiSequence, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 2355879ac6..6223740e31 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -132,6 +132,7 @@ namespace Nif struct NiParticleSystem; struct NiPSysEmitterCtlrData; struct NiPSysModifier; + struct NiPSysSpawnModifier; struct NiBoolData; struct NiSkinPartition; struct BSShaderTextureSet; @@ -175,6 +176,7 @@ namespace Nif using NiParticleModifierPtr = RecordPtrT; using NiParticleSystemPtr = RecordPtrT; using NiPSysEmitterCtlrDataPtr = RecordPtrT; + using NiPSysSpawnModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; From 717b93d61e9b2d7820c7fe3f86c2366cc6301657 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 03:04:36 +0300 Subject: [PATCH 0196/2167] Read BSPSysLODModifier --- components/nif/niffile.cpp | 3 +++ components/nif/particle.cpp | 10 ++++++++++ components/nif/particle.hpp | 10 ++++++++++ components/nif/record.hpp | 1 + 4 files changed, 24 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index a89540ac15..b3cc294d6e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -296,6 +296,9 @@ namespace Nif { "NiPSysAgeDeathModifier", &construct }, { "NiPSysSpawnModifier", &construct }, + // Modifiers, Bethesda + { "BSPSysLODModifier", &construct }, + // Modifier controllers, Gamebryo { "NiPSysAirFieldAirFrictionCtlr", &construct }, { "NiPSysAirFieldInheritVelocityCtlr", diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 04da656e9d..26a22394d8 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -267,6 +267,16 @@ namespace Nif nif->read(mLifespanVariation); } + void BSPSysLODModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mLODStartDistance); + nif->read(mLODEndDistance); + nif->read(mEndEmitScale); + nif->read(mEndSize); + } + void NiPSysModifierCtlr::read(NIFStream* nif) { NiSingleInterpController::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index b556b0b285..a743834d98 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -200,6 +200,16 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysLODModifier : NiPSysModifier + { + float mLODStartDistance; + float mLODEndDistance; + float mEndEmitScale; + float mEndSize; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysModifierCtlr : NiSingleInterpController { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 78bbb0f062..70cb67e749 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -94,6 +94,7 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSPSysLODModifier, RC_BSStripParticleSystem, RC_BSStripPSysData, RC_BSRefractionFirePeriodController, From c6d0df432f9e677a49917bb7a50649f144f92b1c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 03:26:01 +0300 Subject: [PATCH 0197/2167] Read a few Gamebryo particle emitters, NiPSysResetOnLoopCtlr, NiPSysPositionModifier --- components/nif/niffile.cpp | 7 +++++ components/nif/particle.cpp | 57 +++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 51 +++++++++++++++++++++++++++++++++ components/nif/record.hpp | 5 ++++ 4 files changed, 120 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b3cc294d6e..4f0ce46865 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -294,11 +294,17 @@ namespace Nif // Modifiers, Gamebryo { "NiPSysAgeDeathModifier", &construct }, + { "NiPSysPositionModifier", &construct }, { "NiPSysSpawnModifier", &construct }, // Modifiers, Bethesda { "BSPSysLODModifier", &construct }, + // Emitters + { "NiPSysBoxEmitter", &construct }, + { "NiPSysCylinderEmitter", &construct }, + { "NiPSysSphereEmitter", &construct }, + // Modifier controllers, Gamebryo { "NiPSysAirFieldAirFrictionCtlr", &construct }, { "NiPSysAirFieldInheritVelocityCtlr", @@ -336,6 +342,7 @@ namespace Nif { "NiParticleSystemController", &construct }, // Particle system controllers, Gamebryo + { "NiPSysResetOnLoopCtlr", &construct }, { "NiPSysUpdateCtlr", &construct }, // PHYSICS diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 26a22394d8..fa386e2b73 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -277,6 +277,63 @@ namespace Nif nif->read(mEndSize); } + void NiPSysEmitter::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mSpeed); + nif->read(mSpeedVariation); + nif->read(mDeclination); + nif->read(mDeclinationVariation); + nif->read(mPlanarAngle); + nif->read(mPlanarAngleVariation); + nif->read(mInitialColor); + nif->read(mInitialRadius); + if (nif->getVersion() >= NIFStream::generateVersion(10, 4, 0, 1)) + nif->read(mRadiusVariation); + nif->read(mLifespan); + nif->read(mLifespanVariation); + } + + void NiPSysVolumeEmitter::read(NIFStream* nif) + { + NiPSysEmitter::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + mEmitterObject.read(nif); + } + + void NiPSysVolumeEmitter::post(Reader& nif) + { + NiPSysEmitter::post(nif); + + mEmitterObject.post(nif); + } + + void NiPSysBoxEmitter::read(NIFStream* nif) + { + NiPSysVolumeEmitter::read(nif); + + nif->read(mWidth); + nif->read(mHeight); + nif->read(mDepth); + } + + void NiPSysCylinderEmitter::read(NIFStream* nif) + { + NiPSysVolumeEmitter::read(nif); + + nif->read(mRadius); + nif->read(mHeight); + } + + void NiPSysSphereEmitter::read(NIFStream* nif) + { + NiPSysVolumeEmitter::read(nif); + + nif->read(mRadius); + } + void NiPSysModifierCtlr::read(NIFStream* nif) { NiSingleInterpController::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index a743834d98..55ed84b5ae 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -210,6 +210,57 @@ namespace Nif void read(NIFStream* nif) override; }; + // Abstract + struct NiPSysEmitter : public NiPSysModifier + { + float mSpeed; + float mSpeedVariation; + float mDeclination; + float mDeclinationVariation; + float mPlanarAngle; + float mPlanarAngleVariation; + osg::Vec4f mInitialColor; + float mInitialRadius; + float mRadiusVariation; + float mLifespan; + float mLifespanVariation; + + void read(NIFStream* nif) override; + }; + + // Abstract + struct NiPSysVolumeEmitter : public NiPSysEmitter + { + NiAVObjectPtr mEmitterObject; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysBoxEmitter : public NiPSysVolumeEmitter + { + float mWidth; + float mHeight; + float mDepth; + + void read(NIFStream* nif) override; + }; + + struct NiPSysCylinderEmitter : public NiPSysVolumeEmitter + { + float mRadius; + float mHeight; + + void read(NIFStream* nif) override; + }; + + struct NiPSysSphereEmitter : public NiPSysVolumeEmitter + { + float mRadius; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysModifierCtlr : NiSingleInterpController { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 70cb67e749..e519a0bcc3 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -187,6 +187,8 @@ namespace Nif RC_NiPSysAirFieldAirFrictionCtlr, RC_NiPSysAirFieldInheritVelocityCtlr, RC_NiPSysAirFieldSpreadCtlr, + RC_NiPSysBoxEmitter, + RC_NiPSysCylinderEmitter, RC_NiPSysData, RC_NiPSysEmitterCtlr, RC_NiPSysEmitterCtlrData, @@ -206,7 +208,10 @@ namespace Nif RC_NiPSysInitialRotAngleCtlr, RC_NiPSysInitialRotAngleVarCtlr, RC_NiPSysModifierActiveCtlr, + RC_NiPSysPositionModifier, + RC_NiPSysResetOnLoopCtlr, RC_NiPSysSpawnModifier, + RC_NiPSysSphereEmitter, RC_NiPSysUpdateCtlr, RC_NiRollController, RC_NiSequence, From 8594875ccb85c309750d45c96877fec13649c33d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 04:11:32 +0300 Subject: [PATCH 0198/2167] Read a few more Gamebryo/Bethesda modifiers --- components/nif/niffile.cpp | 5 ++++ components/nif/particle.cpp | 46 +++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 38 ++++++++++++++++++++++++++++++ components/nif/record.hpp | 4 ++++ 4 files changed, 93 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 4f0ce46865..b60bb74594 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -294,11 +294,16 @@ namespace Nif // Modifiers, Gamebryo { "NiPSysAgeDeathModifier", &construct }, + { "NiPSysBoundUpdateModifier", &construct }, { "NiPSysPositionModifier", &construct }, { "NiPSysSpawnModifier", &construct }, // Modifiers, Bethesda + { "BSPSysInheritVelocityModifier", + &construct }, { "BSPSysLODModifier", &construct }, + { "BSPSysScaleModifier", &construct }, + { "BSPSysSimpleColorModifier", &construct }, // Emitters { "NiPSysBoxEmitter", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index fa386e2b73..f6e4ae9bc8 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -253,6 +253,13 @@ namespace Nif mSpawnModifier.post(nif); } + void NiPSysBoundUpdateModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mUpdateSkip); + } + void NiPSysSpawnModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -267,6 +274,23 @@ namespace Nif nif->read(mLifespanVariation); } + void BSPSysInheritVelocityModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mInheritObject.read(nif); + nif->read(mInheritChance); + nif->read(mVelocityMult); + nif->read(mVelcoityVariation); + } + + void BSPSysInheritVelocityModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mInheritObject.post(nif); + } + void BSPSysLODModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -277,6 +301,28 @@ namespace Nif nif->read(mEndSize); } + void BSPSysScaleModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->readVector(mScales, nif->get()); + } + + void BSPSysSimpleColorModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mFadeInPercent); + nif->read(mFadeOutPercent); + nif->read(mColor1EndPercent); + nif->read(mColor1StartPercent); + nif->read(mColor2EndPercent); + nif->read(mColor2StartPercent); + nif->readVector(mColors, 3); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->skip(52); // Unknown + } + void NiPSysEmitter::read(NIFStream* nif) { NiPSysModifier::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 55ed84b5ae..e887009a13 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -186,6 +186,13 @@ namespace Nif void post(Reader& nif) override; }; + struct NiPSysBoundUpdateModifier : public NiPSysModifier + { + uint16_t mUpdateSkip; + + void read(NIFStream* nif) override; + }; + struct NiPSysSpawnModifier : NiPSysModifier { uint16_t mNumSpawnGenerations; @@ -200,6 +207,17 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysInheritVelocityModifier : public NiPSysModifier + { + NiObjectNETPtr mInheritObject; + float mInheritChance; + float mVelocityMult; + float mVelcoityVariation; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct BSPSysLODModifier : NiPSysModifier { float mLODStartDistance; @@ -210,6 +228,26 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysScaleModifier : public NiPSysModifier + { + std::vector mScales; + + void read(NIFStream* nif) override; + }; + + struct BSPSysSimpleColorModifier : NiPSysModifier + { + float mFadeInPercent; + float mFadeOutPercent; + float mColor1EndPercent; + float mColor1StartPercent; + float mColor2EndPercent; + float mColor2StartPercent; + std::vector mColors; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysEmitter : public NiPSysModifier { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index e519a0bcc3..770246aa08 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -94,7 +94,10 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, + RC_BSPSysScaleModifier, + RC_BSPSysSimpleColorModifier, RC_BSStripParticleSystem, RC_BSStripPSysData, RC_BSRefractionFirePeriodController, @@ -188,6 +191,7 @@ namespace Nif RC_NiPSysAirFieldInheritVelocityCtlr, RC_NiPSysAirFieldSpreadCtlr, RC_NiPSysBoxEmitter, + RC_NiPSysBoundUpdateModifier, RC_NiPSysCylinderEmitter, RC_NiPSysData, RC_NiPSysEmitterCtlr, From 120223d8dff7c9547c582595aa3fda416c014c63 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 04:42:46 +0300 Subject: [PATCH 0199/2167] Read even more Gamebryo/Bethesda particle records --- components/nif/niffile.cpp | 4 ++ components/nif/particle.cpp | 81 ++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 65 ++++++++++++++++++++++++--- components/nif/record.hpp | 4 ++ components/nifosg/particle.cpp | 6 +-- components/nifosg/particle.hpp | 2 +- 6 files changed, 151 insertions(+), 11 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b60bb74594..6d29972f63 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -295,7 +295,10 @@ namespace Nif // Modifiers, Gamebryo { "NiPSysAgeDeathModifier", &construct }, { "NiPSysBoundUpdateModifier", &construct }, + { "NiPSysDragModifier", &construct }, + { "NiPSysGravityModifier", &construct }, { "NiPSysPositionModifier", &construct }, + { "NiPSysRotationModifier", &construct }, { "NiPSysSpawnModifier", &construct }, // Modifiers, Bethesda @@ -308,6 +311,7 @@ namespace Nif // Emitters { "NiPSysBoxEmitter", &construct }, { "NiPSysCylinderEmitter", &construct }, + { "NiPSysMeshEmitter", &construct }, { "NiPSysSphereEmitter", &construct }, // Modifier controllers, Gamebryo diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index f6e4ae9bc8..42ce8b57f9 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -260,6 +260,69 @@ namespace Nif nif->read(mUpdateSkip); } + void NiPSysDragModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mDragObject.read(nif); + nif->read(mDragAxis); + nif->read(mPercentage); + nif->read(mRange); + nif->read(mRangeFalloff); + } + + void NiPSysDragModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mDragObject.post(nif); + } + + void NiPSysGravityModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mGravityObject.read(nif); + nif->read(mGravityAxis); + nif->read(mDecay); + nif->read(mStrength); + mForceType = static_cast(nif->get()); + nif->read(mTurbulence); + nif->read(mTurbulenceScale); + + if (nif->getBethVersion() >= 17) + nif->read(mWorldAligned); + } + + void NiPSysGravityModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mGravityObject.post(nif); + } + + void NiPSysRotationModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mRotationSpeed); + + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2)) + { + nif->read(mRotationSpeedVariation); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + nif->skip(5); // Unknown + + nif->read(mRotationAngle); + nif->read(mRotationAngleVariation); + nif->read(mRandomRotSpeedSign); + } + + nif->read(mRandomAxis); + nif->read(mAxis); + } + void NiPSysSpawnModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -373,6 +436,24 @@ namespace Nif nif->read(mHeight); } + void NiPSysMeshEmitter::read(NIFStream* nif) + { + NiPSysEmitter::read(nif); + + readRecordList(nif, mEmitterMeshes); + + nif->read(mInitialVelocityType); + nif->read(mEmissionType); + nif->read(mEmissionAxis); + } + + void NiPSysMeshEmitter::post(Reader& nif) + { + NiPSysEmitter::post(nif); + + postRecordList(nif, mEmitterMeshes); + } + void NiPSysSphereEmitter::read(NIFStream* nif) { NiPSysVolumeEmitter::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index e887009a13..0c9379f0f9 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -34,14 +34,14 @@ namespace Nif void post(Reader& nif) override; }; + enum class ForceType : uint32_t + { + Wind = 0, // Fixed direction + Point = 1, // Fixed origin + }; + struct NiGravity : public NiParticleModifier { - enum class ForceType : uint32_t - { - Wind = 0, // Fixed direction - Point = 1, // Fixed origin - }; - float mDecay{ 0.f }; float mForce; ForceType mType; @@ -193,6 +193,46 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPSysDragModifier : public NiPSysModifier + { + NiAVObjectPtr mDragObject; + osg::Vec3f mDragAxis; + float mPercentage; + float mRange; + float mRangeFalloff; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysGravityModifier : public NiPSysModifier + { + NiAVObjectPtr mGravityObject; + osg::Vec3f mGravityAxis; + float mDecay; + float mStrength; + ForceType mForceType; + float mTurbulence; + float mTurbulenceScale; + bool mWorldAligned; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysRotationModifier : public NiPSysModifier + { + float mRotationSpeed; + float mRotationSpeedVariation; + float mRotationAngle; + float mRotationAngleVariation; + bool mRandomRotSpeedSign; + bool mRandomAxis; + osg::Vec3f mAxis; + + void read(NIFStream* nif) override; + }; + struct NiPSysSpawnModifier : NiPSysModifier { uint16_t mNumSpawnGenerations; @@ -209,7 +249,7 @@ namespace Nif struct BSPSysInheritVelocityModifier : public NiPSysModifier { - NiObjectNETPtr mInheritObject; + NiAVObjectPtr mInheritObject; float mInheritChance; float mVelocityMult; float mVelcoityVariation; @@ -292,6 +332,17 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPSysMeshEmitter : public NiPSysEmitter + { + NiAVObjectList mEmitterMeshes; + uint32_t mInitialVelocityType; + uint32_t mEmissionType; + osg::Vec3f mEmissionAxis; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysSphereEmitter : public NiPSysVolumeEmitter { float mRadius; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 770246aa08..3d5ec01bc3 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -194,6 +194,7 @@ namespace Nif RC_NiPSysBoundUpdateModifier, RC_NiPSysCylinderEmitter, RC_NiPSysData, + RC_NiPSysDragModifier, RC_NiPSysEmitterCtlr, RC_NiPSysEmitterCtlrData, RC_NiPSysEmitterDeclinationCtlr, @@ -206,13 +207,16 @@ namespace Nif RC_NiPSysFieldAttenuationCtlr, RC_NiPSysFieldMagnitudeCtlr, RC_NiPSysFieldMaxDistanceCtlr, + RC_NiPSysGravityModifier, RC_NiPSysGravityStrengthCtlr, RC_NiPSysInitialRotSpeedCtlr, RC_NiPSysInitialRotSpeedVarCtlr, RC_NiPSysInitialRotAngleCtlr, RC_NiPSysInitialRotAngleVarCtlr, + RC_NiPSysMeshEmitter, RC_NiPSysModifierActiveCtlr, RC_NiPSysPositionModifier, + RC_NiPSysRotationModifier, RC_NiPSysResetOnLoopCtlr, RC_NiPSysSpawnModifier, RC_NiPSysSphereEmitter, diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 8be0d4ba4f..551cbfeae8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -304,7 +304,7 @@ namespace NifOsg bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); // We don't need the position for Wind gravity, except if decay is being applied - if (mType == Nif::NiGravity::ForceType::Point || mDecay != 0.f) + if (mType == Nif::ForceType::Point || mDecay != 0.f) mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; @@ -316,7 +316,7 @@ namespace NifOsg const float magic = 1.6f; switch (mType) { - case Nif::NiGravity::ForceType::Wind: + case Nif::ForceType::Wind: { float decayFactor = 1.f; if (mDecay != 0.f) @@ -330,7 +330,7 @@ namespace NifOsg break; } - case Nif::NiGravity::ForceType::Point: + case Nif::ForceType::Point: { osg::Vec3f diff = mCachedWorldPosition - particle->getPosition(); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index a9b628f695..967531013a 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -191,7 +191,7 @@ namespace NifOsg private: float mForce{ 0.f }; - Nif::NiGravity::ForceType mType{ Nif::NiGravity::ForceType::Wind }; + Nif::ForceType mType{ Nif::ForceType::Wind }; osg::Vec3f mPosition; osg::Vec3f mDirection; float mDecay{ 0.f }; From 5de3bdd2bcad2cc91b47c4f16fbdc5bd302261c9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 05:01:18 +0300 Subject: [PATCH 0200/2167] Read Gamebryo particle colliders and a few more modifiers --- components/nif/niffile.cpp | 8 ++++ components/nif/particle.cpp | 86 ++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 69 +++++++++++++++++++++++++++++ components/nif/record.hpp | 6 +++ components/nif/recordptr.hpp | 4 ++ 5 files changed, 173 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6d29972f63..7f0d630be1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -305,8 +305,11 @@ namespace Nif { "BSPSysInheritVelocityModifier", &construct }, { "BSPSysLODModifier", &construct }, + { "BSPSysRecycleBoundModifier", &construct }, { "BSPSysScaleModifier", &construct }, { "BSPSysSimpleColorModifier", &construct }, + { "BSPSysStripUpdateModifier", &construct }, + { "BSPSysSubTexModifier", &construct }, // Emitters { "NiPSysBoxEmitter", &construct }, @@ -347,6 +350,11 @@ namespace Nif { "NiPlanarCollider", &construct }, { "NiSphericalCollider", &construct }, + // Colliders, Gamebryo + { "NiPSysColliderManager", &construct }, + { "NiPSysPlanarCollider", &construct }, + { "NiPSysSphericalCollider", &construct }, + // Particle system controllers, 4.0.0.2 { "NiParticleSystemController", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 42ce8b57f9..289210662e 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -364,6 +364,22 @@ namespace Nif nif->read(mEndSize); } + void BSPSysRecycleBoundModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mBoundOffset); + nif->read(mBoundExtents); + mBoundObject.read(nif); + } + + void BSPSysRecycleBoundModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mBoundObject.post(nif); + } + void BSPSysScaleModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -386,6 +402,26 @@ namespace Nif nif->skip(52); // Unknown } + void BSPSysStripUpdateModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mUpdateDeltaTime); + } + + void BSPSysSubTexModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mStartFrame); + nif->read(mStartFrameFudge); + nif->read(mEndFrame); + nif->read(mLoopStartFrame); + nif->read(mLoopStartFrameFudge); + nif->read(mFrameCount); + nif->read(mFrameCountFudge); + } + void NiPSysEmitter::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -496,4 +532,54 @@ namespace Nif mVisKeyList->mKeys[nif->get()].mValue = nif->get() != 0; } + void NiPSysCollider::read(NIFStream* nif) + { + nif->read(mBounce); + nif->read(mCollideSpawn); + nif->read(mCollideDie); + mSpawnModifier.read(nif); + mParent.read(nif); + mNextCollider.read(nif); + mColliderObject.read(nif); + } + + void NiPSysCollider::post(Reader& nif) + { + mSpawnModifier.post(nif); + mParent.post(nif); + mNextCollider.post(nif); + mColliderObject.post(nif); + } + + void NiPSysColliderManager::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mCollider.read(nif); + } + + void NiPSysColliderManager::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mCollider.post(nif); + } + + void NiPSysSphericalCollider::read(NIFStream* nif) + { + NiPSysCollider::read(nif); + + nif->read(mRadius); + } + + void NiPSysPlanarCollider::read(NIFStream* nif) + { + NiPSysCollider::read(nif); + + nif->read(mWidth); + nif->read(mHeight); + nif->read(mXAxis); + nif->read(mYAxis); + } + } diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 0c9379f0f9..876c1c6a75 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -268,6 +268,16 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysRecycleBoundModifier : NiPSysModifier + { + osg::Vec3f mBoundOffset; + osg::Vec3f mBoundExtents; + NiAVObjectPtr mBoundObject; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct BSPSysScaleModifier : public NiPSysModifier { std::vector mScales; @@ -288,6 +298,26 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysStripUpdateModifier : NiPSysModifier + { + float mUpdateDeltaTime; + + void read(NIFStream* nif) override; + }; + + struct BSPSysSubTexModifier : public NiPSysModifier + { + float mStartFrame; + float mStartFrameFudge; + float mEndFrame; + float mLoopStartFrame; + float mLoopStartFrameFudge; + float mFrameCount; + float mFrameCountFudge; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysEmitter : public NiPSysModifier { @@ -399,5 +429,44 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPSysCollider : Record + { + float mBounce; + bool mCollideSpawn; + bool mCollideDie; + NiPSysSpawnModifierPtr mSpawnModifier; + NiPSysColliderManagerPtr mParent; + NiPSysColliderPtr mNextCollider; + NiAVObjectPtr mColliderObject; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysColliderManager : NiPSysModifier + { + NiPSysColliderPtr mCollider; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysPlanarCollider : NiPSysCollider + { + float mWidth; + float mHeight; + osg::Vec3f mXAxis; + osg::Vec3f mYAxis; + + void read(NIFStream* nif) override; + }; + + struct NiPSysSphericalCollider : NiPSysCollider + { + float mRadius; + + void read(NIFStream* nif) override; + }; + } #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 3d5ec01bc3..6b59465a14 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -96,8 +96,11 @@ namespace Nif RC_BSPackedAdditionalGeometryData, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, + RC_BSPSysRecycleBoundModifier, RC_BSPSysScaleModifier, RC_BSPSysSimpleColorModifier, + RC_BSPSysStripUpdateModifier, + RC_BSPSysSubTexModifier, RC_BSStripParticleSystem, RC_BSStripPSysData, RC_BSRefractionFirePeriodController, @@ -192,6 +195,7 @@ namespace Nif RC_NiPSysAirFieldSpreadCtlr, RC_NiPSysBoxEmitter, RC_NiPSysBoundUpdateModifier, + RC_NiPSysColliderManager, RC_NiPSysCylinderEmitter, RC_NiPSysData, RC_NiPSysDragModifier, @@ -215,10 +219,12 @@ namespace Nif RC_NiPSysInitialRotAngleVarCtlr, RC_NiPSysMeshEmitter, RC_NiPSysModifierActiveCtlr, + RC_NiPSysPlanarCollider, RC_NiPSysPositionModifier, RC_NiPSysRotationModifier, RC_NiPSysResetOnLoopCtlr, RC_NiPSysSpawnModifier, + RC_NiPSysSphericalCollider, RC_NiPSysSphereEmitter, RC_NiPSysUpdateCtlr, RC_NiRollController, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 6223740e31..b2168d9f1f 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -130,6 +130,8 @@ namespace Nif struct NiPalette; struct NiParticleModifier; struct NiParticleSystem; + struct NiPSysCollider; + struct NiPSysColliderManager; struct NiPSysEmitterCtlrData; struct NiPSysModifier; struct NiPSysSpawnModifier; @@ -175,6 +177,8 @@ namespace Nif using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; using NiParticleSystemPtr = RecordPtrT; + using NiPSysColliderPtr = RecordPtrT; + using NiPSysColliderManagerPtr = RecordPtrT; using NiPSysEmitterCtlrDataPtr = RecordPtrT; using NiPSysSpawnModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; From 0ad3463d36748959d6ca69d15b2281daedd4d835 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 05:28:18 +0300 Subject: [PATCH 0201/2167] Read BSWindModifier and NiPSysBombModifier --- components/nif/niffile.cpp | 2 ++ components/nif/particle.cpp | 26 ++++++++++++++++++++++++++ components/nif/particle.hpp | 20 ++++++++++++++++++++ components/nif/record.hpp | 4 +++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 7f0d630be1..b0dde04d32 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -294,6 +294,7 @@ namespace Nif // Modifiers, Gamebryo { "NiPSysAgeDeathModifier", &construct }, + { "NiPSysBombModifier", &construct }, { "NiPSysBoundUpdateModifier", &construct }, { "NiPSysDragModifier", &construct }, { "NiPSysGravityModifier", &construct }, @@ -310,6 +311,7 @@ namespace Nif { "BSPSysSimpleColorModifier", &construct }, { "BSPSysStripUpdateModifier", &construct }, { "BSPSysSubTexModifier", &construct }, + { "BSWindModifier", &construct }, // Emitters { "NiPSysBoxEmitter", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 289210662e..4a7603ca77 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -253,6 +253,25 @@ namespace Nif mSpawnModifier.post(nif); } + void NiPSysBombModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mBombObject.read(nif); + nif->read(mBombAxis); + nif->read(mDecay); + nif->read(mDeltaV); + nif->read(mDecayType); + nif->read(mSymmetryType); + } + + void NiPSysBombModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mBombObject.post(nif); + } + void NiPSysBoundUpdateModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -422,6 +441,13 @@ namespace Nif nif->read(mFrameCountFudge); } + void BSWindModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mStrength); + } + void NiPSysEmitter::read(NIFStream* nif) { NiPSysModifier::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 876c1c6a75..6c6306701b 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -186,6 +186,19 @@ namespace Nif void post(Reader& nif) override; }; + struct NiPSysBombModifier : NiPSysModifier + { + NiAVObjectPtr mBombObject; + osg::Vec3f mBombAxis; + float mDecay; + float mDeltaV; + uint32_t mDecayType; + uint32_t mSymmetryType; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysBoundUpdateModifier : public NiPSysModifier { uint16_t mUpdateSkip; @@ -318,6 +331,13 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSWindModifier : NiPSysModifier + { + float mStrength; + + void read(NIFStream* nif) override; + }; + // Abstract struct NiPSysEmitter : public NiPSysModifier { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 6b59465a14..ed497323a9 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -113,6 +113,7 @@ namespace Nif RC_BSTriShape, RC_BSWArray, RC_BSWaterShaderProperty, + RC_BSWindModifier, RC_BSXFlags, RC_DistantLODShaderProperty, RC_HairShaderProperty, @@ -193,8 +194,9 @@ namespace Nif RC_NiPSysAirFieldAirFrictionCtlr, RC_NiPSysAirFieldInheritVelocityCtlr, RC_NiPSysAirFieldSpreadCtlr, - RC_NiPSysBoxEmitter, + RC_NiPSysBombModifier, RC_NiPSysBoundUpdateModifier, + RC_NiPSysBoxEmitter, RC_NiPSysColliderManager, RC_NiPSysCylinderEmitter, RC_NiPSysData, From 655dcef34ca2bb06efbd17c01e2b559a47646e61 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 11:12:35 +0300 Subject: [PATCH 0202/2167] Fix >1 particle radii loading --- components/nif/particle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 4a7603ca77..e9da792e6e 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -102,7 +102,7 @@ namespace Nif nif->read(mNumParticles); bool isBs202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() != 0; - bool numRadii = 1; + uint16_t numRadii = 1; if (nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) numRadii = (nif->get() && !isBs202) ? mNumVertices : 0; nif->readVector(mRadii, numRadii); From a416d18adf51ef0f97109450ffa53061b7c2ac96 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 11:32:28 +0300 Subject: [PATCH 0203/2167] Read NiPSysColorModifier, NiPSysGrowFadeModifier, BSPSysArrayEmitter --- components/nif/niffile.cpp | 7 ++++++- components/nif/particle.cpp | 27 +++++++++++++++++++++++++++ components/nif/particle.hpp | 19 +++++++++++++++++++ components/nif/record.hpp | 3 +++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index b0dde04d32..f526c1e3d4 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -296,8 +296,10 @@ namespace Nif { "NiPSysAgeDeathModifier", &construct }, { "NiPSysBombModifier", &construct }, { "NiPSysBoundUpdateModifier", &construct }, + { "NiPSysColorModifier", &construct }, { "NiPSysDragModifier", &construct }, { "NiPSysGravityModifier", &construct }, + { "NiPSysGrowFadeModifier", &construct }, { "NiPSysPositionModifier", &construct }, { "NiPSysRotationModifier", &construct }, { "NiPSysSpawnModifier", &construct }, @@ -313,12 +315,15 @@ namespace Nif { "BSPSysSubTexModifier", &construct }, { "BSWindModifier", &construct }, - // Emitters + // Emitters, Gamebryo { "NiPSysBoxEmitter", &construct }, { "NiPSysCylinderEmitter", &construct }, { "NiPSysMeshEmitter", &construct }, { "NiPSysSphereEmitter", &construct }, + // Emitters, Bethesda + { "BSPSysArrayEmitter", &construct }, + // Modifier controllers, Gamebryo { "NiPSysAirFieldAirFrictionCtlr", &construct }, { "NiPSysAirFieldInheritVelocityCtlr", diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index e9da792e6e..0d3545acd5 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -279,6 +279,20 @@ namespace Nif nif->read(mUpdateSkip); } + void NiPSysColorModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mData.read(nif); + } + + void NiPSysColorModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mData.post(nif); + } + void NiPSysDragModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -320,6 +334,19 @@ namespace Nif mGravityObject.post(nif); } + void NiPSysGrowFadeModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mGrowTime); + nif->read(mGrowGeneration); + nif->read(mFadeTime); + nif->read(mFadeGeneration); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS + && nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO3) + nif->read(mBaseScale); + } + void NiPSysRotationModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 6c6306701b..f40dac44ce 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -206,6 +206,14 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPSysColorModifier : NiPSysModifier + { + NiColorDataPtr mData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysDragModifier : public NiPSysModifier { NiAVObjectPtr mDragObject; @@ -233,6 +241,17 @@ namespace Nif void post(Reader& nif) override; }; + struct NiPSysGrowFadeModifier : NiPSysModifier + { + float mGrowTime; + uint16_t mGrowGeneration; + float mFadeTime; + uint16_t mFadeGeneration; + float mBaseScale; + + void read(NIFStream* nif) override; + }; + struct NiPSysRotationModifier : public NiPSysModifier { float mRotationSpeed; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index ed497323a9..f7bb4fa648 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -94,6 +94,7 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSPSysArrayEmitter, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, RC_BSPSysRecycleBoundModifier, @@ -198,6 +199,7 @@ namespace Nif RC_NiPSysBoundUpdateModifier, RC_NiPSysBoxEmitter, RC_NiPSysColliderManager, + RC_NiPSysColorModifier, RC_NiPSysCylinderEmitter, RC_NiPSysData, RC_NiPSysDragModifier, @@ -215,6 +217,7 @@ namespace Nif RC_NiPSysFieldMaxDistanceCtlr, RC_NiPSysGravityModifier, RC_NiPSysGravityStrengthCtlr, + RC_NiPSysGrowFadeModifier, RC_NiPSysInitialRotSpeedCtlr, RC_NiPSysInitialRotSpeedVarCtlr, RC_NiPSysInitialRotAngleCtlr, From 470852f88e2d07a05eba54f8e5998e673337f703 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Sep 2023 21:59:24 +0300 Subject: [PATCH 0204/2167] Cleanup --- components/nif/particle.hpp | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index f40dac44ce..8fc24ee407 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -9,7 +9,7 @@ namespace Nif { - struct NiParticleModifier : public Record + struct NiParticleModifier : Record { NiParticleModifierPtr mNext; NiTimeControllerPtr mController; @@ -18,7 +18,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiParticleGrowFade : public NiParticleModifier + struct NiParticleGrowFade : NiParticleModifier { float mGrowTime; float mFadeTime; @@ -26,7 +26,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticleColorModifier : public NiParticleModifier + struct NiParticleColorModifier : NiParticleModifier { NiColorDataPtr mData; @@ -40,7 +40,7 @@ namespace Nif Point = 1, // Fixed origin }; - struct NiGravity : public NiParticleModifier + struct NiGravity : NiParticleModifier { float mDecay{ 0.f }; float mForce; @@ -51,7 +51,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticleCollider : public NiParticleModifier + struct NiParticleCollider : NiParticleModifier { float mBounceFactor; bool mSpawnOnCollision{ false }; @@ -61,7 +61,7 @@ namespace Nif }; // NiPinaColada - struct NiPlanarCollider : public NiParticleCollider + struct NiPlanarCollider : NiParticleCollider { osg::Vec2f mExtents; osg::Vec3f mPosition; @@ -72,7 +72,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiSphericalCollider : public NiParticleCollider + struct NiSphericalCollider : NiParticleCollider { float mRadius; osg::Vec3f mCenter; @@ -80,7 +80,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticleRotation : public NiParticleModifier + struct NiParticleRotation : NiParticleModifier { uint8_t mRandomInitialAxis; osg::Vec3f mInitialAxis; @@ -199,7 +199,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPSysBoundUpdateModifier : public NiPSysModifier + struct NiPSysBoundUpdateModifier : NiPSysModifier { uint16_t mUpdateSkip; @@ -214,7 +214,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPSysDragModifier : public NiPSysModifier + struct NiPSysDragModifier : NiPSysModifier { NiAVObjectPtr mDragObject; osg::Vec3f mDragAxis; @@ -226,7 +226,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPSysGravityModifier : public NiPSysModifier + struct NiPSysGravityModifier : NiPSysModifier { NiAVObjectPtr mGravityObject; osg::Vec3f mGravityAxis; @@ -252,7 +252,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiPSysRotationModifier : public NiPSysModifier + struct NiPSysRotationModifier : NiPSysModifier { float mRotationSpeed; float mRotationSpeedVariation; @@ -279,7 +279,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct BSPSysInheritVelocityModifier : public NiPSysModifier + struct BSPSysInheritVelocityModifier : NiPSysModifier { NiAVObjectPtr mInheritObject; float mInheritChance; @@ -310,7 +310,7 @@ namespace Nif void post(Reader& nif) override; }; - struct BSPSysScaleModifier : public NiPSysModifier + struct BSPSysScaleModifier : NiPSysModifier { std::vector mScales; @@ -337,7 +337,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct BSPSysSubTexModifier : public NiPSysModifier + struct BSPSysSubTexModifier : NiPSysModifier { float mStartFrame; float mStartFrameFudge; @@ -358,7 +358,7 @@ namespace Nif }; // Abstract - struct NiPSysEmitter : public NiPSysModifier + struct NiPSysEmitter : NiPSysModifier { float mSpeed; float mSpeedVariation; @@ -376,7 +376,7 @@ namespace Nif }; // Abstract - struct NiPSysVolumeEmitter : public NiPSysEmitter + struct NiPSysVolumeEmitter : NiPSysEmitter { NiAVObjectPtr mEmitterObject; @@ -384,7 +384,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPSysBoxEmitter : public NiPSysVolumeEmitter + struct NiPSysBoxEmitter : NiPSysVolumeEmitter { float mWidth; float mHeight; @@ -393,7 +393,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiPSysCylinderEmitter : public NiPSysVolumeEmitter + struct NiPSysCylinderEmitter : NiPSysVolumeEmitter { float mRadius; float mHeight; @@ -401,7 +401,7 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiPSysMeshEmitter : public NiPSysEmitter + struct NiPSysMeshEmitter : NiPSysEmitter { NiAVObjectList mEmitterMeshes; uint32_t mInitialVelocityType; @@ -412,7 +412,7 @@ namespace Nif void post(Reader& nif) override; }; - struct NiPSysSphereEmitter : public NiPSysVolumeEmitter + struct NiPSysSphereEmitter : NiPSysVolumeEmitter { float mRadius; From 47a1403717a3e2b879e53ddef5bf3e8eb5a4b606 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 21:06:56 +0300 Subject: [PATCH 0205/2167] Fix bhkConvexSweepShape loading --- components/nif/physics.hpp | 2 +- components/nif/recordptr.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index d9656510c3..cdfb3cc1fb 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -553,7 +553,7 @@ namespace Nif struct bhkConvexSweepShape : bhkShape { - bhkConvexShape mShape; + bhkConvexShapePtr mShape; HavokMaterial mMaterial; float mRadius; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index b2168d9f1f..50d16d5eac 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -147,6 +147,7 @@ namespace Nif struct bhkShape; struct bhkSerializable; struct bhkEntity; + struct bhkConvexShape; struct hkPackedNiTriStripsData; struct NiAccumulator; struct NiInterpolator; @@ -192,6 +193,7 @@ namespace Nif using bhkWorldObjectPtr = RecordPtrT; using bhkShapePtr = RecordPtrT; using bhkEntityPtr = RecordPtrT; + using bhkConvexShapePtr = RecordPtrT; using hkPackedNiTriStripsDataPtr = RecordPtrT; using NiAccumulatorPtr = RecordPtrT; using NiInterpolatorPtr = RecordPtrT; From 7e360df6c0213d57cc164c387e526dc9fbd040d0 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Sep 2023 09:08:02 +0300 Subject: [PATCH 0206/2167] Place ESM4::Flora instances to the scene --- apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwlua/cellbindings.cpp | 4 ++++ apps/openmw/mwlua/types/types.cpp | 3 +++ apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/cellstore.hpp | 3 ++- apps/openmw/mwworld/esmstore.cpp | 1 + apps/openmw/mwworld/esmstore.hpp | 12 +++++++----- apps/openmw/mwworld/store.cpp | 1 + components/esm/records.hpp | 1 + files/lua_api/openmw/types.lua | 3 +++ 10 files changed, 25 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 071389c22c..392cc45b6e 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,7 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 48c7141ab8..d857288979 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,9 @@ namespace MWLua case ESM::REC_DOOR4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_FLOR4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_FURN4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 6095053eee..eeb7061cc3 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -42,6 +42,7 @@ namespace MWLua constexpr std::string_view ESM4Clothing = "ESM4Clothing"; constexpr std::string_view ESM4Container = "ESM4Container"; constexpr std::string_view ESM4Door = "ESM4Door"; + constexpr std::string_view ESM4Flora = "ESM4Flora"; constexpr std::string_view ESM4Furniture = "ESM4Furniture"; constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Light = "ESM4Light"; @@ -85,6 +86,7 @@ namespace MWLua { ESM::REC_CLOT4, ObjectTypeName::ESM4Clothing }, { ESM::REC_CONT4, ObjectTypeName::ESM4Container }, { ESM::REC_DOOR4, ObjectTypeName::ESM4Door }, + { ESM::REC_FLOR4, ObjectTypeName::ESM4Flora }, { ESM::REC_FURN4, ObjectTypeName::ESM4Furniture }, { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, @@ -223,6 +225,7 @@ namespace MWLua addType(ObjectTypeName::ESM4Clothing, { ESM::REC_CLOT4 }); addType(ObjectTypeName::ESM4Container, { ESM::REC_CONT4 }); addESM4DoorBindings(addType(ObjectTypeName::ESM4Door, { ESM::REC_DOOR4 }, ObjectTypeName::Lockable), context); + addType(ObjectTypeName::ESM4Flora, { ESM::REC_FLOR4 }); addType(ObjectTypeName::ESM4Furniture, { ESM::REC_FURN4 }); addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index f7ec3ddcba..a13accf7c8 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 23bd071ff1..2543f24aa7 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -70,6 +70,7 @@ namespace ESM4 struct Container; struct Door; struct Furniture; + struct Flora; struct Ingredient; struct MiscItem; struct Terminal; @@ -93,7 +94,7 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList>; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 209fc39b42..f9b53cf21f 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -284,6 +284,7 @@ namespace MWWorld case ESM::REC_CONT4: case ESM::REC_CREA4: case ESM::REC_DOOR4: + case ESM::REC_FLOR4: case ESM::REC_FURN4: case ESM::REC_INGR4: case ESM::REC_LIGH4: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 80cd5719e2..821ca6f488 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -86,6 +86,7 @@ namespace ESM4 struct Container; struct Creature; struct Door; + struct Flora; struct Furniture; struct Hair; struct HeadPart; @@ -136,11 +137,12 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, Store, - Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store>; + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 6e5b56d9ff..22f6857a24 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1357,6 +1357,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 4af60db8b5..ccd03a05b9 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 03efe885b5..df6e17c6f4 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1803,6 +1803,9 @@ --- Functions for @{#ESM4Door} objects -- @field [parent=#types] #ESM4Door ESM4Door +--- Functions for @{#ESM4Flora} objects +-- @field [parent=#types] #ESM4Flora ESM4Flora + --- Functions for @{#ESM4Terminal} objects -- @field [parent=#types] #ESM4Terminal ESM4Terminal From d0487461461f5f8284b8168b783e35397316d924 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 10:41:24 +0300 Subject: [PATCH 0207/2167] Read BSMasterParticleSystem, BSParentVelocityModifier, NiLookAtInterpolator --- components/nif/controller.cpp | 20 ++++++++++++++++++++ components/nif/controller.hpp | 15 +++++++++++++++ components/nif/niffile.cpp | 3 +++ components/nif/particle.cpp | 22 ++++++++++++++++++++++ components/nif/particle.hpp | 16 ++++++++++++++++ components/nif/record.hpp | 2 ++ 6 files changed, 78 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index a3033357ec..3f4d5a6380 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -600,6 +600,26 @@ namespace Nif mPercentData.post(nif); } + void NiLookAtInterpolator::read(NIFStream* nif) + { + nif->read(mLookAtFlags); + mLookAt.read(nif); + nif->read(mLookAtName); + if (nif->getVersion() <= NIFStream::generateVersion(20, 4, 0, 12)) + nif->read(mTransform); + mTranslation.read(nif); + mRoll.read(nif); + mScale.read(nif); + } + + void NiLookAtInterpolator::post(Reader& nif) + { + mLookAt.post(nif); + mTranslation.post(nif); + mRoll.post(nif); + mScale.post(nif); + } + void NiBlendInterpolator::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 112)) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 8a7d306a85..97f948167b 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -419,6 +419,21 @@ namespace Nif void post(Reader& nif) override; }; + struct NiLookAtInterpolator : NiInterpolator + { + // Uses the same flags as NiLookAtController + uint16_t mLookAtFlags{ 0 }; + NiAVObjectPtr mLookAt; + std::string mLookAtName; + NiQuatTransform mTransform; + NiInterpolatorPtr mTranslation; + NiInterpolatorPtr mRoll; + NiInterpolatorPtr mScale; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + // Abstract struct NiBlendInterpolator : public NiInterpolator { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f526c1e3d4..10a53234f1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -72,6 +72,7 @@ namespace Nif { "BSDebrisNode", &construct }, { "BSFadeNode", &construct }, { "BSLeafAnimNode", &construct }, + { "BSMasterParticleSystem", &construct }, { "BSMultiBoundNode", &construct }, { "BSOrderedNode", &construct }, { "BSRangeNode", &construct }, @@ -164,6 +165,7 @@ namespace Nif { "NiBoolTimelineInterpolator", &construct }, { "NiColorInterpolator", &construct }, { "NiFloatInterpolator", &construct }, + { "NiLookAtInterpolator", &construct }, { "NiPathInterpolator", &construct }, { "NiPoint3Interpolator", &construct }, { "NiTransformInterpolator", &construct }, @@ -305,6 +307,7 @@ namespace Nif { "NiPSysSpawnModifier", &construct }, // Modifiers, Bethesda + { "BSParentVelocityModifier", &construct }, { "BSPSysInheritVelocityModifier", &construct }, { "BSPSysLODModifier", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 0d3545acd5..63eff520b3 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -150,6 +150,21 @@ namespace Nif nif->readVector(mRotations, mNumVertices); } + void BSMasterParticleSystem::read(NIFStream* nif) + { + NiNode::read(nif); + + nif->read(mMaxEmitters); + readRecordList(nif, mParticleSystems); + } + + void BSMasterParticleSystem::post(Reader& nif) + { + NiNode::post(nif); + + postRecordList(nif, mParticleSystems); + } + void NiParticleSystem::read(NIFStream* nif) { // Weird loading to account for inheritance differences starting from SSE @@ -383,6 +398,13 @@ namespace Nif nif->read(mLifespanVariation); } + void BSParentVelocityModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mDamping); + } + void BSPSysInheritVelocityModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 8fc24ee407..732649fabe 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -115,6 +115,15 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSMasterParticleSystem : NiNode + { + uint16_t mMaxEmitters; + NiAVObjectList mParticleSystems; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiParticleSystem : NiParticles { osg::BoundingSpheref mBoundingSphere; @@ -279,6 +288,13 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSParentVelocityModifier : NiPSysModifier + { + float mDamping; + + void read(NIFStream* nif) override; + }; + struct BSPSysInheritVelocityModifier : NiPSysModifier { NiAVObjectPtr mInheritObject; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index f7bb4fa648..d0a51581c4 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -94,6 +94,7 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSParentVelocityModifier, RC_BSPSysArrayEmitter, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, @@ -172,6 +173,7 @@ namespace Nif RC_NiLinesData, RC_NiLODNode, RC_NiLookAtController, + RC_NiLookAtInterpolator, RC_NiMaterialColorController, RC_NiMaterialProperty, RC_NiMorphData, From 3296dadf60aaca9d5b60edd92e35d9612cfeae5b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 11:27:18 +0300 Subject: [PATCH 0208/2167] Read BSPSysMultiTargetEmitterCtlr and bone LOD controllers --- components/nif/controller.cpp | 44 +++++++++++++++++++++++++++++++++++ components/nif/controller.hpp | 18 ++++++++++++++ components/nif/niffile.cpp | 5 ++++ components/nif/particle.cpp | 15 ++++++++++++ components/nif/particle.hpp | 9 +++++++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 5 ++++ 7 files changed, 98 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 3f4d5a6380..6a48ea1e4f 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -460,6 +460,50 @@ namespace Nif mData.post(nif); } + void NiBoneLODController::read(NIFStream* nif) + { + NiTimeController::read(nif); + + nif->read(mLOD); + mNodeGroups.resize(nif->get()); + nif->read(mNumNodeGroups); + for (NiAVObjectList& group : mNodeGroups) + readRecordList(nif, group); + + if (nif->getBethVersion() != 0 || nif->getVersion() < NIFStream::generateVersion(4, 2, 2, 0)) + return; + + mSkinnedShapeGroups.resize(nif->get()); + for (std::vector& group : mSkinnedShapeGroups) + { + group.resize(nif->get()); + for (SkinInfo& info : group) + { + info.mShape.read(nif); + info.mSkin.read(nif); + } + } + readRecordList(nif, mShapeGroups); + } + + void NiBoneLODController::post(Reader& nif) + { + NiTimeController::post(nif); + + for (NiAVObjectList& group : mNodeGroups) + postRecordList(nif, group); + + for (std::vector& group : mSkinnedShapeGroups) + { + for (SkinInfo& info : group) + { + info.mShape.post(nif); + info.mSkin.post(nif); + } + } + postRecordList(nif, mShapeGroups); + } + void bhkBlendController::read(NIFStream* nif) { NiTimeController::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 97f948167b..b6c71e1548 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -300,6 +300,24 @@ namespace Nif void post(Reader& nif) override; }; + struct NiBoneLODController : NiTimeController + { + struct SkinInfo + { + NiTriBasedGeomPtr mShape; + NiSkinInstancePtr mSkin; + }; + + uint32_t mLOD; + uint32_t mNumNodeGroups; + std::vector mNodeGroups; + std::vector> mSkinnedShapeGroups; + NiTriBasedGeomList mShapeGroups; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct bhkBlendController : public NiTimeController { void read(NIFStream* nif) override; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 10a53234f1..e0f6ee422e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -118,6 +118,7 @@ namespace Nif { "NiVisController", &construct }, // Gamebryo + { "NiBoneLODController", &construct }, { "NiControllerManager", &construct }, { "NiLightDimmerController", &construct }, { "NiTransformController", &construct }, @@ -154,6 +155,7 @@ namespace Nif { "BSLightingShaderPropertyFloatController", &construct }, { "bhkBlendController", &construct }, + { "NiBSBoneLODController", &construct }, // Interpolators, Gamebryo { "NiBlendBoolInterpolator", &construct }, @@ -353,6 +355,9 @@ namespace Nif { "NiPSysInitialRotAngleVarCtlr", &construct }, { "NiPSysModifierActiveCtlr", &construct }, + // Modifier controllers, Bethesda + { "BSPSysMultiTargetEmitterCtlr", &construct }, + // Modifier controller data, Gamebryo { "NiPSysEmitterCtlrData", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 63eff520b3..95440af26b 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -597,6 +597,21 @@ namespace Nif mVisInterpolator.post(nif); } + void BSPSysMultiTargetEmitterCtlr::read(NIFStream* nif) + { + NiPSysEmitterCtlr::read(nif); + + nif->read(mMaxEmitters); + mMasterPSys.read(nif); + } + + void BSPSysMultiTargetEmitterCtlr::post(Reader& nif) + { + NiPSysEmitterCtlr::post(nif); + + mMasterPSys.post(nif); + } + void NiPSysEmitterCtlrData::read(NIFStream* nif) { mFloatKeyList = std::make_shared(); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 732649fabe..a2b0567b3b 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -476,6 +476,15 @@ namespace Nif void post(Reader& nif) override; }; + struct BSPSysMultiTargetEmitterCtlr : NiPSysEmitterCtlr + { + uint16_t mMaxEmitters; + BSMasterParticleSystemPtr mMasterPSys; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysEmitterCtlrData : Record { FloatKeyMapPtr mFloatKeyList; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index d0a51581c4..ea37ad4654 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -98,6 +98,7 @@ namespace Nif RC_BSPSysArrayEmitter, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, + RC_BSPSysMultiTargetEmitterCtlr, RC_BSPSysRecycleBoundModifier, RC_BSPSysScaleModifier, RC_BSPSysSimpleColorModifier, @@ -130,6 +131,7 @@ namespace Nif RC_NiBlendFloatInterpolator, RC_NiBlendPoint3Interpolator, RC_NiBlendTransformInterpolator, + RC_NiBoneLODController, RC_NiBoolData, RC_NiBooleanExtraData, RC_NiBoolInterpolator, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 50d16d5eac..0a7bd0ccbd 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -129,6 +129,7 @@ namespace Nif struct NiSourceTexture; struct NiPalette; struct NiParticleModifier; + struct BSMasterParticleSystem; struct NiParticleSystem; struct NiPSysCollider; struct NiPSysColliderManager; @@ -138,6 +139,7 @@ namespace Nif struct NiBoolData; struct NiSkinPartition; struct BSShaderTextureSet; + struct NiTriBasedGeom; struct NiGeometryData; struct BSShaderProperty; struct NiAlphaProperty; @@ -177,6 +179,7 @@ namespace Nif using NiSourceTexturePtr = RecordPtrT; using NiPalettePtr = RecordPtrT; using NiParticleModifierPtr = RecordPtrT; + using BSMasterParticleSystemPtr = RecordPtrT; using NiParticleSystemPtr = RecordPtrT; using NiPSysColliderPtr = RecordPtrT; using NiPSysColliderManagerPtr = RecordPtrT; @@ -185,6 +188,7 @@ namespace Nif using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; using BSShaderTextureSetPtr = RecordPtrT; + using NiTriBasedGeomPtr = RecordPtrT; using NiGeometryDataPtr = RecordPtrT; using BSShaderPropertyPtr = RecordPtrT; using NiAlphaPropertyPtr = RecordPtrT; @@ -216,6 +220,7 @@ namespace Nif using bhkEntityList = RecordListT; using NiControllerSequenceList = RecordListT; using NiPSysModifierList = RecordListT; + using NiTriBasedGeomList = RecordListT; } // Namespace #endif From 02c895c107673ff13f51cfdcf04bb4c7ccd99ef4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 12:31:25 +0300 Subject: [PATCH 0209/2167] Read all remaining Havok constraint records --- components/nif/niffile.cpp | 3 + components/nif/physics.cpp | 126 +++++++++++++++++++++++++++++++++++++ components/nif/physics.hpp | 81 ++++++++++++++++++++++++ components/nif/record.hpp | 3 + 4 files changed, 213 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index e0f6ee422e..06b086c089 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -395,6 +395,9 @@ namespace Nif { "bhkLimitedHingeConstraint", &construct }, { "bhkRagdollConstraint", &construct }, { "bhkStiffSpringConstraint", &construct }, + { "bhkPrismaticConstraint", &construct }, + { "bhkMalleableConstraint", &construct }, + { "bhkBreakableConstraint", &construct }, // Physics body records, Bethesda { "bhkRigidBody", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index f97b4b6169..24c49a77d9 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -345,6 +345,109 @@ namespace Nif nif->read(mLength); } + void bhkPrismaticConstraintCInfo::read(NIFStream* nif) + { + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + nif->read(mDataA.mPivot); + nif->read(mDataA.mRotation); + nif->read(mDataA.mPlane); + nif->read(mDataA.mSliding); + nif->read(mDataB.mSliding); + nif->read(mDataB.mPivot); + nif->read(mDataB.mRotation); + nif->read(mDataB.mPlane); + } + else + { + nif->read(mDataA.mSliding); + nif->read(mDataA.mRotation); + nif->read(mDataA.mPlane); + nif->read(mDataA.mPivot); + nif->read(mDataB.mSliding); + nif->read(mDataB.mRotation); + nif->read(mDataB.mPlane); + nif->read(mDataB.mPivot); + } + nif->read(mMinDistance); + nif->read(mMaxDistance); + nif->read(mFriction); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() >= 17) + mMotor.read(nif); + } + + void bhkMalleableConstraintCInfo::read(NIFStream* nif) + { + mType = static_cast(nif->get()); + mInfo.read(nif); + switch (mType) + { + case hkConstraintType::BallAndSocket: + mBallAndSocketInfo.read(nif); + break; + case hkConstraintType::Hinge: + mHingeInfo.read(nif); + break; + case hkConstraintType::LimitedHinge: + mLimitedHingeInfo.read(nif); + break; + case hkConstraintType::Prismatic: + mPrismaticInfo.read(nif); + break; + case hkConstraintType::Ragdoll: + mRagdollInfo.read(nif); + break; + case hkConstraintType::StiffSpring: + mStiffSpringInfo.read(nif); + break; + default: + throw Nif::Exception( + "Unrecognized constraint type in bhkMalleableConstraint", nif->getFile().getFilename()); + } + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + nif->read(mTau); + nif->read(mDamping); + } + else + { + nif->read(mStrength); + } + } + + void bhkWrappedConstraintData::read(NIFStream* nif) + { + mType = static_cast(nif->get()); + mInfo.read(nif); + switch (mType) + { + case hkConstraintType::BallAndSocket: + mBallAndSocketInfo.read(nif); + break; + case hkConstraintType::Hinge: + mHingeInfo.read(nif); + break; + case hkConstraintType::LimitedHinge: + mLimitedHingeInfo.read(nif); + break; + case hkConstraintType::Prismatic: + mPrismaticInfo.read(nif); + break; + case hkConstraintType::Ragdoll: + mRagdollInfo.read(nif); + break; + case hkConstraintType::StiffSpring: + mStiffSpringInfo.read(nif); + break; + case hkConstraintType::Malleable: + mMalleableInfo.read(nif); + break; + default: + throw Nif::Exception( + "Unrecognized constraint type in bhkWrappedConstraintData", nif->getFile().getFilename()); + } + } + /// Record types void bhkCollisionObject::read(NIFStream* nif) @@ -746,4 +849,27 @@ namespace Nif mConstraint.read(nif); } + void bhkPrismaticConstraint::read(NIFStream* nif) + { + bhkConstraint::read(nif); + + mConstraint.read(nif); + } + + void bhkMalleableConstraint::read(NIFStream* nif) + { + bhkConstraint::read(nif); + + mConstraint.read(nif); + } + + void bhkBreakableConstraint::read(NIFStream* nif) + { + bhkConstraint::read(nif); + + mConstraint.read(nif); + nif->read(mThreshold); + nif->read(mRemoveWhenBroken); + } + } // Namespace diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index cdfb3cc1fb..90185d0417 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -363,6 +363,64 @@ namespace Nif void read(NIFStream* nif); }; + struct bhkPrismaticConstraintCInfo + { + struct Data + { + osg::Vec4f mSliding; + osg::Vec4f mRotation; + osg::Vec4f mPlane; + osg::Vec4f mPivot; + }; + + Data mDataA; + Data mDataB; + float mMinDistance, mMaxDistance; + float mFriction; + bhkConstraintMotorCInfo mMotor; + + void read(NIFStream* nif); + }; + + enum class hkConstraintType : uint32_t + { + BallAndSocket = 0, + Hinge = 1, + LimitedHinge = 2, + Prismatic = 6, + Ragdoll = 7, + StiffSpring = 8, + Malleable = 13, + }; + + struct bhkWrappedConstraintDataBase + { + hkConstraintType mType; + bhkConstraintCInfo mInfo; + bhkBallAndSocketConstraintCInfo mBallAndSocketInfo; + bhkHingeConstraintCInfo mHingeInfo; + bhkLimitedHingeConstraintCInfo mLimitedHingeInfo; + bhkPrismaticConstraintCInfo mPrismaticInfo; + bhkRagdollConstraintCInfo mRagdollInfo; + bhkStiffSpringConstraintCInfo mStiffSpringInfo; + }; + + struct bhkMalleableConstraintCInfo : bhkWrappedConstraintDataBase + { + float mTau; + float mDamping; + float mStrength; + + void read(NIFStream* nif); + }; + + struct bhkWrappedConstraintData : bhkWrappedConstraintDataBase + { + bhkMalleableConstraintCInfo mMalleableInfo; + + void read(NIFStream* nif); + }; + /// Record types // Abstract Bethesda Havok object @@ -713,5 +771,28 @@ namespace Nif void read(NIFStream* nif) override; }; + struct bhkPrismaticConstraint : bhkConstraint + { + bhkPrismaticConstraintCInfo mConstraint; + + void read(NIFStream* nif) override; + }; + + struct bhkMalleableConstraint : bhkConstraint + { + bhkMalleableConstraintCInfo mConstraint; + + void read(NIFStream* nif) override; + }; + + struct bhkBreakableConstraint : bhkConstraint + { + bhkWrappedConstraintData mConstraint; + float mThreshold; + bool mRemoveWhenBroken; + + void read(NIFStream* nif) override; + }; + } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index ea37ad4654..e4a539e8a3 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -40,6 +40,7 @@ namespace Nif RC_bhkBlendCollisionObject, RC_bhkBlendController, RC_bhkBoxShape, + RC_bhkBreakableConstraint, RC_bhkCapsuleShape, RC_bhkCylinderShape, RC_bhkCollisionObject, @@ -52,10 +53,12 @@ namespace Nif RC_bhkHingeConstraint, RC_bhkLimitedHingeConstraint, RC_bhkListShape, + RC_bhkMalleableConstraint, RC_bhkMoppBvTreeShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, RC_bhkPhysicsSystem, + RC_bhkPrismaticConstraint, RC_bhkRagdollConstraint, RC_bhkRagdollSystem, RC_bhkRigidBody, From 285eafbf66069955e4eaebc9aa3cbfaccbd77674 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 13:52:37 +0300 Subject: [PATCH 0210/2167] Read bhkBallSocketConstraintChain and BSProceduralLightingController --- components/nif/controller.cpp | 43 +++++++++++++++++++++++++++++++++++ components/nif/controller.hpp | 28 +++++++++++++++++++++++ components/nif/niffile.cpp | 3 +++ components/nif/physics.cpp | 32 ++++++++++++++++++++++++++ components/nif/physics.hpp | 22 ++++++++++++++++++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 3 +++ 7 files changed, 133 insertions(+) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 6a48ea1e4f..903cf7710c 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -553,6 +553,49 @@ namespace Nif nif->read(mMaximumDistance); } + void BSProceduralLightningController::read(NIFStream* nif) + { + NiTimeController::read(nif); + + mGenerationInterp.read(nif); + mMutationInterp.read(nif); + mSubdivisionInterp.read(nif); + mNumBranchesInterp.read(nif); + mNumBranchesVarInterp.read(nif); + mLengthInterp.read(nif); + mLengthVarInterp.read(nif); + mWidthInterp.read(nif); + mArcOffsetInterp.read(nif); + nif->read(mSubdivisions); + nif->read(mNumBranches); + nif->read(mNumBranchesVar); + nif->read(mLength); + nif->read(mLengthVar); + nif->read(mWidth); + nif->read(mChildWidthMult); + nif->read(mArcOffset); + nif->read(mFadeMainBolt); + nif->read(mFadeChildBolts); + nif->read(mAnimateArcOffset); + mShaderProperty.read(nif); + } + + void BSProceduralLightningController::post(Reader& nif) + { + NiTimeController::post(nif); + + mGenerationInterp.post(nif); + mMutationInterp.post(nif); + mSubdivisionInterp.post(nif); + mNumBranchesInterp.post(nif); + mNumBranchesVarInterp.post(nif); + mLengthInterp.post(nif); + mLengthVarInterp.post(nif); + mWidthInterp.post(nif); + mArcOffsetInterp.post(nif); + mShaderProperty.post(nif); + } + void NiControllerManager::read(NIFStream* nif) { NiTimeController::read(nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index b6c71e1548..33c04a6d35 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -354,6 +354,34 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSProceduralLightningController : NiTimeController + { + NiInterpolatorPtr mGenerationInterp; + NiInterpolatorPtr mMutationInterp; + NiInterpolatorPtr mSubdivisionInterp; + NiInterpolatorPtr mNumBranchesInterp; + NiInterpolatorPtr mNumBranchesVarInterp; + NiInterpolatorPtr mLengthInterp; + NiInterpolatorPtr mLengthVarInterp; + NiInterpolatorPtr mWidthInterp; + NiInterpolatorPtr mArcOffsetInterp; + uint16_t mSubdivisions; + uint16_t mNumBranches; + uint16_t mNumBranchesVar; + float mLength; + float mLengthVar; + float mWidth; + float mChildWidthMult; + float mArcOffset; + bool mFadeMainBolt; + bool mFadeChildBolts; + bool mAnimateArcOffset; + BSShaderPropertyPtr mShaderProperty; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiControllerManager : public NiTimeController { bool mCumulative; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 06b086c089..fb403bccb4 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -138,6 +138,8 @@ namespace Nif { "BSFrustumFOVController", &construct }, { "BSKeyframeController", &construct }, { "BSLagBoneController", &construct }, + { "BSProceduralLightningController", + &construct }, { "BSMaterialEmittanceMultController", &construct }, { "BSNiAlphaPropertyTestRefController", @@ -391,6 +393,7 @@ namespace Nif // Constraint records, Bethesda { "bhkBallAndSocketConstraint", &construct }, + { "bhkBallSocketConstraintChain", &construct }, { "bhkHingeConstraint", &construct }, { "bhkLimitedHingeConstraint", &construct }, { "bhkRagdollConstraint", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 24c49a77d9..4eaf77f510 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -448,6 +448,17 @@ namespace Nif } } + void bhkConstraintChainCInfo::read(NIFStream* nif) + { + readRecordList(nif, mEntities); + mInfo.read(nif); + } + + void bhkConstraintChainCInfo::post(Reader& nif) + { + postRecordList(nif, mEntities); + } + /// Record types void bhkCollisionObject::read(NIFStream* nif) @@ -842,6 +853,27 @@ namespace Nif mConstraint.read(nif); } + void bhkBallSocketConstraintChain::read(NIFStream* nif) + { + uint32_t numPivots = nif->get(); + if (numPivots % 2 != 0) + throw Nif::Exception( + "Invalid number of constraints in bhkBallSocketConstraintChain", nif->getFile().getFilename()); + mConstraints.resize(numPivots / 2); + for (bhkBallAndSocketConstraintCInfo& info : mConstraints) + info.read(nif); + nif->read(mTau); + nif->read(mDamping); + nif->read(mConstraintForceMixing); + nif->read(mMaxErrorDistance); + mConstraintChainInfo.read(nif); + } + + void bhkBallSocketConstraintChain::post(Reader& nif) + { + mConstraintChainInfo.post(nif); + } + void bhkStiffSpringConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 90185d0417..d4eaccfc94 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -421,6 +421,15 @@ namespace Nif void read(NIFStream* nif); }; + struct bhkConstraintChainCInfo + { + bhkRigidBodyList mEntities; + bhkConstraintCInfo mInfo; + + void read(NIFStream* nif); + void post(Reader& nif); + }; + /// Record types // Abstract Bethesda Havok object @@ -764,6 +773,19 @@ namespace Nif void read(NIFStream* nif) override; }; + struct bhkBallSocketConstraintChain : bhkSerializable + { + std::vector mConstraints; + float mTau; + float mDamping; + float mConstraintForceMixing; + float mMaxErrorDistance; + bhkConstraintChainCInfo mConstraintChainInfo; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct bhkStiffSpringConstraint : bhkConstraint { bhkStiffSpringConstraintCInfo mConstraint; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index e4a539e8a3..1b6ea1e99b 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -37,6 +37,7 @@ namespace Nif RC_MISSING = 0, RC_AvoidNode, RC_bhkBallAndSocketConstraint, + RC_bhkBallSocketConstraintChain, RC_bhkBlendCollisionObject, RC_bhkBlendController, RC_bhkBoxShape, @@ -98,6 +99,7 @@ namespace Nif RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, RC_BSParentVelocityModifier, + RC_BSProceduralLightningController, RC_BSPSysArrayEmitter, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 0a7bd0ccbd..209ee010d6 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -150,6 +150,7 @@ namespace Nif struct bhkSerializable; struct bhkEntity; struct bhkConvexShape; + struct bhkRigidBody; struct hkPackedNiTriStripsData; struct NiAccumulator; struct NiInterpolator; @@ -198,6 +199,7 @@ namespace Nif using bhkShapePtr = RecordPtrT; using bhkEntityPtr = RecordPtrT; using bhkConvexShapePtr = RecordPtrT; + using bhkRigidBodyPtr = RecordPtrT; using hkPackedNiTriStripsDataPtr = RecordPtrT; using NiAccumulatorPtr = RecordPtrT; using NiInterpolatorPtr = RecordPtrT; @@ -218,6 +220,7 @@ namespace Nif using bhkShapeList = RecordListT; using bhkSerializableList = RecordListT; using bhkEntityList = RecordListT; + using bhkRigidBodyList = RecordListT; using NiControllerSequenceList = RecordListT; using NiPSysModifierList = RecordListT; using NiTriBasedGeomList = RecordListT; From 7c11d9acbcc6d059440f818db5c43a16bd8d2071 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 14:52:48 +0300 Subject: [PATCH 0211/2167] Read NiMeshPSysData and related modifiers --- components/nif/data.cpp | 2 +- components/nif/niffile.cpp | 3 +++ components/nif/particle.cpp | 48 ++++++++++++++++++++++++++++++++++++ components/nif/particle.hpp | 27 ++++++++++++++++++++ components/nif/record.hpp | 3 +++ components/nif/recordptr.hpp | 1 + 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 9aa61b4db7..62a2a9cce3 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -19,7 +19,7 @@ namespace Nif switch (recType) { case RC_NiPSysData: - // case RC_NiMeshPSysData: + case RC_NiMeshPSysData: case RC_BSStripPSysData: isPSysData = true; break; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index fb403bccb4..6ed6b8fc08 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -287,6 +287,7 @@ namespace Nif { "NiParticleSystem", &construct }, { "NiMeshParticleSystem", &construct }, { "NiPSysData", &construct }, + { "NiMeshPSysData", &construct }, // Geometry, Bethesda { "BSStripParticleSystem", &construct }, @@ -309,9 +310,11 @@ namespace Nif { "NiPSysPositionModifier", &construct }, { "NiPSysRotationModifier", &construct }, { "NiPSysSpawnModifier", &construct }, + { "NiPSysMeshUpdateModifier", &construct }, // Modifiers, Bethesda { "BSParentVelocityModifier", &construct }, + { "BSPSysHavokUpdateModifier", &construct }, { "BSPSysInheritVelocityModifier", &construct }, { "BSPSysLODModifier", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 95440af26b..d74473f468 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -230,6 +230,26 @@ namespace Nif } } + void NiMeshPSysData::read(NIFStream* nif) + { + NiPSysData::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 2, 0, 0)) + { + nif->read(mDefaultPoolSize); + nif->read(mFillPoolsOnLoad); + nif->readVector(mGenerations, nif->get()); + } + mParticleMeshes.read(nif); + } + + void NiMeshPSysData::post(Reader& nif) + { + NiPSysData::post(nif); + + mParticleMeshes.post(nif); + } + void BSStripPSysData::read(NIFStream* nif) { NiPSysData::read(nif); @@ -362,6 +382,20 @@ namespace Nif nif->read(mBaseScale); } + void NiPSysMeshUpdateModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + readRecordList(nif, mMeshes); + } + + void NiPSysMeshUpdateModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + postRecordList(nif, mMeshes); + } + void NiPSysRotationModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); @@ -405,6 +439,20 @@ namespace Nif nif->read(mDamping); } + void BSPSysHavokUpdateModifier::read(NIFStream* nif) + { + NiPSysMeshUpdateModifier::read(nif); + + mModifier.read(nif); + } + + void BSPSysHavokUpdateModifier::post(Reader& nif) + { + NiPSysMeshUpdateModifier::post(nif); + + mModifier.post(nif); + } + void BSPSysInheritVelocityModifier::read(NIFStream* nif) { NiPSysModifier::read(nif); diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index a2b0567b3b..45b6296891 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -147,6 +147,17 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiMeshPSysData : NiPSysData + { + uint32_t mDefaultPoolSize; + bool mFillPoolsOnLoad; + std::vector mGenerations; + NiAVObjectPtr mParticleMeshes; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct BSStripPSysData : NiPSysData { uint16_t mMaxPointCount; @@ -261,6 +272,14 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiPSysMeshUpdateModifier : NiPSysModifier + { + NiAVObjectList mMeshes; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiPSysRotationModifier : NiPSysModifier { float mRotationSpeed; @@ -295,6 +314,14 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSPSysHavokUpdateModifier : NiPSysMeshUpdateModifier + { + NiPSysModifierPtr mModifier; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct BSPSysInheritVelocityModifier : NiPSysModifier { NiAVObjectPtr mInheritObject; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 1b6ea1e99b..332647a661 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -101,6 +101,7 @@ namespace Nif RC_BSParentVelocityModifier, RC_BSProceduralLightningController, RC_BSPSysArrayEmitter, + RC_BSPSysHavokUpdateModifier, RC_BSPSysInheritVelocityModifier, RC_BSPSysLODModifier, RC_BSPSysMultiTargetEmitterCtlr, @@ -183,6 +184,7 @@ namespace Nif RC_NiLookAtInterpolator, RC_NiMaterialColorController, RC_NiMaterialProperty, + RC_NiMeshPSysData, RC_NiMorphData, RC_NiMultiTargetTransformController, RC_NiNode, @@ -232,6 +234,7 @@ namespace Nif RC_NiPSysInitialRotAngleCtlr, RC_NiPSysInitialRotAngleVarCtlr, RC_NiPSysMeshEmitter, + RC_NiPSysMeshUpdateModifier, RC_NiPSysModifierActiveCtlr, RC_NiPSysPlanarCollider, RC_NiPSysPositionModifier, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 209ee010d6..b847f609a4 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -185,6 +185,7 @@ namespace Nif using NiPSysColliderPtr = RecordPtrT; using NiPSysColliderManagerPtr = RecordPtrT; using NiPSysEmitterCtlrDataPtr = RecordPtrT; + using NiPSysModifierPtr = RecordPtrT; using NiPSysSpawnModifierPtr = RecordPtrT; using NiBoolDataPtr = RecordPtrT; using NiSkinPartitionPtr = RecordPtrT; From 6b28f07537fb2cb77aad106469e793e2be6beb92 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 15:42:10 +0300 Subject: [PATCH 0212/2167] Read bhkPlaneShape and bhkMultiSphereShape --- components/nif/niffile.cpp | 2 ++ components/nif/physics.cpp | 23 +++++++++++++++++++++++ components/nif/physics.hpp | 28 ++++++++++++++++++++++++++++ components/nif/record.hpp | 2 ++ 4 files changed, 55 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6ed6b8fc08..682fef1987 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -421,9 +421,11 @@ namespace Nif { "bhkConvexVerticesShape", &construct }, { "bhkListShape", &construct }, { "bhkMoppBvTreeShape", &construct }, + { "bhkMultiSphereShape", &construct }, { "bhkNiTriStripsShape", &construct }, { "bhkPackedNiTriStripsShape", &construct }, { "hkPackedNiTriStripsData", &construct }, + { "bhkPlaneShape", &construct }, { "bhkSimpleShapePhantom", &construct }, { "bhkSphereShape", &construct }, { "bhkTransformShape", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 4eaf77f510..4969bf2cb9 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -716,6 +716,29 @@ namespace Nif nif->skip(12); // Unused } + void bhkHeightfieldShape::read(NIFStream* nif) + { + mHavokMaterial.read(nif); + } + + void bhkPlaneShape::read(NIFStream* nif) + { + bhkHeightfieldShape::read(nif); + + nif->skip(12); // Unused + mPlane = osg::Plane(nif->get()); + nif->read(mExtents); + nif->read(mCenter); + } + + void bhkMultiSphereShape::read(NIFStream* nif) + { + bhkSphereRepShape::read(nif); + + mShapeProperty.read(nif); + nif->readVector(mSpheres, nif->get()); + } + void bhkListShape::read(NIFStream* nif) { readRecordList(nif, mSubshapes); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index d4eaccfc94..a14d1d55f6 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -6,6 +6,7 @@ #include "recordptr.hpp" #include +#include #include #include @@ -676,9 +677,36 @@ namespace Nif void read(NIFStream* nif) override; }; + // Abstract shape that can collide with an array of spheres + struct bhkHeightfieldShape : bhkShape + { + HavokMaterial mHavokMaterial; + + void read(NIFStream* nif) override; + }; + + // A plane bounded by an AABB + struct bhkPlaneShape : bhkHeightfieldShape + { + osg::Plane mPlane; + osg::Vec4f mExtents; + osg::Vec4f mCenter; + + void read(NIFStream* nif) override; + }; + // A sphere using bhkSphereShape = bhkConvexShape; + // Multiple spheres + struct bhkMultiSphereShape : bhkSphereRepShape + { + bhkWorldObjCInfoProperty mShapeProperty; + std::vector mSpheres; + + void read(NIFStream* nif) override; + }; + // A list of shapes struct bhkListShape : public bhkShapeCollection { diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 332647a661..331c2ff645 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -56,8 +56,10 @@ namespace Nif RC_bhkListShape, RC_bhkMalleableConstraint, RC_bhkMoppBvTreeShape, + RC_bhkMultiSphereShape, RC_bhkNiTriStripsShape, RC_bhkPackedNiTriStripsShape, + RC_bhkPlaneShape, RC_bhkPhysicsSystem, RC_bhkPrismaticConstraint, RC_bhkRagdollConstraint, From 01cd7e715b9cc473a9fb8df4478d6ea3ad01b306 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Sep 2023 16:43:40 +0300 Subject: [PATCH 0213/2167] Fix formatting --- components/nif/niffile.cpp | 6 ++++-- components/nif/physics.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 682fef1987..0106f47e2f 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -361,7 +361,8 @@ namespace Nif { "NiPSysModifierActiveCtlr", &construct }, // Modifier controllers, Bethesda - { "BSPSysMultiTargetEmitterCtlr", &construct }, + { "BSPSysMultiTargetEmitterCtlr", + &construct }, // Modifier controller data, Gamebryo { "NiPSysEmitterCtlrData", &construct }, @@ -396,7 +397,8 @@ namespace Nif // Constraint records, Bethesda { "bhkBallAndSocketConstraint", &construct }, - { "bhkBallSocketConstraintChain", &construct }, + { "bhkBallSocketConstraintChain", + &construct }, { "bhkHingeConstraint", &construct }, { "bhkLimitedHingeConstraint", &construct }, { "bhkRagdollConstraint", &construct }, diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index a14d1d55f6..6f7b14f1ee 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -5,8 +5,8 @@ #include "record.hpp" #include "recordptr.hpp" -#include #include +#include #include #include From e22654baa78a78515d8578e2e61b9a921d710ff9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 14:00:44 +0300 Subject: [PATCH 0214/2167] Read bhkMeshShape --- components/nif/niffile.cpp | 1 + components/nif/physics.cpp | 18 ++++++++++++++++++ components/nif/physics.hpp | 12 ++++++++++++ components/nif/record.hpp | 1 + 4 files changed, 32 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 0106f47e2f..53e61ee0ff 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -423,6 +423,7 @@ namespace Nif { "bhkConvexVerticesShape", &construct }, { "bhkListShape", &construct }, { "bhkMoppBvTreeShape", &construct }, + { "bhkMeshShape", &construct }, { "bhkMultiSphereShape", &construct }, { "bhkNiTriStripsShape", &construct }, { "bhkPackedNiTriStripsShape", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 4969bf2cb9..e55cc436eb 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -731,6 +731,24 @@ namespace Nif nif->read(mCenter); } + void bhkMeshShape::read(NIFStream* nif) + { + nif->skip(8); // Unknown + nif->read(mRadius); + nif->skip(8); // Unknown + nif->read(mScale); + mShapeProperties.resize(nif->get()); + for (bhkWorldObjCInfoProperty& property : mShapeProperties) + property.read(nif); + nif->skip(12); // Unknown + readRecordList(nif, mDataList); + } + + void bhkMeshShape::post(Reader& nif) + { + postRecordList(nif, mDataList); + } + void bhkMultiSphereShape::read(NIFStream* nif) { bhkSphereRepShape::read(nif); diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 6f7b14f1ee..5fa01a8248 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -695,6 +695,18 @@ namespace Nif void read(NIFStream* nif) override; }; + /// A shape based on triangle strips + struct bhkMeshShape : bhkShape + { + float mRadius; + osg::Vec4f mScale; + std::vector mShapeProperties; + NiTriStripsDataList mDataList; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + // A sphere using bhkSphereShape = bhkConvexShape; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 331c2ff645..c1ae8d61e5 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -55,6 +55,7 @@ namespace Nif RC_bhkLimitedHingeConstraint, RC_bhkListShape, RC_bhkMalleableConstraint, + RC_bhkMeshShape, RC_bhkMoppBvTreeShape, RC_bhkMultiSphereShape, RC_bhkNiTriStripsShape, From a18601d6e0bea824cd6ce3e855a05a7676e5dad8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 14:21:15 +0300 Subject: [PATCH 0215/2167] Finish bhkPhantom hierarchy, read bhkAabbPhantom --- components/nif/niffile.cpp | 5 ++++- components/nif/physics.cpp | 11 ++++++++++- components/nif/physics.hpp | 21 ++++++++++++++++++++- components/nif/record.hpp | 1 + 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 53e61ee0ff..a80453959e 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -429,10 +429,13 @@ namespace Nif { "bhkPackedNiTriStripsShape", &construct }, { "hkPackedNiTriStripsData", &construct }, { "bhkPlaneShape", &construct }, - { "bhkSimpleShapePhantom", &construct }, { "bhkSphereShape", &construct }, { "bhkTransformShape", &construct }, + // Phantom records, Bethesda + { "bhkAabbPhantom", &construct }, + { "bhkSimpleShapePhantom", &construct }, + // Physics system records, Bethesda { "bhkPhysicsSystem", &construct }, { "bhkRagdollSystem", &construct }, diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index e55cc436eb..616e5721de 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -846,9 +846,18 @@ namespace Nif mBodyFlags = nif->get(); } + void bhkAabbPhantom::read(NIFStream* nif) + { + bhkPhantom::read(nif); + + nif->skip(8); // Unused + nif->read(mAabbMin); + nif->read(mAabbMax); + } + void bhkSimpleShapePhantom::read(NIFStream* nif) { - bhkWorldObject::read(nif); + bhkShapePhantom::read(nif); nif->skip(8); // Unused std::array mat; diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index 5fa01a8248..ff8e23313a 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -769,7 +769,26 @@ namespace Nif void read(NIFStream* nif) override; }; - struct bhkSimpleShapePhantom : public bhkWorldObject + // Abstract non-physical object that receives collision events + struct bhkPhantom : bhkWorldObject + { + }; + + // A Phantom with an AABB + struct bhkAabbPhantom : bhkPhantom + { + osg::Vec4f mAabbMin, mAabbMax; + + void read(NIFStream* nif) override; + }; + + // Abstract Phantom with a collision shape + struct bhkShapePhantom : bhkPhantom + { + }; + + // A ShapePhantom with a transformation + struct bhkSimpleShapePhantom : bhkShapePhantom { osg::Matrixf mTransform; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index c1ae8d61e5..4b05e15da5 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,7 @@ namespace Nif { RC_MISSING = 0, RC_AvoidNode, + RC_bhkAabbPhantom, RC_bhkBallAndSocketConstraint, RC_bhkBallSocketConstraintChain, RC_bhkBlendCollisionObject, From 67e24a0ffea5089969c6db3a7df750328685db25 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 14:36:54 +0300 Subject: [PATCH 0216/2167] Read all Bethesda Havok Action records --- components/nif/niffile.cpp | 4 ++++ components/nif/physics.cpp | 32 ++++++++++++++++++++++++++++++++ components/nif/physics.hpp | 33 +++++++++++++++++++++++++++++++++ components/nif/record.hpp | 2 ++ 4 files changed, 71 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index a80453959e..57bd45ca4d 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -440,6 +440,10 @@ namespace Nif { "bhkPhysicsSystem", &construct }, { "bhkRagdollSystem", &construct }, + // Action records + { "bhkLiquidAction", &construct }, + { "bhkOrientHingedBodyAction", &construct }, + // PROPERTIES // 4.0.0.2 diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index 616e5721de..139f6dda41 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -954,4 +954,36 @@ namespace Nif nif->read(mRemoveWhenBroken); } + void bhkUnaryAction::read(NIFStream* nif) + { + mEntity.read(nif); + nif->skip(8); // Unused + } + + void bhkUnaryAction::post(Reader& nif) + { + mEntity.post(nif); + } + + void bhkLiquidAction::read(NIFStream* nif) + { + nif->skip(12); // Unused + nif->read(mInitialStickForce); + nif->read(mStickStrength); + nif->read(mNeighborDistance); + nif->read(mNeighborStrength); + } + + void bhkOrientHingedBodyAction::read(NIFStream* nif) + { + bhkUnaryAction::read(nif); + + nif->skip(8); // Unused + nif->read(mHingeAxisLS); + nif->read(mForwardLS); + nif->read(mStrength); + nif->read(mDamping); + nif->skip(8); // Unused + } + } // Namespace diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index ff8e23313a..4b8571e20e 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -875,5 +875,38 @@ namespace Nif void read(NIFStream* nif) override; }; + // Abstract action applied during the simulation + struct bhkAction : bhkSerializable + { + }; + + struct bhkUnaryAction : bhkAction + { + bhkRigidBodyPtr mEntity; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct bhkLiquidAction : bhkAction + { + float mInitialStickForce; + float mStickStrength; + float mNeighborDistance; + float mNeighborStrength; + + void read(NIFStream* nif) override; + }; + + struct bhkOrientHingedBodyAction : bhkUnaryAction + { + osg::Vec4f mHingeAxisLS; + osg::Vec4f mForwardLS; + float mStrength; + float mDamping; + + void read(NIFStream* nif) override; + }; + } // Namespace #endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 4b05e15da5..6c7f802f8c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -54,12 +54,14 @@ namespace Nif RC_bhkConvexVerticesShape, RC_bhkHingeConstraint, RC_bhkLimitedHingeConstraint, + RC_bhkLiquidAction, RC_bhkListShape, RC_bhkMalleableConstraint, RC_bhkMeshShape, RC_bhkMoppBvTreeShape, RC_bhkMultiSphereShape, RC_bhkNiTriStripsShape, + RC_bhkOrientHingedBodyAction, RC_bhkPackedNiTriStripsShape, RC_bhkPlaneShape, RC_bhkPhysicsSystem, From 052fb416c61f9310806d0eb9351168311db68e66 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 14:47:37 +0300 Subject: [PATCH 0217/2167] Read BSSegmentedTriShape --- components/nif/niffile.cpp | 1 + components/nif/node.cpp | 14 ++++++++++++++ components/nif/node.hpp | 14 ++++++++++++++ components/nif/record.hpp | 1 + 4 files changed, 30 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 57bd45ca4d..d5faebb55f 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -272,6 +272,7 @@ namespace Nif { "BSDynamicTriShape", &construct }, { "BSLODTriShape", &construct }, { "BSMeshLODTriShape", &construct }, + { "BSSegmentedTriShape", &construct }, // PARTICLES diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 9a40acc99a..860e75671f 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -196,6 +196,7 @@ namespace Nif { case RC_NiTriShape: case RC_BSLODTriShape: + case RC_BSSegmentedTriShape: if (mData->recType != RC_NiTriShapeData) mData = NiGeometryDataPtr(nullptr); break; @@ -217,6 +218,19 @@ namespace Nif } } + void BSSegmentedTriShape::read(NIFStream* nif) + { + NiTriShape::read(nif); + + mSegments.resize(nif->get()); + for (SegmentData& segment : mSegments) + { + nif->read(segment.mFlags); + nif->read(segment.mStartIndex); + nif->read(segment.mNumTriangles); + } + } + void BSLODTriShape::read(NIFStream* nif) { NiTriBasedGeom::read(nif); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index d2857752c3..f560932e22 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -161,6 +161,20 @@ namespace Nif { }; + struct BSSegmentedTriShape : NiTriShape + { + struct SegmentData + { + uint8_t mFlags; + uint32_t mStartIndex; + uint32_t mNumTriangles; + }; + + std::vector mSegments; + + void read(NIFStream* nif); + }; + struct NiTriStrips : NiTriBasedGeom { }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 6c7f802f8c..00eec64b24 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -120,6 +120,7 @@ namespace Nif RC_BSStripPSysData, RC_BSRefractionFirePeriodController, RC_BSRefractionStrengthController, + RC_BSSegmentedTriShape, RC_BSShaderNoLightingProperty, RC_BSShaderPPLightingProperty, RC_BSShaderProperty, From 8c27dca1dff85f354d5bef1912d6687d5f8e4b1f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 19:29:28 +0300 Subject: [PATCH 0218/2167] ESM4: add a way to get the current form version Differentiate between Fallout 4 and TES4 version 1.0 plugins --- components/esm/common.hpp | 5 +++-- components/esm4/loadnavi.cpp | 5 ++--- components/esm4/loadnavm.cpp | 2 +- components/esm4/loadnpc.cpp | 2 +- components/esm4/loadrace.cpp | 2 +- components/esm4/reader.cpp | 9 --------- components/esm4/reader.hpp | 3 +++ 7 files changed, 11 insertions(+), 17 deletions(-) diff --git a/components/esm/common.hpp b/components/esm/common.hpp index c0e6151e24..90c60ad6ab 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -25,12 +25,13 @@ namespace ESM VER_120 = 0x3f99999a, // TES3 VER_130 = 0x3fa66666, // TES3 VER_080 = 0x3f4ccccd, // TES4 - VER_100 = 0x3f800000, // TES4 + VER_100 = 0x3f800000, // TES4, FO4 VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney VER_133 = 0x3faa3d71, // FONV HonestHearts VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 - VER_170 = 0x3fd9999a // TES5 + VER_170 = 0x3fd9999a, // TES5 + VER_095 = 0x3f733333, // FO4 }; // Defines another files (esm or esp) that this file depends upon. diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 09868d258f..5b73af606e 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -249,9 +249,8 @@ void ESM4::Navigation::load(ESM4::Reader& reader) } case ESM4::SUB_NVPP: { - // FIXME: this is both the version for FO4 and for some TES4 files - // How to distinguish? - if (esmVer == ESM::VER_100) + // FIXME: FO4 updates the format + if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 6298964f28..828fd77ca1 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -213,7 +213,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) { // See FIXME in ESM4::Navigation::load. // FO4 updates the format - if (esmVer == ESM::VER_100) + if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 65292e63a8..251af13630 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -39,7 +39,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); - mIsTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + mIsTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100) && !reader.hasFormVersion(); mIsFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; // mIsTES5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; // WARN: FO3 is also VER_094 diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 28f91787e6..7434a7f87f 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -38,7 +38,7 @@ void ESM4::Race::load(ESM4::Reader& reader) mFlags = reader.hdr().record.flags; std::uint32_t esmVer = reader.esmVersion(); - bool isTES4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; + bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100) && !reader.hasFormVersion(); bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; bool isFO3 = false; diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index f81a971e15..ce7f534786 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -201,15 +201,6 @@ namespace ESM4 // restart from the beginning (i.e. "TES4" record header) mStream->seekg(0, mStream->beg); -#if 0 - unsigned int esmVer = mHeader.mData.version.ui; - bool isTes4 = esmVer == ESM::VER_080 || esmVer == ESM::VER_100; - //bool isTes5 = esmVer == ESM::VER_094 || esmVer == ESM::VER_170; - //bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; - - // TES4 header size is 4 bytes smaller than TES5 header - mCtx.recHeaderSize = isTes4 ? sizeof(ESM4::RecordHeader) - 4 : sizeof(ESM4::RecordHeader); -#endif getRecordHeader(); if (mCtx.recordHeader.record.typeId == REC_TES4) { diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index a095c23e85..e18c614885 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -249,6 +249,9 @@ namespace ESM4 inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } inline unsigned int numRecords() const { return mHeader.mData.records; } + inline bool hasFormVersion() const { return mCtx.recHeaderSize == sizeof(RecordHeader); } + inline unsigned int formVersion() const { return mCtx.recordHeader.record.version; } + void buildLStringIndex(); void getLocalizedString(std::string& str); From 9f8f2dd925450c6ec41bf498be44af9dcdb03830 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 20:24:48 +0300 Subject: [PATCH 0219/2167] Use parent worldspace terrain when requested --- apps/openmw/mwrender/landmanager.cpp | 10 +++++++++- apps/openmw/mwrender/terrainstorage.cpp | 9 +++++++++ components/esm4/loadwrld.cpp | 4 ++++ components/esm4/loadwrld.hpp | 22 +++++++++++++--------- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index 835fb9b204..f8a3ebd962 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -20,10 +21,17 @@ namespace MWRender osg::ref_ptr LandManager::getLand(ESM::ExteriorCellLocation cellIndex) { + const MWBase::World& world = *MWBase::Environment::get().getWorld(); + if (ESM::isEsm4Ext(cellIndex.mWorldspace)) + { + const ESM4::World* worldspace = world.getStore().get().find(cellIndex.mWorldspace); + if (!worldspace->mParent.isZeroOrUnset() && worldspace->mParentUseFlags & ESM4::World::UseFlag_Land) + cellIndex.mWorldspace = worldspace->mParent; + } + if (const std::optional> obj = mCache->getRefFromObjectCacheOrNone(cellIndex)) return static_cast(obj->get()); - const MWBase::World& world = *MWBase::Environment::get().getWorld(); osg::ref_ptr landObj = nullptr; if (ESM::isEsm4Ext(cellIndex.mWorldspace)) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 11fdbd774f..a20da97f0f 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,6 +1,7 @@ #include "terrainstorage.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -33,6 +34,10 @@ namespace MWRender if (ESM::isEsm4Ext(cellLocation.mWorldspace)) { + const ESM4::World* worldspace = esmStore.get().find(cellLocation.mWorldspace); + if (!worldspace->mParent.isZeroOrUnset() && worldspace->mParentUseFlags & ESM4::World::UseFlag_Land) + cellLocation.mWorldspace = worldspace->mParent; + return esmStore.get().search(cellLocation) != nullptr; } else @@ -64,6 +69,10 @@ namespace MWRender if (ESM::isEsm4Ext(worldspace)) { + const ESM4::World* worldRec = esmStore.get().find(worldspace); + if (!worldRec->mParent.isZeroOrUnset() && worldRec->mParentUseFlags & ESM4::World::UseFlag_Land) + worldspace = worldRec->mParent; + const auto& lands = esmStore.get().getLands(); for (const auto& [landPos, _] : lands) { diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index c0a3044437..b29cb37eb5 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -185,6 +185,10 @@ void ESM4::World::load(ESM4::Reader& reader) mWaterLevel = 0.f; } } + + // TES4 doesn't define PNAM. Exact parent worldspace behavior needs research + if (!reader.hasFormVersion()) + mParentUseFlags = 0xFFFF; } } diff --git a/components/esm4/loadwrld.hpp b/components/esm4/loadwrld.hpp index d8b0ba2177..f951ce4e8b 100644 --- a/components/esm4/loadwrld.hpp +++ b/components/esm4/loadwrld.hpp @@ -55,6 +55,18 @@ namespace ESM4 WLD_NoGrass = 0x80 // No Grass }; + enum UseFlags + { + UseFlag_Land = 0x01, + UseFlag_LOD = 0x02, + UseFlag_Map = 0x04, + UseFlag_Water = 0x08, + UseFlag_Climate = 0x10, + UseFlag_Imagespace = 0x20, // Unused in TES5 + UseFlag_SkyCell = 0x40, + // cc9cii: 0x80 == needs water adjustment? Set for WastelandNV + }; + struct REFRcoord { ESM::FormId formId; @@ -115,15 +127,7 @@ namespace ESM4 // ---------------------- ESM::FormId mMusic; - // 0x01 use Land data - // 0x02 use LOD data - // 0x04 use Map data - // 0x08 use Water data - // 0x10 use Climate data - // 0x20 use Image Space data (Climate for TES5) - // 0x40 use SkyCell (TES5) - // 0x80 needs water adjustment (this isn't for parent I think? FONV only set for wastelandnv) - std::uint16_t mParentUseFlags; // FO3/FONV + std::uint16_t mParentUseFlags{ 0 }; // cache formId's of children (e.g. CELL, ROAD) std::vector mCells; From 8d655054f127416d915f6e8f992c2fb08554bace Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Sep 2023 20:45:39 +0300 Subject: [PATCH 0220/2167] esmtool: Print human-readable ESM4 file format version --- apps/esmtool/tes4.cpp | 2 +- components/esm4/reader.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/esmtool/tes4.cpp b/apps/esmtool/tes4.cpp index 6791694a7c..5b657da573 100644 --- a/apps/esmtool/tes4.cpp +++ b/apps/esmtool/tes4.cpp @@ -562,7 +562,7 @@ namespace EsmTool { std::cout << "Author: " << reader.getAuthor() << '\n' << "Description: " << reader.getDesc() << '\n' - << "File format version: " << reader.esmVersion() << '\n'; + << "File format version: " << reader.esmVersionF() << '\n'; if (const std::vector& masterData = reader.getGameFiles(); !masterData.empty()) { diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index e18c614885..c63dbd1548 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -247,6 +247,7 @@ namespace ESM4 void setRecHeaderSize(const std::size_t size); inline unsigned int esmVersion() const { return mHeader.mData.version.ui; } + inline float esmVersionF() const { return mHeader.mData.version.f; } inline unsigned int numRecords() const { return mHeader.mData.records; } inline bool hasFormVersion() const { return mCtx.recHeaderSize == sizeof(RecordHeader); } From 641f34a3c9f45718f14deac16ea837282917ec7e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 25 Sep 2023 21:01:32 +0200 Subject: [PATCH 0221/2167] Treat teleportation out of the draft cell as object creation --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 3 ++- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwlua/cellbindings.cpp | 4 ++++ apps/openmw/mwlua/objectbindings.cpp | 29 ++++++++++++++++++++++------ apps/openmw/mwworld/cellstore.cpp | 11 +++++------ apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/class.cpp | 3 ++- files/lua_api/openmw/world.lua | 1 + 9 files changed, 40 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index df1ada96f4..9b705b805f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -692,7 +692,7 @@ namespace MWClass if (newPtr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - newPtr.getContainerStore()->setPtr(newPtr); + newPtr.getClass().getContainerStore(newPtr).setPtr(newPtr); } return newPtr; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 6c517e3dde..c5f9de23b1 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -205,7 +205,8 @@ namespace MWClass newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getRefData().setCount(count); } - newPtr.getCellRef().unsetRefNum(); + if (ptr.getCell() != &MWBase::Environment::get().getWorldModel()->getDraftCell()) + newPtr.getCellRef().unsetRefNum(); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); return newPtr; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1e7dae3600..27d72c61b7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1338,7 +1338,7 @@ namespace MWClass if (newPtr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - newPtr.getContainerStore()->setPtr(newPtr); + newPtr.getClass().getContainerStore(newPtr).setPtr(newPtr); } return newPtr; } diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 48c7141ab8..9e779b0da4 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -198,6 +199,9 @@ namespace MWLua case ESM::REC_STAT: cell.mStore->template forEachType(visitor); break; + case ESM::REC_LEVC: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_ACTI4: cell.mStore->template forEachType(visitor); diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ee746001ae..c607dc9d14 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -77,20 +77,25 @@ namespace MWLua return &wm->getExterior(ESM::positionToExteriorCellLocation(pos.x(), pos.y(), worldspace)); } - void teleportPlayer( - MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot, bool placeOnGround) + ESM::Position toPos(const osg::Vec3f& pos, const osg::Vec3f& rot) { - MWBase::World* world = MWBase::Environment::get().getWorld(); ESM::Position esmPos; static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); std::memcpy(esmPos.pos, &pos, sizeof(osg::Vec3f)); std::memcpy(esmPos.rot, &rot, sizeof(osg::Vec3f)); + return esmPos; + } + + void teleportPlayer( + MWWorld::CellStore* destCell, const osg::Vec3f& pos, const osg::Vec3f& rot, bool placeOnGround) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptr = world->getPlayerPtr(); auto& stats = ptr.getClass().getCreatureStats(ptr); stats.land(true); stats.setTeleported(true); world->getPlayer().setTeleported(true); - world->changeToCell(destCell->getCell()->getId(), esmPos, false); + world->changeToCell(destCell->getCell()->getId(), toPos(pos, rot), false); MWWorld::Ptr newPtr = world->getPlayerPtr(); world->moveObject(newPtr, pos); world->rotateObject(newPtr, rot); @@ -103,6 +108,7 @@ namespace MWLua const osg::Vec3f& rot, bool placeOnGround) { MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel(); const MWWorld::Class& cls = ptr.getClass(); if (cls.isActor()) { @@ -110,8 +116,19 @@ namespace MWLua stats.land(false); stats.setTeleported(true); } - MWWorld::Ptr newPtr = world->moveObject(ptr, destCell, pos); - world->rotateObject(newPtr, rot, MWBase::RotationFlag_none); + MWWorld::Ptr newPtr; + if (ptr.getCell() == &wm->getDraftCell()) + { + newPtr = world->placeObject(ptr, destCell, toPos(pos, rot)); + ptr.getCellRef().unsetRefNum(); + ptr.getRefData().setLuaScripts(nullptr); + ptr.getRefData().setCount(0); + } + else + { + newPtr = world->moveObject(ptr, destCell, pos); + world->rotateObject(newPtr, rot, MWBase::RotationFlag_none); + } if (placeOnGround) world->adjustPosition(newPtr, true); if (cls.isDoor()) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index f7ec3ddcba..a41fbb150a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1235,13 +1235,12 @@ namespace MWWorld clearCorpse(ptr, mStore); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) - { - Ptr ptr = getCurrentPtr(&*it); + forEachType([](Ptr ptr) { // no need to clearCorpse, handled as part of get() - ptr.getClass().respawn(ptr); - } + if (!ptr.getRefData().isDeleted()) + ptr.getClass().respawn(ptr); + return false; + }); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 23bd071ff1..94d0fd6d67 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -262,7 +262,7 @@ namespace MWWorld /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// completed? template - bool forEachType(Visitor& visitor) + bool forEachType(Visitor&& visitor) { if (mState != State_Loaded) return false; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index de3c2b011d..c5e30fc5c3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -372,7 +372,8 @@ namespace MWWorld MWWorld::Ptr Class::copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); - newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference + if (ptr.getCell() != &MWBase::Environment::get().getWorldModel()->getDraftCell()) + newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference newPtr.getRefData().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index c55860ee26..97f596e4a6 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -134,6 +134,7 @@ --- -- Create a new instance of the given record. -- After creation the object is in the disabled state. Use :teleport to place to the world or :moveInto to put it into a container or an inventory. +-- Note that dynamically created creatures, NPCs, and container inventories will not respawn. -- @function [parent=#world] createObject -- @param #string recordId Record ID in lowercase -- @param #number count (optional, 1 by default) The number of objects in stack From 442c03237340e5e2cce7e9f7d79679c176794dda Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Sep 2023 14:39:38 +0400 Subject: [PATCH 0222/2167] Decouple rendering simulation time from Lua simulation time (bug 7576) --- apps/openmw/engine.cpp | 5 ++++- apps/openmw/mwworld/datetimemanager.hpp | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index df89cd3eb6..ee2ce7ae3e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -945,7 +945,7 @@ void OMW::Engine::go() .count() * timeManager.getSimulationTimeScale(); - mViewer->advance(timeManager.getSimulationTime()); + mViewer->advance(timeManager.getRenderingSimulationTime()); if (!frame(dt)) { @@ -954,7 +954,10 @@ void OMW::Engine::go() } timeManager.updateIsPaused(); if (!timeManager.isPaused()) + { timeManager.setSimulationTime(timeManager.getSimulationTime() + dt); + timeManager.setRenderingSimulationTime(timeManager.getRenderingSimulationTime() + dt); + } if (stats) { diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index f89894292f..af62d9ba3f 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,7 +29,11 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio - // Simulation time (the number of seconds passed from the beginning of the game). + // Rendering simulation time (summary simulation time of rendering frames since application start). + double getRenderingSimulationTime() const { return mRenderingSimulationTime; } + void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } + + // World simulation time (the number of seconds passed from the beginning of the game). double getSimulationTime() const { return mSimulationTime; } void setSimulationTime(double t) { mSimulationTime = t; } float getSimulationTimeScale() const { return mSimulationTimeScale; } @@ -64,6 +68,7 @@ namespace MWWorld float mGameHour = 0.f; float mGameTimeScale = 0.f; float mSimulationTimeScale = 1.0; + double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; From cde2b4931314a86db4197254e8b4c7f1642bf1c1 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 27 Sep 2023 07:30:27 +0000 Subject: [PATCH 0223/2167] Fix recordDraft isScroll for types.book in lua --- apps/openmw/mwlua/types/book.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 1323e09a7f..4fe2f9d071 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -60,7 +60,7 @@ namespace if (rec["value"] != sol::nil) book.mData.mValue = rec["value"]; if (rec["isScroll"] != sol::nil) - book.mData.mIsScroll = rec["isScroll"]; + book.mData.mIsScroll = rec["isScroll"] ? 1 : 0; if (rec["skill"] != sol::nil) { From 6bfa3f78bda3ab1560b4805bd5e6eb67e8379b5a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 27 Sep 2023 07:30:52 +0000 Subject: [PATCH 0224/2167] Fix AttributeRecord and SkillRecord documentation in OpenMW.core --- files/lua_api/openmw/core.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 601e8ea27c..2c815d6dfc 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -656,7 +656,7 @@ -- @field #string id Effect ID -- @field #string icon Effect Icon Path -- @field #string name Localized name of the effect --- @field #string school Skill ID +-- @field #string school Skill ID that is this effect's school -- @field #number baseCost -- @field openmw.util#Color color -- @field #boolean harmful @@ -845,12 +845,14 @@ -- @param #string recordId -- @return #SkillRecord +--- -- @type AttributeRecord -- @field #string id Record id -- @field #string name Human-readable name -- @field #string description Human-readable description -- @field #string icon VFS path to the icon +--- -- @type SkillRecord -- @field #string id Record id -- @field #string name Human-readable name From 40c87837902358cff5033fb59bf02a0760073f4e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 26 Sep 2023 18:09:52 +0400 Subject: [PATCH 0225/2167] Handle actors processing range in Lua --- apps/openmw/mwlua/types/actor.cpp | 20 ++++++++++++++++++++ files/lua_api/openmw/types.lua | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 6edb363cd0..58c4d83cf4 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -4,9 +4,11 @@ #include #include +#include #include "apps/openmw/mwbase/mechanicsmanager.hpp" #include "apps/openmw/mwbase/windowmanager.hpp" +#include "apps/openmw/mwmechanics/actorutil.hpp" #include "apps/openmw/mwmechanics/creaturestats.hpp" #include "apps/openmw/mwmechanics/drawstate.hpp" #include "apps/openmw/mwworld/class.hpp" @@ -374,6 +376,24 @@ namespace MWLua result["halfExtents"] = agentBounds.mHalfExtents; return result; }; + actor["isInActorsProcessingRange"] = [](const Object& o) { + const MWWorld::Ptr player = MWMechanics::getPlayer(); + const auto& target = o.ptr(); + if (target == player) + return true; + + if (!target.getClass().isActor()) + throw std::runtime_error("Actor expected"); + + if (target.getCell()->getCell()->getWorldSpace() != player.getCell()->getCell()->getWorldSpace()) + return false; + + const int actorsProcessingRange = Settings::game().mActorsProcessingRange; + const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); + + const float dist = (playerPos - target.getRefData().getPosition().asVec3()).length(); + return dist <= actorsProcessingRange; + }; addActorStatsBindings(actor, context); addActorMagicBindings(actor, context); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index df6e17c6f4..e86ae993c6 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -15,6 +15,12 @@ -- @param openmw.core#GameObject actor -- @return #table with `shapeType` and `halfExtents` +--- +-- Check if given actor is in the actors processing range. +-- @function [parent=#Actor] isInActorsProcessingRange +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Whether the object is an actor. -- @function [parent=#Actor] objectIsInstance From 86127093badf527d75ad462edb1f2c55d9f23e48 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Sep 2023 16:47:44 +0200 Subject: [PATCH 0226/2167] drop atomic as we no longer need it as we droped boost::threads a long time ago --- CMakeLists.txt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43bbcfc2e1..a18ef3d5ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,7 +434,7 @@ if(HAVE_MULTIVIEW) add_definitions(-DOSG_HAS_MULTIVIEW) endif(HAVE_MULTIVIEW) -set(BOOST_COMPONENTS system program_options iostreams) +set(BOOST_COMPONENTS iostreams program_options system) if(WIN32) set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) if(MSVC) @@ -449,12 +449,9 @@ IF(BOOST_STATIC) endif() set(Boost_NO_BOOST_CMAKE ON) +set(Boost_NO_WARN_NEW_VERSIONS ON) # ignore warnings about new releases of boost -find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) - -if(Boost_VERSION_STRING VERSION_GREATER_EQUAL 1.77.0) - find_package(Boost 1.77.0 REQUIRED COMPONENTS atomic) -endif() +find_package(Boost 1.77.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.4.2 REQUIRED) From 2ac4cb6d58c5efdd1cc9e717c1bfdffb8be06edd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Sep 2023 16:49:24 +0200 Subject: [PATCH 0227/2167] 1.6.2 is old, but apparently still okay --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a18ef3d5ed..5d59e87344 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -451,7 +451,7 @@ endif() set(Boost_NO_BOOST_CMAKE ON) set(Boost_NO_WARN_NEW_VERSIONS ON) # ignore warnings about new releases of boost -find_package(Boost 1.77.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) +find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) find_package(MyGUI 3.4.2 REQUIRED) From e9ff87b553972ea4fa6352564c2085ee85492f64 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 27 Sep 2023 17:13:02 +0200 Subject: [PATCH 0228/2167] bump macos to use boost 1.83 which solves issues --- CI/before_install.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index a467b589da..5b305d66b6 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -34,7 +34,7 @@ qmake --version if [[ "${MACOS_AMD64}" ]]; then curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip else - curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20230722_arm64.zip -o ~/openmw-deps.zip + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20230920_arm64.zip -o ~/openmw-deps.zip fi unzip -o ~/openmw-deps.zip -d /tmp > /dev/null From 63d5bd6f8a4c84abee5aaf61c41d16e0f65ddab2 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 24 Sep 2023 12:20:09 +0200 Subject: [PATCH 0229/2167] Apply soft effect to nifs marked with soft effect flag (developed by Cody Glassman) --- components/nif/property.hpp | 2 ++ components/nifosg/nifloader.cpp | 3 +++ components/sceneutil/extradata.cpp | 13 ++++----- components/sceneutil/extradata.hpp | 4 +-- components/shader/shadervisitor.cpp | 7 +++++ .../shaders/compatibility/bs/nolighting.frag | 27 +++++++++++++++++++ .../shaders/compatibility/bs/nolighting.vert | 2 ++ 7 files changed, 48 insertions(+), 10 deletions(-) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 0d4a5b33c6..da908f2eab 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -232,6 +232,7 @@ namespace Nif enum BSLightingShaderFlags1 { BSLSFlag1_Falloff = 0x00000040, + BSLSFlag1_SoftEffect = 0x40000000, }; enum BSLightingShaderFlags2 @@ -354,6 +355,7 @@ namespace Nif void read(NIFStream* nif) override; bool useFalloff() const { return mShaderFlags1 & BSLSFlag1_Falloff; } + bool softEffect() const { return mShaderFlags1 & BSLSFlag1_SoftEffect; } bool doubleSided() const { return mShaderFlags2 & BSLSFlag2_DoubleSided; } bool treeAnim() const { return mShaderFlags2 & BSLSFlag2_TreeAnim; } }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7a8fd6afb2..fbcd2f52bd 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -2561,6 +2562,8 @@ namespace NifOsg polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); } + if (shaderprop->softEffect()) + SceneUtil::setupSoftEffect(*node, shaderprop->mFalloffDepth, true); break; } default: diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index e36efbad9c..d8d3e871dc 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -15,18 +15,12 @@ namespace SceneUtil { - void ProcessExtraDataVisitor::setupSoftEffect(osg::Node& node, float size, bool falloff) + void setupSoftEffect(osg::Node& node, float size, bool falloff) { - if (!mSceneMgr->getSoftParticles()) - return; - - const int unitSoftEffect - = mSceneMgr->getShaderManager().reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); osg::StateSet* stateset = node.getOrCreateStateSet(); - stateset->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); stateset->addUniform(new osg::Uniform("particleSize", size)); stateset->addUniform(new osg::Uniform("particleFade", falloff)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); @@ -36,6 +30,9 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { + if (!mSceneMgr->getSoftParticles()) + return; + std::string source; if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) @@ -64,4 +61,4 @@ namespace SceneUtil traverse(node); } -} \ No newline at end of file +} diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index 4568a5289b..ddcb9b6c5c 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -15,6 +15,8 @@ namespace osg namespace SceneUtil { + void setupSoftEffect(osg::Node& node, float size, bool falloff); + class ProcessExtraDataVisitor : public osg::NodeVisitor { public: @@ -27,8 +29,6 @@ namespace SceneUtil void apply(osg::Node& node) override; private: - void setupSoftEffect(osg::Node& node, float size, bool falloff); - Resource::SceneManager* mSceneMgr; }; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 0112a3a699..0a9d1c74e6 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -690,6 +690,13 @@ namespace Shader writableStateSet->setAttribute(new osg::ColorMaski(1, false, false, false, false)); } + if (reqs.mSoftParticles) + { + const int unitSoftEffect + = mShaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); + } + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); diff --git a/files/shaders/compatibility/bs/nolighting.frag b/files/shaders/compatibility/bs/nolighting.frag index 4a5c1c3b42..c5393d4732 100644 --- a/files/shaders/compatibility/bs/nolighting.frag +++ b/files/shaders/compatibility/bs/nolighting.frag @@ -14,6 +14,7 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif +varying vec3 passViewPos; varying vec3 passNormal; varying float euclideanDepth; varying float linearDepth; @@ -22,6 +23,7 @@ varying float passFalloff; uniform vec2 screenRes; uniform bool useFalloff; uniform float far; +uniform float near; uniform float alphaRef; #include "lib/material/alpha.glsl" @@ -30,6 +32,14 @@ uniform float alphaRef; #include "compatibility/fog.glsl" #include "compatibility/shadows_fragment.glsl" +#if @softParticles +#include "lib/particle/soft.glsl" + +uniform sampler2D opaqueDepthTex; +uniform float particleSize; +uniform bool particleFade; +#endif + void main() { #if @diffuseMap @@ -48,6 +58,23 @@ void main() gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); +#if !defined(FORCE_OPAQUE) && @softParticles + vec2 screenCoords = gl_FragCoord.xy / screenRes; + vec3 viewVec = normalize(passViewPos.xyz); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); + + gl_FragData[0].a *= calcSoftParticleFade( + viewVec, + passViewPos, + viewNormal, + near, + far, + texture2D(opaqueDepthTex, screenCoords).x, + particleSize, + particleFade + ); +#endif + #if defined(FORCE_OPAQUE) && FORCE_OPAQUE gl_FragData[0].a = 1.0; #endif diff --git a/files/shaders/compatibility/bs/nolighting.vert b/files/shaders/compatibility/bs/nolighting.vert index 27be3396ce..3b0fa7b626 100644 --- a/files/shaders/compatibility/bs/nolighting.vert +++ b/files/shaders/compatibility/bs/nolighting.vert @@ -15,6 +15,7 @@ varying vec2 diffuseMapUV; #endif varying vec3 passNormal; +varying vec3 passViewPos; varying float euclideanDepth; varying float linearDepth; varying float passFalloff; @@ -41,6 +42,7 @@ void main(void) #endif passColor = gl_Color; + passViewPos = viewPos.xyz; passNormal = gl_Normal.xyz; if (useFalloff) From 1d94527a19e6aedaf0900740e7f9d1c3ab7090be Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 27 Sep 2023 20:41:01 +0200 Subject: [PATCH 0230/2167] Avoid using settings in components to get actor model Settings::Values are initialized only for engine. Accessing them from other binaries leads to a crash. --- apps/opencs/model/world/actoradapter.cpp | 11 +++++++---- apps/openmw/CMakeLists.txt | 1 + .../openmw/mwrender}/actorutil.cpp | 2 +- .../openmw/mwrender}/actorutil.hpp | 6 +++--- apps/openmw/mwrender/animation.cpp | 13 +++++++------ apps/openmw/mwrender/npcanimation.cpp | 6 +++--- components/CMakeLists.txt | 2 +- 7 files changed, 23 insertions(+), 18 deletions(-) rename {components/sceneutil => apps/openmw/mwrender}/actorutil.cpp (97%) rename {components/sceneutil => apps/openmw/mwrender}/actorutil.hpp (52%) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 3a54bb3b0e..e68d3e833f 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include "data.hpp" @@ -129,11 +129,14 @@ namespace CSMWorld if (mCreature || !mSkeletonOverride.empty()) return "meshes\\" + mSkeletonOverride; - bool firstPerson = false; bool beast = mRaceData ? mRaceData->isBeast() : false; - bool werewolf = false; - return SceneUtil::getActorSkeleton(firstPerson, mFemale, beast, werewolf); + if (beast) + return Settings::Manager::getString("baseanimkna", "Models"); + else if (mFemale) + return Settings::Manager::getString("baseanimfemale", "Models"); + else + return Settings::Manager::getString("baseanim", "Models"); } ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 96d54949b1..535d854239 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -24,6 +24,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass navmeshmode precipitationocclusion ripples + actorutil ) add_openmw_dir (mwinput diff --git a/components/sceneutil/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp similarity index 97% rename from components/sceneutil/actorutil.cpp rename to apps/openmw/mwrender/actorutil.cpp index 63d3beccb7..6cef42d60f 100644 --- a/components/sceneutil/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -2,7 +2,7 @@ #include -namespace SceneUtil +namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool isFemale, bool isBeast, bool isWerewolf) { diff --git a/components/sceneutil/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp similarity index 52% rename from components/sceneutil/actorutil.hpp rename to apps/openmw/mwrender/actorutil.hpp index 038fc4e15e..bbffc4ad24 100644 --- a/components/sceneutil/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -1,9 +1,9 @@ -#ifndef OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP -#define OPENMW_COMPONENTS_SCENEUTIL_ACTORUTIL_HPP +#ifndef OPENMW_APPS_OPENMW_MWRENDER_ACTORUTIL_H +#define OPENMW_APPS_OPENMW_MWRENDER_ACTORUTIL_H #include -namespace SceneUtil +namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index aaf2c9390b..b49f382f66 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -34,7 +34,6 @@ #include -#include #include #include #include @@ -54,6 +53,7 @@ #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority +#include "actorutil.hpp" #include "rotatecontroller.hpp" #include "util.hpp" #include "vismask.hpp" @@ -1411,12 +1411,13 @@ namespace MWRender const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const ESM::Race* race = store.get().find(ref->mBase->mRace); - bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - bool isFemale = !ref->mBase->isMale(); + const bool firstPerson = false; + const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + const bool isFemale = !ref->mBase->isMale(); + const bool werewolf = false; - defaultSkeleton = SceneUtil::getActorSkeleton(false, isFemale, isBeast, false); - defaultSkeleton - = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath( + getActorSkeleton(firstPerson, isFemale, isBeast, werewolf), mResourceSystem->getVFS()); } } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 00f16e4ddc..cb7ef3626f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" +#include "actorutil.hpp" #include "postprocessor.hpp" #include "renderbin.hpp" #include "rotatecontroller.hpp" @@ -500,8 +500,8 @@ namespace MWRender bool is1stPerson = mViewMode == VM_FirstPerson; bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string defaultSkeleton = SceneUtil::getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf); - defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + const std::string defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath( + getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6b78e471e6..5ad7562e96 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -106,7 +106,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt + detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon ) From a401461a6471b303ba846e97ce9598fe1e10f054 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 27 Sep 2023 21:19:59 +0200 Subject: [PATCH 0231/2167] Update addedState in shadervisitor.cpp --- components/shader/shadervisitor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 0a9d1c74e6..1aa0f76e17 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -687,7 +687,9 @@ namespace Shader { if (reqs.mSoftParticles) defineMap["disableNormals"] = "1"; - writableStateSet->setAttribute(new osg::ColorMaski(1, false, false, false, false)); + auto colorMask = new osg::ColorMaski(1, false, false, false, false); + writableStateSet->setAttribute(colorMask); + addedState->setAttribute(colorMask); } if (reqs.mSoftParticles) @@ -695,6 +697,7 @@ namespace Shader const int unitSoftEffect = mShaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); + addedState->addUniform("opaqueDepthTex"); } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT From 33b57d9134f30f9e25b8a6acb1251d96cf3705e0 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Sep 2023 21:23:07 +0200 Subject: [PATCH 0232/2167] Use moveToCell and init mwscript --- apps/openmw/mwclass/misc.cpp | 3 +-- apps/openmw/mwlua/objectbindings.cpp | 12 +++++++++--- apps/openmw/mwworld/class.cpp | 3 +-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index c5f9de23b1..6c517e3dde 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -205,8 +205,7 @@ namespace MWClass newPtr = MWWorld::Ptr(cell.insert(ref), &cell); newPtr.getRefData().setCount(count); } - if (ptr.getCell() != &MWBase::Environment::get().getWorldModel()->getDraftCell()) - newPtr.getCellRef().unsetRefNum(); + newPtr.getCellRef().unsetRefNum(); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); return newPtr; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index c607dc9d14..b2d5824629 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -11,6 +11,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/localscripts.hpp" #include "../mwworld/player.hpp" #include "../mwworld/scene.hpp" #include "../mwworld/worldmodel.hpp" @@ -112,17 +113,22 @@ namespace MWLua const MWWorld::Class& cls = ptr.getClass(); if (cls.isActor()) { - auto& stats = ptr.getClass().getCreatureStats(ptr); + auto& stats = cls.getCreatureStats(ptr); stats.land(false); stats.setTeleported(true); } + const MWWorld::CellStore* srcCell = ptr.getCell(); MWWorld::Ptr newPtr; - if (ptr.getCell() == &wm->getDraftCell()) + if (srcCell == &wm->getDraftCell()) { - newPtr = world->placeObject(ptr, destCell, toPos(pos, rot)); + newPtr = cls.moveToCell(ptr, *destCell, toPos(pos, rot)); ptr.getCellRef().unsetRefNum(); ptr.getRefData().setLuaScripts(nullptr); ptr.getRefData().setCount(0); + ESM::RefId script = cls.getScript(newPtr); + if (!script.empty()) + world->getLocalScripts().add(script, newPtr); + world->addContainerScripts(newPtr, newPtr.getCell()); } else { diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c5e30fc5c3..de3c2b011d 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -372,8 +372,7 @@ namespace MWWorld MWWorld::Ptr Class::copyToCell(const ConstPtr& ptr, CellStore& cell, int count) const { Ptr newPtr = copyToCellImpl(ptr, cell); - if (ptr.getCell() != &MWBase::Environment::get().getWorldModel()->getDraftCell()) - newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference + newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference newPtr.getRefData().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); From f6626e36cf162013eee43e0919c10b25e3e4b5c4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Sep 2023 22:07:55 +0200 Subject: [PATCH 0233/2167] Unbreak respawns and fix #7588 --- apps/openmw/mwlua/objectbindings.cpp | 6 ++++++ apps/openmw/mwworld/cellstore.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index b2d5824629..0a1098f450 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -133,6 +133,12 @@ namespace MWLua else { newPtr = world->moveObject(ptr, destCell, pos); + if (MWBase::Environment::get().getWorldScene()->isCellActive(*srcCell)) + { + ESM::RefId script = cls.getScript(newPtr); + if (!script.empty()) + world->getLocalScripts().add(script, newPtr); + } world->rotateObject(newPtr, rot, MWBase::RotationFlag_none); } if (placeOnGround) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index a41fbb150a..8fb86f2cad 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1239,7 +1239,7 @@ namespace MWWorld // no need to clearCorpse, handled as part of get() if (!ptr.getRefData().isDeleted()) ptr.getClass().respawn(ptr); - return false; + return true; }); } } From 7594d9402407c2420141e6fcc2fcb3a47c926a68 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 27 Sep 2023 22:24:28 +0200 Subject: [PATCH 0234/2167] Prevent re-adding local scripts --- apps/openmw/mwlua/objectbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 0a1098f450..ef25dadfab 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -133,7 +133,7 @@ namespace MWLua else { newPtr = world->moveObject(ptr, destCell, pos); - if (MWBase::Environment::get().getWorldScene()->isCellActive(*srcCell)) + if (srcCell == destCell) { ESM::RefId script = cls.getScript(newPtr); if (!script.empty()) From 9aa992eede3d6e0ee711eac7fcf9709b94bf4fca Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Wed, 27 Sep 2023 17:19:08 -0700 Subject: [PATCH 0235/2167] add more lua bindings for encumbrance and capacity --- apps/openmw/mwlua/types/actor.cpp | 5 +++++ apps/openmw/mwlua/types/container.cpp | 6 ++++-- apps/openmw/mwlua/types/npc.cpp | 4 ++++ files/lua_api/openmw/types.lua | 16 ++++++++++++++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 6edb363cd0..0de5603ecf 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -375,6 +375,11 @@ namespace MWLua return result; }; + actor["getEncumbrance"] = [](const Object& actor) -> float { + const MWWorld::Ptr ptr = actor.ptr(); + return ptr.getClass().getEncumbrance(ptr); + }; + addActorStatsBindings(actor, context); addActorMagicBindings(actor, context); } diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index dd246a73d5..25a1c1adce 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -31,14 +31,16 @@ namespace MWLua container["content"] = sol::overload([](const LObject& o) { return Inventory{ o }; }, [](const GObject& o) { return Inventory{ o }; }); container["inventory"] = container["content"]; - container["encumbrance"] = [](const Object& obj) -> float { + container["getEncumbrance"] = [](const Object& obj) -> float { const MWWorld::Ptr& ptr = containerPtr(obj); return ptr.getClass().getEncumbrance(ptr); }; - container["capacity"] = [](const Object& obj) -> float { + container["encumbrance"] = container["getEncumbrance"]; // for compatibility; should be removed later + container["getCapacity"] = [](const Object& obj) -> float { const MWWorld::Ptr& ptr = containerPtr(obj); return ptr.getClass().getCapacity(ptr); }; + container["capacity"] = container["getCapacity"]; // for compatibility; should be removed later auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index a3bb82098d..25997b6468 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -305,5 +305,9 @@ namespace MWLua return res; }; + npc["getCapacity"] = [](const Object& actor) -> float { + const MWWorld::Ptr ptr = actor.ptr(); + return ptr.getClass().getCapacity(ptr); + }; } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index df6e17c6f4..e44673e13a 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -9,6 +9,12 @@ --- Common functions for Creature, NPC, and Player. -- @type Actor +--- +-- Get the total weight of everything the actor is carrying, plus modifications from magic effects. +-- @function [parent=#Actor] getEncumbrance +-- @param openmw.core#GameObject actor +-- @return #number + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds @@ -855,6 +861,12 @@ -- @param openmw.core#GameObject player The player that you want to check the disposition for. -- @return #number +--- +-- Get the total weight that the actor can carry. +-- @function [parent=#NPC] getCapacity +-- @param openmw.core#GameObject actor +-- @return #number + --- -- Whether the NPC or player is in the werewolf form at the moment. -- @function [parent=#NPC] isWerewolf @@ -1639,13 +1651,13 @@ --- -- Returns the total weight of everything in a container --- @function [parent=#Container] encumbrance +-- @function [parent=#Container] getEncumbrance -- @param openmw.core#GameObject object -- @return #number --- -- Returns the capacity of a container --- @function [parent=#Container] capacity +-- @function [parent=#Container] getCapacity -- @param openmw.core#GameObject object -- @return #number From 02de5e82d82c8a40f9aea4dc4e420188cad9596f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 28 Sep 2023 00:20:47 +0200 Subject: [PATCH 0236/2167] Take into account "Enable Parent" subrecord and disable ESM4 objects that should be initially disabled. --- apps/openmw/mwworld/cellstore.cpp | 27 ++++++++++++++++++++++++--- components/esm4/reference.hpp | 8 +++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index c7d0ae3165..bad2181a06 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -406,10 +406,31 @@ namespace MWWorld static void loadImpl(const R& ref, const MWWorld::ESMStore& esmStore, auto& list) { const MWWorld::Store& store = esmStore.get(); - if (const X* ptr = store.search(ref.mBaseObj)) - list.emplace_back(ref, ptr); - else + const X* ptr = store.search(ref.mBaseObj); + if (!ptr) + { Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mId << " (dropping reference)"; + return; + } + LiveCellRef liveCellRef(ref, ptr); + if (!ref.mEsp.parent.isZeroOrUnset()) + { + // Disable objects that are linked to an initially disabled parent. + // Actually when we will start working on Oblivion/Skyrim scripting we will need to: + // - use the current state of the parent instead of initial state of the parent + // - every time when the parent is enabled/disabled we should also enable/disable + // all objects that are linked to it. + // But for now we assume that the parent remains in its initial state. + const ESM4::Reference* parentRef = esmStore.get().searchStatic(ref.mEsp.parent); + if (parentRef) + { + bool parentDisabled = parentRef->mFlags & ESM4::Rec_Disabled; + bool inversed = ref.mEsp.flags & ESM4::EnableParent::Flag_Inversed; + if (parentDisabled != inversed) + liveCellRef.mData.disable(); + } + } + list.push_back(liveCellRef); } template diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp index debb39f839..9d6efdfd82 100644 --- a/components/esm4/reference.hpp +++ b/components/esm4/reference.hpp @@ -39,7 +39,13 @@ namespace ESM4 struct EnableParent { ESM::FormId parent; - std::uint32_t flags; // 0x0001 = Set Enable State Opposite Parent, 0x0002 = Pop In + std::uint32_t flags; + + enum Flags + { + Flag_Inversed = 0x1, // Set enable state opposite to the parent + Flag_PopIn = 0x2, + }; }; struct LODReference From 1ac0a3b25b4e96804d195f8830f5c638b74aad5a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Sep 2023 10:49:34 +0400 Subject: [PATCH 0237/2167] Update canvas size every frame when it is visible (bug 7603) --- CHANGELOG.md | 1 + apps/openmw/mwgui/settingswindow.cpp | 12 ++++++++++-- apps/openmw/mwgui/settingswindow.hpp | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abb10007ee..42db494f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default + Bug #7603: Scripts menu size is not updated properly Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b077d9f640..9fa842bfa9 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -230,6 +230,16 @@ namespace MWGui } } + void SettingsWindow::onFrame(float duration) + { + if (mScriptView->getVisible()) + { + const auto scriptsSize = mScriptAdapter->getSize(); + if (mScriptView->getCanvasSize() != scriptsSize) + mScriptView->setCanvasSize(scriptsSize); + } + } + void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value) { std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); @@ -1005,7 +1015,6 @@ namespace MWGui mScriptDisabled->setVisible(disabled); LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); - mScriptView->setCanvasSize(mScriptAdapter->getSize()); } void SettingsWindow::onScriptFilterChange(MyGUI::EditBox*) @@ -1022,7 +1031,6 @@ namespace MWGui mCurrentPage = *mScriptList->getItemDataAt(index); LuaUi::attachPageAt(mCurrentPage, mScriptAdapter); } - mScriptView->setCanvasSize(mScriptAdapter->getSize()); } void SettingsWindow::onRebindAction(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index ecf9bf0d94..1f96f7de54 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -14,6 +14,8 @@ namespace MWGui void onOpen() override; + void onFrame(float duration) override; + void updateControlsBox(); void updateLightSettings(); From 01aa67c792886f602b26ade4cfe6b26fc42402b6 Mon Sep 17 00:00:00 2001 From: PharisMods Date: Sun, 1 Oct 2023 00:54:24 +0000 Subject: [PATCH 0238/2167] Use thick border textures for thick border UI templates --- files/data/scripts/omw/mwui/borders.lua | 75 +++++++++++++------------ 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/files/data/scripts/omw/mwui/borders.lua b/files/data/scripts/omw/mwui/borders.lua index 5d056254fc..624ea8b54e 100644 --- a/files/data/scripts/omw/mwui/borders.lua +++ b/files/data/scripts/omw/mwui/borders.lua @@ -22,49 +22,52 @@ local cornerParts = { bottom_right = v2(1, 1), } -local borderSidePattern = 'textures/menu_thin_border_%s.dds' -local borderCornerPattern = 'textures/menu_thin_border_%s_corner.dds' +local borderSidePattern = 'textures/menu_%s_border_%s.dds' +local borderCornerPattern = 'textures/menu_%s_border_%s_corner.dds' local borderResources = {} -do +local borderPieces = {} +for _, thickness in ipairs{'thin', 'thick'} do + borderResources[thickness] = {} for k in pairs(sideParts) do - borderResources[k] = ui.texture{ path = borderSidePattern:format(k) } + borderResources[thickness][k] = ui.texture{ path = borderSidePattern:format(thickness, k) } end for k in pairs(cornerParts) do - borderResources[k] = ui.texture{ path = borderCornerPattern:format(k) } + borderResources[thickness][k] = ui.texture{ path = borderCornerPattern:format(thickness, k) } + end + + borderPieces[thickness] = {} + for k in pairs(sideParts) do + local horizontal = k == 'top' or k == 'bottom' + borderPieces[thickness][k] = { + type = ui.TYPE.Image, + props = { + resource = borderResources[thickness][k], + tileH = horizontal, + tileV = not horizontal, + }, + } + end + for k in pairs(cornerParts) do + borderPieces[thickness][k] = { + type = ui.TYPE.Image, + props = { + resource = borderResources[thickness][k], + }, + } end end -local borderPieces = {} -for k in pairs(sideParts) do - local horizontal = k == 'top' or k == 'bottom' - borderPieces[k] = { - type = ui.TYPE.Image, - props = { - resource = borderResources[k], - tileH = horizontal, - tileV = not horizontal, - }, - } -end -for k in pairs(cornerParts) do - borderPieces[k] = { - type = ui.TYPE.Image, - props = { - resource = borderResources[k], - }, - } -end - -local function borderTemplates(borderSize) +local function borderTemplates(thickness) + local borderSize = (thickness == 'thin') and constants.border or constants.thickBorder local borderV = v2(1, 1) * borderSize local result = {} result.horizontalLine = { type = ui.TYPE.Image, props = { - resource = borderResources.top, + resource = borderResources[thickness].top, tileH = true, tileV = false, size = v2(0, borderSize), @@ -75,7 +78,7 @@ local function borderTemplates(borderSize) result.verticalLine = { type = ui.TYPE.Image, props = { - resource = borderResources.left, + resource = borderResources[thickness].left, tileH = false, tileV = true, size = v2(borderSize, 0), @@ -90,7 +93,7 @@ local function borderTemplates(borderSize) local horizontal = k == 'top' or k == 'bottom' local direction = horizontal and v2(1, 0) or v2(0, 1) result.borders.content:add { - template = borderPieces[k], + template = borderPieces[thickness][k], props = { position = (direction - v) * borderSize, relativePosition = v, @@ -101,7 +104,7 @@ local function borderTemplates(borderSize) end for k, v in pairs(cornerParts) do result.borders.content:add { - template = borderPieces[k], + template = borderPieces[thickness][k], props = { position = -v * borderSize, relativePosition = v, @@ -126,7 +129,7 @@ local function borderTemplates(borderSize) local horizontal = k == 'top' or k == 'bottom' local direction = horizontal and v2(1, 0) or v2(0, 1) result.box.content:add { - template = borderPieces[k], + template = borderPieces[thickness][k], props = { position = (direction + v) * borderSize, relativePosition = v, @@ -137,7 +140,7 @@ local function borderTemplates(borderSize) end for k, v in pairs(cornerParts) do result.box.content:add { - template = borderPieces[k], + template = borderPieces[thickness][k], props = { position = v * borderSize, relativePosition = v, @@ -190,8 +193,8 @@ local function borderTemplates(borderSize) return result end -local thinBorders = borderTemplates(constants.border) -local thickBorders = borderTemplates(constants.thickBorder) +local thinBorders = borderTemplates('thin') +local thickBorders = borderTemplates('thick') return function(templates) for k, t in pairs(thinBorders) do @@ -200,4 +203,4 @@ return function(templates) for k, t in pairs(thickBorders) do templates[k .. 'Thick'] = t end -end \ No newline at end of file +end From b3c8a15af1c6d48f052d031b1da268ed88a79377 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Oct 2023 10:36:18 +0200 Subject: [PATCH 0239/2167] Parse navmesh render mode on reading settings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/debugbindings.cpp | 8 ++++---- apps/openmw/mwrender/navmesh.cpp | 12 ++++++------ apps/openmw/mwrender/navmesh.hpp | 9 ++++----- apps/openmw/mwrender/navmeshmode.cpp | 16 ---------------- apps/openmw/mwrender/navmeshmode.hpp | 17 ----------------- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- apps/openmw/mwrender/renderingmanager.hpp | 3 +-- components/settings/categories/navigator.hpp | 4 ++-- components/settings/navmeshrendermode.hpp | 13 +++++++++++++ components/settings/settings.cpp | 10 ++++++++++ components/settings/settings.hpp | 9 +++++++++ components/settings/settingvalue.hpp | 10 ++++++++++ 13 files changed, 62 insertions(+), 55 deletions(-) delete mode 100644 apps/openmw/mwrender/navmeshmode.cpp delete mode 100644 apps/openmw/mwrender/navmeshmode.hpp create mode 100644 components/settings/navmeshrendermode.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 535d854239..b54d3bac92 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 precipitationocclusion ripples + postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples actorutil ) diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 1c9854df5e..0aa1f4ace5 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -55,12 +55,12 @@ namespace MWLua api["reloadLua"] = []() { MWBase::Environment::get().getLuaManager()->reloadAllScripts(); }; api["NAV_MESH_RENDER_MODE"] - = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ - { "AreaType", MWRender::NavMeshMode::AreaType }, - { "UpdateFrequency", MWRender::NavMeshMode::UpdateFrequency }, + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "AreaType", Settings::NavMeshRenderMode::AreaType }, + { "UpdateFrequency", Settings::NavMeshRenderMode::UpdateFrequency }, })); - api["setNavMeshRenderMode"] = [context](MWRender::NavMeshMode value) { + api["setNavMeshRenderMode"] = [context](Settings::NavMeshRenderMode value) { context.mLuaManager->addAction( [value] { MWBase::Environment::get().getWorld()->getRenderingManager()->setNavMeshMode(value); }); }; diff --git a/apps/openmw/mwrender/navmesh.cpp b/apps/openmw/mwrender/navmesh.cpp index 8d638c918b..df3cd22699 100644 --- a/apps/openmw/mwrender/navmesh.cpp +++ b/apps/openmw/mwrender/navmesh.cpp @@ -46,7 +46,7 @@ namespace MWRender const osg::ref_ptr mDebugDrawStateSet; const DetourNavigator::Settings mSettings; std::map mTiles; - NavMeshMode mMode; + Settings::NavMeshRenderMode mMode; std::atomic_bool mAborted{ false }; std::mutex mMutex; bool mStarted = false; @@ -57,7 +57,7 @@ namespace MWRender std::weak_ptr navMesh, const osg::ref_ptr& groupStateSet, const osg::ref_ptr& debugDrawStateSet, const DetourNavigator::Settings& settings, const std::map& tiles, - NavMeshMode mode) + Settings::NavMeshRenderMode mode) : mId(id) , mVersion(version) , mNavMesh(std::move(navMesh)) @@ -110,13 +110,13 @@ namespace MWRender const unsigned char flags = SceneUtil::NavMeshTileDrawFlagsOffMeshConnections | SceneUtil::NavMeshTileDrawFlagsClosedList - | (mMode == NavMeshMode::UpdateFrequency ? SceneUtil::NavMeshTileDrawFlagsHeat : 0); + | (mMode == Settings::NavMeshRenderMode::UpdateFrequency ? SceneUtil::NavMeshTileDrawFlagsHeat : 0); for (const auto& [position, version] : existingTiles) { const auto it = mTiles.find(position); if (it != mTiles.end() && it->second.mGroup != nullptr && it->second.mVersion == version - && mMode != NavMeshMode::UpdateFrequency) + && mMode != Settings::NavMeshRenderMode::UpdateFrequency) continue; osg::ref_ptr group; @@ -163,7 +163,7 @@ namespace MWRender }; NavMesh::NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, - bool enabled, NavMeshMode mode) + bool enabled, Settings::NavMeshRenderMode mode) : mRootNode(root) , mWorkQueue(workQueue) , mGroupStateSet(SceneUtil::makeNavMeshTileStateSet()) @@ -310,7 +310,7 @@ namespace MWRender mEnabled = false; } - void NavMesh::setMode(NavMeshMode value) + void NavMesh::setMode(Settings::NavMeshRenderMode value) { if (mMode == value) return; diff --git a/apps/openmw/mwrender/navmesh.hpp b/apps/openmw/mwrender/navmesh.hpp index 4b4e50f791..193a474ec4 100644 --- a/apps/openmw/mwrender/navmesh.hpp +++ b/apps/openmw/mwrender/navmesh.hpp @@ -1,11 +1,10 @@ #ifndef OPENMW_MWRENDER_NAVMESH_H #define OPENMW_MWRENDER_NAVMESH_H -#include "navmeshmode.hpp" - #include #include #include +#include #include @@ -41,7 +40,7 @@ namespace MWRender { public: explicit NavMesh(const osg::ref_ptr& root, const osg::ref_ptr& workQueue, - bool enabled, NavMeshMode mode); + bool enabled, Settings::NavMeshRenderMode mode); ~NavMesh(); bool toggle(); @@ -57,7 +56,7 @@ namespace MWRender bool isEnabled() const { return mEnabled; } - void setMode(NavMeshMode value); + void setMode(Settings::NavMeshRenderMode value); private: struct Tile @@ -75,7 +74,7 @@ namespace MWRender osg::ref_ptr mGroupStateSet; osg::ref_ptr mDebugDrawStateSet; bool mEnabled; - NavMeshMode mMode; + Settings::NavMeshRenderMode mMode; std::size_t mId; DetourNavigator::Version mVersion; std::map mTiles; diff --git a/apps/openmw/mwrender/navmeshmode.cpp b/apps/openmw/mwrender/navmeshmode.cpp deleted file mode 100644 index d08e7cf693..0000000000 --- a/apps/openmw/mwrender/navmeshmode.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "navmeshmode.hpp" - -#include -#include - -namespace MWRender -{ - NavMeshMode parseNavMeshMode(std::string_view value) - { - if (value == "area type") - return NavMeshMode::AreaType; - if (value == "update frequency") - return NavMeshMode::UpdateFrequency; - throw std::logic_error("Unsupported navigation mesh rendering mode: " + std::string(value)); - } -} diff --git a/apps/openmw/mwrender/navmeshmode.hpp b/apps/openmw/mwrender/navmeshmode.hpp deleted file mode 100644 index 9401479e21..0000000000 --- a/apps/openmw/mwrender/navmeshmode.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef OPENMW_MWRENDER_NAVMESHMODE_H -#define OPENMW_MWRENDER_NAVMESHMODE_H - -#include - -namespace MWRender -{ - enum class NavMeshMode - { - AreaType, - UpdateFrequency, - }; - - NavMeshMode parseNavMeshMode(std::string_view value); -} - -#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6155325410..70724cd08c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -427,7 +427,7 @@ namespace MWRender mNavMesh = std::make_unique(mRootNode, mWorkQueue, Settings::Manager::getBool("enable nav mesh render", "Navigator"), - parseNavMeshMode(Settings::Manager::getString("nav mesh render mode", "Navigator"))); + Settings::navigator().mNavMeshRenderMode); mActorsPaths = std::make_unique( mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")); mRecastMesh = std::make_unique( @@ -1666,7 +1666,7 @@ namespace MWRender mObjectPaging->getPagedRefnums(activeGrid, out); } - void RenderingManager::setNavMeshMode(NavMeshMode value) + void RenderingManager::setNavMeshMode(Settings::NavMeshRenderMode value) { mNavMesh->setMode(value); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 0e2ece7de9..f1760d3960 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -9,7 +9,6 @@ #include -#include "navmeshmode.hpp" #include "objects.hpp" #include "renderinginterface.hpp" #include "rendermode.hpp" @@ -275,7 +274,7 @@ namespace MWRender void setScreenRes(int width, int height); - void setNavMeshMode(NavMeshMode value); + void setNavMeshMode(Settings::NavMeshRenderMode value); private: void updateTextureFiltering(); diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index 9dcf08d299..b820b2c950 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -3,6 +3,7 @@ #include "components/settings/sanitizerimpl.hpp" #include "components/settings/settingvalue.hpp" +#include #include #include @@ -52,8 +53,7 @@ namespace Settings SettingValue mRecastMeshPathPrefix{ mIndex, "Navigator", "recast mesh path prefix" }; SettingValue mNavMeshPathPrefix{ mIndex, "Navigator", "nav mesh path prefix" }; SettingValue mEnableNavMeshRender{ mIndex, "Navigator", "enable nav mesh render" }; - SettingValue mNavMeshRenderMode{ mIndex, "Navigator", "nav mesh render mode", - makeEnumSanitizerString({ "area type", "update frequency" }) }; + SettingValue mNavMeshRenderMode{ mIndex, "Navigator", "nav mesh render mode" }; SettingValue mEnableAgentsPathsRender{ mIndex, "Navigator", "enable agents paths render" }; SettingValue mEnableRecastMeshRender{ mIndex, "Navigator", "enable recast mesh render" }; SettingValue mMaxTilesNumber{ mIndex, "Navigator", "max tiles number", makeMaxSanitizerInt(0) }; diff --git a/components/settings/navmeshrendermode.hpp b/components/settings/navmeshrendermode.hpp new file mode 100644 index 0000000000..416e2afab1 --- /dev/null +++ b/components/settings/navmeshrendermode.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_NAVMESHRENDERMODE_H +#define OPENMW_COMPONENTS_SETTINGS_NAVMESHRENDERMODE_H + +namespace Settings +{ + enum class NavMeshRenderMode + { + AreaType, + UpdateFrequency, + }; +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 0b5260fb8e..305d4edc88 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -481,4 +481,14 @@ namespace Settings throw std::runtime_error("Invalid gyroscope axis: " + std::string(value)); } + + NavMeshRenderMode parseNavMeshRenderMode(std::string_view value) + { + if (value == "area type") + return NavMeshRenderMode::AreaType; + if (value == "update frequency") + return NavMeshRenderMode::UpdateFrequency; + + throw std::invalid_argument("Invalid navigation mesh rendering mode: " + std::string(value)); + } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c9a5dbc6ea..784c7c613c 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -3,6 +3,7 @@ #include "categories.hpp" #include "gyroscopeaxis.hpp" +#include "navmeshrendermode.hpp" #include "components/detournavigator/collisionshapetype.hpp" @@ -206,6 +207,14 @@ namespace Settings { return parseGyroscopeAxis(getString(setting, category)); } + + NavMeshRenderMode parseNavMeshRenderMode(std::string_view value); + + template <> + inline NavMeshRenderMode Manager::getImpl(std::string_view setting, std::string_view category) + { + return parseNavMeshRenderMode(getString(setting, category)); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 540ef1fe0b..407fd15baf 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SETTINGS_SETTINGVALUE_H #include "gyroscopeaxis.hpp" +#include "navmeshrendermode.hpp" #include "sanitizer.hpp" #include "settings.hpp" @@ -38,6 +39,7 @@ namespace Settings StringArray, MyGuiColour, GyroscopeAxis, + NavMeshRenderMode, }; template @@ -139,6 +141,12 @@ namespace Settings return SettingValueType::GyroscopeAxis; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::NavMeshRenderMode; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -175,6 +183,8 @@ namespace Settings return "colour"; case SettingValueType::GyroscopeAxis: return "gyroscope axis"; + case SettingValueType::NavMeshRenderMode: + return "navmesh render mode"; } return "unsupported"; } From 68de5690ef047d44141018fed70f005dd59aa172 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Oct 2023 10:36:55 +0200 Subject: [PATCH 0240/2167] Use settings values for Navigator settings --- apps/navmeshtool/main.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 9 +++------ apps/openmw/mwworld/worldimp.cpp | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index f82bd09f9e..793a08ba2c 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -199,7 +199,7 @@ namespace NavMeshTool Settings::game().mActorCollisionShapeType, Settings::game().mDefaultActorPathfindHalfExtents, }; - const std::uint64_t maxDbFileSize = Settings::Manager::getUInt64("max navmeshdb file size", "Navigator"); + const std::uint64_t maxDbFileSize = Settings::navigator().mMaxNavmeshdbFileSize; const auto dbPath = Files::pathToUnicodeString(config.getUserDataPath() / "navmesh.db"); Log(Debug::Info) << "Using navmeshdb at " << dbPath; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 70724cd08c..8b370c73b6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -425,13 +425,10 @@ namespace MWRender // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); - mNavMesh = std::make_unique(mRootNode, mWorkQueue, - Settings::Manager::getBool("enable nav mesh render", "Navigator"), + mNavMesh = std::make_unique(mRootNode, mWorkQueue, Settings::navigator().mEnableNavMeshRender, Settings::navigator().mNavMeshRenderMode); - mActorsPaths = std::make_unique( - mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")); - mRecastMesh = std::make_unique( - mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator")); + mActorsPaths = std::make_unique(mRootNode, Settings::navigator().mEnableAgentsPathsRender); + mRecastMesh = std::make_unique(mRootNode, Settings::navigator().mEnableRecastMeshRender); mPathgrid = std::make_unique(mRootNode); mObjects = std::make_unique(mResourceSystem, sceneRoot, unrefQueue); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 02e4d066bc..b9bf454895 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -294,7 +294,7 @@ namespace MWWorld { mPhysics = std::make_unique(mResourceSystem, rootNode); - if (Settings::Manager::getBool("enable", "Navigator")) + if (Settings::navigator().mEnable) { auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); navigatorSettings.mRecast.mSwimHeightScale = mSwimHeightScale; From 0814ef5697b290a68025012e294c932b93c12ee1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 1 Oct 2023 11:22:20 +0200 Subject: [PATCH 0241/2167] Allow creatures to use potions --- CHANGELOG.md | 1 + apps/openmw/mwclass/actor.cpp | 13 +++++++ apps/openmw/mwclass/actor.hpp | 2 + apps/openmw/mwclass/npc.cpp | 12 ------ apps/openmw/mwclass/npc.hpp | 2 - apps/openmw/mwmechanics/aicombataction.cpp | 45 +++++++++++----------- 6 files changed, 39 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42db494f3c..facb9df9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,7 @@ Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default Bug #7603: Scripts menu size is not updated properly + Bug #7604: Goblins Grunt becomes idle once injured Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 152a4bbba9..7f60ae5c2c 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -3,6 +3,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" @@ -14,6 +15,7 @@ #include "../mwphysics/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/worldmodel.hpp" namespace MWClass { @@ -90,4 +92,15 @@ namespace MWClass moveSpeed *= 0.75f; return moveSpeed; } + + bool Actor::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const + { + MWBase::Environment::get().getWorld()->breakInvisibility(actor); + MWMechanics::CastSpell cast(actor, actor); + const ESM::RefId& recordId = consumable.getCellRef().getRefId(); + MWBase::Environment::get().getWorldModel()->registerPtr(consumable); + MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); + actor.getClass().getContainerStore(actor).remove(consumable, 1); + return cast.cast(recordId); + } } diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 5a143c7a7d..41d06cf5bd 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -62,6 +62,8 @@ namespace MWClass /// Return current movement speed. float getCurrentSpeed(const MWWorld::Ptr& ptr) const override; + bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const override; + // not implemented Actor(const Actor&) = delete; Actor& operator=(const Actor&) = delete; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f1514a4ffe..dab6dc99ae 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -21,7 +21,6 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" -#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -1123,17 +1122,6 @@ namespace MWClass return getNpcStats(ptr).isWerewolf() ? 0.0f : Actor::getEncumbrance(ptr); } - bool Npc::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const - { - MWBase::Environment::get().getWorld()->breakInvisibility(actor); - MWMechanics::CastSpell cast(actor, actor); - const ESM::RefId& recordId = consumable.getCellRef().getRefId(); - MWBase::Environment::get().getWorldModel()->registerPtr(consumable); - MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); - actor.getClass().getContainerStore(actor).remove(consumable, 1); - return cast.cast(recordId); - } - void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { MWMechanics::NpcStats& stats = getNpcStats(ptr); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 9b53143c4d..eb8cafc9d1 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -114,8 +114,6 @@ namespace MWClass float getArmorRating(const MWWorld::Ptr& ptr) const override; ///< @return combined armor rating of this actor - bool consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const override; - void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const override; /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index a7bcd1a0df..563bd8b8cd 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -176,34 +176,35 @@ namespace MWMechanics return bestAction; } - if (actor.getClass().hasInventoryStore(actor)) + const bool hasInventoryStore = actor.getClass().hasInventoryStore(actor); + MWWorld::ContainerStore& store = actor.getClass().getContainerStore(actor); + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + if (it->getType() == ESM::Potion::sRecordId) { - if (it->getType() == ESM::Potion::sRecordId) + float rating = ratePotion(*it, actor); + if (rating > bestActionRating) { - float rating = ratePotion(*it, actor); - if (rating > bestActionRating) - { - bestActionRating = rating; - bestAction = std::make_unique(*it); - antiFleeRating = std::numeric_limits::max(); - } - } - else if (!it->getClass().getEnchantment(*it).empty()) - { - float rating = rateMagicItem(*it, actor, enemy); - if (rating > bestActionRating) - { - bestActionRating = rating; - bestAction = std::make_unique(it); - antiFleeRating = std::numeric_limits::max(); - } + bestActionRating = rating; + bestAction = std::make_unique(*it); + antiFleeRating = std::numeric_limits::max(); } } + // TODO remove inventory store check, creatures should be able to use enchanted items they cannot equip + else if (hasInventoryStore && !it->getClass().getEnchantment(*it).empty()) + { + float rating = rateMagicItem(*it, actor, enemy); + if (rating > bestActionRating) + { + bestActionRating = rating; + bestAction = std::make_unique(it); + antiFleeRating = std::numeric_limits::max(); + } + } + } + if (hasInventoryStore) + { MWWorld::Ptr bestArrow; float bestArrowRating = rateAmmo(actor, enemy, bestArrow, ESM::Weapon::Arrow); From 6161953106fe431d2cc3595f91f1acebf95f8c7b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 3 Oct 2023 02:04:18 +0200 Subject: [PATCH 0242/2167] Allow reading ESM4 books --- apps/openmw/mwgui/bookwindow.cpp | 14 ++- apps/openmw/mwgui/formatting.cpp | 114 +++++++++++++----- apps/openmw/mwgui/formatting.hpp | 11 +- apps/openmw/mwgui/scrollwindow.cpp | 12 +- .../source/reference/lua-scripting/events.rst | 18 +++ .../reference/lua-scripting/overview.rst | 3 +- files/data/scripts/omw/activationhandlers.lua | 7 ++ files/data/scripts/omw/ui.lua | 2 + 8 files changed, 135 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 60a86851fa..ef875a18b9 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -83,7 +84,7 @@ namespace MWGui void BookWindow::setPtr(const MWWorld::Ptr& book) { - if (book.isEmpty() || book.getType() != ESM::REC_BOOK) + if (book.isEmpty() || (book.getType() != ESM::REC_BOOK && book.getType() != ESM::REC_BOOK4)) throw std::runtime_error("Invalid argument in BookWindow::setPtr"); mBook = book; @@ -93,11 +94,16 @@ namespace MWGui clearPages(); mCurrentPage = 0; - MWWorld::LiveCellRef* ref = mBook.get(); + const std::string* text; + if (book.getType() == ESM::REC_BOOK) + text = &book.get()->mBase->mText; + else + text = &book.get()->mBase->mText; + bool shrinkTextAtLastTag = book.getType() == ESM::REC_BOOK; Formatting::BookFormatter formatter; - mPages = formatter.markupToWidget(mLeftPage, ref->mBase->mText); - formatter.markupToWidget(mRightPage, ref->mBase->mText); + mPages = formatter.markupToWidget(mLeftPage, *text, shrinkTextAtLastTag); + formatter.markupToWidget(mRightPage, *text, shrinkTextAtLastTag); updatePages(); diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 8479379976..7f62bbf49c 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -23,7 +23,7 @@ namespace MWGui::Formatting { /* BookTextParser */ - BookTextParser::BookTextParser(const std::string& text) + BookTextParser::BookTextParser(const std::string& text, bool shrinkTextAtLastTag) : mIndex(0) , mText(text) , mIgnoreNewlineTags(true) @@ -36,20 +36,25 @@ namespace MWGui::Formatting Misc::StringUtils::replaceAll(mText, "\r", {}); - // vanilla game does not show any text after the last EOL tag. - const std::string lowerText = Misc::StringUtils::lowerCase(mText); - size_t brIndex = lowerText.rfind("
"); - size_t pIndex = lowerText.rfind("

"); - mPlainTextEnd = 0; - if (brIndex != pIndex) + if (shrinkTextAtLastTag) { - if (brIndex != std::string::npos && pIndex != std::string::npos) - mPlainTextEnd = std::max(brIndex, pIndex); - else if (brIndex != std::string::npos) - mPlainTextEnd = brIndex; - else - mPlainTextEnd = pIndex; + // vanilla game does not show any text after the last EOL tag. + const std::string lowerText = Misc::StringUtils::lowerCase(mText); + size_t brIndex = lowerText.rfind("
"); + size_t pIndex = lowerText.rfind("

"); + mPlainTextEnd = 0; + if (brIndex != pIndex) + { + if (brIndex != std::string::npos && pIndex != std::string::npos) + mPlainTextEnd = std::max(brIndex, pIndex); + else if (brIndex != std::string::npos) + mPlainTextEnd = brIndex; + else + mPlainTextEnd = pIndex; + } } + else + mPlainTextEnd = mText.size(); registerTag("br", Event_BrTag); registerTag("p", Event_PTag); @@ -73,6 +78,17 @@ namespace MWGui::Formatting while (mIndex < mText.size()) { char ch = mText[mIndex]; + if (ch == '[') + { + constexpr std::string_view pageBreakTag = "[pagebreak]\n"; + if (std::string_view(mText.data() + mIndex, mText.size() - mIndex).starts_with(pageBreakTag)) + { + mIndex += pageBreakTag.size(); + flushBuffer(); + mIgnoreNewlineTags = false; + return Event_PageBreak; + } + } if (ch == '<') { const size_t tagStart = mIndex + 1; @@ -98,6 +114,8 @@ namespace MWGui::Formatting } } mIgnoreLineEndings = true; + if (type == Event_PTag && !mAttributes.empty()) + flushBuffer(); } else flushBuffer(); @@ -180,9 +198,9 @@ namespace MWGui::Formatting if (tag.empty()) return; - if (tag[0] == '"') + if (tag[0] == '"' || tag[0] == '\'') { - size_t quoteEndPos = tag.find('"', 1); + size_t quoteEndPos = tag.find(tag[0], 1); if (quoteEndPos == std::string::npos) throw std::runtime_error("BookTextParser Error: Missing end quote in tag"); value = tag.substr(1, quoteEndPos - 1); @@ -208,8 +226,8 @@ namespace MWGui::Formatting } /* BookFormatter */ - Paginator::Pages BookFormatter::markupToWidget( - MyGUI::Widget* parent, const std::string& markup, const int pageWidth, const int pageHeight) + Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget* parent, const std::string& markup, + const int pageWidth, const int pageHeight, bool shrinkTextAtLastTag) { Paginator pag(pageWidth, pageHeight); @@ -225,14 +243,16 @@ namespace MWGui::Formatting MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); paper->setNeedMouseFocus(false); - BookTextParser parser(markup); + BookTextParser parser(markup, shrinkTextAtLastTag); bool brBeforeLastTag = false; bool isPrevImg = false; + bool inlineImageInserted = false; for (;;) { BookTextParser::Events event = parser.next(); - if (event == BookTextParser::Event_BrTag || event == BookTextParser::Event_PTag) + if (event == BookTextParser::Event_BrTag + || (event == BookTextParser::Event_PTag && parser.getAttributes().empty())) continue; std::string plainText = parser.getReadyText(); @@ -272,6 +292,12 @@ namespace MWGui::Formatting if (!plainText.empty() || brBeforeLastTag || isPrevImg) { + if (inlineImageInserted) + { + pag.setCurrentTop(pag.getCurrentTop() - mTextStyle.mTextSize); + plainText = " " + plainText; + inlineImageInserted = false; + } TextElement elem(paper, pag, mBlockStyle, mTextStyle, plainText); elem.paginate(); } @@ -286,6 +312,10 @@ namespace MWGui::Formatting switch (event) { + case BookTextParser::Event_PageBreak: + pag << Paginator::Page(pag.getStartTop(), pag.getCurrentTop()); + pag.setStartTop(pag.getCurrentTop()); + break; case BookTextParser::Event_ImgTag: { const BookTextParser::Attributes& attr = parser.getAttributes(); @@ -293,22 +323,38 @@ namespace MWGui::Formatting auto srcIt = attr.find("src"); if (srcIt == attr.end()) continue; - auto widthIt = attr.find("width"); - if (widthIt == attr.end()) - continue; - auto heightIt = attr.find("height"); - if (heightIt == attr.end()) - continue; + int width = 0; + if (auto widthIt = attr.find("width"); widthIt != attr.end()) + width = MyGUI::utility::parseInt(widthIt->second); + int height = 0; + if (auto heightIt = attr.find("height"); heightIt != attr.end()) + height = MyGUI::utility::parseInt(heightIt->second); const std::string& src = srcIt->second; - int width = MyGUI::utility::parseInt(widthIt->second); - int height = MyGUI::utility::parseInt(heightIt->second); - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - std::string correctedSrc = Misc::ResourceHelpers::correctBookartPath(src, width, height, vfs); - bool exists = vfs->exists(correctedSrc); - if (!exists) + std::string correctedSrc; + + constexpr std::string_view imgPrefix = "img://"; + if (src.starts_with(imgPrefix)) + { + correctedSrc = src.substr(imgPrefix.size(), src.size() - imgPrefix.size()); + if (width == 0) + { + width = 50; + inlineImageInserted = true; + } + if (height == 0) + height = 50; + } + else + { + if (width == 0 || height == 0) + continue; + correctedSrc = Misc::ResourceHelpers::correctBookartPath(src, width, height, vfs); + } + + if (!vfs->exists(correctedSrc)) { Log(Debug::Warning) << "Warning: Could not find \"" << src << "\" referenced by an tag."; break; @@ -326,6 +372,7 @@ namespace MWGui::Formatting else handleFont(parser.getAttributes()); break; + case BookTextParser::Event_PTag: case BookTextParser::Event_DivTag: handleDiv(parser.getAttributes()); break; @@ -343,9 +390,10 @@ namespace MWGui::Formatting return pag.getPages(); } - Paginator::Pages BookFormatter::markupToWidget(MyGUI::Widget* parent, const std::string& markup) + Paginator::Pages BookFormatter::markupToWidget( + MyGUI::Widget* parent, const std::string& markup, bool shrinkTextAtLastTag) { - return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight()); + return markupToWidget(parent, markup, parent->getWidth(), parent->getHeight(), shrinkTextAtLastTag); } void BookFormatter::resetFontProperties() diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index 421bda6f1d..9a215b200b 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -46,10 +46,11 @@ namespace MWGui Event_PTag, Event_ImgTag, Event_DivTag, - Event_FontTag + Event_FontTag, + Event_PageBreak, }; - BookTextParser(const std::string& text); + BookTextParser(const std::string& text, bool shrinkTextAtLastTag); Events next(); @@ -120,9 +121,9 @@ namespace MWGui class BookFormatter { public: - Paginator::Pages markupToWidget( - MyGUI::Widget* parent, const std::string& markup, const int pageWidth, const int pageHeight); - Paginator::Pages markupToWidget(MyGUI::Widget* parent, const std::string& markup); + Paginator::Pages markupToWidget(MyGUI::Widget* parent, const std::string& markup, const int pageWidth, + const int pageHeight, bool shrinkTextAtLastTag); + Paginator::Pages markupToWidget(MyGUI::Widget* parent, const std::string& markup, bool shrinkTextAtLastTag); private: void resetFontProperties(); diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 3debf0d66d..0b1658fd84 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -42,17 +43,22 @@ namespace MWGui void ScrollWindow::setPtr(const MWWorld::Ptr& scroll) { - if (scroll.isEmpty() || scroll.getType() != ESM::REC_BOOK) + if (scroll.isEmpty() || (scroll.getType() != ESM::REC_BOOK && scroll.getType() != ESM::REC_BOOK4)) throw std::runtime_error("Invalid argument in ScrollWindow::setPtr"); mScroll = scroll; MWWorld::Ptr player = MWMechanics::getPlayer(); bool showTakeButton = scroll.getContainerStore() != &player.getClass().getContainerStore(player); - MWWorld::LiveCellRef* ref = mScroll.get(); + const std::string* text; + if (scroll.getType() == ESM::REC_BOOK) + text = &scroll.get()->mBase->mText; + else + text = &scroll.get()->mBase->mText; + bool shrinkTextAtLastTag = scroll.getType() == ESM::REC_BOOK; Formatting::BookFormatter formatter; - formatter.markupToWidget(mTextView, ref->mBase->mText, 390, mTextView->getHeight()); + formatter.markupToWidget(mTextView, *text, 390, mTextView->getHeight(), shrinkTextAtLastTag); MyGUI::IntSize size = mTextView->getChildAt(0)->getSize(); // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index b0e600413a..7f0a764b86 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -32,6 +32,8 @@ Example: UI events --------- +**UiModeChanged** + Every time UI mode is changed built-in scripts send to player the event ``UiModeChanged`` with arguments ``oldMode, ``newMode`` (same as ``I.UI.getMode()``) and ``arg`` (for example in the mode ``Book`` the argument is the book the player is reading). @@ -43,6 +45,22 @@ and ``arg`` (for example in the mode ``Book`` the argument is the book the playe end } +**AddUiMode** + +Equivalent to ``I.UI.addMode``, but can be sent from another object or global script. + +.. code-block:: Lua + + player:sendEvent('AddUiMode', {mode = 'Book', target = book}) + +**SetUiMode** + +Equivalent to ``I.UI.setMode``, but can be sent from another object or global script. + +.. code-block:: Lua + + player:sendEvent('SetUiMode', {mode = 'Book', target = book}) + World events ------------ diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 90014f637b..5515351e20 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -459,7 +459,8 @@ Using the interface: The order in which the scripts are started is important. So if one mod should override an interface provided by another mod, make sure that load order (i.e. the sequence of `lua-scripts=...` in `openmw.cfg`) is correct. -**Interfaces of built-in scripts** +Interfaces of built-in scripts +------------------------------ .. include:: tables/interfaces.rst diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index 2f63f59e92..edadc30f79 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -17,9 +17,16 @@ local function ESM4DoorActivation(door, actor) return false -- disable activation handling in C++ mwmechanics code end +local function ESM4BookActivation(book, actor) + if actor.type == types.Player then + actor:sendEvent('AddUiMode', { mode = 'Book', target = book }) + end +end + local handlersPerObject = {} local handlersPerType = {} +handlersPerType[types.ESM4Book] = { ESM4BookActivation } handlersPerType[types.ESM4Door] = { ESM4DoorActivation } local function onActivate(obj, actor) diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 5837b01f36..6fd80bf394 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -240,5 +240,7 @@ return { }, eventHandlers = { UiModeChanged = onUiModeChangedEvent, + AddUiMode = function(options) addMode(options.mode, options) end, + SetUiMode = function(options) setMode(options.mode, options) end, }, } From fcc7b9bb9eaa9a62bfcc46ac9013bd34906d1dfb Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 10 Sep 2023 16:12:26 +0200 Subject: [PATCH 0243/2167] Support tags in I.Camera.disable*, I.Camera.enable* in the same way as in world.pause; Update I.Camera docs. --- files/data/scripts/omw/camera/camera.lua | 128 ++++++++++++------ .../data/scripts/omw/camera/third_person.lua | 4 +- 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index a3d2ae7b3f..ba0b1ee05f 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -39,10 +39,10 @@ end local primaryMode -local noModeControl = 0 -local noStandingPreview = 0 -local noHeadBobbing = 0 -local noZoom = 0 +local noModeControl = {} +local noStandingPreview = {} +local noHeadBobbing = {} +local noZoom = {} local function init() camera.setFieldOfView(camera.getBaseFieldOfView()) @@ -117,19 +117,19 @@ local maxDistance = 800 local function zoom(delta) if not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or - camera.getMode() == MODE.Static or noZoom > 0 then + camera.getMode() == MODE.Static or next(noZoom) then return end if camera.getMode() ~= MODE.FirstPerson then local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance() if delta > 0 and third_person.baseDistance == minDistance and - (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and noModeControl == 0 then + (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then primaryMode = MODE.FirstPerson camera.setMode(primaryMode) elseif delta > 0 or obstacleDelta < -delta then third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance) end - elseif delta < 0 and noModeControl == 0 then + elseif delta < 0 and not next(noModeControl) then primaryMode = MODE.ThirdPerson camera.setMode(primaryMode) third_person.baseDistance = minDistance @@ -149,7 +149,7 @@ end local function updateStandingPreview() local mode = camera.getMode() - if not previewIfStandStill or noStandingPreview > 0 + if not previewIfStandStill or next(noStandingPreview) or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then third_person.standingPreview = false return @@ -197,7 +197,7 @@ local function onFrame(dt) primaryMode = mode end if mode ~= MODE.Static then - if noModeControl == 0 then + if not next(noModeControl) then updatePOV(dt) updateVanity(dt) end @@ -206,7 +206,7 @@ local function onFrame(dt) end applyControllerZoom(dt) third_person.update(dt, smoothedSpeed) - if noHeadBobbing == 0 then head_bobbing.update(dt, smoothedSpeed) end + if not next(noHeadBobbing) then head_bobbing.update(dt, smoothedSpeed) end if slowViewChange then local maxIncrease = dt * (100 + third_person.baseDistance) camera.setPreferredThirdPersonDistance( @@ -221,54 +221,92 @@ return { -- @module Camera -- @usage require('openmw.interfaces').Camera interface = { - --- Interface version + --- Interface version is 1 -- @field [parent=#Camera] #number version - version = 0, + version = 1, --- Return primary mode (MODE.FirstPerson or MODE.ThirdPerson). -- @function [parent=#Camera] getPrimaryMode + -- @return #number @{openmw.camera#MODE} getPrimaryMode = function() return primaryMode end, - --- @function [parent=#Camera] getBaseThirdPersonDistance + + --- Get base third person distance (without applying angle and speed modifiers). + -- @function [parent=#Camera] getBaseThirdPersonDistance + -- @return #number getBaseThirdPersonDistance = function() return third_person.baseDistance end, - --- @function [parent=#Camera] setBaseThirdPersonDistance + --- Set base third person distance + -- @function [parent=#Camera] setBaseThirdPersonDistance + -- @param #number value setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end, - --- @function [parent=#Camera] getTargetThirdPersonDistance + --- Get the desired third person distance if there would be no obstacles (with angle and speed modifiers) + -- @function [parent=#Camera] getTargetThirdPersonDistance + -- @return #number getTargetThirdPersonDistance = function() return third_person.preferredDistance end, - --- @function [parent=#Camera] isModeControlEnabled - isModeControlEnabled = function() return noModeControl == 0 end, - --- @function [parent=#Camera] disableModeControl - disableModeControl = function() noModeControl = noModeControl + 1 end, - --- @function [parent=#Camera] enableModeControl - enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end, + --- Whether the built-in mode control logic is enabled. + -- @function [parent=#Camera] isModeControlEnabled + -- @return #boolean + isModeControlEnabled = function() return not next(noModeControl) end, + --- Disable with (optional) tag until the corresponding enable function is called with the same tag. + -- @function [parent=#Camera] disableModeControl + -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + disableModeControl = function(tag) noModeControl[tag or ''] = true end, + --- Undo disableModeControl + -- @function [parent=#Camera] enableModeControl + -- @param #string tag (optional) + enableModeControl = function(tag) noModeControl[tag or ''] = nil end, - --- @function [parent=#Camera] isStandingPreviewEnabled - isStandingPreviewEnabled = function() return previewIfStandStill and noStandingPreview == 0 end, - --- @function [parent=#Camera] disableStandingPreview - disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end, - --- @function [parent=#Camera] enableStandingPreview - enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end, + --- Whether the built-in standing preview logic is enabled. + -- @function [parent=#Camera] isStandingPreviewEnabled + -- @return #boolean + isStandingPreviewEnabled = function() return previewIfStandStill and not next(noStandingPreview) end, + --- Disable with (optional) tag until the corresponding enable function is called with the same tag. + -- @function [parent=#Camera] disableStandingPreview + -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + disableStandingPreview = function(tag) noStandingPreview[tag or ''] = true end, + --- Undo disableStandingPreview + -- @function [parent=#Camera] enableStandingPreview + -- @param #string tag (optional) + enableStandingPreview = function(tag) noStandingPreview[tag or ''] = nil end, - --- @function [parent=#Camera] isHeadBobbingEnabled - isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end, - --- @function [parent=#Camera] disableHeadBobbing - disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end, - --- @function [parent=#Camera] enableHeadBobbing - enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end, + --- Whether head bobbing is enabled. + -- @function [parent=#Camera] isHeadBobbingEnabled + -- @return #boolean + isHeadBobbingEnabled = function() return head_bobbing.enabled and not next(noHeadBobbing) end, + --- Disable with (optional) tag until the corresponding enable function is called with the same tag. + -- @function [parent=#Camera] disableHeadBobbing + -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + disableHeadBobbing = function(tag) noHeadBobbing[tag or ''] = true end, + --- Undo disableHeadBobbing + -- @function [parent=#Camera] enableHeadBobbing + -- @param #string tag (optional) + enableHeadBobbing = function(tag) noHeadBobbing[tag or ''] = nil end, - --- @function [parent=#Camera] isZoomEnabled - isZoomEnabled = function() return noZoom == 0 end, - --- @function [parent=#Camera] disableZoom - disableZoom = function() noZoom = noZoom + 1 end, - --- @function [parent=#Camera] enableZoom - enableZoom = function() noZoom = math.max(0, noZoom - 1) end, + --- Whether the built-in zooming is enabled. + -- @function [parent=#Camera] isZoomEnabled + -- @return #boolean + isZoomEnabled = function() return not next(noZoom) end, + --- Disable with (optional) tag until the corresponding enable function is called with the same tag. + -- @function [parent=#Camera] disableZoom + -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + disableZoom = function(tag) noZoom[tag or ''] = true end, + --- Undo disableZoom + -- @function [parent=#Camera] enableZoom + -- @param #string tag (optional) + enableZoom = function(tag) noZoom[tag or ''] = nil end, - --- @function [parent=#Camera] isThirdPersonOffsetControlEnabled - isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end, - --- @function [parent=#Camera] disableThirdPersonOffsetControl - disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end, - --- @function [parent=#Camera] enableThirdPersonOffsetControl - enableThirdPersonOffsetControl = function() third_person.noOffsetControl = math.max(0, third_person.noOffsetControl - 1) end, + --- Whether the the third person offset can be changed by the built-in camera script. + -- @function [parent=#Camera] isThirdPersonOffsetControlEnabled + -- @return #boolean + isThirdPersonOffsetControlEnabled = function() return not next(third_person.noOffsetControl) end, + --- Disable with (optional) tag until the corresponding enable function is called with the same tag. + -- @function [parent=#Camera] disableThirdPersonOffsetControl + -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + disableThirdPersonOffsetControl = function(tag) third_person.noOffsetControl[tag or ''] = true end, + --- Undo disableThirdPersonOffsetControl + -- @function [parent=#Camera] enableThirdPersonOffsetControl + -- @param #string tag (optional) + enableThirdPersonOffsetControl = function(tag) third_person.noOffsetControl[tag or ''] = nil end, }, engineHandlers = { onUpdate = onUpdate, diff --git a/files/data/scripts/omw/camera/third_person.lua b/files/data/scripts/omw/camera/third_person.lua index 66fdef7bf4..8c68d1596d 100644 --- a/files/data/scripts/omw/camera/third_person.lua +++ b/files/data/scripts/omw/camera/third_person.lua @@ -15,7 +15,7 @@ local M = { baseDistance = 192, preferredDistance = 0, standingPreview = false, - noOffsetControl = 0, + noOffsetControl = {}, } local viewOverShoulder, autoSwitchShoulder @@ -142,7 +142,7 @@ function M.update(dt, smoothedSpeed) return end - if M.noOffsetControl == 0 then + if not next(M.noOffsetControl) then updateState() else state = nil From 54cc1f25a2100dc1079fb4e6571649918237efcd Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 4 Oct 2023 10:36:54 +0200 Subject: [PATCH 0244/2167] Mention in Lua documentation that default tag is an empty string --- files/data/scripts/omw/camera/camera.lua | 20 ++++++++++---------- files/lua_api/openmw/world.lua | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index ba0b1ee05f..18fd37d730 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -249,11 +249,11 @@ return { isModeControlEnabled = function() return not next(noModeControl) end, --- Disable with (optional) tag until the corresponding enable function is called with the same tag. -- @function [parent=#Camera] disableModeControl - -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + -- @param #string tag (optional, empty string by default) Will be disabled until the enabling function is called with the same tag disableModeControl = function(tag) noModeControl[tag or ''] = true end, --- Undo disableModeControl -- @function [parent=#Camera] enableModeControl - -- @param #string tag (optional) + -- @param #string tag (optional, empty string by default) enableModeControl = function(tag) noModeControl[tag or ''] = nil end, --- Whether the built-in standing preview logic is enabled. @@ -262,11 +262,11 @@ return { isStandingPreviewEnabled = function() return previewIfStandStill and not next(noStandingPreview) end, --- Disable with (optional) tag until the corresponding enable function is called with the same tag. -- @function [parent=#Camera] disableStandingPreview - -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + -- @param #string tag (optional, empty string by default) Will be disabled until the enabling function is called with the same tag disableStandingPreview = function(tag) noStandingPreview[tag or ''] = true end, --- Undo disableStandingPreview -- @function [parent=#Camera] enableStandingPreview - -- @param #string tag (optional) + -- @param #string tag (optional, empty string by default) enableStandingPreview = function(tag) noStandingPreview[tag or ''] = nil end, --- Whether head bobbing is enabled. @@ -275,11 +275,11 @@ return { isHeadBobbingEnabled = function() return head_bobbing.enabled and not next(noHeadBobbing) end, --- Disable with (optional) tag until the corresponding enable function is called with the same tag. -- @function [parent=#Camera] disableHeadBobbing - -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + -- @param #string tag (optional, empty string by default) Will be disabled until the enabling function is called with the same tag disableHeadBobbing = function(tag) noHeadBobbing[tag or ''] = true end, --- Undo disableHeadBobbing -- @function [parent=#Camera] enableHeadBobbing - -- @param #string tag (optional) + -- @param #string tag (optional, empty string by default) enableHeadBobbing = function(tag) noHeadBobbing[tag or ''] = nil end, --- Whether the built-in zooming is enabled. @@ -288,11 +288,11 @@ return { isZoomEnabled = function() return not next(noZoom) end, --- Disable with (optional) tag until the corresponding enable function is called with the same tag. -- @function [parent=#Camera] disableZoom - -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + -- @param #string tag (optional, empty string by default) Will be disabled until the enabling function is called with the same tag disableZoom = function(tag) noZoom[tag or ''] = true end, --- Undo disableZoom -- @function [parent=#Camera] enableZoom - -- @param #string tag (optional) + -- @param #string tag (optional, empty string by default) enableZoom = function(tag) noZoom[tag or ''] = nil end, --- Whether the the third person offset can be changed by the built-in camera script. @@ -301,11 +301,11 @@ return { isThirdPersonOffsetControlEnabled = function() return not next(third_person.noOffsetControl) end, --- Disable with (optional) tag until the corresponding enable function is called with the same tag. -- @function [parent=#Camera] disableThirdPersonOffsetControl - -- @param #string tag (optional) Will be disabled until the enabling function is called with the same tag + -- @param #string tag (optional, empty string by default) Will be disabled until the enabling function is called with the same tag disableThirdPersonOffsetControl = function(tag) third_person.noOffsetControl[tag or ''] = true end, --- Undo disableThirdPersonOffsetControl -- @function [parent=#Camera] enableThirdPersonOffsetControl - -- @param #string tag (optional) + -- @param #string tag (optional, empty string by default) enableThirdPersonOffsetControl = function(tag) third_person.noOffsetControl[tag or ''] = nil end, }, engineHandlers = { diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 97f596e4a6..90868387e5 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -109,12 +109,12 @@ --- -- Pause the game starting from the next frame. -- @function [parent=#world] pause --- @param #string tag (optional) The game will be paused until `unpause` is called with the same tag. +-- @param #string tag (optional, empty string by default) The game will be paused until `unpause` is called with the same tag. --- -- Remove given tag from the list of pause tags. Resume the game starting from the next frame if the list became empty. -- @function [parent=#world] unpause --- @param #string tag (optional) Needed to undo `pause` called with this tag. +-- @param #string tag (optional, empty string by default) Needed to undo `pause` called with this tag. --- -- The tags that are currently pausing the game. From 6c2a79184d6c3510b134d1ffbb2ee9a7cd71753e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 12:17:44 +0300 Subject: [PATCH 0245/2167] Read FO4 skinning data --- components/nif/data.cpp | 37 ++++++++++++++++++++++++++++++++++++ components/nif/data.hpp | 24 +++++++++++++++++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 2 ++ components/nif/recordptr.hpp | 2 ++ 5 files changed, 67 insertions(+) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 62a2a9cce3..8b459d015a 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -302,6 +302,33 @@ namespace Nif } } + void BSSkinInstance::read(NIFStream* nif) + { + mRoot.read(nif); + mData.read(nif); + readRecordList(nif, mBones); + nif->readVector(mScales, nif->get()); + } + + void BSSkinInstance::post(Reader& nif) + { + mRoot.post(nif); + mData.post(nif); + postRecordList(nif, mBones); + if (mData.empty() || mRoot.empty()) + throw Nif::Exception("BSSkin::Instance missing root or data", nif.getFilename()); + + if (mBones.size() != mData->mBones.size()) + throw Nif::Exception("Mismatch in BSSkin::BoneData bone count", nif.getFilename()); + + for (auto& bone : mBones) + { + if (bone.empty()) + throw Nif::Exception("Oops: Missing bone! Don't know how to handle this.", nif.getFilename()); + bone->setBone(); + } + } + void NiSkinData::read(NIFStream* nif) { nif->read(mTransform); @@ -344,6 +371,16 @@ namespace Nif mPartitions.post(nif); } + void BSSkinBoneData::read(NIFStream* nif) + { + mBones.resize(nif->get()); + for (BoneInfo& bone : mBones) + { + nif->read(bone.mBoundSphere); + nif->read(bone.mTransform); + } + } + void NiSkinPartition::read(NIFStream* nif) { mPartitions.resize(nif->get()); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index efab514223..75c18d657a 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -223,6 +223,17 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSSkinInstance : Record + { + NiAVObjectPtr mRoot; + BSSkinBoneDataPtr mData; + NiAVObjectList mBones; + std::vector mScales; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + struct NiSkinData : public Record { using VertWeight = std::pair; @@ -242,6 +253,19 @@ namespace Nif void post(Reader& nif) override; }; + struct BSSkinBoneData : Record + { + struct BoneInfo + { + osg::BoundingSpheref mBoundSphere; + NiTransform mTransform; + }; + + std::vector mBones; + + void read(NIFStream* nif) override; + }; + struct NiSkinPartition : public Record { struct Partition diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d5faebb55f..2e3cc8f9aa 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -268,6 +268,8 @@ namespace Nif // Bethesda { "BSDismemberSkinInstance", &construct }, + { "BSSkin::Instance", &construct }, + { "BSSkin::BoneData", &construct }, { "BSTriShape", &construct }, { "BSDynamicTriShape", &construct }, { "BSLODTriShape", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 00eec64b24..eeafc7abd5 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -125,6 +125,8 @@ namespace Nif RC_BSShaderPPLightingProperty, RC_BSShaderProperty, RC_BSShaderTextureSet, + RC_BSSkinBoneData, + RC_BSSkinInstance, RC_BSSkyShaderProperty, RC_BSTriShape, RC_BSWArray, diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index b847f609a4..7056abe6a3 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -162,6 +162,7 @@ namespace Nif struct bhkCompressedMeshShapeData; struct BSMultiBound; struct BSMultiBoundData; + struct BSSkinBoneData; using NiAVObjectPtr = RecordPtrT; using ExtraPtr = RecordPtrT; @@ -211,6 +212,7 @@ namespace Nif using bhkCompressedMeshShapeDataPtr = RecordPtrT; using BSMultiBoundPtr = RecordPtrT; using BSMultiBoundDataPtr = RecordPtrT; + using BSSkinBoneDataPtr = RecordPtrT; using NiAVObjectList = RecordListT; using NiPropertyList = RecordListT; From 94b286a088ebfb96dad35fd535367d30a3cd34af Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 12:30:47 +0300 Subject: [PATCH 0246/2167] Read NiLightRadiusController --- components/nif/niffile.cpp | 1 + components/nif/record.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 2e3cc8f9aa..524be1f5d1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -158,6 +158,7 @@ namespace Nif &construct }, { "bhkBlendController", &construct }, { "NiBSBoneLODController", &construct }, + { "NiLightRadiusController", &construct }, // Interpolators, Gamebryo { "NiBlendBoolInterpolator", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index eeafc7abd5..612ec199a5 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -186,6 +186,7 @@ namespace Nif RC_NiLight, RC_NiLightColorController, RC_NiLightDimmerController, + RC_NiLightRadiusController, RC_NiLines, RC_NiLinesData, RC_NiLODNode, From 291d0de79e186e0ab06399db66922dd042bb7abf Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 12:54:38 +0300 Subject: [PATCH 0247/2167] Read more FO4 extra data records --- components/nif/extra.cpp | 26 ++++++++++++++++++++++++++ components/nif/extra.hpp | 29 +++++++++++++++++++++++++++++ components/nif/niffile.cpp | 4 ++++ components/nif/record.hpp | 4 ++++ 4 files changed, 63 insertions(+) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 69f66014d6..1fc24e5f89 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -136,4 +136,30 @@ namespace Nif nif->readVector(mData, nif->get()); } + void BSConnectPoint::Point::read(NIFStream* nif) + { + mParent = nif->getSizedString(); + mName = nif->getSizedString(); + nif->read(mTransform.mRotation); + nif->read(mTransform.mTranslation); + nif->read(mTransform.mScale); + } + + void BSConnectPoint::Parents::read(NIFStream* nif) + { + NiExtraData::read(nif); + + mPoints.resize(nif->get()); + for (Point& point : mPoints) + point.read(nif); + } + + void BSConnectPoint::Children::read(NIFStream* nif) + { + NiExtraData::read(nif); + + nif->read(mSkinned); + nif->getSizedStrings(mPointNames, nif->get()); + } + } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 49c77723fb..0808e91b16 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -45,6 +45,8 @@ namespace Nif using NiFloatsExtraData = TypedVectorExtra; using NiIntegersExtraData = TypedVectorExtra; + using BSEyeCenterExtraData = TypedVectorExtra; + using BSPositionData = TypedVectorExtra; using BSWArray = TypedVectorExtra; // Distinct from NiBinaryExtraData, uses mRecordSize as its size @@ -170,5 +172,32 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSConnectPoint + { + struct Point + { + std::string mParent; + std::string mName; + NiQuatTransform mTransform; + + void read(NIFStream* nif); + }; + + struct Parents : NiExtraData + { + std::vector mPoints; + + void read(NIFStream* nif) override; + }; + + struct Children : NiExtraData + { + bool mSkinned; + std::vector mPointNames; + + void read(NIFStream* nif) override; + }; + }; + } #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 524be1f5d1..89559a9149 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -246,11 +246,15 @@ namespace Nif { "BSBehaviorGraphExtraData", &construct }, { "BSBoneLODExtraData", &construct }, { "BSClothExtraData", &construct }, + { "BSConnectPoint::Children", &construct }, + { "BSConnectPoint::Parents", &construct }, { "BSDecalPlacementVectorExtraData", &construct }, { "BSDistantObjectExtraData", &construct }, { "BSDistantObjectLargeRefExtraData", &construct }, + { "BSEyeCenterExtraData", &construct }, + { "BSPositionData", &construct }, { "BSWArray", &construct }, { "BSXFlags", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 612ec199a5..a06dcfe2a9 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -77,6 +77,8 @@ namespace Nif RC_BSBound, RC_BSBoneLODExtraData, RC_BSClothExtraData, + RC_BSConnectPointChildren, + RC_BSConnectPointParents, RC_BSDecalPlacementVectorExtraData, RC_BSDistantTreeShaderProperty, RC_BSDynamicTriShape, @@ -87,6 +89,7 @@ namespace Nif RC_BSEffectShaderPropertyColorController, RC_BSEffectShaderPropertyFloatController, RC_BSExtraData, + RC_BSEyeCenterExtraData, RC_BSFrustumFOVController, RC_BSFurnitureMarker, RC_BSInvMarker, @@ -105,6 +108,7 @@ namespace Nif RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, RC_BSParentVelocityModifier, + RC_BSPositionData, RC_BSProceduralLightningController, RC_BSPSysArrayEmitter, RC_BSPSysHavokUpdateModifier, From 8fb900da85ffe38959dd24b6d559284d0a6d414f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 13:36:29 +0300 Subject: [PATCH 0248/2167] Remove GeometryInterface Actually, it was a terrible idea --- components/nif/node.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index f560932e22..d17de999ee 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -119,14 +119,7 @@ namespace Nif void post(Reader& nif) override; }; - struct GeometryInterface - { - NiSkinInstancePtr mSkin; - BSShaderPropertyPtr mShaderProperty; - NiAlphaPropertyPtr mAlphaProperty; - }; - - struct NiGeometry : NiAVObject, GeometryInterface + struct NiGeometry : NiAVObject { /* Possible flags: 0x40 - mesh has no vertex normals ? @@ -145,8 +138,12 @@ namespace Nif void read(NIFStream* nif); }; + NiGeometryDataPtr mData; + NiSkinInstancePtr mSkin; MaterialData mMaterial; + BSShaderPropertyPtr mShaderProperty; + NiAlphaPropertyPtr mAlphaProperty; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -365,10 +362,13 @@ namespace Nif void read(NIFStream* nif, uint16_t flags); }; - struct BSTriShape : NiAVObject, GeometryInterface + struct BSTriShape : NiAVObject { osg::BoundingSpheref mBoundingSphere; std::array mBoundMinMax; + RecordPtrT mSkin; + BSShaderPropertyPtr mShaderProperty; + NiAlphaPropertyPtr mAlphaProperty; BSVertexDesc mVertDesc; uint32_t mDataSize; std::vector mVertData; From af24d3fd3c6a7fea57be4d3d078f0619a3cd86f2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 14:16:57 +0300 Subject: [PATCH 0249/2167] Nth revision of NifLoader geometry handling Handle BSSegmentedTriShape --- components/nifbullet/bulletnifloader.cpp | 34 +++- components/nifosg/nifloader.cpp | 242 +++++++++++------------ 2 files changed, 146 insertions(+), 130 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index df4155db7d..a72ec36cad 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -32,6 +32,32 @@ namespace return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } + bool isTypeNiGeometry(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_NiTriStrips: + case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + return false; + } + + bool isTypeTriShape(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + + return false; + } + void prepareTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriBasedGeomData& data) { // FIXME: copying vertices/indices individually is unreasonable @@ -81,7 +107,7 @@ namespace auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) -> decltype(function(static_cast(geometry.mData.get()))) { - if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) + if (isTypeTriShape(geometry.recType)) { auto data = static_cast(geometry.mData.getPtr()); if (data->mTriangles.empty()) @@ -329,12 +355,8 @@ namespace NifBullet // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV - && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips - || node.recType == Nif::RC_BSLODTriShape)) - { + if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && isTypeNiGeometry(node.recType)) handleNiTriShape(static_cast(node), parent, args); - } } // For NiNodes, loop through children diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index fbcd2f52bd..a5c95fbd67 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -88,7 +88,7 @@ namespace } } - bool isTypeGeometry(int type) + bool isTypeNiGeometry(int type) { switch (type) { @@ -96,6 +96,19 @@ namespace case Nif::RC_NiTriStrips: case Nif::RC_NiLines: case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + return false; + } + + bool isTypeBSGeometry(int type) + { + switch (type) + { + case Nif::RC_BSTriShape: + case Nif::RC_BSDynamicTriShape: + case Nif::RC_BSMeshLODTriShape: return true; } return false; @@ -125,15 +138,6 @@ namespace } } } - - auto geometry = dynamic_cast(nifNode); - if (geometry) - { - if (!geometry->mShaderProperty.empty()) - out.emplace_back(geometry->mShaderProperty.getPtr()); - if (!geometry->mAlphaProperty.empty()) - out.emplace_back(geometry->mAlphaProperty.getPtr()); - } } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale @@ -443,11 +447,16 @@ namespace NifOsg } } - auto geometry = dynamic_cast(nifNode); - // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property - if (geometry && !geometry->mShaderProperty.empty()) - handleProperty(geometry->mShaderProperty.getPtr(), applyTo, composite, imageManager, boundTextures, - animflags, hasStencilProperty); + // NiAlphaProperty is handled as a drawable property + Nif::BSShaderPropertyPtr shaderprop = nullptr; + if (isTypeNiGeometry(nifNode->recType)) + shaderprop = static_cast(nifNode)->mShaderProperty; + else if (isTypeBSGeometry(nifNode->recType)) + shaderprop = static_cast(nifNode)->mShaderProperty; + + if (!shaderprop.empty()) + handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, + hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -747,7 +756,9 @@ namespace NifOsg applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); - const bool isGeometry = isTypeGeometry(nifNode->recType); + const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); + const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); + const bool isGeometry = isNiGeometry || isBSGeometry; if (isGeometry && !args.mSkipMeshes) { @@ -762,12 +773,8 @@ namespace NifOsg skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); if (!skip) { - Nif::NiSkinInstancePtr skin = static_cast(nifNode)->mSkin; - - if (skin.empty()) - handleGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); - else - handleSkinnedGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); + if (isNiGeometry) + handleNiGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); if (!nifNode->mController.empty()) handleMeshControllers(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); @@ -1342,41 +1349,7 @@ namespace NifOsg } } - void handleNiGeometryData(osg::Geometry* geometry, const Nif::NiGeometryData* data, - const std::vector& boundTextures, const std::string& name) - { - const auto& vertices = data->mVertices; - const auto& normals = data->mNormals; - const auto& colors = data->mColors; - if (!vertices.empty()) - geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); - if (!normals.empty()) - geometry->setNormalArray( - new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); - if (!colors.empty()) - geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); - - const auto& uvlist = data->mUVList; - int textureStage = 0; - for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); - ++it, ++textureStage) - { - unsigned int uvSet = *it; - if (uvSet >= uvlist.size()) - { - Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " - << mFilename; - if (uvlist.empty()) - continue; - uvSet = 0; - } - - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), - osg::Array::BIND_PER_VERTEX); - } - } - - void handleNiGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, + void handleNiGeometryData(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -1458,7 +1431,36 @@ namespace NifOsg new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), line.data())); } } - handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->mName); + + const auto& vertices = niGeometryData->mVertices; + const auto& normals = niGeometryData->mNormals; + const auto& colors = niGeometryData->mColors; + if (!vertices.empty()) + geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); + if (!normals.empty()) + geometry->setNormalArray( + new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); + if (!colors.empty()) + geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); + + const auto& uvlist = niGeometryData->mUVList; + int textureStage = 0; + for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); + ++it, ++textureStage) + { + unsigned int uvSet = *it; + if (uvSet >= uvlist.size()) + { + Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << nifNode->mName + << "\" in " << mFilename; + if (uvlist.empty()) + continue; + uvSet = 0; + } + + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), + osg::Array::BIND_PER_VERTEX); + } // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1466,100 +1468,92 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); + if (!niGeometry->mShaderProperty.empty()) + drawableProps.emplace_back(niGeometry->mShaderProperty.getPtr()); + if (!niGeometry->mAlphaProperty.empty()) + drawableProps.emplace_back(niGeometry->mAlphaProperty.getPtr()); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->mColors.empty(), animflags); } - void handleGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + void handleNiGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(isTypeGeometry(nifNode->recType)); + assert(isTypeNiGeometry(nifNode->recType)); + osg::ref_ptr geom(new osg::Geometry); - handleNiGeometry(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); + handleNiGeometryData(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); // If the record had no valid geometry data in it, early-out if (geom->empty()) return; - osg::ref_ptr drawable; + + osg::ref_ptr drawable = geom; + + auto niGeometry = static_cast(nifNode); + if (!niGeometry->mSkin.empty()) + { + osg::ref_ptr rig(new SceneUtil::RigGeometry); + rig->setSourceGeometry(geom); + + // Assign bone weights + osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); + + const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); + const Nif::NiSkinData* data = skin->mData.getPtr(); + const Nif::NiAVObjectList& bones = skin->mBones; + for (std::size_t i = 0; i < bones.size(); ++i) + { + std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + + SceneUtil::RigGeometry::BoneInfluence influence; + influence.mWeights = data->mBones[i].mWeights; + influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + influence.mBoundSphere = data->mBones[i].mBoundSphere; + + map->mData.emplace_back(boneName, influence); + } + rig->setInfluenceMap(map); + + drawable = rig; + } + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiGeomMorpherController) { - const Nif::NiGeomMorpherController* nimorphctrl - = static_cast(ctrl.getPtr()); + if (!niGeometry->mSkin.empty()) + continue; + + auto nimorphctrl = static_cast(ctrl.getPtr()); if (nimorphctrl->mData.empty()) continue; - drawable = handleMorphGeometry(nimorphctrl, geom, parentNode, composite, boundTextures, animflags); + + const std::vector& morphs = nimorphctrl->mData.getPtr()->mMorphs; + if (morphs.empty() || morphs[0].mVertices.size() + != static_cast(geom->getVertexArray())->size()) + continue; + + osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; + morphGeom->setSourceGeometry(geom); + for (unsigned int i = 0; i < morphs.size(); ++i) + morphGeom->addMorphTarget( + new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); osg::ref_ptr morphctrl = new GeomMorpherController(nimorphctrl); setupController(ctrl.getPtr(), morphctrl, animflags); - drawable->setUpdateCallback(morphctrl); + morphGeom->setUpdateCallback(morphctrl); + + drawable = morphGeom; break; } } - if (!drawable.get()) - drawable = geom; + drawable->setName(nifNode->mName); parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, - osg::ref_ptr sourceGeometry, osg::Node* parentNode, - SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, - int animflags) - { - osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - morphGeom->setSourceGeometry(sourceGeometry); - - const std::vector& morphs = morpher->mData.getPtr()->mMorphs; - if (morphs.empty()) - return morphGeom; - if (morphs[0].mVertices.size() - != static_cast(sourceGeometry->getVertexArray())->size()) - return morphGeom; - for (unsigned int i = 0; i < morphs.size(); ++i) - morphGeom->addMorphTarget( - new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); - - return morphGeom; - } - - void handleSkinnedGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, - SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, - int animflags) - { - assert(isTypeGeometry(nifNode->recType)); - osg::ref_ptr geometry(new osg::Geometry); - handleNiGeometry(nifNode, parent, geometry, parentNode, composite, boundTextures, animflags); - if (geometry->empty()) - return; - osg::ref_ptr rig(new SceneUtil::RigGeometry); - rig->setSourceGeometry(geometry); - rig->setName(nifNode->mName); - - // Assign bone weights - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - - const Nif::NiSkinInstance* skin = static_cast(nifNode)->mSkin.getPtr(); - const Nif::NiSkinData* data = skin->mData.getPtr(); - const Nif::NiAVObjectList& bones = skin->mBones; - for (std::size_t i = 0; i < bones.size(); ++i) - { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = data->mBones[i].mWeights; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); - } - rig->setInfluenceMap(map); - - parentNode->addChild(rig); - } - osg::BlendFunc::BlendFuncMode getBlendMode(int mode) { switch (mode) From 0497fd111f60b21d7fa400c39428c7fc004b3be4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 16:28:04 +0300 Subject: [PATCH 0250/2167] Handle BSTriShape basic geometry --- components/nif/node.cpp | 7 ++- components/nifosg/nifloader.cpp | 96 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 860e75671f..62c32f3c15 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -393,6 +393,8 @@ namespace Nif mShaderProperty.read(nif); mAlphaProperty.read(nif); mVertDesc.read(nif); + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) + mVertDesc.mFlags |= BSVertexDesc::VertexAttribute::Full_Precision; if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) mTriangles.resize(nif->get() * 3); @@ -464,10 +466,7 @@ namespace Nif void BSVertexData::read(NIFStream* nif, uint16_t flags) { - bool fullPrecision = true; - if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_SSE) - fullPrecision = flags & BSVertexDesc::VertexAttribute::Full_Precision; - + bool fullPrecision = flags & BSVertexDesc::VertexAttribute::Full_Precision; bool hasVertex = flags & BSVertexDesc::VertexAttribute::Vertex; bool hasTangent = flags & BSVertexDesc::VertexAttribute::Tangents; bool hasUV = flags & BSVertexDesc::VertexAttribute::UVs; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5c95fbd67..9c06293adb 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -775,6 +775,8 @@ namespace NifOsg { if (isNiGeometry) handleNiGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); + else // isBSGeometry + handleBSGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); if (!nifNode->mController.empty()) handleMeshControllers(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); @@ -1554,6 +1556,100 @@ namespace NifOsg parentNode->addChild(drawable); } + void handleBSGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, + int animflags) + { + assert(isTypeBSGeometry(nifNode->recType)); + + auto bsTriShape = static_cast(nifNode); + const std::vector& triangles = bsTriShape->mTriangles; + if (triangles.empty()) + return; + + osg::ref_ptr geometry(new osg::Geometry); + geometry->addPrimitiveSet(new osg::DrawElementsUShort( + osg::PrimitiveSet::TRIANGLES, triangles.size(), triangles.data())); + + auto normbyteToFloat = [](uint8_t value) { return value / 255.f * 2.f - 1.f; }; + auto halfToFloat = [](uint16_t value) + { + uint32_t bits = static_cast(value & 0x8000) << 16; + + const uint32_t exp16 = (value & 0x7c00) >> 10; + uint32_t frac16 = value & 0x3ff; + if (exp16) + bits |= (exp16 + 0x70) << 23; + else if (frac16) + { + uint8_t offset = 0; + do + { + ++offset; + frac16 <<= 1; + } + while ((frac16 & 0x400) != 0x400); + frac16 &= 0x3ff; + bits |= (0x71 - offset) << 23; + } + bits |= frac16 << 13; + + float result; + std::memcpy(&result, &bits, sizeof(float)); + return result; + }; + + const bool fullPrecision = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Full_Precision; + const bool hasVertices = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Vertex; + const bool hasNormals = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Normals; + const bool hasColors = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Vertex_Colors; + const bool hasUV = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::UVs; + + std::vector vertices; + std::vector normals; + std::vector colors; + std::vector uvlist; + for (auto& elem : bsTriShape->mVertData) + { + if (hasVertices) + { + if (fullPrecision) + vertices.emplace_back(elem.mVertex.x(), elem.mVertex.y(), elem.mVertex.z()); + else + vertices.emplace_back(halfToFloat(elem.mHalfVertex[0]), halfToFloat(elem.mHalfVertex[1]), halfToFloat(elem.mHalfVertex[2])); + } + if (hasNormals) + normals.emplace_back(normbyteToFloat(elem.mNormal[0]), normbyteToFloat(elem.mNormal[1]), normbyteToFloat(elem.mNormal[2])); + if (hasColors) + colors.emplace_back(elem.mVertColor[0], elem.mVertColor[1], elem.mVertColor[2], elem.mVertColor[3]); + if (hasUV) + uvlist.emplace_back(halfToFloat(elem.mUV[0]), 1.0 - halfToFloat(elem.mUV[1])); + } + + if (!vertices.empty()) + geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); + if (!normals.empty()) + geometry->setNormalArray( + new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); + if (!colors.empty()) + geometry->setColorArray( + new osg::Vec4ubArray(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); + if (!uvlist.empty()) + geometry->setTexCoordArray(0, new osg::Vec2Array(uvlist.size(), uvlist.data()), + osg::Array::BIND_PER_VERTEX); + + std::vector drawableProps; + collectDrawableProperties(nifNode, parent, drawableProps); + if (!bsTriShape->mShaderProperty.empty()) + drawableProps.emplace_back(bsTriShape->mShaderProperty.getPtr()); + if (!bsTriShape->mAlphaProperty.empty()) + drawableProps.emplace_back(bsTriShape->mAlphaProperty.getPtr()); + applyDrawableProperties(parentNode, drawableProps, composite, !colors.empty(), animflags); + + geometry->setName(nifNode->mName); + parentNode->addChild(geometry); + } + osg::BlendFunc::BlendFuncMode getBlendMode(int mode) { switch (mode) From b4f81fb4faeb13907bc1582b7116bab1be49646e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 17:20:30 +0300 Subject: [PATCH 0251/2167] Fix formatting --- components/nif/node.hpp | 1 - components/nifosg/nifloader.cpp | 31 ++++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index d17de999ee..e873fc27dc 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -138,7 +138,6 @@ namespace Nif void read(NIFStream* nif); }; - NiGeometryDataPtr mData; NiSkinInstancePtr mSkin; MaterialData mMaterial; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9c06293adb..3eae14cba8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -456,7 +456,7 @@ namespace NifOsg if (!shaderprop.empty()) handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -1533,8 +1533,9 @@ namespace NifOsg continue; const std::vector& morphs = nimorphctrl->mData.getPtr()->mMorphs; - if (morphs.empty() || morphs[0].mVertices.size() - != static_cast(geom->getVertexArray())->size()) + if (morphs.empty() + || morphs[0].mVertices.size() + != static_cast(geom->getVertexArray())->size()) continue; osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; @@ -1568,12 +1569,11 @@ namespace NifOsg return; osg::ref_ptr geometry(new osg::Geometry); - geometry->addPrimitiveSet(new osg::DrawElementsUShort( - osg::PrimitiveSet::TRIANGLES, triangles.size(), triangles.data())); + geometry->addPrimitiveSet( + new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(), triangles.data())); auto normbyteToFloat = [](uint8_t value) { return value / 255.f * 2.f - 1.f; }; - auto halfToFloat = [](uint16_t value) - { + auto halfToFloat = [](uint16_t value) { uint32_t bits = static_cast(value & 0x8000) << 16; const uint32_t exp16 = (value & 0x7c00) >> 10; @@ -1587,8 +1587,7 @@ namespace NifOsg { ++offset; frac16 <<= 1; - } - while ((frac16 & 0x400) != 0x400); + } while ((frac16 & 0x400) != 0x400); frac16 &= 0x3ff; bits |= (0x71 - offset) << 23; } @@ -1599,7 +1598,7 @@ namespace NifOsg return result; }; - const bool fullPrecision = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Full_Precision; + const bool fullPrec = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Full_Precision; const bool hasVertices = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Vertex; const bool hasNormals = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Normals; const bool hasColors = bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Vertex_Colors; @@ -1613,13 +1612,15 @@ namespace NifOsg { if (hasVertices) { - if (fullPrecision) + if (fullPrec) vertices.emplace_back(elem.mVertex.x(), elem.mVertex.y(), elem.mVertex.z()); else - vertices.emplace_back(halfToFloat(elem.mHalfVertex[0]), halfToFloat(elem.mHalfVertex[1]), halfToFloat(elem.mHalfVertex[2])); + vertices.emplace_back(halfToFloat(elem.mHalfVertex[0]), halfToFloat(elem.mHalfVertex[1]), + halfToFloat(elem.mHalfVertex[2])); } if (hasNormals) - normals.emplace_back(normbyteToFloat(elem.mNormal[0]), normbyteToFloat(elem.mNormal[1]), normbyteToFloat(elem.mNormal[2])); + normals.emplace_back(normbyteToFloat(elem.mNormal[0]), normbyteToFloat(elem.mNormal[1]), + normbyteToFloat(elem.mNormal[2])); if (hasColors) colors.emplace_back(elem.mVertColor[0], elem.mVertColor[1], elem.mVertColor[2], elem.mVertColor[3]); if (hasUV) @@ -1635,8 +1636,8 @@ namespace NifOsg geometry->setColorArray( new osg::Vec4ubArray(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); if (!uvlist.empty()) - geometry->setTexCoordArray(0, new osg::Vec2Array(uvlist.size(), uvlist.data()), - osg::Array::BIND_PER_VERTEX); + geometry->setTexCoordArray( + 0, new osg::Vec2Array(uvlist.size(), uvlist.data()), osg::Array::BIND_PER_VERTEX); std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); From b9d42946bed9da99079db7997870c8809d29255a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 18:41:06 +0300 Subject: [PATCH 0252/2167] Fix SSE mesh loading --- components/nif/node.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 62c32f3c15..2de337128c 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -393,8 +393,6 @@ namespace Nif mShaderProperty.read(nif); mAlphaProperty.read(nif); mVertDesc.read(nif); - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) - mVertDesc.mFlags |= BSVertexDesc::VertexAttribute::Full_Precision; if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) mTriangles.resize(nif->get() * 3); @@ -462,6 +460,8 @@ namespace Nif mLandscapeDataOffset = (data & 0xF00000000) >> 0x20; mEyeDataOffset = (data & 0xF000000000) >> 0x24; mFlags = (data & 0xFFF00000000000) >> 0x2C; + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) + mFlags |= BSVertexDesc::VertexAttribute::Full_Precision; } void BSVertexData::read(NIFStream* nif, uint16_t flags) From 284129b9ec4f2c73d48e63b0efe75c92b4b01d09 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Sep 2023 14:35:58 +0300 Subject: [PATCH 0253/2167] Support Fallout 4 skinning Convert the skinning data into NiSkinData-compatible format --- components/nif/node.cpp | 2 ++ components/nifosg/nifloader.cpp | 39 +++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 2de337128c..8f5443b28e 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -426,6 +426,8 @@ namespace Nif mSkin.post(nif); mShaderProperty.post(nif); mAlphaProperty.post(nif); + if (!mSkin.empty()) + nif.setUseSkinning(true); } void BSDynamicTriShape::read(NIFStream* nif) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3eae14cba8..e42cafeade 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1572,6 +1572,8 @@ namespace NifOsg geometry->addPrimitiveSet( new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, triangles.size(), triangles.data())); + osg::ref_ptr drawable = geometry; + auto normbyteToFloat = [](uint8_t value) { return value / 255.f * 2.f - 1.f; }; auto halfToFloat = [](uint16_t value) { uint32_t bits = static_cast(value & 0x8000) << 16; @@ -1639,6 +1641,39 @@ namespace NifOsg geometry->setTexCoordArray( 0, new osg::Vec2Array(uvlist.size(), uvlist.data()), osg::Array::BIND_PER_VERTEX); + if (!bsTriShape->mSkin.empty() && bsTriShape->mSkin->recType == Nif::RC_BSSkinInstance + && bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Skinned) + { + osg::ref_ptr rig(new SceneUtil::RigGeometry); + rig->setSourceGeometry(geometry); + + osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); + + auto skin = static_cast(bsTriShape->mSkin.getPtr()); + const Nif::BSSkinBoneData* data = skin->mData.getPtr(); + const Nif::NiAVObjectList& bones = skin->mBones; + std::vector> vertWeights(data->mBones.size()); + for (size_t i = 0; i < vertices.size(); i++) + for (int j = 0; j < 4; j++) + vertWeights[bsTriShape->mVertData[i].mBoneIndices[j]].emplace_back( + i, halfToFloat(bsTriShape->mVertData[i].mBoneWeights[j])); + + for (std::size_t i = 0; i < bones.size(); ++i) + { + std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + + SceneUtil::RigGeometry::BoneInfluence influence; + influence.mWeights = vertWeights[i]; + influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + influence.mBoundSphere = data->mBones[i].mBoundSphere; + + map->mData.emplace_back(boneName, influence); + } + rig->setInfluenceMap(map); + + drawable = rig; + } + std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); if (!bsTriShape->mShaderProperty.empty()) @@ -1647,8 +1682,8 @@ namespace NifOsg drawableProps.emplace_back(bsTriShape->mAlphaProperty.getPtr()); applyDrawableProperties(parentNode, drawableProps, composite, !colors.empty(), animflags); - geometry->setName(nifNode->mName); - parentNode->addChild(geometry); + drawable->setName(nifNode->mName); + parentNode->addChild(drawable); } osg::BlendFunc::BlendFuncMode getBlendMode(int mode) From 1da9038b35d93da797876d83eb947a9c1f4adb75 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Sep 2023 18:29:22 +0300 Subject: [PATCH 0254/2167] Updates to resource path handling - Remove consecutive slashes - Only use backslashes - Try to find the top level directory in the path before prepending it --- components/misc/resourcehelpers.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 0ae45c6c26..cddcbe463b 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -56,13 +56,26 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string correctedPath = Misc::StringUtils::lowerCase(resPath); - // Apparently, leading separators are allowed - while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) + // Flatten slashes + std::replace(correctedPath.begin(), correctedPath.end(), '/', '\\'); + auto bothSeparators = [](char a, char b) { return a == '\\' && b == '\\'; }; + correctedPath.erase(std::unique(correctedPath.begin(), correctedPath.end(), bothSeparators), correctedPath.end()); + + // Remove leading separator + if (!correctedPath.empty() && correctedPath[0] == '\\') correctedPath.erase(0, 1); + // Handle top level directory if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() - || (correctedPath[topLevelDirectory.size()] != '/' && correctedPath[topLevelDirectory.size()] != '\\')) - correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath; + || correctedPath[topLevelDirectory.size()] != '\\') + { + std::string topLevelPrefix = std::string{ topLevelDirectory } + '\\'; + size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); + if (topLevelPos == std::string::npos) + correctedPath = topLevelPrefix + correctedPath; + else + correctedPath.erase(0, topLevelPos + 1); + } std::string origExt = correctedPath; From 79e6c9a92a4ad4f0da0ce0c07184e85044581769 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 2 Oct 2023 21:12:08 +0300 Subject: [PATCH 0255/2167] Add more comments to handleBSGeometry --- components/nifosg/nifloader.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e42cafeade..fdf51486ed 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1574,7 +1574,11 @@ namespace NifOsg osg::ref_ptr drawable = geometry; + // Some input geometry may not be used as is so it needs to be converted. + // Normals, tangents and bitangents use a special normal map-like format not equivalent to snorm8 or unorm8 auto normbyteToFloat = [](uint8_t value) { return value / 255.f * 2.f - 1.f; }; + // Vertices and UV sets may be half-precision. + // OSG doesn't have a way to pass half-precision data at the moment. auto halfToFloat = [](uint16_t value) { uint32_t bits = static_cast(value & 0x8000) << 16; @@ -1641,6 +1645,8 @@ namespace NifOsg geometry->setTexCoordArray( 0, new osg::Vec2Array(uvlist.size(), uvlist.data()), osg::Array::BIND_PER_VERTEX); + // This is the skinning data Fallout 4 provides + // TODO: support Skyrim SE skinning data if (!bsTriShape->mSkin.empty() && bsTriShape->mSkin->recType == Nif::RC_BSSkinInstance && bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Skinned) { From 79bda2e694940a6b594fed46dd22c9022751f2ea Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 4 Oct 2023 20:46:36 +0300 Subject: [PATCH 0256/2167] Avoid redundant animation resets in updateIdleStormState --- apps/openmw/mwmechanics/character.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 823dd0f598..92bc2d143e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1166,9 +1166,14 @@ namespace MWMechanics void CharacterController::updateIdleStormState(bool inwater) const { - if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater) + if (!mAnimation->hasAnimation("idlestorm")) + return; + + bool animPlaying = mAnimation->isPlaying("idlestorm"); + if (mUpperBodyState != UpperBodyState::None || inwater) { - mAnimation->disable("idlestorm"); + if (animPlaying) + mAnimation->disable("idlestorm"); return; } @@ -1181,7 +1186,7 @@ namespace MWMechanics characterDirection.normalize(); if (stormDirection * characterDirection < -0.5f) { - if (!mAnimation->isPlaying("idlestorm")) + if (!animPlaying) { int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul); @@ -1194,7 +1199,7 @@ namespace MWMechanics } } - if (mAnimation->isPlaying("idlestorm")) + if (animPlaying) { mAnimation->setLoopingEnabled("idlestorm", false); } From 3c03555b96116601531c1ba5a16db59a27d65c08 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 4 Oct 2023 21:53:20 +0200 Subject: [PATCH 0257/2167] Move werewolf FOV from unused to float and use string_view --- components/fallback/validate.cpp | 189 +++++++++++++++---------------- 1 file changed, 94 insertions(+), 95 deletions(-) diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index ca9a218888..300d2b5dd1 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -5,7 +5,7 @@ #include #include -static const std::set> allowedKeysInt = { "LightAttenuation_LinearMethod", +static const std::set allowedKeysInt = { "LightAttenuation_LinearMethod", "LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant", "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", @@ -13,7 +13,7 @@ static const std::set> allowedKeysInt = { "LightAttenua "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip" }; -static const std::set> allowedKeysFloat = { "Inventory_DirectionalAmbientB", +static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", "Inventory_DirectionalDiffuseG", "Inventory_DirectionalDiffuseR", "Inventory_DirectionalRotationX", "Inventory_DirectionalRotationY", "LightAttenuation_ConstantValue", "LightAttenuation_LinearValue", @@ -94,91 +94,90 @@ static const std::set> allowedKeysFloat = { "Inventory_ "Weather_Thunderstorm_Thunder_Frequency", "Weather_Thunderstorm_Thunder_Threshold", "Weather_Thunderstorm_Transition_Delta", "Weather_Thunderstorm_Wind_Speed" }; -static const std::set> allowedKeysNonNumeric = { "Blood_Model_0", "Blood_Model_1", - "Blood_Model_2", "FontColor_color_active", "FontColor_color_active_over", "FontColor_color_active_pressed", - "FontColor_color_answer", "FontColor_color_answer_over", "FontColor_color_answer_pressed", - "FontColor_color_background", "FontColor_color_big_answer", "FontColor_color_big_answer_over", - "FontColor_color_big_answer_pressed", "FontColor_color_big_header", "FontColor_color_big_link", - "FontColor_color_big_link_over", "FontColor_color_big_link_pressed", "FontColor_color_big_normal", - "FontColor_color_big_normal_over", "FontColor_color_big_normal_pressed", "FontColor_color_big_notify", - "FontColor_color_count", "FontColor_color_disabled", "FontColor_color_disabled_over", - "FontColor_color_disabled_pressed", "FontColor_color_fatigue", "FontColor_color_focus", "FontColor_color_header", - "FontColor_color_health", "FontColor_color_journal_link", "FontColor_color_journal_link_over", - "FontColor_color_journal_link_pressed", "FontColor_color_journal_topic", "FontColor_color_journal_topic_over", - "FontColor_color_journal_topic_pressed", "FontColor_color_link", "FontColor_color_link_over", - "FontColor_color_link_pressed", "FontColor_color_magic", "FontColor_color_magic_fill", "FontColor_color_misc", - "FontColor_color_negative", "FontColor_color_normal", "FontColor_color_normal_over", - "FontColor_color_normal_pressed", "FontColor_color_notify", "FontColor_color_positive", - "FontColor_color_weapon_fill", "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", "Level_Up_Default", - "Moons_Script_Color", "Movies_Company_Logo", "Movies_Morrowind_Logo", "Movies_New_Game", "Question_10_AnswerOne", - "Question_10_AnswerThree", "Question_10_AnswerTwo", "Question_10_Question", "Question_10_Sound", - "Question_1_AnswerOne", "Question_1_AnswerThree", "Question_1_AnswerTwo", "Question_1_Question", "Question_1_Sound", - "Question_2_AnswerOne", "Question_2_AnswerThree", "Question_2_AnswerTwo", "Question_2_Question", "Question_2_Sound", - "Question_3_AnswerOne", "Question_3_AnswerThree", "Question_3_AnswerTwo", "Question_3_Question", "Question_3_Sound", - "Question_4_AnswerOne", "Question_4_AnswerThree", "Question_4_AnswerTwo", "Question_4_Question", "Question_4_Sound", - "Question_5_AnswerOne", "Question_5_AnswerThree", "Question_5_AnswerTwo", "Question_5_Question", "Question_5_Sound", - "Question_6_AnswerOne", "Question_6_AnswerThree", "Question_6_AnswerTwo", "Question_6_Question", "Question_6_Sound", - "Question_7_AnswerOne", "Question_7_AnswerThree", "Question_7_AnswerTwo", "Question_7_Question", "Question_7_Sound", - "Question_8_AnswerOne", "Question_8_AnswerThree", "Question_8_AnswerTwo", "Question_8_Question", "Question_8_Sound", - "Question_9_AnswerOne", "Question_9_AnswerThree", "Question_9_AnswerTwo", "Question_9_Question", "Question_9_Sound", - "Water_NearWaterIndoorID", "Water_NearWaterOutdoorID", "Water_RippleTexture", "Water_SurfaceTexture", - "Water_UnderwaterColor", "Weather_Blizzard_Ambient_Day_Color", "Weather_Blizzard_Ambient_Loop_Sound_ID", - "Weather_Blizzard_Ambient_Night_Color", "Weather_Blizzard_Ambient_Sunrise_Color", - "Weather_Blizzard_Ambient_Sunset_Color", "Weather_Blizzard_Cloud_Texture", "Weather_Blizzard_Fog_Day_Color", - "Weather_Blizzard_Fog_Night_Color", "Weather_Blizzard_Fog_Sunrise_Color", "Weather_Blizzard_Fog_Sunset_Color", - "Weather_Blizzard_Sky_Day_Color", "Weather_Blizzard_Sky_Night_Color", "Weather_Blizzard_Sky_Sunrise_Color", - "Weather_Blizzard_Sky_Sunset_Color", "Weather_Blizzard_Storm_Threshold", "Weather_Blizzard_Sun_Day_Color", - "Weather_Blizzard_Sun_Disc_Sunset_Color", "Weather_Blizzard_Sun_Night_Color", "Weather_Blizzard_Sun_Sunrise_Color", - "Weather_Blizzard_Sun_Sunset_Color", "Weather_BumpFadeColor", "Weather_Clear_Ambient_Day_Color", - "Weather_Clear_Ambient_Loop_Sound_ID", "Weather_Clear_Ambient_Night_Color", "Weather_Clear_Ambient_Sunrise_Color", - "Weather_Clear_Ambient_Sunset_Color", "Weather_Clear_Cloud_Texture", "Weather_Clear_Fog_Day_Color", - "Weather_Clear_Fog_Night_Color", "Weather_Clear_Fog_Sunrise_Color", "Weather_Clear_Fog_Sunset_Color", - "Weather_Clear_Sky_Day_Color", "Weather_Clear_Sky_Night_Color", "Weather_Clear_Sky_Sunrise_Color", - "Weather_Clear_Sky_Sunset_Color", "Weather_Clear_Sun_Day_Color", "Weather_Clear_Sun_Disc_Sunset_Color", - "Weather_Clear_Sun_Night_Color", "Weather_Clear_Sun_Sunrise_Color", "Weather_Clear_Sun_Sunset_Color", - "Weather_Cloudy_Ambient_Day_Color", "Weather_Cloudy_Ambient_Loop_Sound_ID", "Weather_Cloudy_Ambient_Night_Color", - "Weather_Cloudy_Ambient_Sunrise_Color", "Weather_Cloudy_Ambient_Sunset_Color", "Weather_Cloudy_Cloud_Texture", - "Weather_Cloudy_Fog_Day_Color", "Weather_Cloudy_Fog_Night_Color", "Weather_Cloudy_Fog_Sunrise_Color", - "Weather_Cloudy_Fog_Sunset_Color", "Weather_Cloudy_Sky_Day_Color", "Weather_Cloudy_Sky_Night_Color", - "Weather_Cloudy_Sky_Sunrise_Color", "Weather_Cloudy_Sky_Sunset_Color", "Weather_Cloudy_Sun_Day_Color", - "Weather_Cloudy_Sun_Disc_Sunset_Color", "Weather_Cloudy_Sun_Night_Color", "Weather_Cloudy_Sun_Sunrise_Color", - "Weather_Cloudy_Sun_Sunset_Color", "Weather_EnvReduceColor", "Weather_Fog_Depth_Change_Speed", - "Weather_Foggy_Ambient_Day_Color", "Weather_Foggy_Ambient_Loop_Sound_ID", "Weather_Foggy_Ambient_Night_Color", - "Weather_Foggy_Ambient_Sunrise_Color", "Weather_Foggy_Ambient_Sunset_Color", "Weather_Foggy_Cloud_Texture", - "Weather_Foggy_Fog_Day_Color", "Weather_Foggy_Fog_Night_Color", "Weather_Foggy_Fog_Sunrise_Color", - "Weather_Foggy_Fog_Sunset_Color", "Weather_Foggy_Sky_Day_Color", "Weather_Foggy_Sky_Night_Color", - "Weather_Foggy_Sky_Sunrise_Color", "Weather_Foggy_Sky_Sunset_Color", "Weather_Foggy_Sun_Day_Color", - "Weather_Foggy_Sun_Disc_Sunset_Color", "Weather_Foggy_Sun_Night_Color", "Weather_Foggy_Sun_Sunrise_Color", - "Weather_Foggy_Sun_Sunset_Color", "Weather_LerpCloseColor", "Weather_Overcast_Ambient_Day_Color", - "Weather_Overcast_Ambient_Loop_Sound_ID", "Weather_Overcast_Ambient_Night_Color", - "Weather_Overcast_Ambient_Sunrise_Color", "Weather_Overcast_Ambient_Sunset_Color", "Weather_Overcast_Cloud_Texture", - "Weather_Overcast_Fog_Day_Color", "Weather_Overcast_Fog_Night_Color", "Weather_Overcast_Fog_Sunrise_Color", - "Weather_Overcast_Fog_Sunset_Color", "Weather_Overcast_Sky_Day_Color", "Weather_Overcast_Sky_Night_Color", - "Weather_Overcast_Sky_Sunrise_Color", "Weather_Overcast_Sky_Sunset_Color", "Weather_Overcast_Sun_Day_Color", - "Weather_Overcast_Sun_Disc_Sunset_Color", "Weather_Overcast_Sun_Night_Color", "Weather_Overcast_Sun_Sunrise_Color", - "Weather_Overcast_Sun_Sunset_Color", "Weather_Rain_Ambient_Day_Color", "Weather_Rain_Ambient_Loop_Sound_ID", - "Weather_Rain_Ambient_Night_Color", "Weather_Rain_Ambient_Sunrise_Color", "Weather_Rain_Ambient_Sunset_Color", - "Weather_Rain_Cloud_Texture", "Weather_Rain_Fog_Day_Color", "Weather_Rain_Fog_Night_Color", - "Weather_Rain_Fog_Sunrise_Color", "Weather_Rain_Fog_Sunset_Color", "Weather_Rain_Rain_Loop_Sound_ID", - "Weather_Rain_Ripple_Radius", "Weather_Rain_Ripples", "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", - "Weather_Rain_Ripples_Per_Drop", "Weather_Rain_Sky_Day_Color", "Weather_Rain_Sky_Night_Color", - "Weather_Rain_Sky_Sunrise_Color", "Weather_Rain_Sky_Sunset_Color", "Weather_Rain_Sun_Day_Color", - "Weather_Rain_Sun_Disc_Sunset_Color", "Weather_Rain_Sun_Night_Color", "Weather_Rain_Sun_Sunrise_Color", - "Weather_Rain_Sun_Sunset_Color", "Weather_Snow_Ambient_Day_Color", "Weather_Snow_Ambient_Loop_Sound_ID", - "Weather_Snow_Ambient_Night_Color", "Weather_Snow_Ambient_Sunrise_Color", "Weather_Snow_Ambient_Sunset_Color", - "Weather_Snow_Cloud_Texture", "Weather_Snow_Fog_Day_Color", "Weather_Snow_Fog_Night_Color", - "Weather_Snow_Fog_Sunrise_Color", "Weather_Snow_Fog_Sunset_Color", "Weather_Snow_Gravity_Scale", - "Weather_Snow_High_Kill", "Weather_Snow_Low_Kill", "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", - "Weather_Snow_Ripples", "Weather_Snow_Ripple_Scale", "Weather_Snow_Ripple_Speed", "Weather_Snow_Ripples_Per_Flake", - "Weather_Snow_Sky_Day_Color", "Weather_Snow_Sky_Night_Color", "Weather_Snow_Sky_Sunrise_Color", - "Weather_Snow_Sky_Sunset_Color", "Weather_Snow_Snow_Diameter", "Weather_Snow_Snow_Entrance_Speed", - "Weather_Snow_Snow_Height_Max", "Weather_Snow_Snow_Height_Min", "Weather_Snow_Snow_Threshold", - "Weather_Snow_Sun_Day_Color", "Weather_Snow_Sun_Disc_Sunset_Color", "Weather_Snow_Sun_Night_Color", - "Weather_Snow_Sun_Sunrise_Color", "Weather_Snow_Sun_Sunset_Color", "Weather_Sun_Glare_Fader_Color", - "Weather_Thunderstorm_Ambient_Day_Color", "Weather_Thunderstorm_Ambient_Loop_Sound_ID", - "Weather_Thunderstorm_Ambient_Night_Color", "Weather_Thunderstorm_Ambient_Sunrise_Color", - "Weather_Thunderstorm_Ambient_Sunset_Color", "Weather_Thunderstorm_Cloud_Texture", - "Weather_Thunderstorm_Fog_Day_Color", "Weather_Thunderstorm_Fog_Night_Color", +static const std::set allowedKeysNonNumeric = { "Blood_Model_0", "Blood_Model_1", "Blood_Model_2", + "FontColor_color_active", "FontColor_color_active_over", "FontColor_color_active_pressed", "FontColor_color_answer", + "FontColor_color_answer_over", "FontColor_color_answer_pressed", "FontColor_color_background", + "FontColor_color_big_answer", "FontColor_color_big_answer_over", "FontColor_color_big_answer_pressed", + "FontColor_color_big_header", "FontColor_color_big_link", "FontColor_color_big_link_over", + "FontColor_color_big_link_pressed", "FontColor_color_big_normal", "FontColor_color_big_normal_over", + "FontColor_color_big_normal_pressed", "FontColor_color_big_notify", "FontColor_color_count", + "FontColor_color_disabled", "FontColor_color_disabled_over", "FontColor_color_disabled_pressed", + "FontColor_color_fatigue", "FontColor_color_focus", "FontColor_color_header", "FontColor_color_health", + "FontColor_color_journal_link", "FontColor_color_journal_link_over", "FontColor_color_journal_link_pressed", + "FontColor_color_journal_topic", "FontColor_color_journal_topic_over", "FontColor_color_journal_topic_pressed", + "FontColor_color_link", "FontColor_color_link_over", "FontColor_color_link_pressed", "FontColor_color_magic", + "FontColor_color_magic_fill", "FontColor_color_misc", "FontColor_color_negative", "FontColor_color_normal", + "FontColor_color_normal_over", "FontColor_color_normal_pressed", "FontColor_color_notify", + "FontColor_color_positive", "FontColor_color_weapon_fill", "Fonts_Font_0", "Fonts_Font_1", "Fonts_Font_2", + "Level_Up_Default", "Moons_Script_Color", "Movies_Company_Logo", "Movies_Morrowind_Logo", "Movies_New_Game", + "Question_10_AnswerOne", "Question_10_AnswerThree", "Question_10_AnswerTwo", "Question_10_Question", + "Question_10_Sound", "Question_1_AnswerOne", "Question_1_AnswerThree", "Question_1_AnswerTwo", + "Question_1_Question", "Question_1_Sound", "Question_2_AnswerOne", "Question_2_AnswerThree", "Question_2_AnswerTwo", + "Question_2_Question", "Question_2_Sound", "Question_3_AnswerOne", "Question_3_AnswerThree", "Question_3_AnswerTwo", + "Question_3_Question", "Question_3_Sound", "Question_4_AnswerOne", "Question_4_AnswerThree", "Question_4_AnswerTwo", + "Question_4_Question", "Question_4_Sound", "Question_5_AnswerOne", "Question_5_AnswerThree", "Question_5_AnswerTwo", + "Question_5_Question", "Question_5_Sound", "Question_6_AnswerOne", "Question_6_AnswerThree", "Question_6_AnswerTwo", + "Question_6_Question", "Question_6_Sound", "Question_7_AnswerOne", "Question_7_AnswerThree", "Question_7_AnswerTwo", + "Question_7_Question", "Question_7_Sound", "Question_8_AnswerOne", "Question_8_AnswerThree", "Question_8_AnswerTwo", + "Question_8_Question", "Question_8_Sound", "Question_9_AnswerOne", "Question_9_AnswerThree", "Question_9_AnswerTwo", + "Question_9_Question", "Question_9_Sound", "Water_NearWaterIndoorID", "Water_NearWaterOutdoorID", + "Water_RippleTexture", "Water_SurfaceTexture", "Water_UnderwaterColor", "Weather_Blizzard_Ambient_Day_Color", + "Weather_Blizzard_Ambient_Loop_Sound_ID", "Weather_Blizzard_Ambient_Night_Color", + "Weather_Blizzard_Ambient_Sunrise_Color", "Weather_Blizzard_Ambient_Sunset_Color", "Weather_Blizzard_Cloud_Texture", + "Weather_Blizzard_Fog_Day_Color", "Weather_Blizzard_Fog_Night_Color", "Weather_Blizzard_Fog_Sunrise_Color", + "Weather_Blizzard_Fog_Sunset_Color", "Weather_Blizzard_Sky_Day_Color", "Weather_Blizzard_Sky_Night_Color", + "Weather_Blizzard_Sky_Sunrise_Color", "Weather_Blizzard_Sky_Sunset_Color", "Weather_Blizzard_Storm_Threshold", + "Weather_Blizzard_Sun_Day_Color", "Weather_Blizzard_Sun_Disc_Sunset_Color", "Weather_Blizzard_Sun_Night_Color", + "Weather_Blizzard_Sun_Sunrise_Color", "Weather_Blizzard_Sun_Sunset_Color", "Weather_BumpFadeColor", + "Weather_Clear_Ambient_Day_Color", "Weather_Clear_Ambient_Loop_Sound_ID", "Weather_Clear_Ambient_Night_Color", + "Weather_Clear_Ambient_Sunrise_Color", "Weather_Clear_Ambient_Sunset_Color", "Weather_Clear_Cloud_Texture", + "Weather_Clear_Fog_Day_Color", "Weather_Clear_Fog_Night_Color", "Weather_Clear_Fog_Sunrise_Color", + "Weather_Clear_Fog_Sunset_Color", "Weather_Clear_Sky_Day_Color", "Weather_Clear_Sky_Night_Color", + "Weather_Clear_Sky_Sunrise_Color", "Weather_Clear_Sky_Sunset_Color", "Weather_Clear_Sun_Day_Color", + "Weather_Clear_Sun_Disc_Sunset_Color", "Weather_Clear_Sun_Night_Color", "Weather_Clear_Sun_Sunrise_Color", + "Weather_Clear_Sun_Sunset_Color", "Weather_Cloudy_Ambient_Day_Color", "Weather_Cloudy_Ambient_Loop_Sound_ID", + "Weather_Cloudy_Ambient_Night_Color", "Weather_Cloudy_Ambient_Sunrise_Color", "Weather_Cloudy_Ambient_Sunset_Color", + "Weather_Cloudy_Cloud_Texture", "Weather_Cloudy_Fog_Day_Color", "Weather_Cloudy_Fog_Night_Color", + "Weather_Cloudy_Fog_Sunrise_Color", "Weather_Cloudy_Fog_Sunset_Color", "Weather_Cloudy_Sky_Day_Color", + "Weather_Cloudy_Sky_Night_Color", "Weather_Cloudy_Sky_Sunrise_Color", "Weather_Cloudy_Sky_Sunset_Color", + "Weather_Cloudy_Sun_Day_Color", "Weather_Cloudy_Sun_Disc_Sunset_Color", "Weather_Cloudy_Sun_Night_Color", + "Weather_Cloudy_Sun_Sunrise_Color", "Weather_Cloudy_Sun_Sunset_Color", "Weather_EnvReduceColor", + "Weather_Fog_Depth_Change_Speed", "Weather_Foggy_Ambient_Day_Color", "Weather_Foggy_Ambient_Loop_Sound_ID", + "Weather_Foggy_Ambient_Night_Color", "Weather_Foggy_Ambient_Sunrise_Color", "Weather_Foggy_Ambient_Sunset_Color", + "Weather_Foggy_Cloud_Texture", "Weather_Foggy_Fog_Day_Color", "Weather_Foggy_Fog_Night_Color", + "Weather_Foggy_Fog_Sunrise_Color", "Weather_Foggy_Fog_Sunset_Color", "Weather_Foggy_Sky_Day_Color", + "Weather_Foggy_Sky_Night_Color", "Weather_Foggy_Sky_Sunrise_Color", "Weather_Foggy_Sky_Sunset_Color", + "Weather_Foggy_Sun_Day_Color", "Weather_Foggy_Sun_Disc_Sunset_Color", "Weather_Foggy_Sun_Night_Color", + "Weather_Foggy_Sun_Sunrise_Color", "Weather_Foggy_Sun_Sunset_Color", "Weather_LerpCloseColor", + "Weather_Overcast_Ambient_Day_Color", "Weather_Overcast_Ambient_Loop_Sound_ID", + "Weather_Overcast_Ambient_Night_Color", "Weather_Overcast_Ambient_Sunrise_Color", + "Weather_Overcast_Ambient_Sunset_Color", "Weather_Overcast_Cloud_Texture", "Weather_Overcast_Fog_Day_Color", + "Weather_Overcast_Fog_Night_Color", "Weather_Overcast_Fog_Sunrise_Color", "Weather_Overcast_Fog_Sunset_Color", + "Weather_Overcast_Sky_Day_Color", "Weather_Overcast_Sky_Night_Color", "Weather_Overcast_Sky_Sunrise_Color", + "Weather_Overcast_Sky_Sunset_Color", "Weather_Overcast_Sun_Day_Color", "Weather_Overcast_Sun_Disc_Sunset_Color", + "Weather_Overcast_Sun_Night_Color", "Weather_Overcast_Sun_Sunrise_Color", "Weather_Overcast_Sun_Sunset_Color", + "Weather_Rain_Ambient_Day_Color", "Weather_Rain_Ambient_Loop_Sound_ID", "Weather_Rain_Ambient_Night_Color", + "Weather_Rain_Ambient_Sunrise_Color", "Weather_Rain_Ambient_Sunset_Color", "Weather_Rain_Cloud_Texture", + "Weather_Rain_Fog_Day_Color", "Weather_Rain_Fog_Night_Color", "Weather_Rain_Fog_Sunrise_Color", + "Weather_Rain_Fog_Sunset_Color", "Weather_Rain_Rain_Loop_Sound_ID", "Weather_Rain_Ripple_Radius", + "Weather_Rain_Ripples", "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", "Weather_Rain_Ripples_Per_Drop", + "Weather_Rain_Sky_Day_Color", "Weather_Rain_Sky_Night_Color", "Weather_Rain_Sky_Sunrise_Color", + "Weather_Rain_Sky_Sunset_Color", "Weather_Rain_Sun_Day_Color", "Weather_Rain_Sun_Disc_Sunset_Color", + "Weather_Rain_Sun_Night_Color", "Weather_Rain_Sun_Sunrise_Color", "Weather_Rain_Sun_Sunset_Color", + "Weather_Snow_Ambient_Day_Color", "Weather_Snow_Ambient_Loop_Sound_ID", "Weather_Snow_Ambient_Night_Color", + "Weather_Snow_Ambient_Sunrise_Color", "Weather_Snow_Ambient_Sunset_Color", "Weather_Snow_Cloud_Texture", + "Weather_Snow_Fog_Day_Color", "Weather_Snow_Fog_Night_Color", "Weather_Snow_Fog_Sunrise_Color", + "Weather_Snow_Fog_Sunset_Color", "Weather_Snow_Gravity_Scale", "Weather_Snow_High_Kill", "Weather_Snow_Low_Kill", + "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", "Weather_Snow_Ripples", "Weather_Snow_Ripple_Scale", + "Weather_Snow_Ripple_Speed", "Weather_Snow_Ripples_Per_Flake", "Weather_Snow_Sky_Day_Color", + "Weather_Snow_Sky_Night_Color", "Weather_Snow_Sky_Sunrise_Color", "Weather_Snow_Sky_Sunset_Color", + "Weather_Snow_Snow_Diameter", "Weather_Snow_Snow_Entrance_Speed", "Weather_Snow_Snow_Height_Max", + "Weather_Snow_Snow_Height_Min", "Weather_Snow_Snow_Threshold", "Weather_Snow_Sun_Day_Color", + "Weather_Snow_Sun_Disc_Sunset_Color", "Weather_Snow_Sun_Night_Color", "Weather_Snow_Sun_Sunrise_Color", + "Weather_Snow_Sun_Sunset_Color", "Weather_Sun_Glare_Fader_Color", "Weather_Thunderstorm_Ambient_Day_Color", + "Weather_Thunderstorm_Ambient_Loop_Sound_ID", "Weather_Thunderstorm_Ambient_Night_Color", + "Weather_Thunderstorm_Ambient_Sunrise_Color", "Weather_Thunderstorm_Ambient_Sunset_Color", + "Weather_Thunderstorm_Cloud_Texture", "Weather_Thunderstorm_Fog_Day_Color", "Weather_Thunderstorm_Fog_Night_Color", "Weather_Thunderstorm_Fog_Sunrise_Color", "Weather_Thunderstorm_Fog_Sunset_Color", "Weather_Thunderstorm_Rain_Loop_Sound_ID", "Weather_Thunderstorm_Sky_Day_Color", "Weather_Thunderstorm_Sky_Night_Color", "Weather_Thunderstorm_Sky_Sunrise_Color", @@ -214,14 +213,14 @@ static const std::set> allowedKeysNonNumeric = { "Blood "Weather_Blizzard_Thunder_Sound_ID_1", "Weather_Blizzard_Thunder_Sound_ID_2", "Weather_Blizzard_Thunder_Sound_ID_3" }; -static const std::set> allowedKeysUnused = { "General_Werewolf_FOV", - "Inventory_UniformScaling", "Map_Show_Travel_Lines", "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", - "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", - "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", - "PixelWater_Resolution", "PixelWater_SurfaceFPS", "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", - "Movies_Project_Logo", "Water_MaxNumberRipples", "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", - "Water_PSWaterReflectTerrain", "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", - "Water_SurfaceTextureSize", "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold", +static const std::set allowedKeysUnused = { "Inventory_UniformScaling", "Map_Show_Travel_Lines", + "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", + "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", + "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS", + "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_MaxNumberRipples", + "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain", + "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize", + "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold", "Weather_Blight_Disease_Chance", "Weather_Blight_Storm_Threshold" }; bool Fallback::isAllowedIntFallbackKey(std::string_view key) From f9c5edf6b9e1c403b544034298e06a1d44cce310 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 6 Oct 2023 16:44:18 +0200 Subject: [PATCH 0258/2167] Replace more sized reads and unsized ints --- components/esm3/loadmisc.cpp | 2 +- components/esm3/loadmisc.hpp | 6 +++--- components/esm3/loadprob.cpp | 2 +- components/esm3/loadprob.hpp | 6 +++--- components/esm3/loadsoun.cpp | 2 +- components/esm3/loadsoun.hpp | 2 +- components/esm3/loadspel.cpp | 6 ++---- components/esm3/loadspel.hpp | 8 ++++---- 8 files changed, 16 insertions(+), 18 deletions(-) diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index 780e8077fe..b38ce63294 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -28,7 +28,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("MCDT"): - esm.getHTSized<12>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadmisc.hpp b/components/esm3/loadmisc.hpp index 9c46b7494e..f3eaf7d10f 100644 --- a/components/esm3/loadmisc.hpp +++ b/components/esm3/loadmisc.hpp @@ -27,8 +27,8 @@ namespace ESM struct MCDTstruct { float mWeight; - int mValue; - int mFlags; + int32_t mValue; + int32_t mFlags; }; enum Flags @@ -38,7 +38,7 @@ namespace ESM MCDTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel, mIcon; diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 779f0d1534..3f9ba95bf1 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -28,7 +28,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("PBDT"): - esm.getHTSized<16>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadprob.hpp b/components/esm3/loadprob.hpp index 328c1eaecd..74e02e7d10 100644 --- a/components/esm3/loadprob.hpp +++ b/components/esm3/loadprob.hpp @@ -22,14 +22,14 @@ namespace ESM struct Data { float mWeight; - int mValue; + int32_t mValue; float mQuality; - int mUses; + int32_t mUses; }; // Size = 16 Data mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel, mIcon; diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index af7d7b3710..fd403e3429 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -25,7 +25,7 @@ namespace ESM mSound = esm.getHString(); break; case fourCC("DATA"): - esm.getHTSized<3>(mData); + esm.getHT(mData.mVolume, mData.mMinRange, mData.mMaxRange); hasData = true; break; case SREC_DELE: diff --git a/components/esm3/loadsoun.hpp b/components/esm3/loadsoun.hpp index 0da915b0f1..0129d1fe40 100644 --- a/components/esm3/loadsoun.hpp +++ b/components/esm3/loadsoun.hpp @@ -25,7 +25,7 @@ namespace ESM static std::string_view getRecordType() { return "Sound"; } SOUNstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mSound; RefId mId; diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index d94073d33c..e4f63b8219 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -27,13 +27,11 @@ namespace ESM mName = esm.getHString(); break; case fourCC("SPDT"): - esm.getHTSized<12>(mData); + esm.getHT(mData.mType, mData.mCost, mData.mFlags); hasData = true; break; case fourCC("ENAM"): - ENAMstruct s; - esm.getHTSized<24>(s); - mEffects.mList.push_back(s); + mEffects.add(esm); break; case SREC_DELE: esm.skipHSub(); diff --git a/components/esm3/loadspel.hpp b/components/esm3/loadspel.hpp index 50ed65d3de..6477f8c7df 100644 --- a/components/esm3/loadspel.hpp +++ b/components/esm3/loadspel.hpp @@ -39,13 +39,13 @@ namespace ESM struct SPDTstruct { - int mType; // SpellType - int mCost; // Mana cost - int mFlags; // Flags + int32_t mType; // SpellType + int32_t mCost; // Mana cost + int32_t mFlags; // Flags }; SPDTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mName; RefId mId; EffectList mEffects; From b99f58613efa044627c5b55583744b44bbaa5233 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 6 Oct 2023 16:46:09 +0200 Subject: [PATCH 0259/2167] Remove signed/unsigned conversions in pathgrid loading code and use meaningful member names --- apps/esmtool/record.cpp | 8 +-- apps/opencs/model/tools/pathgridcheck.cpp | 4 +- .../model/world/nestedcoladapterimp.cpp | 6 +- components/esm3/loadpgrd.cpp | 59 +++++++++---------- components/esm3/loadpgrd.hpp | 11 ++-- 5 files changed, 42 insertions(+), 46 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 55a850cf7e..b13668dafc 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1133,9 +1133,9 @@ namespace EsmTool { std::cout << " Cell: " << mData.mCell << std::endl; std::cout << " Coordinates: (" << mData.mData.mX << "," << mData.mData.mY << ")" << std::endl; - std::cout << " Unknown S1: " << mData.mData.mS1 << std::endl; - if (static_cast(mData.mData.mS2) != mData.mPoints.size()) - std::cout << " Reported Point Count: " << mData.mData.mS2 << std::endl; + std::cout << " Granularity: " << mData.mData.mGranularity << std::endl; + if (mData.mData.mPoints != mData.mPoints.size()) + std::cout << " Reported Point Count: " << mData.mData.mPoints << std::endl; std::cout << " Point Count: " << mData.mPoints.size() << std::endl; std::cout << " Edge Count: " << mData.mEdges.size() << std::endl; @@ -1154,7 +1154,7 @@ namespace EsmTool for (const ESM::Pathgrid::Edge& edge : mData.mEdges) { std::cout << " Edge[" << i << "]: " << edge.mV0 << " -> " << edge.mV1 << std::endl; - if (edge.mV0 >= static_cast(mData.mData.mS2) || edge.mV1 >= static_cast(mData.mData.mS2)) + if (edge.mV0 >= mData.mData.mPoints || edge.mV1 >= mData.mData.mPoints) std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 6420c1c83c..f03b896321 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -44,9 +44,9 @@ void CSMTools::PathgridCheckStage::perform(int stage, CSMDoc::Messages& messages CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); // check the number of pathgrid points - if (pathgrid.mData.mS2 < static_cast(pathgrid.mPoints.size())) + if (pathgrid.mData.mPoints < pathgrid.mPoints.size()) messages.add(id, "Less points than expected", "", CSMDoc::Message::Severity_Error); - else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) + else if (pathgrid.mData.mPoints > pathgrid.mPoints.size()) messages.add(id, "More points than expected", "", CSMDoc::Message::Severity_Error); std::vector pointList(pathgrid.mPoints.size()); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 9572d8de39..0f3670431d 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -41,7 +41,7 @@ namespace CSMWorld point.mUnknown = 0; points.insert(points.begin() + position, point); - pathgrid.mData.mS2 = pathgrid.mPoints.size(); + pathgrid.mData.mPoints = pathgrid.mPoints.size(); record.setModified(pathgrid); } @@ -58,7 +58,7 @@ namespace CSMWorld // Do not remove dangling edges, does not work with current undo mechanism // Do not automatically adjust indices, what would be done with dangling edges? points.erase(points.begin() + rowToRemove); - pathgrid.mData.mS2 = pathgrid.mPoints.size(); + pathgrid.mData.mPoints = pathgrid.mPoints.size(); record.setModified(pathgrid); } @@ -67,7 +67,7 @@ namespace CSMWorld { Pathgrid pathgrid = record.get(); pathgrid.mPoints = static_cast&>(nestedTable).mNestedTable; - pathgrid.mData.mS2 = pathgrid.mPoints.size(); + pathgrid.mData.mPoints = pathgrid.mPoints.size(); record.setModified(pathgrid); } diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index ea0b53d8d2..8d60d25524 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -7,18 +7,18 @@ namespace ESM { Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { - mX = static_cast(rhs[0]); - mY = static_cast(rhs[1]); - mZ = static_cast(rhs[2]); + mX = static_cast(rhs[0]); + mY = static_cast(rhs[1]); + mZ = static_cast(rhs[2]); mAutogenerated = 0; mConnectionNum = 0; mUnknown = 0; return *this; } Pathgrid::Point::Point(const float rhs[3]) - : mX(static_cast(rhs[0])) - , mY(static_cast(rhs[1])) - , mZ(static_cast(rhs[2])) + : mX(static_cast(rhs[0])) + , mY(static_cast(rhs[1])) + , mZ(static_cast(rhs[2])) , mAutogenerated(0) , mConnectionNum(0) , mUnknown(0) @@ -42,7 +42,7 @@ namespace ESM mEdges.clear(); // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; + size_t edgeCount = 0; bool hasData = false; while (esm.hasMoreSubs()) @@ -54,21 +54,20 @@ namespace ESM mCell = esm.getRefId(); break; case fourCC("DATA"): - esm.getHTSized<12>(mData); + esm.getHT(mData.mX, mData.mY, mData.mGranularity, mData.mPoints); hasData = true; break; case fourCC("PGRP"): { esm.getSubHeader(); - int size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * s2 (path points) - if (size != static_cast(sizeof(Point) * mData.mS2)) + uint32_t size = esm.getSubSize(); + // Check that the sizes match up. Size = 16 * path points + if (size != sizeof(Point) * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { - int pointCount = mData.mS2; - mPoints.reserve(pointCount); - for (int i = 0; i < pointCount; ++i) + mPoints.reserve(mData.mPoints); + for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; esm.getExact(&p, sizeof(Point)); @@ -81,21 +80,19 @@ namespace ESM case fourCC("PGRC"): { esm.getSubHeader(); - int size = esm.getSubSize(); - if (size % sizeof(int) != 0) + uint32_t size = esm.getSubSize(); + if (size % sizeof(uint32_t) != 0) esm.fail("PGRC size not a multiple of 4"); else { - int rawConnNum = size / sizeof(int); + size_t rawConnNum = size / sizeof(uint32_t); std::vector rawConnections; rawConnections.reserve(rawConnNum); - for (int i = 0; i < rawConnNum; ++i) + for (size_t i = 0; i < rawConnNum; ++i) { - int currentValue; + uint32_t currentValue; esm.getT(currentValue); - if (currentValue < 0) - esm.fail("currentValue is less than 0"); - rawConnections.push_back(static_cast(currentValue)); + rawConnections.push_back(currentValue); } auto rawIt = rawConnections.begin(); @@ -138,7 +135,7 @@ namespace ESM // Correct connection count and sort edges by point // Can probably be optimized PointList correctedPoints = mPoints; - std::vector sortedEdges; + std::vector sortedEdges; sortedEdges.reserve(mEdges.size()); @@ -150,7 +147,7 @@ namespace ESM { if (edge.mV0 == point) { - sortedEdges.push_back(static_cast(edge.mV1)); + sortedEdges.push_back(static_cast(edge.mV1)); ++correctedPoints[point].mConnectionNum; } } @@ -162,16 +159,16 @@ namespace ESM if (isDeleted) { - esm.writeHNString("DELE", "", 3); + esm.writeHNString("DELE", {}, 3); return; } if (!correctedPoints.empty()) { esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = correctedPoints.begin(); it != correctedPoints.end(); ++it) + for (const Point& point : correctedPoints) { - esm.writeT(*it); + esm.writeT(point); } esm.endRecord("PGRP"); } @@ -179,9 +176,9 @@ namespace ESM if (!sortedEdges.empty()) { esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = sortedEdges.begin(); it != sortedEdges.end(); ++it) + for (const uint32_t& edge : sortedEdges) { - esm.writeT(*it); + esm.writeT(edge); } esm.endRecord("PGRC"); } @@ -192,8 +189,8 @@ namespace ESM mCell = ESM::RefId(); mData.mX = 0; mData.mY = 0; - mData.mS1 = 0; - mData.mS2 = 0; + mData.mGranularity = 0; + mData.mPoints = 0; mPoints.clear(); mEdges.clear(); } diff --git a/components/esm3/loadpgrd.hpp b/components/esm3/loadpgrd.hpp index 464358fef4..a343552efb 100644 --- a/components/esm3/loadpgrd.hpp +++ b/components/esm3/loadpgrd.hpp @@ -25,18 +25,17 @@ namespace ESM struct DATAstruct { - int mX, mY; // Grid location, matches cell for exterior cells - short mS1; // ?? Usually but not always a power of 2. Doesn't seem - // to have any relation to the size of PGRC. - short mS2; // Number of path points. + int32_t mX, mY; // Grid location, matches cell for exterior cells + int16_t mGranularity; // Granularity with which the graph was autogenerated + uint16_t mPoints; // Number of path points. }; // 12 bytes struct Point // path grid point { - int mX, mY, mZ; // Location of point + int32_t mX, mY, mZ; // Location of point unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point - short mUnknown; + int16_t mUnknown; Point& operator=(const float[3]); Point(const float[3]); Point(); From cecb2b71ea0b5f7f4a916fb032aaa1f2bf16e9a3 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 6 Oct 2023 22:46:11 +0300 Subject: [PATCH 0260/2167] Handle AllowWerewolfForceGreeting variable (bug #7609) --- CHANGELOG.md | 1 + apps/openmw/mwscript/dialogueextensions.cpp | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index facb9df9e5..e923e08a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Bug #7573: Drain Fatigue can't bring fatigue below zero by default Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured + Bug #7609: ForceGreeting should not open dialogue for werewolves Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 5a361e1bdc..1f0a9a37cf 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -135,6 +135,15 @@ namespace MWScript return; } + bool greetWerewolves = false; + const ESM::RefId& script = ptr.getClass().getScript(ptr); + if (!script.empty()) + greetWerewolves = ptr.getRefData().getLocals().hasVar(script, "allowwerewolfforcegreeting"); + + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf() && !greetWerewolves) + return; + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); } }; From 799da630e416a97f1dbb08f7bcfd54d675d68d8a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 7 Oct 2023 00:02:39 +0300 Subject: [PATCH 0261/2167] CopyRigVisitor: don't copy unskinned geometry (bug #5280) --- CHANGELOG.md | 1 + components/sceneutil/attach.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index facb9df9e5..01dc0c33f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5129: Stuttering animation on Centurion Archer + Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X Bug #5714: Touch spells cast using ExplodeSpell don't always explode Bug #5849: Paralysis breaks landing diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index c855c9a458..1c21221ac4 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include "visitor.hpp" @@ -37,6 +39,12 @@ namespace SceneUtil return; const osg::Node* node = &drawable; + bool isRig = dynamic_cast(node) != nullptr; + if (!isRig) + isRig = dynamic_cast(node) != nullptr; + if (!isRig) + return; + for (auto it = getNodePath().rbegin() + 1; it != getNodePath().rend(); ++it) { const osg::Node* parent = *it; From be455469baefddb02c784031abcb6b8def59902d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 30 Sep 2023 19:58:00 +0200 Subject: [PATCH 0262/2167] Read SUB_DNAM in ESM4::ArmorAddon and SUB_PNAM in ESM4::HeadPart --- components/esm4/loadarma.cpp | 9 +++++++++ components/esm4/loadarma.hpp | 12 ++++++++++++ components/esm4/loadhdpt.cpp | 2 ++ components/esm4/loadhdpt.hpp | 12 ++++++++++++ 4 files changed, 35 insertions(+) diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 3390e59828..1de298c689 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -95,6 +95,15 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) break; case ESM4::SUB_DNAM: + reader.get(mMalePriority); + reader.get(mFemalePriority); + reader.get(mWeightSliderMale); + reader.get(mWeightSliderFemale); + reader.get(mUnknown1); + reader.get(mDetectionSoundValue); + reader.get(mUnknown2); + reader.get(mWeaponAdjust); + break; case ESM4::SUB_MO2T: // FIXME: should group with MOD2 case ESM4::SUB_MO2S: // FIXME: should group with MOD2 case ESM4::SUB_MO2C: // FIXME: should group with MOD2 diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp index 24fa8b1093..fa36d22bbe 100644 --- a/components/esm4/loadarma.hpp +++ b/components/esm4/loadarma.hpp @@ -59,6 +59,18 @@ namespace ESM4 BodyTemplate mBodyTemplate; // TES5 + std::uint8_t mMalePriority; + std::uint8_t mFemalePriority; + + // Flag 0x2 in mWeightSlider means that there are 2 world models for different weights: _0.nif and _1.nif + std::uint8_t mWeightSliderMale; + std::uint8_t mWeightSliderFemale; + + std::uint16_t mUnknown1; + std::uint8_t mDetectionSoundValue; + std::uint8_t mUnknown2; + float mWeaponAdjust; + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index 7592db5486..250a687042 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -88,6 +88,8 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) reader.getFormId(mBaseTexture); break; case ESM4::SUB_PNAM: + reader.get(mType); + break; case ESM4::SUB_MODT: // Model data case ESM4::SUB_MODC: case ESM4::SUB_MODS: diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index 6f08d72961..7686a4789f 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -49,6 +49,18 @@ namespace ESM4 std::string mModel; std::uint8_t mData; + std::uint32_t mType; + + enum Type : std::uint32_t + { + Type_Misc = 0, + Type_Face = 1, + Type_Eyes = 2, + Type_Hair = 3, + Type_FacialHair = 4, + Type_Scar = 5, + Type_Eyebrows = 6, + }; ESM::FormId mAdditionalPart; From 2900351777e1abb58e8e23b6c39cac7267602a4f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 2 Aug 2023 00:55:16 +0200 Subject: [PATCH 0263/2167] Render ESM4 NPCs --- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwclass/classes.cpp | 11 +- apps/openmw/mwclass/esm4base.hpp | 66 +++++---- apps/openmw/mwclass/esm4npc.cpp | 161 ++++++++++++++++++++++ apps/openmw/mwclass/esm4npc.hpp | 71 ++++++++++ apps/openmw/mwrender/esm4npcanimation.cpp | 156 +++++++++++++++++++++ apps/openmw/mwrender/esm4npcanimation.hpp | 22 +++ apps/openmw/mwrender/objects.cpp | 22 ++- apps/openmw/mwworld/customdata.cpp | 13 ++ apps/openmw/mwworld/customdata.hpp | 4 + 10 files changed, 482 insertions(+), 48 deletions(-) create mode 100644 apps/openmw/mwclass/esm4npc.cpp create mode 100644 apps/openmw/mwclass/esm4npc.hpp create mode 100644 apps/openmw/mwrender/esm4npcanimation.cpp create mode 100644 apps/openmw/mwrender/esm4npcanimation.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 535d854239..cf055be50e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation vismask + actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation esm4npcanimation vismask 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 @@ -91,7 +91,7 @@ add_openmw_dir (mwphysics add_openmw_dir (mwclass classes activator creature npc weapon armor potion apparatus book clothing container door ingredient creaturelevlist itemlevlist light lockpick misc probe repair static actor bodypart - esm4base light4 + esm4base esm4npc light4 ) add_openmw_dir (mwmechanics diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 392cc45b6e..1668799eba 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -33,7 +33,6 @@ #include "ingredient.hpp" #include "itemlevlist.hpp" #include "light.hpp" -#include "light4.hpp" #include "lockpick.hpp" #include "misc.hpp" #include "npc.hpp" @@ -44,6 +43,8 @@ #include "weapon.hpp" #include "esm4base.hpp" +#include "esm4npc.hpp" +#include "light4.hpp" namespace MWClass { @@ -72,23 +73,23 @@ namespace MWClass BodyPart::registerSelf(); ESM4Named::registerSelf(); - ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); + ESM4Light::registerSelf(); ESM4Named::registerSelf(); + ESM4Npc::registerSelf(); + ESM4Named::registerSelf(); ESM4Static::registerSelf(); ESM4Named::registerSelf(); ESM4Tree::registerSelf(); ESM4Named::registerSelf(); - ESM4Light::registerSelf(); - ESM4Actor::registerSelf(); - ESM4Actor::registerSelf(); } } diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index c59e8e1dc2..5c05d81692 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWCLASS_ESM4BASE_H #define GAME_MWCLASS_ESM4BASE_H +#include #include #include #include @@ -9,6 +10,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/registeredclass.hpp" #include "classmodel.hpp" @@ -23,13 +25,40 @@ namespace MWClass void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics); MWGui::ToolTipInfo getToolTipInfo(std::string_view name, int count); + + // We don't handle ESM4 player stats yet, so for resolving levelled object we use an arbitrary number. + constexpr int sDefaultLevel = 5; + + template + const TargetRecord* resolveLevelled(const ESM::RefId& id, int level = sDefaultLevel) + { + if (id.empty()) + return nullptr; + const MWWorld::ESMStore* esmStore = MWBase::Environment::get().getESMStore(); + const auto& targetStore = esmStore->get(); + const TargetRecord* res = targetStore.search(id); + if (res) + return res; + const LevelledRecord* lvlRec = esmStore->get().search(id); + if (!lvlRec) + return nullptr; + for (const ESM4::LVLO& obj : lvlRec->mLvlObject) + { + ESM::RefId candidateId = ESM::FormId::fromUint32(obj.item); + if (candidateId == id) + continue; + const TargetRecord* candidate = resolveLevelled(candidateId, level); + if (candidate && (!res || obj.level <= level)) + res = candidate; + } + return res; + } } - // Base for all ESM4 Classes + // Base for many ESM4 Classes template class ESM4Base : public MWWorld::Class { - MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override { const MWWorld::LiveCellRef* ref = ptr.get(); @@ -104,14 +133,11 @@ namespace MWClass class ESM4Named : public MWWorld::RegisteredClass, ESM4Base> { public: - friend MWWorld::RegisteredClass>; - ESM4Named() : MWWorld::RegisteredClass>(Record::sRecordId) { } - public: bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override @@ -124,36 +150,6 @@ namespace MWClass return ptr.get()->mBase->mFullName; } }; - - template - class ESM4Actor : public MWWorld::RegisteredClass, ESM4Base> - { - public: - friend MWWorld::RegisteredClass>; - - ESM4Actor() - : MWWorld::RegisteredClass>(Record::sRecordId) - { - } - - void insertObjectPhysics( - const MWWorld::Ptr&, const std::string&, const osg::Quat&, MWPhysics::PhysicsSystem&) const override - { - } - - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } - - MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override - { - return ESM4Impl::getToolTipInfo(ptr.get()->mBase->mEditorId, count); - } - - std::string getModel(const MWWorld::ConstPtr& ptr) const override - { - // TODO: Implement actor rendering. This function will typically return the skeleton. - return {}; - } - }; } #endif // GAME_MWCLASS_ESM4BASE_H diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp new file mode 100644 index 0000000000..5399635fee --- /dev/null +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -0,0 +1,161 @@ +#include "esm4npc.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" + +#include "esm4base.hpp" + +namespace MWClass +{ + template + static std::vector withBaseTemplates( + const TargetRecord* rec, int level = MWClass::ESM4Impl::sDefaultLevel) + { + std::vector res{ rec }; + while (true) + { + const TargetRecord* newRec + = MWClass::ESM4Impl::resolveLevelled(rec->mBaseTemplate, level); + if (!newRec || newRec == rec) + return res; + res.push_back(rec = newRec); + } + } + + static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) + { + for (const auto* rec : recs) + if (rec->mIsTES4 || !(rec->mBaseConfig.tes5.templateFlags & flag)) + return rec; + return nullptr; + } + + class ESM4NpcCustomData : public MWWorld::TypedCustomData + { + public: + const ESM4::Npc* mTraits; + const ESM4::Npc* mBaseData; + const ESM4::Race* mRace; + bool mIsFemale; + + // TODO: Use InventoryStore instead (currently doesn't support ESM4 objects) + std::vector mEquippedArmor; + std::vector mEquippedClothing; + + ESM4NpcCustomData& asESM4NpcCustomData() override { return *this; } + const ESM4NpcCustomData& asESM4NpcCustomData() const override { return *this; } + }; + + ESM4NpcCustomData& ESM4Npc::getCustomData(const MWWorld::Ptr& ptr) + { + if (auto* data = ptr.getRefData().getCustomData()) + return data->asESM4NpcCustomData(); + + auto data = std::make_unique(); + + const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); + auto npcRecs = withBaseTemplates(ptr.get()->mBase); + + data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits); + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); + + if (!data->mTraits) + throw std::runtime_error("ESM4 Npc traits not found"); + if (!data->mBaseData) + throw std::runtime_error("ESM4 Npc base data not found"); + + data->mRace = store->get().find(data->mTraits->mRace); + if (data->mTraits->mIsTES4) + data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; + else if (data->mTraits->mIsFONV) + data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else + data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + + if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseInventory)) + { + for (const ESM4::InventoryItem& item : inv->mInventory) + { + if (auto* armor + = ESM4Impl::resolveLevelled(ESM::FormId::fromUint32(item.item))) + data->mEquippedArmor.push_back(armor); + else if (data->mTraits->mIsTES4) + { + const auto* clothing = ESM4Impl::resolveLevelled( + ESM::FormId::fromUint32(item.item)); + if (clothing) + data->mEquippedClothing.push_back(clothing); + } + } + if (!inv->mDefaultOutfit.isZeroOrUnset()) + { + if (const ESM4::Outfit* outfit = store->get().search(inv->mDefaultOutfit)) + { + for (ESM::FormId itemId : outfit->mInventory) + if (auto* armor = ESM4Impl::resolveLevelled(itemId)) + data->mEquippedArmor.push_back(armor); + } + else + Log(Debug::Error) << "Outfit not found: " << ESM::RefId(inv->mDefaultOutfit); + } + } + + ESM4NpcCustomData& res = *data; + ptr.getRefData().setCustomData(std::move(data)); + return res; + } + + const std::vector& ESM4Npc::getEquippedArmor(const MWWorld::Ptr& ptr) + { + return getCustomData(ptr).mEquippedArmor; + } + + const std::vector& ESM4Npc::getEquippedClothing(const MWWorld::Ptr& ptr) + { + return getCustomData(ptr).mEquippedClothing; + } + + const ESM4::Npc* ESM4Npc::getTraitsRecord(const MWWorld::Ptr& ptr) + { + return getCustomData(ptr).mTraits; + } + + const ESM4::Race* ESM4Npc::getRace(const MWWorld::Ptr& ptr) + { + return getCustomData(ptr).mRace; + } + + bool ESM4Npc::isFemale(const MWWorld::Ptr& ptr) + { + return getCustomData(ptr).mIsFemale; + } + + std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const + { + if (!ptr.getRefData().getCustomData()) + return ""; + const ESM4NpcCustomData& data = ptr.getRefData().getCustomData()->asESM4NpcCustomData(); + if (data.mTraits->mIsTES4) + return "meshes\\" + data.mTraits->mModel; + if (data.mIsFemale) + return "meshes\\" + data.mRace->mModelFemale; + else + return "meshes\\" + data.mRace->mModelMale; + } + + std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const + { + if (!ptr.getRefData().getCustomData()) + return ""; + const ESM4NpcCustomData& data = ptr.getRefData().getCustomData()->asESM4NpcCustomData(); + return data.mBaseData->mFullName; + } +} diff --git a/apps/openmw/mwclass/esm4npc.hpp b/apps/openmw/mwclass/esm4npc.hpp new file mode 100644 index 0000000000..efa5a48ba6 --- /dev/null +++ b/apps/openmw/mwclass/esm4npc.hpp @@ -0,0 +1,71 @@ +#ifndef GAME_MWCLASS_ESM4ACTOR_H +#define GAME_MWCLASS_ESM4ACTOR_H + +#include +#include + +#include "../mwgui/tooltips.hpp" + +#include "../mwrender/objects.hpp" +#include "../mwrender/renderinginterface.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/registeredclass.hpp" + +#include "esm4base.hpp" + +namespace MWClass +{ + class ESM4Npc final : public MWWorld::RegisteredClass + { + public: + ESM4Npc() + : MWWorld::RegisteredClass(ESM4::Npc::sRecordId) + { + } + + MWWorld::Ptr copyToCellImpl(const MWWorld::ConstPtr& ptr, MWWorld::CellStore& cell) const override + { + const MWWorld::LiveCellRef* ref = ptr.get(); + return MWWorld::Ptr(cell.insert(ref), &cell); + } + + void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, + MWRender::RenderingInterface& renderingInterface) const override + { + renderingInterface.getObjects().insertNPC(ptr); + } + + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override + { + insertObjectPhysics(ptr, model, rotation, physics); + } + + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, + MWPhysics::PhysicsSystem& physics) const override + { + // ESM4Impl::insertObjectPhysics(ptr, getModel(ptr), rotation, physics); + } + + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override + { + return ESM4Impl::getToolTipInfo(getName(ptr), count); + } + + std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getName(const MWWorld::ConstPtr& ptr) const override; + + static const ESM4::Npc* getTraitsRecord(const MWWorld::Ptr& ptr); + static const ESM4::Race* getRace(const MWWorld::Ptr& ptr); + static bool isFemale(const MWWorld::Ptr& ptr); + static const std::vector& getEquippedArmor(const MWWorld::Ptr& ptr); + static const std::vector& getEquippedClothing(const MWWorld::Ptr& ptr); + + private: + static ESM4NpcCustomData& getCustomData(const MWWorld::Ptr& ptr); + }; +} + +#endif // GAME_MWCLASS_ESM4ACTOR_H diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp new file mode 100644 index 0000000000..6e64e93d0b --- /dev/null +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -0,0 +1,156 @@ +#include "esm4npcanimation.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" + +#include +#include + +#include "../mwclass/esm4npc.hpp" + +namespace MWRender +{ + ESM4NpcAnimation::ESM4NpcAnimation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) + : Animation(ptr, std::move(parentNode), resourceSystem) + { + getOrCreateObjectRoot(); + const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + if (traits->mIsTES4) + insertTes4NpcBodyPartsAndEquipment(); + else + insertTes5NpcBodyPartsAndEquipment(); + } + + void ESM4NpcAnimation::insertMesh(std::string_view model) + { + std::string path = "meshes\\"; + path.append(model); + mResourceSystem->getSceneManager()->getInstance(path, mObjectRoot.get()); + } + + template + static std::string_view chooseTes4EquipmentModel(const Record* rec, bool isFemale) + { + if (isFemale && !rec->mModelFemale.empty()) + return rec->mModelFemale; + else if (!isFemale && !rec->mModelMale.empty()) + return rec->mModelMale; + else + return rec->mModel; + } + + void ESM4NpcAnimation::insertTes4NpcBodyPartsAndEquipment() + { + const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); + bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); + + // TODO: Body and head parts are placed incorrectly, need to attach to bones + + for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mBodyPartsFemale : race->mBodyPartsMale)) + if (!bodyPart.mesh.empty()) + insertMesh(bodyPart.mesh); + for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mHeadPartsFemale : race->mHeadParts)) + if (!bodyPart.mesh.empty()) + insertMesh(bodyPart.mesh); + if (!traits->mHair.isZeroOrUnset()) + { + const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); + if (const ESM4::Hair* hair = store->get().search(traits->mHair)) + insertMesh(hair->mModel); + } + + for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) + insertMesh(chooseTes4EquipmentModel(armor, isFemale)); + for (const ESM4::Clothing* clothing : MWClass::ESM4Npc::getEquippedClothing(mPtr)) + insertMesh(chooseTes4EquipmentModel(clothing, isFemale)); + } + + void ESM4NpcAnimation::insertTes5NpcBodyPartsAndEquipment() + { + const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); + + const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); + bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); + + std::set usedHeadPartTypes; + auto addHeadParts = [&](const std::vector& partIds) { + for (ESM::FormId partId : partIds) + { + if (partId.isZeroOrUnset()) + continue; + const ESM4::HeadPart* part = store->get().search(partId); + if (!part) + { + Log(Debug::Error) << "Head part not found: " << ESM::RefId(partId); + continue; + } + if (usedHeadPartTypes.contains(part->mType)) + continue; + usedHeadPartTypes.insert(part->mType); + insertMesh(part->mModel); + } + }; + + std::vector armorAddons; + + auto findArmorAddons = [&](const ESM4::Armor* armor) { + for (ESM::FormId armaId : armor->mAddOns) + { + const ESM4::ArmorAddon* arma = store->get().search(armaId); + if (!arma) + { + Log(Debug::Error) << "ArmorAddon not found: " << ESM::RefId(armaId); + continue; + } + bool compatibleRace = arma->mRacePrimary == traits->mRace; + for (auto r : arma->mRaces) + if (r == traits->mRace) + compatibleRace = true; + if (compatibleRace) + armorAddons.push_back(arma); + } + }; + + for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) + findArmorAddons(armor); + if (!traits->mWornArmor.isZeroOrUnset()) + findArmorAddons(store->get().find(traits->mWornArmor)); + findArmorAddons(store->get().find(race->mSkin)); + + if (isFemale) + std::sort(armorAddons.begin(), armorAddons.end(), + [](auto x, auto y) { return x->mFemalePriority > y->mFemalePriority; }); + else + std::sort(armorAddons.begin(), armorAddons.end(), + [](auto x, auto y) { return x->mMalePriority > y->mMalePriority; }); + + uint32_t usedParts = 0; + for (const ESM4::ArmorAddon* arma : armorAddons) + { + const uint32_t covers = arma->mBodyTemplate.bodyPart; + if (covers & usedParts & ESM4::Armor::TES5_Body) + continue; // if body is already covered, skip to avoid clipping + if (covers & ~usedParts) + { // if covers at least something that wasn't covered before - add model + usedParts |= covers; + insertMesh(isFemale ? arma->mModelFemale : arma->mModelMale); + } + } + + if (usedParts & ESM4::Armor::TES5_Hair) + usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair); + addHeadParts(traits->mHeadParts); + addHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale); + } +} diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp new file mode 100644 index 0000000000..a8c451555f --- /dev/null +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -0,0 +1,22 @@ +#ifndef GAME_RENDER_ESM4NPCANIMATION_H +#define GAME_RENDER_ESM4NPCANIMATION_H + +#include "animation.hpp" + +namespace MWRender +{ + class ESM4NpcAnimation : public Animation + { + public: + ESM4NpcAnimation( + const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); + + private: + void insertMesh(std::string_view model); + + void insertTes4NpcBodyPartsAndEquipment(); + void insertTes5NpcBodyPartsAndEquipment(); + }; +} + +#endif // GAME_RENDER_ESM4NPCANIMATION_H diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index b8b7d62309..89e192f6c8 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -13,6 +13,7 @@ #include "animation.hpp" #include "creatureanimation.hpp" +#include "esm4npcanimation.hpp" #include "npcanimation.hpp" #include "vismask.hpp" @@ -116,13 +117,22 @@ namespace MWRender insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); - osg::ref_ptr anim( - new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); - - if (mObjects.emplace(ptr.mRef, anim).second) + if (ptr.getType() == ESM::REC_NPC_4) { - ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get()); - ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); + osg::ref_ptr anim( + new ESM4NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + mObjects.emplace(ptr.mRef, anim); + } + else + { + osg::ref_ptr anim( + new NpcAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), mResourceSystem)); + + if (mObjects.emplace(ptr.mRef, anim).second) + { + ptr.getClass().getInventoryStore(ptr).setInvListener(anim.get()); + ptr.getClass().getInventoryStore(ptr).setContListener(anim.get()); + } } } diff --git a/apps/openmw/mwworld/customdata.cpp b/apps/openmw/mwworld/customdata.cpp index 395d230a1a..db340302ce 100644 --- a/apps/openmw/mwworld/customdata.cpp +++ b/apps/openmw/mwworld/customdata.cpp @@ -77,4 +77,17 @@ namespace MWWorld throw std::logic_error(error.str()); } + MWClass::ESM4NpcCustomData& CustomData::asESM4NpcCustomData() + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ESM4NpcCustomData"; + throw std::logic_error(error.str()); + } + + const MWClass::ESM4NpcCustomData& CustomData::asESM4NpcCustomData() const + { + std::stringstream error; + error << "bad cast " << typeid(this).name() << " to ESM4NpcCustomData"; + throw std::logic_error(error.str()); + } } diff --git a/apps/openmw/mwworld/customdata.hpp b/apps/openmw/mwworld/customdata.hpp index 9d9283f085..8051876309 100644 --- a/apps/openmw/mwworld/customdata.hpp +++ b/apps/openmw/mwworld/customdata.hpp @@ -6,6 +6,7 @@ namespace MWClass { class CreatureCustomData; + class ESM4NpcCustomData; class NpcCustomData; class ContainerCustomData; class DoorCustomData; @@ -38,6 +39,9 @@ namespace MWWorld virtual MWClass::CreatureLevListCustomData& asCreatureLevListCustomData(); virtual const MWClass::CreatureLevListCustomData& asCreatureLevListCustomData() const; + + virtual MWClass::ESM4NpcCustomData& asESM4NpcCustomData(); + virtual const MWClass::ESM4NpcCustomData& asESM4NpcCustomData() const; }; template From 4beed294047897f41580eba76a44ba08ad6f627b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 4 Oct 2023 22:48:17 +0200 Subject: [PATCH 0264/2167] Changes required during review --- apps/openmw/mwclass/esm4npc.cpp | 7 ++- apps/openmw/mwrender/esm4npcanimation.cpp | 67 +++++++++++++---------- apps/openmw/mwrender/esm4npcanimation.hpp | 7 ++- components/esm4/loadarma.cpp | 23 +++++--- components/esm4/loadarma.hpp | 14 ++--- components/misc/resourcehelpers.cpp | 6 +- components/misc/resourcehelpers.hpp | 2 +- 7 files changed, 73 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 5399635fee..8bca91a519 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -32,6 +32,9 @@ namespace MWClass static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) { + // If the record is neither TES4 nor TES5 (though maybe FO4 is compatible with tes5.templateFlags), then + // the function can return nullptr that will lead to "ESM4 NPC traits not found" exception and the NPC + // will not be added to the scene. But in any way it shouldn't cause a crash. for (const auto* rec : recs) if (rec->mIsTES4 || !(rec->mBaseConfig.tes5.templateFlags & flag)) return rec; @@ -68,9 +71,9 @@ namespace MWClass data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); if (!data->mTraits) - throw std::runtime_error("ESM4 Npc traits not found"); + throw std::runtime_error("ESM4 NPC traits not found"); if (!data->mBaseData) - throw std::runtime_error("ESM4 Npc base data not found"); + throw std::runtime_error("ESM4 NPC base data not found"); data->mRace = store->get().find(data->mTraits->mRace); if (data->mTraits->mIsTES4) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 6e64e93d0b..005751c420 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -8,13 +8,13 @@ #include #include -#include "../mwworld/customdata.hpp" -#include "../mwworld/esmstore.hpp" - +#include #include #include #include "../mwclass/esm4npc.hpp" +#include "../mwworld/customdata.hpp" +#include "../mwworld/esmstore.hpp" namespace MWRender { @@ -23,18 +23,28 @@ namespace MWRender : Animation(ptr, std::move(parentNode), resourceSystem) { getOrCreateObjectRoot(); - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); - if (traits->mIsTES4) - insertTes4NpcBodyPartsAndEquipment(); - else - insertTes5NpcBodyPartsAndEquipment(); + updateParts(); } - void ESM4NpcAnimation::insertMesh(std::string_view model) + void ESM4NpcAnimation::updateParts() { - std::string path = "meshes\\"; - path.append(model); - mResourceSystem->getSceneManager()->getInstance(path, mObjectRoot.get()); + mObjectRoot->removeChildren(0, mObjectRoot->getNumChildren()); + const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + // There is no flag "mIsTES5", so we can not distinguish from other cases. + // But calling wrong `updateParts*` function shouldn't crash the game and will + // only lead to the NPC not being rendered. + if (traits->mIsTES4) + updatePartsTES4(); + else + updatePartsTES5(); + } + + void ESM4NpcAnimation::insertPart(std::string_view model) + { + if (model.empty()) + return; + mResourceSystem->getSceneManager()->getInstance( + Misc::ResourceHelpers::correctMeshPath(model, mResourceSystem->getVFS()), mObjectRoot.get()); } template @@ -48,7 +58,7 @@ namespace MWRender return rec->mModel; } - void ESM4NpcAnimation::insertTes4NpcBodyPartsAndEquipment() + void ESM4NpcAnimation::updatePartsTES4() { const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); @@ -57,25 +67,23 @@ namespace MWRender // TODO: Body and head parts are placed incorrectly, need to attach to bones for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mBodyPartsFemale : race->mBodyPartsMale)) - if (!bodyPart.mesh.empty()) - insertMesh(bodyPart.mesh); + insertPart(bodyPart.mesh); for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mHeadPartsFemale : race->mHeadParts)) - if (!bodyPart.mesh.empty()) - insertMesh(bodyPart.mesh); + insertPart(bodyPart.mesh); if (!traits->mHair.isZeroOrUnset()) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); if (const ESM4::Hair* hair = store->get().search(traits->mHair)) - insertMesh(hair->mModel); + insertPart(hair->mModel); } for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) - insertMesh(chooseTes4EquipmentModel(armor, isFemale)); + insertPart(chooseTes4EquipmentModel(armor, isFemale)); for (const ESM4::Clothing* clothing : MWClass::ESM4Npc::getEquippedClothing(mPtr)) - insertMesh(chooseTes4EquipmentModel(clothing, isFemale)); + insertPart(chooseTes4EquipmentModel(clothing, isFemale)); } - void ESM4NpcAnimation::insertTes5NpcBodyPartsAndEquipment() + void ESM4NpcAnimation::updatePartsTES5() { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); @@ -95,10 +103,8 @@ namespace MWRender Log(Debug::Error) << "Head part not found: " << ESM::RefId(partId); continue; } - if (usedHeadPartTypes.contains(part->mType)) - continue; - usedHeadPartTypes.insert(part->mType); - insertMesh(part->mModel); + if (usedHeadPartTypes.emplace(part->mType).second) + insertPart(part->mModel); } }; @@ -126,7 +132,8 @@ namespace MWRender findArmorAddons(armor); if (!traits->mWornArmor.isZeroOrUnset()) findArmorAddons(store->get().find(traits->mWornArmor)); - findArmorAddons(store->get().find(race->mSkin)); + if (!race->mSkin.isZeroOrUnset()) + findArmorAddons(store->get().find(race->mSkin)); if (isFemale) std::sort(armorAddons.begin(), armorAddons.end(), @@ -139,12 +146,14 @@ namespace MWRender for (const ESM4::ArmorAddon* arma : armorAddons) { const uint32_t covers = arma->mBodyTemplate.bodyPart; + // if body is already covered, skip to avoid clipping if (covers & usedParts & ESM4::Armor::TES5_Body) - continue; // if body is already covered, skip to avoid clipping + continue; + // if covers at least something that wasn't covered before - add model if (covers & ~usedParts) - { // if covers at least something that wasn't covered before - add model + { usedParts |= covers; - insertMesh(isFemale ? arma->mModelFemale : arma->mModelMale); + insertPart(isFemale ? arma->mModelFemale : arma->mModelMale); } } diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp index a8c451555f..1d93dace45 100644 --- a/apps/openmw/mwrender/esm4npcanimation.hpp +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -12,10 +12,11 @@ namespace MWRender const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); private: - void insertMesh(std::string_view model); + void insertPart(std::string_view model); - void insertTes4NpcBodyPartsAndEquipment(); - void insertTes5NpcBodyPartsAndEquipment(); + void updateParts(); + void updatePartsTES4(); + void updatePartsTES5(); }; } diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 1de298c689..2bb6240ee8 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -95,14 +95,21 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) break; case ESM4::SUB_DNAM: - reader.get(mMalePriority); - reader.get(mFemalePriority); - reader.get(mWeightSliderMale); - reader.get(mWeightSliderFemale); - reader.get(mUnknown1); - reader.get(mDetectionSoundValue); - reader.get(mUnknown2); - reader.get(mWeaponAdjust); + if (subHdr.dataSize == 12) + { + std::uint16_t unknownInt16; + std::uint8_t unknownInt8; + reader.get(mMalePriority); + reader.get(mFemalePriority); + reader.get(mWeightSliderMale); + reader.get(mWeightSliderFemale); + reader.get(unknownInt16); + reader.get(mDetectionSoundValue); + reader.get(unknownInt8); + reader.get(mWeaponAdjust); + } + else + reader.skipSubRecordData(); break; case ESM4::SUB_MO2T: // FIXME: should group with MOD2 case ESM4::SUB_MO2S: // FIXME: should group with MOD2 diff --git a/components/esm4/loadarma.hpp b/components/esm4/loadarma.hpp index fa36d22bbe..6733184aa8 100644 --- a/components/esm4/loadarma.hpp +++ b/components/esm4/loadarma.hpp @@ -59,17 +59,15 @@ namespace ESM4 BodyTemplate mBodyTemplate; // TES5 - std::uint8_t mMalePriority; - std::uint8_t mFemalePriority; + std::uint8_t mMalePriority = 0; + std::uint8_t mFemalePriority = 0; // Flag 0x2 in mWeightSlider means that there are 2 world models for different weights: _0.nif and _1.nif - std::uint8_t mWeightSliderMale; - std::uint8_t mWeightSliderFemale; + std::uint8_t mWeightSliderMale = 0; + std::uint8_t mWeightSliderFemale = 0; - std::uint16_t mUnknown1; - std::uint8_t mDetectionSoundValue; - std::uint8_t mUnknown2; - float mWeaponAdjust; + std::uint8_t mDetectionSoundValue = 0; + float mWeaponAdjust = 0; void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index cddcbe463b..ce552df4f7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -159,9 +159,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } -std::string Misc::ResourceHelpers::correctMeshPath(const std::string& resPath, const VFS::Manager* vfs) +std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath, const VFS::Manager* vfs) { - return "meshes\\" + resPath; + std::string res = "meshes\\"; + res.append(resPath); + return res; } std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 37932ea155..478569ed14 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -33,7 +33,7 @@ namespace Misc std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); // Adds "meshes\\". - std::string correctMeshPath(const std::string& resPath, const VFS::Manager* vfs); + std::string correctMeshPath(std::string_view resPath, const VFS::Manager* vfs); // Adds "sound\\". std::string correctSoundPath(const std::string& resPath); From 1acac873cfcf04d5726a3c910b944798f5cc0c03 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 Oct 2023 01:40:23 +0200 Subject: [PATCH 0265/2167] Use "setObjectRoot" in esm4npcanimation.cpp --- apps/openmw/mwrender/esm4npcanimation.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 005751c420..0a4d3c9b64 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -22,13 +22,16 @@ namespace MWRender const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : Animation(ptr, std::move(parentNode), resourceSystem) { - getOrCreateObjectRoot(); + std::string smodel + = Misc::ResourceHelpers::correctMeshPath(mPtr.getClass().getModel(mPtr), mResourceSystem->getVFS()); + setObjectRoot(smodel, true, true, false); updateParts(); } void ESM4NpcAnimation::updateParts() { - mObjectRoot->removeChildren(0, mObjectRoot->getNumChildren()); + if (!mObjectRoot.get()) + return; const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); // There is no flag "mIsTES5", so we can not distinguish from other cases. // But calling wrong `updateParts*` function shouldn't crash the game and will From b4552d47f6e9f70d2b17c607210fd4840d592df0 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 Oct 2023 15:41:51 +0200 Subject: [PATCH 0266/2167] Don't use ESM4 mHeadFeamleParts; switch from manually adding "meshes\\" to `correctMeshPath` --- apps/openmw/mwclass/esm4npc.cpp | 9 ++++++--- apps/openmw/mwrender/esm4npcanimation.cpp | 6 ++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 8bca91a519..c5857c489a 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" @@ -146,12 +148,13 @@ namespace MWClass if (!ptr.getRefData().getCustomData()) return ""; const ESM4NpcCustomData& data = ptr.getRefData().getCustomData()->asESM4NpcCustomData(); + const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); if (data.mTraits->mIsTES4) - return "meshes\\" + data.mTraits->mModel; + return Misc::ResourceHelpers::correctMeshPath(data.mTraits->mModel, vfs); if (data.mIsFemale) - return "meshes\\" + data.mRace->mModelFemale; + return Misc::ResourceHelpers::correctMeshPath(data.mRace->mModelFemale, vfs); else - return "meshes\\" + data.mRace->mModelMale; + return Misc::ResourceHelpers::correctMeshPath(data.mRace->mModelMale, vfs); } std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 0a4d3c9b64..a7e311b388 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -22,9 +22,7 @@ namespace MWRender const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : Animation(ptr, std::move(parentNode), resourceSystem) { - std::string smodel - = Misc::ResourceHelpers::correctMeshPath(mPtr.getClass().getModel(mPtr), mResourceSystem->getVFS()); - setObjectRoot(smodel, true, true, false); + setObjectRoot(mPtr.getClass().getModel(mPtr), true, true, false); updateParts(); } @@ -71,7 +69,7 @@ namespace MWRender for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mBodyPartsFemale : race->mBodyPartsMale)) insertPart(bodyPart.mesh); - for (const ESM4::Race::BodyPart& bodyPart : (isFemale ? race->mHeadPartsFemale : race->mHeadParts)) + for (const ESM4::Race::BodyPart& bodyPart : race->mHeadParts) insertPart(bodyPart.mesh); if (!traits->mHair.isZeroOrUnset()) { From 3882a5f25af662892a469b52148444ea7a1852d2 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 6 Oct 2023 22:08:00 +0200 Subject: [PATCH 0267/2167] update --- apps/openmw/mwclass/esm4npc.cpp | 39 ++++++------- apps/openmw/mwclass/esm4npc.hpp | 2 +- apps/openmw/mwrender/esm4npcanimation.cpp | 69 +++++++++++++++-------- apps/openmw/mwrender/esm4npcanimation.hpp | 3 + 4 files changed, 69 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index c5857c489a..78cbd89b50 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -34,11 +34,10 @@ namespace MWClass static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) { - // If the record is neither TES4 nor TES5 (though maybe FO4 is compatible with tes5.templateFlags), then - // the function can return nullptr that will lead to "ESM4 NPC traits not found" exception and the NPC - // will not be added to the scene. But in any way it shouldn't cause a crash. + // In case of FO3 the function may return nullptr that will lead to "ESM4 NPC traits not found" + // exception and the NPC will not be added to the scene. But in any way it shouldn't cause a crash. for (const auto* rec : recs) - if (rec->mIsTES4 || !(rec->mBaseConfig.tes5.templateFlags & flag)) + if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag)) return rec; return nullptr; } @@ -59,9 +58,16 @@ namespace MWClass const ESM4NpcCustomData& asESM4NpcCustomData() const override { return *this; } }; - ESM4NpcCustomData& ESM4Npc::getCustomData(const MWWorld::Ptr& ptr) + ESM4NpcCustomData& ESM4Npc::getCustomData(const MWWorld::ConstPtr& ptr) { - if (auto* data = ptr.getRefData().getCustomData()) + // Note: the argument is ConstPtr because this function is used in `getModel` and `getName` + // which are virtual and work with ConstPtr. `getModel` and `getName` use custom data + // because they require a lot of work including levelled records resolving and it would be + // stupid to not to cache the results. Maybe we should stop using ConstPtr at all + // to avoid such workarounds. + MWWorld::RefData& refData = const_cast(ptr.getRefData()); + + if (auto* data = refData.getCustomData()) return data->asESM4NpcCustomData(); auto data = std::make_unique(); @@ -114,7 +120,7 @@ namespace MWClass } ESM4NpcCustomData& res = *data; - ptr.getRefData().setCustomData(std::move(data)); + refData.setCustomData(std::move(data)); return res; } @@ -145,23 +151,18 @@ namespace MWClass std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const { - if (!ptr.getRefData().getCustomData()) - return ""; - const ESM4NpcCustomData& data = ptr.getRefData().getCustomData()->asESM4NpcCustomData(); - const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + const ESM4NpcCustomData& data = getCustomData(ptr); + std::string_view model; if (data.mTraits->mIsTES4) - return Misc::ResourceHelpers::correctMeshPath(data.mTraits->mModel, vfs); - if (data.mIsFemale) - return Misc::ResourceHelpers::correctMeshPath(data.mRace->mModelFemale, vfs); + model = data.mTraits->mModel; else - return Misc::ResourceHelpers::correctMeshPath(data.mRace->mModelMale, vfs); + model = data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale; + const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + return Misc::ResourceHelpers::correctMeshPath(model, vfs); } std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const { - if (!ptr.getRefData().getCustomData()) - return ""; - const ESM4NpcCustomData& data = ptr.getRefData().getCustomData()->asESM4NpcCustomData(); - return data.mBaseData->mFullName; + return getCustomData(ptr).mBaseData->mFullName; } } diff --git a/apps/openmw/mwclass/esm4npc.hpp b/apps/openmw/mwclass/esm4npc.hpp index efa5a48ba6..7830f20f32 100644 --- a/apps/openmw/mwclass/esm4npc.hpp +++ b/apps/openmw/mwclass/esm4npc.hpp @@ -64,7 +64,7 @@ namespace MWClass static const std::vector& getEquippedClothing(const MWWorld::Ptr& ptr); private: - static ESM4NpcCustomData& getCustomData(const MWWorld::Ptr& ptr); + static ESM4NpcCustomData& getCustomData(const MWWorld::ConstPtr& ptr); }; } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index a7e311b388..1f06e68bc2 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -31,13 +31,19 @@ namespace MWRender if (!mObjectRoot.get()) return; const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); - // There is no flag "mIsTES5", so we can not distinguish from other cases. - // But calling wrong `updateParts*` function shouldn't crash the game and will - // only lead to the NPC not being rendered. if (traits->mIsTES4) updatePartsTES4(); + else if (traits->mIsFONV) + { + // Not implemented yet + } else + { + // There is no easy way to distinguish TES5 and FO3. + // In case of FO3 the function shouldn't crash the game and will + // only lead to the NPC not being rendered. updatePartsTES5(); + } } void ESM4NpcAnimation::insertPart(std::string_view model) @@ -76,6 +82,8 @@ namespace MWRender const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); if (const ESM4::Hair* hair = store->get().search(traits->mHair)) insertPart(hair->mModel); + else + Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits->mHair); } for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) @@ -84,6 +92,25 @@ namespace MWRender insertPart(chooseTes4EquipmentModel(clothing, isFemale)); } + void ESM4NpcAnimation::insertHeadParts( + const std::vector& partIds, std::set& usedHeadPartTypes) + { + const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); + for (ESM::FormId partId : partIds) + { + if (partId.isZeroOrUnset()) + continue; + const ESM4::HeadPart* part = store->get().search(partId); + if (!part) + { + Log(Debug::Error) << "Head part not found: " << ESM::RefId(partId); + continue; + } + if (usedHeadPartTypes.emplace(part->mType).second) + insertPart(part->mModel); + } + } + void ESM4NpcAnimation::updatePartsTES5() { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); @@ -92,23 +119,6 @@ namespace MWRender const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); - std::set usedHeadPartTypes; - auto addHeadParts = [&](const std::vector& partIds) { - for (ESM::FormId partId : partIds) - { - if (partId.isZeroOrUnset()) - continue; - const ESM4::HeadPart* part = store->get().search(partId); - if (!part) - { - Log(Debug::Error) << "Head part not found: " << ESM::RefId(partId); - continue; - } - if (usedHeadPartTypes.emplace(part->mType).second) - insertPart(part->mModel); - } - }; - std::vector armorAddons; auto findArmorAddons = [&](const ESM4::Armor* armor) { @@ -132,9 +142,19 @@ namespace MWRender for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) findArmorAddons(armor); if (!traits->mWornArmor.isZeroOrUnset()) - findArmorAddons(store->get().find(traits->mWornArmor)); + { + if (const ESM4::Armor* armor = store->get().search(traits->mWornArmor)) + findArmorAddons(armor); + else + Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits->mWornArmor); + } if (!race->mSkin.isZeroOrUnset()) - findArmorAddons(store->get().find(race->mSkin)); + { + if (const ESM4::Armor* armor = store->get().search(race->mSkin)) + findArmorAddons(armor); + else + Log(Debug::Error) << "Skin not found: " << ESM::RefId(race->mSkin); + } if (isFemale) std::sort(armorAddons.begin(), armorAddons.end(), @@ -158,9 +178,10 @@ namespace MWRender } } + std::set usedHeadPartTypes; if (usedParts & ESM4::Armor::TES5_Hair) usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair); - addHeadParts(traits->mHeadParts); - addHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale); + insertHeadParts(traits->mHeadParts, usedHeadPartTypes); + insertHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale, usedHeadPartTypes); } } diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp index 1d93dace45..7bb3fe1103 100644 --- a/apps/openmw/mwrender/esm4npcanimation.hpp +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -14,6 +14,9 @@ namespace MWRender private: void insertPart(std::string_view model); + // Works for FO3/FONV/TES5 + void insertHeadParts(const std::vector& partIds, std::set& usedHeadPartTypes); + void updateParts(); void updatePartsTES4(); void updatePartsTES5(); From 7dc08b5b64159e06780cf264b1da11f683e7f509 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 7 Oct 2023 17:20:35 +0200 Subject: [PATCH 0268/2167] More headpart types --- components/esm4/loadhdpt.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index 7686a4789f..aca3b0ca7b 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -60,6 +60,14 @@ namespace ESM4 Type_FacialHair = 4, Type_Scar = 5, Type_Eyebrows = 6, + // FO4+ + Type_Meatcaps = 7, + Type_Teeth = 8, + Type_HeadRear = 9, + // Starfield + // 10 and 11 are unknown + Type_LeftEye = 12, + Type_Eyelashes = 13, }; ESM::FormId mAdditionalPart; From 7b4b8763ff2813ccc685416094f216bed6e95bd1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Oct 2023 10:56:28 +0200 Subject: [PATCH 0269/2167] Use settings values for Post Processing settings --- apps/openmw/mwrender/luminancecalculator.cpp | 7 ++----- apps/openmw/mwrender/postprocessor.cpp | 13 +++++-------- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/settings/categories/postprocessing.hpp | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index 53240205c7..5b7fe272aa 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -1,7 +1,7 @@ #include "luminancecalculator.hpp" #include -#include +#include #include #include "pingpongcanvas.hpp" @@ -10,11 +10,8 @@ namespace MWRender { LuminanceCalculator::LuminanceCalculator(Shader::ShaderManager& shaderManager) { - const float hdrExposureTime - = std::max(Settings::Manager::getFloat("auto exposure speed", "Post Processing"), 0.0001f); - Shader::ShaderManager::DefineMap defines = { - { "hdrExposureTime", std::to_string(hdrExposureTime) }, + { "hdrExposureTime", std::to_string(Settings::postProcessing().mAutoExposureSpeed) }, }; auto vertex = shaderManager.getShader("fullscreen_tri.vert", {}); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index d64e9651bc..bcaba03161 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -121,7 +121,7 @@ namespace MWRender , mTriggerShaderReload(false) , mReload(false) , mEnabled(false) - , mUsePostProcessing(false) + , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSoftParticles(false) , mDisableDepthPasses(false) , mLastFrameNumber(0) @@ -136,7 +136,6 @@ namespace MWRender , mPrevPassLights(false) { mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders"); - mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing"); osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -237,7 +236,7 @@ namespace MWRender { mReload = true; mEnabled = true; - bool postPass = Settings::Manager::getBool("transparent postpass", "Post Processing"); + const bool postPass = Settings::postProcessing().mTransparentPostpass; mUsePostProcessing = usePostProcessing; mDisableDepthPasses = !mSoftParticles && !postPass; @@ -839,9 +838,7 @@ namespace MWRender mTechniques.clear(); - std::vector techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing"); - - for (auto& techniqueName : techniqueStrings) + for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) { if (techniqueName.empty()) continue; @@ -863,7 +860,7 @@ namespace MWRender chain.push_back(technique->getName()); } - Settings::Manager::setStringArray("chain", "Post Processing", chain); + Settings::postProcessing().mChain.set(chain); } void PostProcessor::toggleMode() diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6155325410..c6bb8f34aa 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1441,7 +1441,7 @@ namespace MWRender } else if (it->first == "Post Processing" && it->second == "enabled") { - if (Settings::Manager::getBool("enabled", "Post Processing")) + if (Settings::postProcessing().mEnabled) mPostProcessor->enable(); else { diff --git a/components/settings/categories/postprocessing.hpp b/components/settings/categories/postprocessing.hpp index b1e217b611..04810b847c 100644 --- a/components/settings/categories/postprocessing.hpp +++ b/components/settings/categories/postprocessing.hpp @@ -19,7 +19,7 @@ namespace Settings using WithIndex::WithIndex; SettingValue mEnabled{ mIndex, "Post Processing", "enabled" }; - SettingValue mChain{ mIndex, "Post Processing", "chain" }; + SettingValue> mChain{ mIndex, "Post Processing", "chain" }; SettingValue mAutoExposureSpeed{ mIndex, "Post Processing", "auto exposure speed", makeMaxStrictSanitizerFloat(0.0001f) }; SettingValue mTransparentPostpass{ mIndex, "Post Processing", "transparent postpass" }; From 4a7886816e1e7a20a2fb94582d3b7ef7a5ad5db6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 1 Oct 2023 10:48:37 +0200 Subject: [PATCH 0270/2167] Use settings values for Physics settings --- apps/openmw/engine.cpp | 2 +- apps/openmw/mwphysics/mtphysics.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ee2ce7ae3e..7393562cfb 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -105,7 +105,7 @@ namespace }); // the forEachUserStatsValue loop is "run" at compile time, hence the settings manager is not available. // Unconditionnally add the async physics stats, and then remove it at runtime if necessary - if (Settings::Manager::getInt("async num threads", "Physics") == 0) + if (Settings::physics().mAsyncNumThreads == 0) profiler.removeUserStatsLine(" -Async"); } diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 653380decf..52b96d9d13 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -16,8 +16,8 @@ #include "components/debug/debuglog.hpp" #include "components/misc/convert.hpp" -#include "components/settings/settings.hpp" #include +#include #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -314,7 +314,7 @@ namespace MWPhysics LockingPolicy detectLockingPolicy() { - if (Settings::Manager::getInt("async num threads", "Physics") < 1) + if (Settings::physics().mAsyncNumThreads < 1) return LockingPolicy::NoLocks; if (getMaxBulletSupportedThreads() > 1) return LockingPolicy::AllowSharedLocks; @@ -331,8 +331,8 @@ namespace MWPhysics case LockingPolicy::ExclusiveLocksOnly: return 1; case LockingPolicy::AllowSharedLocks: - return std::clamp( - Settings::Manager::getInt("async num threads", "Physics"), 0, getMaxBulletSupportedThreads()); + return static_cast(std::clamp( + Settings::physics().mAsyncNumThreads, 0, static_cast(getMaxBulletSupportedThreads()))); } throw std::runtime_error("Unsupported LockingPolicy: " @@ -407,7 +407,7 @@ namespace MWPhysics , mNumThreads(getNumThreads(mLockingPolicy)) , mNumJobs(0) , mRemainingSteps(0) - , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) + , mLOSCacheExpiry(Settings::physics().mLineofsightKeepInactiveCache) , mAdvanceSimulation(false) , mNextJob(0) , mNextLOS(0) From 1bd388faf3594b19a7888084c8dbacb6ff2718c8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 9 Oct 2023 06:59:28 +0300 Subject: [PATCH 0271/2167] Fix creating archives in bsatool --- apps/bsatool/bsatool.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index e2029f3245..de755e7d1d 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -320,6 +320,10 @@ int main(int argc, char** argv) // Open file + // TODO: add a version argument for this mode after compressed BSA writing is a thing + if (info.mode == "create") + return call(info); + Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(info.filename); switch (bsaVersion) From 0d7b3d1eeeea11e64474b50544e3fe4a13b641eb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 9 Oct 2023 13:52:01 +0100 Subject: [PATCH 0272/2167] Upgrade to standalone aqt v3.1.7 --- CI/before_script.msvc.sh | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 2996a3d772..abedc7f965 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -14,16 +14,6 @@ MISSINGTOOLS=0 command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; } command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; } -MISSINGPYTHON=0 -if ! command -v python >/dev/null 2>&1; then - echo "Warning: Python is not on the path, automatic Qt installation impossible." - MISSINGPYTHON=1 -elif ! python --version >/dev/null 2>&1; then - echo "Warning: Python is (probably) fake stub Python that comes bundled with newer versions of Windows, automatic Qt installation impossible." - echo "If you think you have Python installed, try changing the order of your PATH environment variable in Advanced System Settings." - MISSINGPYTHON=1 -fi - if [ $MISSINGTOOLS -ne 0 ]; then wrappedExit 1 fi @@ -889,30 +879,12 @@ printf "Qt ${QT_VER}... " if [ -d "Qt/${QT_VER}" ]; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then - if [ $MISSINGPYTHON -ne 0 ]; then - echo "Can't be automatically installed without Python." - wrappedExit 1 - fi - pushd "$DEPS" > /dev/null - if ! [ -d 'aqt-venv' ]; then - echo " Creating Virtualenv for aqt..." - run_cmd python -m venv aqt-venv - fi - if [ -d 'aqt-venv/bin' ]; then - VENV_BIN_DIR='bin' - elif [ -d 'aqt-venv/Scripts' ]; then - VENV_BIN_DIR='Scripts' - else - echo "Error: Failed to create virtualenv in expected location." - wrappedExit 1 - fi - - # check version - aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ] - if [ $? -eq 0 ]; then - echo " Installing aqt wheel into virtualenv..." - run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3 + AQT_VERSION="v3.1.7" + if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then + download "aqt ${AQT_VERSION}"\ + "https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \ + "aqt_x64-${AQT_VERSION}.exe" fi popd > /dev/null @@ -921,7 +893,7 @@ printf "Qt ${QT_VER}... " mkdir Qt cd Qt - run_cmd "${DEPS}/aqt-venv/${VENV_BIN_DIR}/aqt" install ${QT_VER} windows desktop "win${BITS}_msvc${QT_MSVC_YEAR}${SUFFIX}" + run_cmd "${DEPS}/aqt_x64-${AQT_VERSION}.exe" install-qt windows desktop ${QT_VER} "win${BITS}_msvc${QT_MSVC_YEAR}${SUFFIX}" printf " Cleaning up extraneous data... " rm -rf Qt/{aqtinstall.log,Tools} From 00c13b8dcd9026ee0200a07380acd5da7075313c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 9 Oct 2023 13:54:37 +0100 Subject: [PATCH 0273/2167] Ditch python in Windows CI - we don't need it --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6201785e8c..27c57fff62 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -531,7 +531,6 @@ macOS13_Xcode14_arm64: - choco install ccache -y - choco install vswhere -y - choco install ninja -y - - choco install python -y - choco install awscli -y - refreshenv - | @@ -653,7 +652,6 @@ macOS13_Xcode14_arm64: - choco install 7zip -y - choco install ccache -y - choco install vswhere -y - - choco install python -y - choco install awscli -y - refreshenv - | From 15306c7d49c4a96e9d19b0c8ff4150e48e118f73 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 10 Oct 2023 00:23:30 +0200 Subject: [PATCH 0274/2167] [Lua] Add CONTROL_SWITCH functions to `types.Player` and deprecate them in `openmw.input` --- CMakeLists.txt | 2 +- apps/openmw/mwlua/inputbindings.cpp | 2 ++ apps/openmw/mwlua/types/player.cpp | 26 +++++++++++++++ files/data/scripts/omw/camera/camera.lua | 9 ++--- files/data/scripts/omw/camera/move360.lua | 5 +-- files/data/scripts/omw/playercontrols.lua | 14 ++++---- files/lua_api/openmw/input.lua | 18 +++++----- files/lua_api/openmw/types.lua | 33 +++++++++++++++++++ .../integration_tests/test_lua_api/player.lua | 12 +++---- scripts/data/morrowind_tests/player.lua | 12 +++---- 10 files changed, 98 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d59e87344..ba0cd672cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 49) +set(OPENMW_LUA_API_REVISION 50) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 9384eccdbc..02babf0399 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -77,6 +77,7 @@ namespace MWLua return input->getActionValue(axis - SDL_CONTROLLER_AXIS_MAX) * 2 - 1; }; + // input.CONTROL_SWITCH is deprecated, remove after releasing 0.49 api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; @@ -134,6 +135,7 @@ namespace MWLua { "ZoomOut", MWInput::A_ZoomOut }, })); + // input.CONTROL_SWITCH is deprecated, remove after releasing 0.49 api["CONTROL_SWITCH"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "Controls", "playercontrols" }, diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index c0eb61bfc7..ab15385f08 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,6 +1,7 @@ #include "types.hpp" #include "../luamanagerimp.hpp" +#include #include #include #include @@ -119,6 +120,31 @@ namespace MWLua }, "addJournalEntryAction"); }; + + player["CONTROL_SWITCH"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Controls", "playercontrols" }, + { "Fighting", "playerfighting" }, + { "Jumping", "playerjumping" }, + { "Looking", "playerlooking" }, + { "Magic", "playermagic" }, + { "ViewMode", "playerviewswitch" }, + { "VanityMode", "vanitymode" }, + })); + + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); + player["getControlSwitch"] = [input](const Object& player, std::string_view key) { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + return input->getControlSwitch(key); + }; + player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + if (dynamic_cast(&player) && !dynamic_cast(&player)) + throw std::runtime_error("Only player and global scripts can toggle control switches."); + input->toggleControlSwitch(key, v); + }; } void addPlayerBindings(sol::table player, const Context& context) diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 18fd37d730..38441f9e59 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -8,6 +8,7 @@ local async = require('openmw.async') local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor +local Player = require('openmw.types').Player local settings = require('scripts.omw.camera.settings').thirdPerson local head_bobbing = require('scripts.omw.camera.head_bobbing') @@ -62,7 +63,7 @@ local previewTimer = 0 local function updatePOV(dt) local switchLimit = 0.25 - if input.isActionPressed(input.ACTION.TogglePOV) and input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) then + if input.isActionPressed(input.ACTION.TogglePOV) and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then previewTimer = previewTimer + dt if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then third_person.standingPreview = false @@ -91,7 +92,7 @@ local idleTimer = 0 local vanityDelay = core.getGMST('fVanityDelay') local function updateVanity(dt) - local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode) + local vanityAllowed = Player.getControlSwitch(self, Player.CONTROL_SWITCH.VanityMode) if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then camera.setMode(MODE.Vanity) end @@ -115,8 +116,8 @@ local minDistance = 30 local maxDistance = 800 local function zoom(delta) - if not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or - not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or + if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or camera.getMode() == MODE.Static or next(noZoom) then return end diff --git a/files/data/scripts/omw/camera/move360.lua b/files/data/scripts/omw/camera/move360.lua index 76ee3be9d6..18c30ae77b 100644 --- a/files/data/scripts/omw/camera/move360.lua +++ b/files/data/scripts/omw/camera/move360.lua @@ -6,6 +6,7 @@ local util = require('openmw.util') local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor +local Player = require('openmw.types').Player local MODE = camera.MODE @@ -60,8 +61,8 @@ end function M.onInputAction(action) if not active or core.isWorldPaused() or - not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or - not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or input.isActionPressed(input.ACTION.TogglePOV) or not I.Camera.isModeControlEnabled() then return diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index bea7e5392f..0bd7cf6bea 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -87,7 +87,7 @@ local function processMovement() elseif autoMove then self.controls.movement = 1 end - self.controls.jump = attemptJump and input.getControlSwitch(input.CONTROL_SWITCH.Jumping) + self.controls.jump = attemptJump and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) if not settings:get('toggleSneak') then self.controls.sneak = input.isActionPressed(input.ACTION.Sneak) end @@ -107,7 +107,7 @@ local function processAttacking() end local function onFrame(dt) - local controlsAllowed = input.getControlSwitch(input.CONTROL_SWITCH.Controls) + local controlsAllowed = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not core.isWorldPaused() and not I.UI.getMode() if not movementControlsOverridden then if controlsAllowed then @@ -140,7 +140,7 @@ local function isJournalAllowed() end local function onInputAction(action) - if not input.getControlSwitch(input.CONTROL_SWITCH.Controls) then + if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) then return end @@ -185,7 +185,7 @@ local function onInputAction(action) elseif action == input.ACTION.ToggleSpell and not combatControlsOverridden then if Actor.stance(self) == Actor.STANCE.Spell then Actor.setStance(self, Actor.STANCE.Nothing) - elseif input.getControlSwitch(input.CONTROL_SWITCH.Magic) then + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then if checkNotWerewolf() then Actor.setStance(self, Actor.STANCE.Spell) end @@ -193,7 +193,7 @@ local function onInputAction(action) elseif action == input.ACTION.ToggleWeapon and not combatControlsOverridden then if Actor.stance(self) == Actor.STANCE.Weapon then Actor.setStance(self, Actor.STANCE.Nothing) - elseif input.getControlSwitch(input.CONTROL_SWITCH.Fighting) then + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then Actor.setStance(self, Actor.STANCE.Weapon) end end @@ -214,13 +214,13 @@ return { version = 1, --- When set to true then the movement controls including jump and sneak are not processed and can be handled by another script. - -- If movement should be disallowed completely, consider to use `input.setControlSwitch` instead. + -- If movement should be disallowed completely, consider to use `types.Player.setControlSwitch` instead. -- @function [parent=#Controls] overrideMovementControls -- @param #boolean value overrideMovementControls = function(v) movementControlsOverridden = v end, --- When set to true then the controls "attack", "toggle spell", "toggle weapon" are not processed and can be handled by another script. - -- If combat should be disallowed completely, consider to use `input.setControlSwitch` instead. + -- If combat should be disallowed completely, consider to use `types.Player.setControlSwitch` instead. -- @function [parent=#Controls] overrideCombatControls -- @param #boolean value overrideCombatControls = function(v) combatControlsOverridden = v end, diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 3012c24e76..4ca4e5af4e 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -72,23 +72,23 @@ -- @return #number Value in range [-1, 1]. --- --- Get state of a control switch. I.e. is player able to move/fight/jump/etc. +-- Returns a human readable name for the given key code +-- @function [parent=#input] getKeyName +-- @param #KeyCode code A key code (see @{openmw.input#KEY}) +-- @return #string + +--- +-- [Deprecated, moved to types.Player] Get state of a control switch. I.e. is player able to move/fight/jump/etc. -- @function [parent=#input] getControlSwitch -- @param #ControlSwitch key Control type (see @{openmw.input#CONTROL_SWITCH}) -- @return #boolean --- --- Set state of a control switch. I.e. forbid or allow player to move/fight/jump/etc. +-- [Deprecated, moved to types.Player] Set state of a control switch. I.e. forbid or allow player to move/fight/jump/etc. -- @function [parent=#input] setControlSwitch -- @param #ControlSwitch key Control type (see @{openmw.input#CONTROL_SWITCH}) -- @param #boolean value ---- --- Returns a human readable name for the given key code --- @function [parent=#input] getKeyName --- @param #KeyCode code A key code (see @{openmw.input#KEY}) --- @return #string - --- -- String id of a @{#CONTROL_SWITCH} -- @type ControlSwitch @@ -104,7 +104,7 @@ -- @field [parent=#CONTROL_SWITCH] #ControlSwitch VanityMode Vanity view if player doesn't touch controls for a long time --- --- Values that can be used with getControlSwitch/setControlSwitch. +-- [Deprecated, moved to types.Player] Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH --- diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 87cd462b6f..a5b606ea1d 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -951,6 +951,39 @@ -- @param #number stage Quest stage -- @param openmw.core#GameObject actor (optional) The actor who is the source of the journal entry, it may be used in journal entries with variables such as `%name(The speaker's name)` or `%race(The speaker's race)`. +--- +-- Get state of a control switch. I.e. is the player able to move/fight/jump/etc. +-- @function [parent=#Player] getControlSwitch +-- @param openmw.core#GameObject player +-- @param #ControlSwitch key Control type (see @{openmw.types#CONTROL_SWITCH}) +-- @return #boolean + +--- +-- Set state of a control switch. I.e. forbid or allow the player to move/fight/jump/etc. +-- Can be used only in global or player scripts. +-- @function [parent=#Player] setControlSwitch +-- @param openmw.core#GameObject player +-- @param #ControlSwitch key Control type (see @{openmw.types#CONTROL_SWITCH}) +-- @param #boolean value + +--- +-- String id of a @{#CONTROL_SWITCH} +-- @type ControlSwitch + +--- +-- @type CONTROL_SWITCH +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch Controls Ability to move +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch Fighting Ability to attack +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch Jumping Ability to jump +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch Looking Ability to change view direction +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch Magic Ability to use magic +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch ViewMode Ability to toggle 1st/3rd person view +-- @field [parent=#CONTROL_SWITCH] #ControlSwitch VanityMode Vanity view if player doesn't touch controls for a long time + +--- +-- Values that can be used with getControlSwitch/setControlSwitch. +-- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH + -------------------------------------------------------------------------------- -- @{#Armor} functions diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 62344af3d2..93c16a8b88 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -6,12 +6,12 @@ local input = require('openmw.input') local types = require('openmw.types') local nearby = require('openmw.nearby') -input.setControlSwitch(input.CONTROL_SWITCH.Fighting, false) -input.setControlSwitch(input.CONTROL_SWITCH.Jumping, false) -input.setControlSwitch(input.CONTROL_SWITCH.Looking, false) -input.setControlSwitch(input.CONTROL_SWITCH.Magic, false) -input.setControlSwitch(input.CONTROL_SWITCH.VanityMode, false) -input.setControlSwitch(input.CONTROL_SWITCH.ViewMode, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Jumping, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Looking, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Magic, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.VanityMode, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.ViewMode, false) testing.registerLocalTest('playerRotation', function() diff --git a/scripts/data/morrowind_tests/player.lua b/scripts/data/morrowind_tests/player.lua index b9e791829d..c366d91ebd 100644 --- a/scripts/data/morrowind_tests/player.lua +++ b/scripts/data/morrowind_tests/player.lua @@ -6,12 +6,12 @@ local util = require('openmw.util') local types = require('openmw.types') local nearby = require('openmw.nearby') -input.setControlSwitch(input.CONTROL_SWITCH.Fighting, false) -input.setControlSwitch(input.CONTROL_SWITCH.Jumping, false) -input.setControlSwitch(input.CONTROL_SWITCH.Looking, false) -input.setControlSwitch(input.CONTROL_SWITCH.Magic, false) -input.setControlSwitch(input.CONTROL_SWITCH.VanityMode, false) -input.setControlSwitch(input.CONTROL_SWITCH.ViewMode, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Fighting, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Jumping, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Looking, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Magic, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.VanityMode, false) +types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.ViewMode, false) testing.registerLocalTest('Player should be able to walk up stairs in Ebonheart docks (#4247)', function() From 1038a68fbf11157f7b77f86f13d6801790a4b533 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 Oct 2023 13:55:17 +0200 Subject: [PATCH 0275/2167] Use settings values for Saves settings --- apps/openmw/mwgui/savegamedialog.cpp | 6 +++--- apps/openmw/mwgui/waitdialog.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 14 ++++---------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 71b39328f6..0d754f71e5 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -17,7 +17,7 @@ #include -#include +#include #include #include @@ -168,7 +168,7 @@ namespace MWGui mCurrentCharacter = mgr->getCurrentCharacter(); - const std::string& directory = Settings::Manager::getString("character", "Saves"); + const std::string& directory = Settings::saves().mCharacter; size_t selectedIndex = MyGUI::ITEM_NONE; @@ -427,7 +427,7 @@ namespace MWGui mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}"); - if (Settings::Manager::getBool("timeplayed", "Saves")) + if (Settings::saves().mTimeplayed) { text << "\n" << "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ab17031168..8f2c720d3e 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -186,7 +186,7 @@ namespace MWGui void WaitDialog::startWaiting(int hoursToWait) { - if (Settings::Manager::getBool("autosave", "Saves")) // autosaves when enabled + if (Settings::saves().mAutosave) // autosaves when enabled MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 50fd123b4f..6b09b37964 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include @@ -314,8 +314,7 @@ void MWState::StateManager::saveGame(std::string_view description, const Slot* s if (filestream.fail()) throw std::runtime_error("Write operation failed (file stream)"); - Settings::Manager::setString( - "character", "Saves", Files::pathToUnicodeString(slot->mPath.parent_path().filename())); + Settings::saves().mCharacter.set(Files::pathToUnicodeString(slot->mPath.parent_path().filename())); const auto finish = std::chrono::steady_clock::now(); @@ -354,12 +353,8 @@ void MWState::StateManager::quickSave(std::string name) return; } - int maxSaves = Settings::Manager::getInt("max quicksaves", "Saves"); - if (maxSaves < 1) - maxSaves = 1; - Character* currentCharacter = getCurrentCharacter(); // Get current character - QuickSaveManager saveFinder = QuickSaveManager(name, maxSaves); + QuickSaveManager saveFinder(name, Settings::saves().mMaxQuicksaves); if (currentCharacter) { @@ -542,8 +537,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file mState = State_Running; if (character) - Settings::Manager::setString( - "character", "Saves", Files::pathToUnicodeString(character->getPath().filename())); + Settings::saves().mCharacter.set(Files::pathToUnicodeString(character->getPath().filename())); MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->saveLoaded(); From d2a79c4205d7bb47980a291e83fa77821f459ce6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 9 Oct 2023 04:33:38 +0300 Subject: [PATCH 0276/2167] Use a more clear error message for clamp wrap mode --- components/fx/technique.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index fd1556674f..0b5d784ad9 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -916,6 +916,11 @@ namespace fx return mode; } + if (asLiteral() == "clamp") + error( + "unsupported wrap mode 'clamp'; 'clamp_to_edge' was likely intended, look for an updated shader or " + "contact author"); + error(Misc::StringUtils::format("unrecognized wrap mode '%s'", std::string{ asLiteral() })); } From 58a16dacbe794574d00117f4d768af0b0b4817f3 Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 13 Oct 2023 00:53:59 +0800 Subject: [PATCH 0277/2167] take2 resolve --- apps/openmw/mwclass/light.cpp | 5 ----- apps/openmw/mwclass/light.hpp | 2 -- apps/openmw/mwlua/itemdata.cpp | 25 +++++++++++++++++++------ apps/openmw/mwlua/stats.cpp | 4 ++-- apps/openmw/mwworld/cellref.cpp | 27 ++++++++++++++++----------- apps/openmw/mwworld/cellref.hpp | 13 +++++++++++++ apps/openmw/mwworld/class.hpp | 2 -- components/esm3/cellref.hpp | 2 +- 8 files changed, 51 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 8933491e68..931ed73dfe 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -91,11 +91,6 @@ namespace MWClass return ptr.get()->mBase->mData.mFlags & ESM::Light::Carry; } - bool Light::isLight(const MWWorld::ConstPtr& ptr) const - { - return true; - } - std::unique_ptr Light::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index d5119aa0b5..70d6852ff8 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -38,8 +38,6 @@ namespace MWClass bool isItem(const MWWorld::ConstPtr&) const override; - bool isLight(const MWWorld::ConstPtr&) const override; - std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 33a1a21310..0741b5ebe2 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -71,10 +71,11 @@ namespace MWLua if (prop == "condition") { MWWorld::Ptr o = mObject.ptr(); - if (o.getClass().isLight(o)) + if (o.mRef->getType() == ESM::REC_LIGH) return sol::make_object(context.mLua->sol(), o.getClass().getRemainingUsageTime(o)); else if (o.getClass().hasItemHealth(o)) - return sol::make_object(context.mLua->sol(), o.getClass().getItemHealth(o)); + return sol::make_object( + context.mLua->sol(), o.getClass().getItemHealth(o) + o.getCellRef().getChargeIntRemainder()); } return sol::lua_nil; @@ -84,14 +85,26 @@ namespace MWLua { if (prop == "condition") { - double cond = value.as(); - if (ptr.getClass().isLight(ptr)) + float cond = LuaUtil::cast(value); + if (ptr.mRef->getType() == ESM::REC_LIGH) ptr.getClass().setRemainingUsageTime(ptr, cond); else if (ptr.getClass().hasItemHealth(ptr)) - ptr.getCellRef().setCharge(std::max(0, static_cast(cond))); - else /*ignore or error?*/ + { + const float lastChargeRemainder = ptr.getCellRef().getChargeIntRemainder(); + // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 + ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); + ptr.getCellRef().setCharge(std::max(0.f, cond)); + // resolve the remaining charge remainder to be subtracted + if (lastChargeRemainder < 0) + ptr.getCellRef().applyChargeRemainderToBeSubtracted(lastChargeRemainder); + } + else invalidPropErr(prop, ptr); + return; } + + /*ignore or error?*/ + invalidPropErr(prop, ptr); } }; } diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 5c1e536dd6..35273a64e8 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -85,7 +85,7 @@ namespace MWLua public: sol::object getCurrent(const Context& context) const { - return getValue(context, mObject, &LevelStat::setValue, 0, "current", + return getValue(context, mObject, &LevelStat::setValue, std::monostate{}, "current", [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } @@ -93,7 +93,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, 0, "current" }] = value; + obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, std::monostate{}, "current" }] = value; } sol::object getProgress(const Context& context) const diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 37e1be0ddf..73fd3d4ac8 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -188,19 +188,14 @@ namespace MWWorld void CellRef::applyChargeRemainderToBeSubtracted(float chargeRemainder) { auto esm3Visit = [&](ESM::CellRef& cellRef3) { - cellRef3.mChargeIntRemainder += std::abs(chargeRemainder); - if (cellRef3.mChargeIntRemainder > 1.0f) + cellRef3.mChargeIntRemainder -= std::abs(chargeRemainder); + if (cellRef3.mChargeIntRemainder <= -1.0f) { - float newChargeRemainder = (cellRef3.mChargeIntRemainder - std::floor(cellRef3.mChargeIntRemainder)); - if (cellRef3.mChargeInt <= static_cast(cellRef3.mChargeIntRemainder)) - { - cellRef3.mChargeInt = 0; - } - else - { - cellRef3.mChargeInt -= static_cast(cellRef3.mChargeIntRemainder); - } + float newChargeRemainder = std::modf(cellRef3.mChargeIntRemainder, &cellRef3.mChargeIntRemainder); + cellRef3.mChargeInt += static_cast(cellRef3.mChargeIntRemainder); cellRef3.mChargeIntRemainder = newChargeRemainder; + if (cellRef3.mChargeInt < 0) + cellRef3.mChargeInt = 0; } }; std::visit(ESM::VisitOverload{ @@ -211,6 +206,16 @@ namespace MWWorld mCellRef.mVariant); } + void CellRef::setChargeIntRemainder(float chargeRemainder) + { + std::visit(ESM::VisitOverload{ + [&](ESM4::Reference& /*ref*/) {}, + [&](ESM4::ActorCharacter&) {}, + [&](ESM::CellRef& ref) { ref.mChargeIntRemainder = chargeRemainder; }, + }, + mCellRef.mVariant); + } + void CellRef::setChargeFloat(float charge) { std::visit(ESM::VisitOverload{ diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 73e721278e..a1ddd8d0f0 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -118,10 +118,23 @@ namespace MWWorld }; return std::visit(Visitor(), mCellRef.mVariant); } // Implemented as union with int charge + float getChargeIntRemainder() const + { + struct Visitor + { + float operator()(const ESM::CellRef& ref) { return ref.mChargeIntRemainder; } + float operator()(const ESM4::Reference& /*ref*/) { return 0; } + float operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); } + }; + return std::visit(Visitor(), mCellRef.mVariant); + } void setCharge(int charge); void setChargeFloat(float charge); void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1 + // Stores fractional part of mChargeInt + void setChargeIntRemainder(float chargeRemainder); + // The NPC that owns this object (and will get angry if you steal it) ESM::RefId getOwner() const { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 162fa88a34..7b7e9135ba 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -332,8 +332,6 @@ namespace MWWorld // True if it is an item that can be picked up. virtual bool isItem(const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isLight(const MWWorld::ConstPtr& ptr) const { return false; } - virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 10ab4505ce..55e5afcbf5 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -60,7 +60,7 @@ namespace ESM int32_t mChargeInt; // Used by everything except lights float mChargeFloat; // Used only by lights }; - float mChargeIntRemainder; // Stores amount of charge not subtracted from mChargeInt + float mChargeIntRemainder; // Fractional part of mChargeInt // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; From 08902371b4b274b57b2e459efea289c8cebceac4 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 8 Oct 2023 13:58:49 +0200 Subject: [PATCH 0278/2167] Use settings values for Shaders settings --- apps/launcher/graphicspage.cpp | 28 +++++-- apps/opencs/view/render/scenewidget.cpp | 4 +- apps/openmw/mwgui/settingswindow.cpp | 12 ++- apps/openmw/mwrender/characterpreview.cpp | 15 ++-- apps/openmw/mwrender/localmap.cpp | 3 +- apps/openmw/mwrender/postprocessor.cpp | 14 ++-- apps/openmw/mwrender/postprocessor.hpp | 3 - .../mwrender/precipitationocclusion.cpp | 3 +- apps/openmw/mwrender/renderingmanager.cpp | 79 +++++++++---------- apps/openmw/mwrender/renderingmanager.hpp | 1 - apps/openmw/mwrender/sky.cpp | 6 +- apps/openmw/mwrender/util.cpp | 6 ++ apps/openmw/mwrender/util.hpp | 3 + apps/openmw/mwrender/water.cpp | 7 +- components/CMakeLists.txt | 2 +- components/resource/scenemanager.cpp | 7 +- components/resource/scenemanager.hpp | 5 +- components/sceneutil/lightingmethod.hpp | 14 ++++ components/sceneutil/lightmanager.cpp | 45 ++++------- components/sceneutil/lightmanager.hpp | 27 ++++--- components/sceneutil/rtt.cpp | 10 ++- components/sceneutil/rtt.hpp | 3 +- components/sceneutil/util.cpp | 8 +- components/sceneutil/util.hpp | 5 +- components/settings/categories/shaders.hpp | 7 +- components/settings/settings.cpp | 35 ++++++++ components/settings/settings.hpp | 13 ++- components/settings/settingvalue.hpp | 9 +++ components/shader/shadervisitor.cpp | 3 +- components/shader/shadervisitor.hpp | 3 + 30 files changed, 234 insertions(+), 146 deletions(-) create mode 100644 components/sceneutil/lightingmethod.hpp diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 5f37c0defe..9a10bf6d9c 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -2,6 +2,8 @@ #include "sdlinit.hpp" +#include + #include #include @@ -144,10 +146,18 @@ bool Launcher::GraphicsPage::loadSettings() // Lighting int lightingMethod = 1; - if (Settings::Manager::getString("lighting method", "Shaders") == "legacy") - lightingMethod = 0; - else if (Settings::Manager::getString("lighting method", "Shaders") == "shaders") - lightingMethod = 2; + switch (Settings::shaders().mLightingMethod) + { + case SceneUtil::LightingMethod::FFP: + lightingMethod = 0; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + lightingMethod = 1; + break; + case SceneUtil::LightingMethod::SingleUBO: + lightingMethod = 2; + break; + } lightingMethodComboBox->setCurrentIndex(lightingMethod); // Shadows @@ -246,10 +256,12 @@ void Launcher::GraphicsPage::saveSettings() } // Lighting - static std::array lightingMethodMap = { "legacy", "shaders compatibility", "shaders" }; - const std::string& cLightingMethod = lightingMethodMap[lightingMethodComboBox->currentIndex()]; - if (cLightingMethod != Settings::Manager::getString("lighting method", "Shaders")) - Settings::Manager::setString("lighting method", "Shaders", cLightingMethod); + static constexpr std::array lightingMethodMap = { + SceneUtil::LightingMethod::FFP, + SceneUtil::LightingMethod::PerObjectUniform, + SceneUtil::LightingMethod::SingleUBO, + }; + Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); // Shadows int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 12170c86a0..953e3076b3 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -87,10 +87,10 @@ namespace CSVRender mView->getCamera()->setGraphicsContext(window); - SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager; + osg::ref_ptr lightMgr = new SceneUtil::LightManager; lightMgr->setStartLight(1); lightMgr->setLightingMask(Mask_Lighting); - mRootNode = lightMgr; + mRootNode = std::move(lightMgr); mView->getCamera()->setViewport(new osg::Viewport(0, 0, width(), height())); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 9fa842bfa9..ac25b5984a 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -147,7 +147,7 @@ namespace constexpr int min = 8; constexpr int max = 32; constexpr int increment = 8; - int maxLights = Settings::Manager::getInt("max lights", "Shaders"); + const int maxLights = Settings::shaders().mMaxLights; // show increments of 8 in dropdown if (maxLights >= min && maxLights <= max && !(maxLights % increment)) box->setIndexSelected((maxLights / increment) - 1); @@ -559,7 +559,8 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->interactiveMessageBox( "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); - Settings::Manager::setString("lighting method", "Shaders", *_sender->getItemDataAt(pos)); + Settings::shaders().mLightingMethod.set( + Settings::parseLightingMethod(*_sender->getItemDataAt(pos))); apply(); } @@ -630,9 +631,7 @@ namespace MWGui void SettingsWindow::onMaxLightsChanged(MyGUI::ComboBox* _sender, size_t pos) { - int count = 8 * (pos + 1); - - Settings::Manager::setInt("max lights", "Shaders", count); + Settings::shaders().mMaxLights.set(8 * (pos + 1)); apply(); configureWidgets(mMainWidget, false); } @@ -653,8 +652,7 @@ namespace MWGui Settings::shaders().mMaxLights.reset(); Settings::shaders().mLightingMethod.reset(); - const SceneUtil::LightingMethod lightingMethod - = SceneUtil::LightManager::getLightingMethodFromString(Settings::shaders().mLightingMethod); + const SceneUtil::LightingMethod lightingMethod = Settings::shaders().mLightingMethod; const std::size_t lightIndex = mLightingMethodButton->findItemIndexWith(lightingMethodToStr(lightingMethod)); mLightingMethodButton->setIndexSelected(lightIndex); updateMaxLightsComboBox(mMaxLights); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5357f5025c..fbf5bf112a 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include "../mwworld/class.hpp" @@ -34,6 +34,7 @@ #include "../mwmechanics/weapontype.hpp" #include "npcanimation.hpp" +#include "util.hpp" #include "vismask.hpp" namespace MWRender @@ -154,7 +155,7 @@ namespace MWRender public: CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, - StereoAwareness::Unaware_MultiViewShaders) + StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget()) , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) { if (SceneUtil::AutoDepth::isReversed()) @@ -226,9 +227,13 @@ namespace MWRender mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY); mRTTNode->setNodeMask(Mask_RenderToTexture); - bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; - - osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); + osg::ref_ptr lightManager = new SceneUtil::LightManager(SceneUtil::LightSettings{ + .mLightingMethod = mResourceSystem->getSceneManager()->getLightingMethod(), + .mMaxLights = Settings::shaders().mMaxLights, + .mMaximumLightDistance = Settings::shaders().mMaximumLightDistance, + .mLightFadeStart = Settings::shaders().mLightFadeStart, + .mLightBoundsMultiplier = Settings::shaders().mLightBoundsMultiplier, + }); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setDefine("FORCE_OPAQUE", "1", osg::StateAttribute::ON); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index a07fa10b09..3d28bf477d 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -30,6 +30,7 @@ #include "../mwworld/cellstore.hpp" +#include "util.hpp" #include "vismask.hpp" namespace @@ -679,7 +680,7 @@ namespace MWRender LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax) - : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders) + : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget()) , mSceneRoot(sceneRoot) , mActive(true) { diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index bcaba03161..9162657e30 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -122,7 +122,6 @@ namespace MWRender , mReload(false) , mEnabled(false) , mUsePostProcessing(Settings::postProcessing().mEnabled) - , mSoftParticles(false) , mDisableDepthPasses(false) , mLastFrameNumber(0) , mLastSimulationTime(0.f) @@ -135,8 +134,6 @@ namespace MWRender , mPassLights(false) , mPrevPassLights(false) { - mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders"); - osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -155,7 +152,7 @@ namespace MWRender else Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; - if (mSoftParticles) + if (Settings::shaders().mSoftParticles) { for (int i = 0; i < 2; ++i) { @@ -172,7 +169,8 @@ namespace MWRender mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); - if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing) + if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !Settings::shaders().mSoftParticles + && !mUsePostProcessing) return; enable(mUsePostProcessing); @@ -239,7 +237,7 @@ namespace MWRender const bool postPass = Settings::postProcessing().mTransparentPostpass; mUsePostProcessing = usePostProcessing; - mDisableDepthPasses = !mSoftParticles && !postPass; + mDisableDepthPasses = !Settings::shaders().mSoftParticles && !postPass; #ifdef ANDROID mDisableDepthPasses = true; @@ -276,10 +274,10 @@ namespace MWRender void PostProcessor::disable() { - if (!mSoftParticles) + if (!Settings::shaders().mSoftParticles) osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); - if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles) + if (!SceneUtil::AutoDepth::isReversed() && !Settings::shaders().mSoftParticles) { removeChild(mHUDCamera); setCullCallback(nullptr); diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index fa230ec2e8..4473ade836 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -175,8 +175,6 @@ namespace MWRender bool isEnabled() const { return mUsePostProcessing && mEnabled; } - bool softParticlesEnabled() const { return mSoftParticles; } - bool getHDR() const { return mHDR; } void disable(); @@ -247,7 +245,6 @@ namespace MWRender bool mReload; bool mEnabled; bool mUsePostProcessing; - bool mSoftParticles; bool mDisableDepthPasses; size_t mLastFrameNumber; diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp index e7c9245fef..204b5c07bd 100644 --- a/apps/openmw/mwrender/precipitationocclusion.cpp +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -112,7 +113,7 @@ namespace MWRender mCamera->attach(osg::Camera::DEPTH_BUFFER, mDepthTexture); mCamera->addChild(mSceneNode); mCamera->setSmallFeatureCullingPixelSize( - Settings::Manager::getFloat("weather particle occlusion small feature culling pixel size", "Shaders")); + Settings::shaders().mWeatherParticleOcclusionSmallFeatureCullingPixelSize); SceneUtil::setCameraClearDepth(mCamera); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d470273862..b7685e0cd8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -82,6 +82,7 @@ #include "screenshotmanager.hpp" #include "sky.hpp" #include "terrainstorage.hpp" +#include "util.hpp" #include "vismask.hpp" #include "water.hpp" @@ -312,7 +313,6 @@ namespace MWRender , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mNavigator(navigator) - , mMinimumAmbientLuminance(0.f) , mNightEyeFactor(0.f) // TODO: Near clip should not need to be bounded like this, but too small values break OSG shadow calculations // CPU-side. See issue: #6072 @@ -325,46 +325,40 @@ namespace MWRender , mGroundCoverStore(groundcoverStore) { bool reverseZ = SceneUtil::AutoDepth::isReversed(); - auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString( - Settings::Manager::getString("lighting method", "Shaders")); + const SceneUtil::LightingMethod lightingMethod = Settings::shaders().mLightingMethod; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog - || Settings::Manager::getBool("soft particles", "Shaders") - || Settings::Manager::getBool("force shaders", "Shaders") + || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped - resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); - resourceSystem->getSceneManager()->setAutoUseNormalMaps( - Settings::Manager::getBool("auto use object normal maps", "Shaders")); - resourceSystem->getSceneManager()->setNormalMapPattern( - Settings::Manager::getString("normal map pattern", "Shaders")); - resourceSystem->getSceneManager()->setNormalHeightMapPattern( - Settings::Manager::getString("normal height map pattern", "Shaders")); - resourceSystem->getSceneManager()->setAutoUseSpecularMaps( - Settings::Manager::getBool("auto use object specular maps", "Shaders")); - resourceSystem->getSceneManager()->setSpecularMapPattern( - Settings::Manager::getString("specular map pattern", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(Settings::shaders().mClampLighting); + resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::shaders().mAutoUseObjectNormalMaps); + resourceSystem->getSceneManager()->setNormalMapPattern(Settings::shaders().mNormalMapPattern); + resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::shaders().mNormalHeightMapPattern); + resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::shaders().mAutoUseObjectSpecularMaps); + resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::shaders().mSpecularMapPattern); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps( - Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage( - Settings::Manager::getBool("antialias alpha test", "Shaders") - && Settings::Manager::getInt("antialiasing", "Video") > 1); + Settings::shaders().mApplyLightingToEnvironmentMaps); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(shouldAddMSAAIntermediateTarget()); resourceSystem->getSceneManager()->setAdjustCoverageForAlphaTest( - Settings::Manager::getBool("adjust coverage for alpha test", "Shaders")); + Settings::shaders().mAdjustCoverageForAlphaTest); // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this // depends on support for various OpenGL extensions. - osg::ref_ptr sceneRoot - = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); + osg::ref_ptr sceneRoot = new SceneUtil::LightManager(SceneUtil::LightSettings{ + .mLightingMethod = lightingMethod, + .mMaxLights = Settings::shaders().mMaxLights, + .mMaximumLightDistance = Settings::shaders().mMaximumLightDistance, + .mLightFadeStart = Settings::shaders().mLightFadeStart, + .mLightBoundsMultiplier = Settings::shaders().mLightBoundsMultiplier, + }); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); - mMinimumAmbientLuminance - = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; @@ -396,10 +390,9 @@ namespace MWRender for (auto itr = shadowDefines.begin(); itr != shadowDefines.end(); itr++) globalDefines[itr->first] = itr->second; - globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0"; - globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; - globalDefines["preLightEnv"] - = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; + globalDefines["forcePPL"] = Settings::shaders().mForcePerPixelLighting ? "1" : "0"; + globalDefines["clamp"] = Settings::shaders().mClampLighting ? "1" : "0"; + globalDefines["preLightEnv"] = Settings::shaders().mApplyLightingToEnvironmentMaps ? "1" : "0"; const bool exponentialFog = Settings::fog().mExponentialFog; globalDefines["radialFog"] = (exponentialFog || Settings::fog().mRadialFog) ? "1" : "0"; globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; @@ -445,11 +438,11 @@ namespace MWRender mEffectManager = std::make_unique(sceneRoot, mResourceSystem); - const std::string& normalMapPattern = Settings::Manager::getString("normal map pattern", "Shaders"); - const std::string& heightMapPattern = Settings::Manager::getString("normal height map pattern", "Shaders"); - const std::string& specularMapPattern = Settings::Manager::getString("terrain specular map pattern", "Shaders"); - const bool useTerrainNormalMaps = Settings::Manager::getBool("auto use terrain normal maps", "Shaders"); - const bool useTerrainSpecularMaps = Settings::Manager::getBool("auto use terrain specular maps", "Shaders"); + const std::string& normalMapPattern = Settings::shaders().mNormalMapPattern; + const std::string& heightMapPattern = Settings::shaders().mNormalHeightMapPattern; + const std::string& specularMapPattern = Settings::shaders().mTerrainSpecularMapPattern; + const bool useTerrainNormalMaps = Settings::shaders().mAutoUseTerrainNormalMaps; + const bool useTerrainSpecularMaps = Settings::shaders().mAutoUseTerrainSpecularMaps; mTerrainStorage = std::make_unique(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); @@ -472,8 +465,9 @@ namespace MWRender resourceSystem->getSceneManager()->setOpaqueDepthTex( mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 0), mPostProcessor->getTexture(PostProcessor::Tex_OpaqueDepth, 1)); - resourceSystem->getSceneManager()->setSoftParticles(mPostProcessor->softParticlesEnabled()); + resourceSystem->getSceneManager()->setSoftParticles(Settings::shaders().mSoftParticles); resourceSystem->getSceneManager()->setSupportsNormalsRT(mPostProcessor->getSupportsNormalsRT()); + resourceSystem->getSceneManager()->setWeatherParticleOcclusion(Settings::shaders().mWeatherParticleOcclusion); // water goes after terrain for correct waterculling order mWater = std::make_unique( @@ -680,15 +674,16 @@ namespace MWRender // we already work in linear RGB so no conversions are needed for the luminosity function float relativeLuminance = pR * ambient.r() + pG * ambient.g() + pB * ambient.b(); - if (relativeLuminance < mMinimumAmbientLuminance) + const float minimumAmbientLuminance = Settings::shaders().mMinimumInteriorBrightness; + if (relativeLuminance < minimumAmbientLuminance) { // brighten ambient so it reaches the minimum threshold but no more, we want to mess with content data // as least we can if (ambient.r() == 0.f && ambient.g() == 0.f && ambient.b() == 0.f) ambient = osg::Vec4( - mMinimumAmbientLuminance, mMinimumAmbientLuminance, mMinimumAmbientLuminance, ambient.a()); + minimumAmbientLuminance, minimumAmbientLuminance, minimumAmbientLuminance, ambient.a()); else - ambient *= mMinimumAmbientLuminance / relativeLuminance; + ambient *= minimumAmbientLuminance / relativeLuminance; } } @@ -1408,8 +1403,6 @@ namespace MWRender } else if (it->first == "Shaders" && it->second == "minimum interior brightness") { - mMinimumAmbientLuminance - = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); if (MWMechanics::getPlayer().isInCell()) configureAmbient(*MWMechanics::getPlayer().getCell()->getCell()); } @@ -1418,13 +1411,15 @@ namespace MWRender || it->second == "light fade start" || it->second == "max lights")) { auto* lightManager = getLightRoot(); - lightManager->processChangedSettings(changed); + + lightManager->processChangedSettings(Settings::shaders().mLightBoundsMultiplier, + Settings::shaders().mMaximumLightDistance, Settings::shaders().mLightFadeStart); if (it->second == "max lights" && !lightManager->usingFFP()) { mViewer->stopThreading(); - lightManager->updateMaxLights(); + lightManager->updateMaxLights(Settings::shaders().mMaxLights); auto defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); for (const auto& [name, key] : lightManager->getLightDefines()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f1760d3960..22ef987c01 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -342,7 +342,6 @@ namespace MWRender osg::ref_ptr mPerViewUniformStateUpdater; osg::Vec4f mAmbientColor; - float mMinimumAmbientLuminance; float mNightEyeFactor; float mNearClip; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 05c7a1bab2..51018e93f9 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -35,6 +35,7 @@ #include "renderbin.hpp" #include "skyutil.hpp" +#include "util.hpp" #include "vismask.hpp" namespace @@ -205,7 +206,8 @@ namespace { public: SkyRTT(osg::Vec2f size, osg::Group* earlyRenderBinRoot) - : RTTNode(static_cast(size.x()), static_cast(size.y()), 0, false, 1, StereoAwareness::Aware) + : RTTNode(static_cast(size.x()), static_cast(size.y()), 0, false, 1, StereoAwareness::Aware, + MWRender::shouldAddMSAAIntermediateTarget()) , mEarlyRenderBinRoot(earlyRenderBinRoot) { setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); @@ -292,7 +294,7 @@ namespace MWRender mRootNode->addChild(mEarlyRenderBinRoot); mUnderwaterSwitch = new UnderwaterSwitchCallback(skyroot); - mPrecipitationOcclusion = Settings::Manager::getBool("weather particle occlusion", "Shaders"); + mPrecipitationOcclusion = Settings::shaders().mWeatherParticleOcclusion; mPrecipitationOccluder = std::make_unique(skyroot, parentNode, rootNode, camera); } diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 509ea2efa7..234b022f5d 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace MWRender { @@ -67,4 +68,9 @@ namespace MWRender node->setStateSet(stateset); } + bool shouldAddMSAAIntermediateTarget() + { + return Settings::shaders().mAntialiasAlphaTest && Settings::Manager::getInt("antialiasing", "Video") > 1; + } + } diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index 457b23f94b..64edaf8e18 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -3,6 +3,7 @@ #include #include + #include namespace osg @@ -35,6 +36,8 @@ namespace MWRender // no traverse() } }; + + bool shouldAddMSAAIntermediateTarget(); } #endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 51ce06069c..f823378e46 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -35,11 +35,14 @@ #include +#include + #include "../mwworld/cellstore.hpp" #include "renderbin.hpp" #include "ripples.hpp" #include "ripplesimulation.hpp" +#include "util.hpp" #include "vismask.hpp" namespace MWRender @@ -234,7 +237,7 @@ namespace MWRender { public: Refraction(uint32_t rttSize) - : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) + : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware, shouldAddMSAAIntermediateTarget()) , mNodeMask(Refraction::sDefaultCullMask) { setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); @@ -315,7 +318,7 @@ namespace MWRender { public: Reflection(uint32_t rttSize, bool isInterior) - : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) + : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware, shouldAddMSAAIntermediateTarget()) { setInterior(isInterior); setDepthBufferInternalFormat(GL_DEPTH24_STENCIL8); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5ad7562e96..dd680dc98d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -107,7 +107,7 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt - screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon + screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod ) add_component_dir (nif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 66fee5256d..b3c9eee5e7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1118,10 +1118,10 @@ namespace Resource stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); } - Shader::ShaderVisitor* SceneManager::createShaderVisitor(const std::string& shaderPrefix) + osg::ref_ptr SceneManager::createShaderVisitor(const std::string& shaderPrefix) { - Shader::ShaderVisitor* shaderVisitor - = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); + osg::ref_ptr shaderVisitor( + new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix)); shaderVisitor->setForceShaders(mForceShaders); shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps); shaderVisitor->setNormalMapPattern(mNormalMapPattern); @@ -1132,6 +1132,7 @@ namespace Resource shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); shaderVisitor->setAdjustCoverageForAlphaTest(mAdjustCoverageForAlphaTest); shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT); + shaderVisitor->setWeatherParticleOcclusion(mWeatherParticleOcclusion); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index e8a9640ce9..c7663a4d91 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -227,8 +227,10 @@ namespace Resource void setSoftParticles(bool enabled) { mSoftParticles = enabled; } bool getSoftParticles() const { return mSoftParticles; } + void setWeatherParticleOcclusion(bool value) { mWeatherParticleOcclusion = value; } + private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); + osg::ref_ptr createShaderVisitor(const std::string& shaderPrefix = "objects"); osg::ref_ptr loadErrorMarker(); osg::ref_ptr cloneErrorMarker(); @@ -248,6 +250,7 @@ namespace Resource bool mSupportsNormalsRT; std::array, 2> mOpaqueDepthTex; bool mSoftParticles = false; + bool mWeatherParticleOcclusion = false; osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/sceneutil/lightingmethod.hpp b/components/sceneutil/lightingmethod.hpp new file mode 100644 index 0000000000..00aafd1cb7 --- /dev/null +++ b/components/sceneutil/lightingmethod.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTINGMETHOD_H +#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTINGMETHOD_H + +namespace SceneUtil +{ + enum class LightingMethod + { + FFP, + PerObjectUniform, + SingleUBO, + }; +} + +#endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index ea7d3459ae..6bca92fdb4 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -23,8 +23,6 @@ namespace { - constexpr int maxLightsLowerLimit = 2; - constexpr int maxLightsUpperLimit = 64; constexpr int ffpMaxLights = 8; bool sortLights(const SceneUtil::LightManager::LightSourceViewBound* left, @@ -817,7 +815,7 @@ namespace SceneUtil return ""; } - LightManager::LightManager(bool ffp) + LightManager::LightManager(const LightSettings& settings) : mStartLight(0) , mLightingMask(~0u) , mSun(nullptr) @@ -835,18 +833,15 @@ namespace SceneUtil setUpdateCallback(new LightManagerUpdateCallback); - if (ffp) + if (settings.mLightingMethod == LightingMethod::FFP) { initFFP(ffpMaxLights); return; } - const std::string& lightingMethodString = Settings::Manager::getString("lighting method", "Shaders"); - auto lightingMethod = LightManager::getLightingMethodFromString(lightingMethodString); - static bool hasLoggedWarnings = false; - if (lightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) + if (settings.mLightingMethod == LightingMethod::SingleUBO && !hasLoggedWarnings) { if (!supportsUBO) Log(Debug::Warning) @@ -857,15 +852,12 @@ namespace SceneUtil hasLoggedWarnings = true; } - const int targetLights - = std::clamp(Settings::Manager::getInt("max lights", "Shaders"), maxLightsLowerLimit, maxLightsUpperLimit); - - if (!supportsUBO || !supportsGPU4 || lightingMethod == LightingMethod::PerObjectUniform) - initPerObjectUniform(targetLights); + if (!supportsUBO || !supportsGPU4 || settings.mLightingMethod == LightingMethod::PerObjectUniform) + initPerObjectUniform(settings.mMaxLights); else - initSingleUBO(targetLights); + initSingleUBO(settings.mMaxLights); - updateSettings(); + updateSettings(settings.mLightBoundsMultiplier, settings.mMaximumLightDistance, settings.mLightFadeStart); getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); @@ -931,18 +923,18 @@ namespace SceneUtil return defines; } - void LightManager::processChangedSettings(const Settings::CategorySettingVector& changed) + void LightManager::processChangedSettings( + float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart) { - updateSettings(); + updateSettings(lightBoundsMultiplier, maximumLightDistance, lightFadeStart); } - void LightManager::updateMaxLights() + void LightManager::updateMaxLights(int maxLights) { if (usingFFP()) return; - setMaxLights( - std::clamp(Settings::Manager::getInt("max lights", "Shaders"), maxLightsLowerLimit, maxLightsUpperLimit)); + setMaxLights(maxLights); if (getLightingMethod() == LightingMethod::PerObjectUniform) { @@ -954,20 +946,15 @@ namespace SceneUtil cache.clear(); } - void LightManager::updateSettings() + void LightManager::updateSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart) { if (getLightingMethod() == LightingMethod::FFP) return; - mPointLightRadiusMultiplier - = std::clamp(Settings::Manager::getFloat("light bounds multiplier", "Shaders"), 0.f, 5.f); - - mPointLightFadeEnd = std::max(0.f, Settings::Manager::getFloat("maximum light distance", "Shaders")); + mPointLightRadiusMultiplier = lightBoundsMultiplier; + mPointLightFadeEnd = maximumLightDistance; if (mPointLightFadeEnd > 0) - { - mPointLightFadeStart = std::clamp(Settings::Manager::getFloat("light fade start", "Shaders"), 0.f, 1.f); - mPointLightFadeStart = mPointLightFadeEnd * mPointLightFadeStart; - } + mPointLightFadeStart = mPointLightFadeEnd * lightFadeStart; } void LightManager::initFFP(int targetLights) diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 93bddb800b..0b30a77e5c 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -12,7 +12,8 @@ #include #include -#include + +#include "lightingmethod.hpp" namespace SceneUtil { @@ -82,13 +83,6 @@ namespace SceneUtil std::array, 2> mUniformCount; }; - enum class LightingMethod - { - FFP, - PerObjectUniform, - SingleUBO, - }; - /// LightSource managed by a LightManager. /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole /// scene @@ -186,6 +180,15 @@ namespace SceneUtil osg::ref_ptr mTemplate; }; + struct LightSettings + { + LightingMethod mLightingMethod = LightingMethod::FFP; + int mMaxLights = 0; + float mMaximumLightDistance = 0; + float mLightFadeStart = 0; + float mLightBoundsMultiplier = 0; + }; + /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the /// subgraph. class LightManager : public osg::Group @@ -212,7 +215,7 @@ namespace SceneUtil META_Node(SceneUtil, LightManager) - LightManager(bool ffp = true); + explicit LightManager(const LightSettings& settings = LightSettings{}); LightManager(const LightManager& copy, const osg::CopyOp& copyop); @@ -264,10 +267,10 @@ namespace SceneUtil std::map getLightDefines() const; - void processChangedSettings(const Settings::CategorySettingVector& changed); + void processChangedSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart); /// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer - void updateMaxLights(); + void updateMaxLights(int maxLights); osg::ref_ptr generateLightBufferUniform(const osg::Matrixf& sun); @@ -281,7 +284,7 @@ namespace SceneUtil void initPerObjectUniform(int targetLights); void initSingleUBO(int targetLights); - void updateSettings(); + void updateSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart); void setLightingMethod(LightingMethod method); void setMaxLights(int value); diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index eec4aaa35d..54d53ddab9 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -20,7 +20,7 @@ namespace SceneUtil }; RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, - int renderOrderNum, StereoAwareness stereoAwareness) + int renderOrderNum, StereoAwareness stereoAwareness, bool addMSAAIntermediateTarget) : mTextureWidth(textureWidth) , mTextureHeight(textureHeight) , mSamples(samples) @@ -29,6 +29,7 @@ namespace SceneUtil , mDepthBufferInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) , mStereoAwareness(stereoAwareness) + , mAddMSAAIntermediateTarget(addMSAAIntermediateTarget) { addCullCallback(new CullCallback); setCullingActive(false); @@ -206,7 +207,8 @@ namespace SceneUtil camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), mGenerateMipmaps, mSamples); SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, - vdd->mColorTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), mGenerateMipmaps); + vdd->mColorTexture, 0, Stereo::osgFaceControlledByMultiviewShader(), mGenerateMipmaps, + mAddMSAAIntermediateTarget); } if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) @@ -234,8 +236,8 @@ namespace SceneUtil { vdd->mColorTexture = createTexture(mColorBufferInternalFormat); camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps, mSamples); - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera( - camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, + vdd->mColorTexture, 0, 0, mGenerateMipmaps, mAddMSAAIntermediateTarget); } if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) diff --git a/components/sceneutil/rtt.hpp b/components/sceneutil/rtt.hpp index 5c8183f6c0..d2f43f78ed 100644 --- a/components/sceneutil/rtt.hpp +++ b/components/sceneutil/rtt.hpp @@ -49,7 +49,7 @@ namespace SceneUtil }; RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, - int renderOrderNum, StereoAwareness stereoAwareness); + int renderOrderNum, StereoAwareness stereoAwareness, bool addMSAAIntermediateTarget); ~RTTNode(); osg::Texture* getColorTexture(osgUtil::CullVisitor* cv); @@ -110,6 +110,7 @@ namespace SceneUtil GLint mDepthBufferInternalFormat; int mRenderOrderNum; StereoAwareness mStereoAwareness; + bool mAddMSAAIntermediateTarget; }; } #endif diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 7b921d15f4..6ff366f76e 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -239,13 +239,12 @@ namespace SceneUtil return glowUpdater; } - bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, - osg::Texture* texture, unsigned int level, unsigned int face, bool mipMapGeneration) + void attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, + osg::Texture* texture, unsigned int level, unsigned int face, bool mipMapGeneration, + bool addMSAAIntermediateTarget) { unsigned int samples = 0; unsigned int colourSamples = 0; - bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") - && Settings::Manager::getInt("antialiasing", "Video") > 1; if (addMSAAIntermediateTarget) { // Alpha-to-coverage requires a multisampled framebuffer. @@ -255,7 +254,6 @@ namespace SceneUtil colourSamples = 1; } camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples); - return addMSAAIntermediateTarget; } OperationSequence::OperationSequence(bool keep) diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0a65a9980f..29fee09176 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -96,8 +96,9 @@ namespace SceneUtil const osg::Vec4f& glowColor, float glowDuration = -1); // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs - bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, - osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); + void attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, + osg::Texture* texture, unsigned int level, unsigned int face, bool mipMapGeneration, + bool addMSAAIntermediateTarget); class OperationSequence : public osg::Operation { diff --git a/components/settings/categories/shaders.hpp b/components/settings/categories/shaders.hpp index e7d1e34713..7efb891822 100644 --- a/components/settings/categories/shaders.hpp +++ b/components/settings/categories/shaders.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H +#include "components/sceneutil/lightingmethod.hpp" #include "components/settings/sanitizerimpl.hpp" #include "components/settings/settingvalue.hpp" @@ -30,11 +31,11 @@ namespace Settings SettingValue mSpecularMapPattern{ mIndex, "Shaders", "specular map pattern" }; SettingValue mTerrainSpecularMapPattern{ mIndex, "Shaders", "terrain specular map pattern" }; SettingValue mApplyLightingToEnvironmentMaps{ mIndex, "Shaders", "apply lighting to environment maps" }; - SettingValue mLightingMethod{ mIndex, "Shaders", "lighting method", - makeEnumSanitizerString({ "legacy", "shaders compatibility", "shaders" }) }; + SettingValue mLightingMethod{ mIndex, "Shaders", "lighting method" }; SettingValue mLightBoundsMultiplier{ mIndex, "Shaders", "light bounds multiplier", makeClampSanitizerFloat(0, 5) }; - SettingValue mMaximumLightDistance{ mIndex, "Shaders", "maximum light distance" }; + SettingValue mMaximumLightDistance{ mIndex, "Shaders", "maximum light distance", + makeMaxSanitizerFloat(0) }; SettingValue mLightFadeStart{ mIndex, "Shaders", "light fade start", makeClampSanitizerFloat(0, 1) }; SettingValue mMaxLights{ mIndex, "Shaders", "max lights", makeClampSanitizerInt(2, 64) }; SettingValue mMinimumInteriorBrightness{ mIndex, "Shaders", "minimum interior brightness", diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 305d4edc88..bab8f41c1d 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -16,6 +16,7 @@ #endif +#include #include #include #include @@ -87,6 +88,21 @@ namespace Settings stream << value; return stream.str(); } + + std::string toString(SceneUtil::LightingMethod value) + { + switch (value) + { + case SceneUtil::LightingMethod::FFP: + return "legacy"; + case SceneUtil::LightingMethod::PerObjectUniform: + return "shaders compatibility"; + case SceneUtil::LightingMethod::SingleUBO: + return "shaders"; + } + + throw std::invalid_argument("Invalid LightingMethod value: " + std::to_string(static_cast(value))); + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); @@ -459,6 +475,11 @@ namespace Settings setString(setting, category, value.print()); } + void Manager::set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value) + { + setString(setting, category, toString(value)); + } + void Manager::recordInit(std::string_view setting, std::string_view category) { sInitialized.emplace(category, setting); @@ -491,4 +512,18 @@ namespace Settings throw std::invalid_argument("Invalid navigation mesh rendering mode: " + std::string(value)); } + + SceneUtil::LightingMethod parseLightingMethod(std::string_view value) + { + if (value == "legacy") + return SceneUtil::LightingMethod::FFP; + if (value == "shaders compatibility") + return SceneUtil::LightingMethod::PerObjectUniform; + if (value == "shaders") + return SceneUtil::LightingMethod::SingleUBO; + + constexpr const char* fallback = "shaders compatibility"; + Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; + return SceneUtil::LightingMethod::PerObjectUniform; + } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 784c7c613c..0177b5fbf2 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -5,7 +5,8 @@ #include "gyroscopeaxis.hpp" #include "navmeshrendermode.hpp" -#include "components/detournavigator/collisionshapetype.hpp" +#include +#include #include #include @@ -110,6 +111,7 @@ namespace Settings static void set(std::string_view setting, std::string_view category, DetourNavigator::CollisionShapeType value); static void set(std::string_view setting, std::string_view category, const std::vector& value); static void set(std::string_view setting, std::string_view category, const MyGUI::Colour& value); + static void set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value); private: static std::set> sInitialized; @@ -215,6 +217,15 @@ namespace Settings { return parseNavMeshRenderMode(getString(setting, category)); } + + SceneUtil::LightingMethod parseLightingMethod(std::string_view value); + + template <> + inline SceneUtil::LightingMethod Manager::getImpl( + std::string_view setting, std::string_view category) + { + return parseLightingMethod(getString(setting, category)); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 407fd15baf..35726cb25b 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -40,6 +40,7 @@ namespace Settings MyGuiColour, GyroscopeAxis, NavMeshRenderMode, + LightingMethod, }; template @@ -147,6 +148,12 @@ namespace Settings return SettingValueType::NavMeshRenderMode; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::LightingMethod; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -185,6 +192,8 @@ namespace Settings return "gyroscope axis"; case SettingValueType::NavMeshRenderMode: return "navmesh render mode"; + case SettingValueType::LightingMethod: + return "lighting method"; } return "unsupported"; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 1aa0f76e17..96e3d42f78 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -680,8 +680,7 @@ namespace Shader bool particleOcclusion = false; node.getUserValue("particleOcclusion", particleOcclusion); - defineMap["particleOcclusion"] - = particleOcclusion && Settings::Manager::getBool("weather particle occlusion", "Shaders") ? "1" : "0"; + defineMap["particleOcclusion"] = particleOcclusion && mWeatherParticleOcclusion ? "1" : "0"; if (reqs.mAlphaBlend && mSupportsNormalsRT) { diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 0bd8bb9cd7..66bd8c2a9d 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -51,6 +51,8 @@ namespace Shader void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } + void setWeatherParticleOcclusion(bool value) { mWeatherParticleOcclusion = value; } + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -78,6 +80,7 @@ namespace Shader bool mAdjustCoverageForAlphaTest; bool mSupportsNormalsRT; + bool mWeatherParticleOcclusion = false; ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; From a84e412a37988a33470f142109cf92467049b909 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 12 Oct 2023 20:25:55 +0200 Subject: [PATCH 0279/2167] Use settings values for Sound settings --- apps/launcher/settingspage.cpp | 27 +++++++++--- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwsound/sound_buffer.cpp | 8 ++-- apps/openmw/mwsound/sound_output.hpp | 11 ++--- apps/openmw/mwsound/soundmanagerimp.cpp | 43 ++++++++++++------ apps/openmw/mwsound/soundmanagerimp.hpp | 5 --- apps/openmw/mwsound/volumesettings.cpp | 56 ------------------------ apps/openmw/mwsound/volumesettings.hpp | 26 ----------- components/settings/categories/sound.hpp | 9 +--- components/settings/hrtfmode.hpp | 14 ++++++ components/settings/settings.cpp | 21 +++++++++ components/settings/settings.hpp | 13 ++++++ components/settings/settingvalue.hpp | 9 ++++ 13 files changed, 117 insertions(+), 127 deletions(-) delete mode 100644 apps/openmw/mwsound/volumesettings.cpp delete mode 100644 apps/openmw/mwsound/volumesettings.hpp create mode 100644 components/settings/hrtfmode.hpp diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 07570272dd..5869cc3a73 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -55,6 +55,20 @@ namespace { value.set(spinBox.value()); } + + int toIndex(Settings::HrtfMode value) + { + switch (value) + { + case Settings::HrtfMode::Auto: + return 0; + case Settings::HrtfMode::Disable: + return 1; + case Settings::HrtfMode::Enable: + return 2; + } + return 0; + } } Launcher::SettingsPage::SettingsPage(Config::GameSettings& gameSettings, QWidget* parent) @@ -210,11 +224,7 @@ bool Launcher::SettingsPage::loadSettings() audioDeviceSelectorComboBox->setCurrentIndex(audioDeviceIndex); } } - const int hrtfEnabledIndex = Settings::sound().mHrtfEnable; - if (hrtfEnabledIndex >= -1 && hrtfEnabledIndex <= 1) - { - enableHRTFComboBox->setCurrentIndex(hrtfEnabledIndex + 1); - } + enableHRTFComboBox->setCurrentIndex(toIndex(Settings::sound().mHrtfEnable)); const std::string& selectedHRTFProfile = Settings::sound().mHrtf; if (selectedHRTFProfile.empty() == false) { @@ -356,7 +366,12 @@ void Launcher::SettingsPage::saveSettings() else Settings::sound().mDevice.set({}); - Settings::sound().mHrtfEnable.set(enableHRTFComboBox->currentIndex() - 1); + static constexpr std::array hrtfModes{ + Settings::HrtfMode::Auto, + Settings::HrtfMode::Disable, + Settings::HrtfMode::Enable, + }; + Settings::sound().mHrtfEnable.set(hrtfModes[enableHRTFComboBox->currentIndex()]); if (hrtfProfileSelectorComboBox->currentIndex() != 0) Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 16aea9736b..57e2b9d708 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwlua add_openmw_dir (mwsound soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output - loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings + loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index d2645a1fe8..a3fdcb8b5c 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -37,11 +37,9 @@ namespace MWSound SoundBufferPool::SoundBufferPool(Sound_Output& output) : mOutput(&output) - , mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024) + , mBufferCacheMax(Settings::sound().mBufferCacheMax * 1024 * 1024) , mBufferCacheMin( - std::min(static_cast(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) - * 1024 * 1024, - mBufferCacheMax)) + std::min(static_cast(Settings::sound().mBufferCacheMin) * 1024 * 1024, mBufferCacheMax)) { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index d7c35fbbc6..8eec20bcba 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwbase/soundmanager.hpp" namespace MWSound @@ -19,19 +21,14 @@ namespace MWSound // An opaque handle for the implementation's sound instances. typedef void* Sound_Instance; - enum class HrtfMode - { - Disable, - Enable, - Auto - }; - enum Environment { Env_Normal, Env_Underwater }; + using HrtfMode = Settings::HrtfMode; + class Sound_Output { SoundManager& mManager; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 73cfeba3db..64f8959218 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,33 @@ namespace MWSound return 1.0; } + + // Gets the combined volume settings for the given sound type + float volumeFromType(Type type) + { + float volume = Settings::sound().mMasterVolume; + + switch (type) + { + case Type::Sfx: + volume *= Settings::sound().mSfxVolume; + break; + case Type::Voice: + volume *= Settings::sound().mVoiceVolume; + break; + case Type::Foot: + volume *= Settings::sound().mFootstepsVolume; + break; + case Type::Music: + volume *= Settings::sound().mMusicVolume; + break; + case Type::Movie: + case Type::Mask: + break; + } + + return volume; + } } // For combining PlayMode and Type flags @@ -103,12 +131,7 @@ namespace MWSound return; } - const std::string& hrtfname = Settings::Manager::getString("hrtf", "Sound"); - int hrtfstate = Settings::Manager::getInt("hrtf enable", "Sound"); - HrtfMode hrtfmode = hrtfstate < 0 ? HrtfMode::Auto : hrtfstate > 0 ? HrtfMode::Enable : HrtfMode::Disable; - - const std::string& devname = Settings::Manager::getString("device", "Sound"); - if (!mOutput->init(devname, hrtfname, hrtfmode)) + if (!mOutput->init(Settings::sound().mDevice, Settings::sound().mHrtf, Settings::sound().mHrtfEnable)) { Log(Debug::Error) << "Failed to initialize audio output, sound disabled"; return; @@ -219,12 +242,6 @@ namespace MWSound return sound; } - // Gets the combined volume settings for the given sound type - float SoundManager::volumeFromType(Type type) const - { - return mVolumeSettings.getVolumeFromType(type); - } - void SoundManager::stopMusic() { if (mMusic) @@ -1156,8 +1173,6 @@ namespace MWSound void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings) { - mVolumeSettings.update(); - if (!mOutput->isInitialized()) return; mOutput->startUpdate(); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 94d407c11b..6154d202cd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -16,7 +16,6 @@ #include "regionsoundselector.hpp" #include "sound_buffer.hpp" #include "type.hpp" -#include "volumesettings.hpp" #include "watersoundupdater.hpp" namespace VFS @@ -57,8 +56,6 @@ namespace MWSound std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played - VolumeSettings mVolumeSettings; - WaterSoundUpdater mWaterSoundUpdater; SoundBufferPool mSoundBuffers; @@ -144,8 +141,6 @@ namespace MWSound void updateWaterSound(); void updateMusic(float duration); - float volumeFromType(Type type) const; - enum class WaterSoundAction { DoNothing, diff --git a/apps/openmw/mwsound/volumesettings.cpp b/apps/openmw/mwsound/volumesettings.cpp deleted file mode 100644 index 4306bc5268..0000000000 --- a/apps/openmw/mwsound/volumesettings.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "volumesettings.hpp" - -#include - -#include - -namespace MWSound -{ - namespace - { - float clamp(float value) - { - return std::clamp(value, 0.f, 1.f); - } - } - - VolumeSettings::VolumeSettings() - : mMasterVolume(clamp(Settings::Manager::getFloat("master volume", "Sound"))) - , mSFXVolume(clamp(Settings::Manager::getFloat("sfx volume", "Sound"))) - , mMusicVolume(clamp(Settings::Manager::getFloat("music volume", "Sound"))) - , mVoiceVolume(clamp(Settings::Manager::getFloat("voice volume", "Sound"))) - , mFootstepsVolume(clamp(Settings::Manager::getFloat("footsteps volume", "Sound"))) - { - } - - float VolumeSettings::getVolumeFromType(Type type) const - { - float volume = mMasterVolume; - - switch (type) - { - case Type::Sfx: - volume *= mSFXVolume; - break; - case Type::Voice: - volume *= mVoiceVolume; - break; - case Type::Foot: - volume *= mFootstepsVolume; - break; - case Type::Music: - volume *= mMusicVolume; - break; - case Type::Movie: - case Type::Mask: - break; - } - - return volume; - } - - void VolumeSettings::update() - { - *this = VolumeSettings(); - } -} diff --git a/apps/openmw/mwsound/volumesettings.hpp b/apps/openmw/mwsound/volumesettings.hpp deleted file mode 100644 index 884a3e1bca..0000000000 --- a/apps/openmw/mwsound/volumesettings.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GAME_SOUND_VOLUMESETTINGS_H -#define GAME_SOUND_VOLUMESETTINGS_H - -#include "type.hpp" - -namespace MWSound -{ - class VolumeSettings - { - public: - VolumeSettings(); - - float getVolumeFromType(Type type) const; - - void update(); - - private: - float mMasterVolume; - float mSFXVolume; - float mMusicVolume; - float mVoiceVolume; - float mFootstepsVolume; - }; -} - -#endif diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index f4ce7e0269..43313e622d 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -1,16 +1,11 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H +#include "components/settings/hrtfmode.hpp" #include "components/settings/sanitizerimpl.hpp" #include "components/settings/settingvalue.hpp" -#include -#include -#include - -#include #include -#include namespace Settings { @@ -26,7 +21,7 @@ namespace Settings SettingValue mVoiceVolume{ mIndex, "Sound", "voice volume", makeClampSanitizerFloat(0, 1) }; SettingValue mBufferCacheMin{ mIndex, "Sound", "buffer cache min", makeMaxSanitizerInt(1) }; SettingValue mBufferCacheMax{ mIndex, "Sound", "buffer cache max", makeMaxSanitizerInt(1) }; - SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable", makeEnumSanitizerInt({ -1, 0, 1 }) }; + SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; }; } diff --git a/components/settings/hrtfmode.hpp b/components/settings/hrtfmode.hpp new file mode 100644 index 0000000000..9b741922b5 --- /dev/null +++ b/components/settings/hrtfmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_HRTFMODE_H +#define OPENMW_COMPONENTS_SETTINGS_HRTFMODE_H + +namespace Settings +{ + enum class HrtfMode + { + Disable, + Enable, + Auto, + }; +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index bab8f41c1d..7b31bf6aad 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -103,6 +103,22 @@ namespace Settings throw std::invalid_argument("Invalid LightingMethod value: " + std::to_string(static_cast(value))); } + + int toInt(HrtfMode value) + { + switch (value) + { + case HrtfMode::Auto: + return -1; + case HrtfMode::Disable: + return 0; + case HrtfMode::Enable: + return 1; + } + + Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast(value) << ", fallback to auto (-1)"; + return -1; + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); @@ -480,6 +496,11 @@ namespace Settings setString(setting, category, toString(value)); } + void Manager::set(std::string_view setting, std::string_view category, HrtfMode value) + { + setInt(setting, category, toInt(value)); + } + void Manager::recordInit(std::string_view setting, std::string_view category) { sInitialized.emplace(category, setting); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 0177b5fbf2..a5b75fd445 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -3,6 +3,7 @@ #include "categories.hpp" #include "gyroscopeaxis.hpp" +#include "hrtfmode.hpp" #include "navmeshrendermode.hpp" #include @@ -112,6 +113,7 @@ namespace Settings static void set(std::string_view setting, std::string_view category, const std::vector& value); static void set(std::string_view setting, std::string_view category, const MyGUI::Colour& value); static void set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value); + static void set(std::string_view setting, std::string_view category, HrtfMode value); private: static std::set> sInitialized; @@ -226,6 +228,17 @@ namespace Settings { return parseLightingMethod(getString(setting, category)); } + + template <> + inline HrtfMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0) + return HrtfMode::Auto; + if (value > 0) + return HrtfMode::Enable; + return HrtfMode::Disable; + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 35726cb25b..7b49c9bda2 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -41,6 +41,7 @@ namespace Settings GyroscopeAxis, NavMeshRenderMode, LightingMethod, + HrtfMode, }; template @@ -154,6 +155,12 @@ namespace Settings return SettingValueType::LightingMethod; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::HrtfMode; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -194,6 +201,8 @@ namespace Settings return "navmesh render mode"; case SettingValueType::LightingMethod: return "lighting method"; + case SettingValueType::HrtfMode: + return "hrtf mode"; } return "unsupported"; } From fc74cc49dd8ebebc99197fdca51856f19a44f93e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 13 Oct 2023 12:05:32 +0400 Subject: [PATCH 0280/2167] Add additional fields to save metadata (feature 7618) --- CHANGELOG.md | 1 + apps/openmw/mwgui/savegamedialog.cpp | 7 +++++++ apps/openmw/mwstate/statemanagerimp.cpp | 5 +++++ components/esm3/formatversion.hpp | 2 +- components/esm3/savedgame.cpp | 8 ++++++++ components/esm3/savedgame.hpp | 4 ++++ 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42db494f3c..d48e5cf841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music + Feature #7618: Show the player character's health in the save details Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 71b39328f6..85495426d1 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -412,6 +412,10 @@ namespace MWGui text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; + if (mCurrentSlot->mProfile.mMaximumHealth > 0) + text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/" + << mCurrentSlot->mProfile.mMaximumHealth << "\n"; + text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n"; @@ -422,6 +426,9 @@ namespace MWGui if (hour == 0) hour = 12; + if (mCurrentSlot->mProfile.mCurrentDay > 0) + text << "#{Calendar:day} " << mCurrentSlot->mProfile.mCurrentDay << "\n"; + text << mCurrentSlot->mProfile.mInGameTime.mDay << " " << MWBase::Environment::get().getWorld()->getTimeManager()->getMonthName( mCurrentSlot->mProfile.mInGameTime.mMonth) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 50fd123b4f..2f0f1d877d 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -227,10 +227,15 @@ void MWState::StateManager::saveGame(std::string_view description, const Slot* s else profile.mPlayerClassId = classId; + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + profile.mPlayerCellName = world.getCellName(); profile.mInGameTime = world.getTimeManager()->getEpochTimeStamp(); profile.mTimePlayed = mTimePlayed; profile.mDescription = description; + profile.mCurrentDay = world.getTimeManager()->getTimeStamp().getDay(); + profile.mCurrentHealth = stats.getHealth().getCurrent(); + profile.mMaximumHealth = stats.getHealth().getModified(); Log(Debug::Info) << "Making a screenshot for saved game '" << description << "'"; writeScreenshot(profile.mScreenshot); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 644298b00d..12a73fc12b 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -25,7 +25,7 @@ namespace ESM inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 28; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; } #endif diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 6652179ea9..e84cb27ad8 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -28,6 +28,10 @@ namespace ESM esm.getSubHeader(); mScreenshot.resize(esm.getSubSize()); esm.getExact(mScreenshot.data(), mScreenshot.size()); + + esm.getHNOT(mCurrentDay, "CDAY"); + esm.getHNOT(mCurrentHealth, "CHLT"); + esm.getHNOT(mMaximumHealth, "MHLT"); } void SavedGame::save(ESMWriter& esm) const @@ -51,6 +55,10 @@ namespace ESM esm.startSubRecord("SCRN"); esm.write(mScreenshot.data(), mScreenshot.size()); esm.endRecord("SCRN"); + + esm.writeHNT("CDAY", mCurrentDay); + esm.writeHNT("CHLT", mCurrentHealth); + esm.writeHNT("MHLT", mMaximumHealth); } } diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 0777b2498b..2048244ac2 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -34,6 +34,10 @@ namespace ESM std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data + int mCurrentDay = 0; + float mCurrentHealth = 0; + float mMaximumHealth = 0; + void load(ESMReader& esm); void save(ESMWriter& esm) const; }; From 2ebd544dfbd5245950794aa8bcb1720612f4e805 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 14 Oct 2023 14:18:50 +0200 Subject: [PATCH 0281/2167] Fix #7624 --- apps/openmw/mwlua/magicbindings.cpp | 3 --- files/lua_api/openmw/types.lua | 1 - 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 0e99a57c97..fb714c81c6 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -843,9 +843,6 @@ namespace MWLua auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); activeSpells.purgeEffect(ptr, key.mId, key.mArg); - - // Now remove any leftover effects that have been added by script/console. - effects.getStore()->remove(key); }; // types.Actor.activeEffects(o):set(value, id, ?arg) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a5b606ea1d..c65a7323cb 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -227,7 +227,6 @@ --- -- Completely removes the active effect from the actor. --- This removes both the effects incurred by active spells and effect added by console, mwscript, or luascript. -- @function [parent=#ActorActiveEffects] remove -- @param self -- @param #string effectId effect ID From 1c4706208397fb5b856cbef7fc1c7545db24094e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 14 Oct 2023 16:22:28 +0200 Subject: [PATCH 0282/2167] Replace more explicitly sized reads --- components/esm3/loadrepa.cpp | 2 +- components/esm3/loadrepa.hpp | 6 +++--- components/esm3/loadscpt.cpp | 12 ++++++++---- components/esm3/loadscpt.hpp | 4 ++-- components/esm3/loadskil.cpp | 2 +- components/esm3/loadskil.hpp | 8 ++++---- components/esm3/loadweap.cpp | 3 ++- components/esm3/loadweap.hpp | 12 ++++++------ 8 files changed, 27 insertions(+), 22 deletions(-) diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index ea0fc29f19..c911cb1a23 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -28,7 +28,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RIDT"): - esm.getHTSized<16>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mUses, mData.mQuality); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadrepa.hpp b/components/esm3/loadrepa.hpp index 63f0a2959d..37fe94c349 100644 --- a/components/esm3/loadrepa.hpp +++ b/components/esm3/loadrepa.hpp @@ -22,14 +22,14 @@ namespace ESM struct Data { float mWeight; - int mValue; + int32_t mValue; - int mUses; + int32_t mUses; float mQuality; }; // Size = 16 Data mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel, mIcon; diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index 51752deca3..c1dc759cdc 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -12,12 +12,12 @@ namespace ESM { void Script::loadSCVR(ESMReader& esm) { - int s = mData.mStringTableSize; + uint32_t s = mData.mStringTableSize; std::vector tmp(s); // not using getHExact, vanilla doesn't seem to mind unused bytes at the end esm.getSubHeader(); - int left = esm.getSubSize(); + uint32_t left = esm.getSubSize(); if (left < s) esm.fail("SCVR string list is smaller than specified"); esm.getExact(tmp.data(), s); @@ -99,7 +99,11 @@ namespace ESM { esm.getSubHeader(); mId = esm.getMaybeFixedRefIdSize(32); - esm.getTSized<20>(mData); + esm.getT(mData.mNumShorts); + esm.getT(mData.mNumLongs); + esm.getT(mData.mNumFloats); + esm.getT(mData.mScriptDataSize); + esm.getT(mData.mStringTableSize); hasHeader = true; break; @@ -114,7 +118,7 @@ namespace ESM esm.getSubHeader(); uint32_t subSize = esm.getSubSize(); - if (subSize != static_cast(mData.mScriptDataSize)) + if (subSize != mData.mScriptDataSize) { std::stringstream ss; ss << "Script data size defined in SCHD subrecord does not match size of SCDT subrecord"; diff --git a/components/esm3/loadscpt.hpp b/components/esm3/loadscpt.hpp index f0fab766cf..61b27f1423 100644 --- a/components/esm3/loadscpt.hpp +++ b/components/esm3/loadscpt.hpp @@ -29,7 +29,7 @@ namespace ESM { /// Data from script-precompling in the editor. /// \warning Do not use them. OpenCS currently does not precompile scripts. - int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; + uint32_t mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; }; struct SCHD { @@ -37,7 +37,7 @@ namespace ESM Script::SCHDstruct mData; }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; SCHDstruct mData; diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index 8661230e8c..fd53726f90 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -55,7 +55,7 @@ namespace ESM hasIndex = true; break; case fourCC("SKDT"): - esm.getHTSized<24>(mData); + esm.getHT(mData.mAttribute, mData.mSpecialization, mData.mUseValue); hasData = true; break; case fourCC("DESC"): diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index dac7745d3f..978e3d5dc4 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -24,7 +24,7 @@ namespace ESM ESM::RefId mFailureSound; ESM::RefId mHitSound; std::string mName; - int mAutoCalcMax; + int32_t mAutoCalcMax; static constexpr int Length = 6; @@ -44,13 +44,13 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Skill"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; SkillId mId; struct SKDTstruct { - int mAttribute; // see defs.hpp - int mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth + int32_t mAttribute; // see defs.hpp + int32_t mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth float mUseValue[4]; // How much skill improves through use. Meaning // of each field depends on what skill this // is. We should document this better later. diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index 501f0602e4..31c03b00fe 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -29,7 +29,8 @@ namespace ESM mName = esm.getHString(); break; case fourCC("WPDT"): - esm.getHTSized<32>(mData); + esm.getHT(mData.mWeight, mData.mValue, mData.mType, mData.mHealth, mData.mSpeed, mData.mReach, + mData.mEnchant, mData.mChop, mData.mSlash, mData.mThrust, mData.mFlags); hasData = true; break; case fourCC("SCRI"): diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index 6eec3a9ef4..e8355d0f55 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -62,19 +62,19 @@ namespace ESM struct WPDTstruct { float mWeight; - int mValue; - short mType; - unsigned short mHealth; + int32_t mValue; + int16_t mType; + uint16_t mHealth; float mSpeed, mReach; - unsigned short mEnchant; // Enchantment points. The real value is mEnchant/10.f + uint16_t mEnchant; // Enchantment points. The real value is mEnchant/10.f unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max - int mFlags; + int32_t mFlags; }; // 32 bytes #pragma pack(pop) WPDTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mEnchant, mScript; std::string mName, mModel, mIcon; From 514723a4e6ba0281cb7bfe5bcb1af2f11cd64d03 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 14 Oct 2023 16:36:45 +0200 Subject: [PATCH 0283/2167] Use fixed size ints for Region and use an array for weather odds --- apps/esmtool/record.cpp | 14 ++-- apps/opencs/model/tools/regioncheck.cpp | 4 +- .../model/world/nestedcoladapterimp.cpp | 65 ++----------------- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwscript/skyextensions.cpp | 4 +- apps/openmw/mwworld/weather.cpp | 38 +++-------- apps/openmw/mwworld/weather.hpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldimp.hpp | 2 +- components/esm3/loadregn.cpp | 18 +++-- components/esm3/loadregn.hpp | 13 ++-- components/esm3/weatherstate.cpp | 6 +- components/esm3/weatherstate.hpp | 10 +-- 13 files changed, 47 insertions(+), 137 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b13668dafc..e293055919 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1201,16 +1201,10 @@ namespace EsmTool std::cout << " Name: " << mData.mName << std::endl; std::cout << " Weather:" << std::endl; - std::cout << " Clear: " << (int)mData.mData.mClear << std::endl; - std::cout << " Cloudy: " << (int)mData.mData.mCloudy << std::endl; - std::cout << " Foggy: " << (int)mData.mData.mFoggy << std::endl; - std::cout << " Overcast: " << (int)mData.mData.mOvercast << std::endl; - std::cout << " Rain: " << (int)mData.mData.mOvercast << std::endl; - std::cout << " Thunder: " << (int)mData.mData.mThunder << std::endl; - std::cout << " Ash: " << (int)mData.mData.mAsh << std::endl; - std::cout << " Blight: " << (int)mData.mData.mBlight << std::endl; - std::cout << " Snow: " << (int)mData.mData.mSnow << std::endl; - std::cout << " Blizzard: " << (int)mData.mData.mBlizzard << std::endl; + std::array weathers + = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; + for (size_t i = 0; i < weathers.size(); ++i) + std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl; std::cout << " Map Color: " << mData.mMapColor << std::endl; if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 4affc1bd44..08e8a17714 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -47,9 +47,7 @@ void CSMTools::RegionCheckStage::perform(int stage, CSMDoc::Messages& messages) /// \todo test that the ID in mSleeplist exists // test that chances add up to 100 - int chances = region.mData.mClear + region.mData.mCloudy + region.mData.mFoggy + region.mData.mOvercast - + region.mData.mRain + region.mData.mThunder + region.mData.mAsh + region.mData.mBlight + region.mData.mSnow - + region.mData.mBlizzard; + auto chances = std::accumulate(region.mData.mProbabilities.begin(), region.mData.mProbabilities.end(), 0u); if (chances != 100) messages.add(id, "Weather chances do not add up to 100", "", CSMDoc::Message::Severity_Error); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 0f3670431d..1f9958e84e 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1076,31 +1076,8 @@ namespace CSMWorld } else if (subColIndex == 1) { - switch (subRowIndex) - { - case 0: - return region.mData.mClear; - case 1: - return region.mData.mCloudy; - case 2: - return region.mData.mFoggy; - case 3: - return region.mData.mOvercast; - case 4: - return region.mData.mRain; - case 5: - return region.mData.mThunder; - case 6: - return region.mData.mAsh; - case 7: - return region.mData.mBlight; - case 8: - return region.mData.mSnow; - case 9: - return region.mData.mBlizzard; - default: - break; - } + if (subRowIndex >= 0 && subRowIndex < region.mData.mProbabilities.size()) + return region.mData.mProbabilities[subRowIndex]; } throw std::runtime_error("index out of range"); @@ -1110,45 +1087,11 @@ namespace CSMWorld Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Region region = record.get(); - unsigned char chance = static_cast(value.toInt()); + uint8_t chance = static_cast(value.toInt()); if (subColIndex == 1) { - switch (subRowIndex) - { - case 0: - region.mData.mClear = chance; - break; - case 1: - region.mData.mCloudy = chance; - break; - case 2: - region.mData.mFoggy = chance; - break; - case 3: - region.mData.mOvercast = chance; - break; - case 4: - region.mData.mRain = chance; - break; - case 5: - region.mData.mThunder = chance; - break; - case 6: - region.mData.mAsh = chance; - break; - case 7: - region.mData.mBlight = chance; - break; - case 8: - region.mData.mSnow = chance; - break; - case 9: - region.mData.mBlizzard = chance; - break; - default: - throw std::runtime_error("index out of range"); - } + region.mData.mProbabilities.at(subRowIndex) = chance; record.setModified(region); } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e5499f6680..14e3b2b3b7 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -232,7 +232,7 @@ namespace MWBase virtual void setMoonColour(bool red) = 0; - virtual void modRegion(const ESM::RefId& regionid, const std::vector& chances) = 0; + virtual void modRegion(const ESM::RefId& regionid, const std::vector& chances) = 0; virtual void changeToInteriorCell( std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index 39944970bf..d2b41fb87a 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -102,11 +102,11 @@ namespace MWScript std::string_view region{ runtime.getStringLiteral(runtime[0].mInteger) }; runtime.pop(); - std::vector chances; + std::vector chances; chances.reserve(10); while (arg0 > 0) { - chances.push_back(std::clamp(runtime[0].mInteger, 0, 127)); + chances.push_back(std::clamp(runtime[0].mInteger, 0, 100)); runtime.pop(); arg0--; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 3338acd832..0da70bdd48 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -285,19 +285,8 @@ namespace MWWorld RegionWeather::RegionWeather(const ESM::Region& region) : mWeather(invalidWeatherID) - , mChances() + , mChances(region.mData.mProbabilities.begin(), region.mData.mProbabilities.end()) { - mChances.reserve(10); - mChances.push_back(region.mData.mClear); - mChances.push_back(region.mData.mCloudy); - mChances.push_back(region.mData.mFoggy); - mChances.push_back(region.mData.mOvercast); - mChances.push_back(region.mData.mRain); - mChances.push_back(region.mData.mThunder); - mChances.push_back(region.mData.mAsh); - mChances.push_back(region.mData.mBlight); - mChances.push_back(region.mData.mSnow); - mChances.push_back(region.mData.mBlizzard); } RegionWeather::RegionWeather(const ESM::RegionWeatherState& state) @@ -313,19 +302,9 @@ namespace MWWorld return state; } - void RegionWeather::setChances(const std::vector& chances) + void RegionWeather::setChances(const std::vector& chances) { - if (mChances.size() < chances.size()) - { - mChances.reserve(chances.size()); - } - - int i = 0; - for (char chance : chances) - { - mChances[i] = chance; - i++; - } + mChances = chances; // Regional weather no longer supports the current type, select a new weather pattern. if ((static_cast(mWeather) >= mChances.size()) || (mChances[mWeather] == 0)) @@ -357,15 +336,14 @@ namespace MWWorld // If chances A and B has values 30 and 70 then by generating 100 numbers 1..100, 30% will be lesser or equal 30 // and 70% will be greater than 30 (in theory). auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int chance = Misc::Rng::rollDice(100, prng) + 1; // 1..100 - int sum = 0; - int i = 0; - for (; static_cast(i) < mChances.size(); ++i) + unsigned int chance = static_cast(Misc::Rng::rollDice(100, prng) + 1); // 1..100 + unsigned int sum = 0; + for (size_t i = 0; i < mChances.size(); ++i) { sum += mChances[i]; if (chance <= sum) { - mWeather = i; + mWeather = static_cast(i); return; } } @@ -649,7 +627,7 @@ namespace MWWorld } } - void WeatherManager::modRegion(const ESM::RefId& regionID, const std::vector& chances) + void WeatherManager::modRegion(const ESM::RefId& regionID, const std::vector& chances) { // Sets the region's probability for various weather patterns. Note that this appears to be saved permanently. // In Morrowind, this seems to have the following behavior when applied to the current region: diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 57c69cde11..65f926c096 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -230,7 +230,7 @@ namespace MWWorld operator ESM::RegionWeatherState() const; - void setChances(const std::vector& chances); + void setChances(const std::vector& chances); void setWeather(int weatherID); @@ -238,7 +238,7 @@ namespace MWWorld private: int mWeather; - std::vector mChances; + std::vector mChances; void chooseNewWeather(); }; @@ -286,7 +286,7 @@ namespace MWWorld * @param ID of the weather setting to shift to */ void changeWeather(const ESM::RefId& regionID, const unsigned int weatherID); - void modRegion(const ESM::RefId& regionID, const std::vector& chances); + void modRegion(const ESM::RefId& regionID, const std::vector& chances); void playerTeleported(const ESM::RefId& playerRegion, bool isExterior); /** diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b9bf454895..5be1e52530 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1879,7 +1879,7 @@ namespace MWWorld mWeatherManager->changeWeather(region, id); } - void World::modRegion(const ESM::RefId& regionid, const std::vector& chances) + void World::modRegion(const ESM::RefId& regionid, const std::vector& chances) { mWeatherManager->modRegion(regionid, chances); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 46043afe46..4b9a0ccb98 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -329,7 +329,7 @@ namespace MWWorld void setMoonColour(bool red) override; - void modRegion(const ESM::RefId& regionid, const std::vector& chances) override; + void modRegion(const ESM::RefId& regionid, const std::vector& chances) override; void changeToInteriorCell(const std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) override; diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 3411d49a7c..5148a446c2 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -27,15 +27,14 @@ namespace ESM { esm.getSubHeader(); // Cold weather not included before 1.3 - if (esm.getSubSize() == sizeof(mData)) + if (esm.getSubSize() == mData.mProbabilities.size()) { - esm.getTSized<10>(mData); + esm.getT(mData.mProbabilities); } - else if (esm.getSubSize() == sizeof(mData) - 2) + else if (esm.getSubSize() == mData.mProbabilities.size() - 2) { - mData.mSnow = 0; - mData.mBlizzard = 0; - esm.getExact(&mData, sizeof(mData) - 2); + mData.mProbabilities.fill(0); + esm.getExact(&mData.mProbabilities, esm.getSubSize()); } else { @@ -85,9 +84,9 @@ namespace ESM esm.writeHNOCString("FNAM", mName); if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + esm.writeHNT("WEAT", mData.mProbabilities, mData.mProbabilities.size() - 2); else - esm.writeHNT("WEAT", mData); + esm.writeHNT("WEAT", mData.mProbabilities); esm.writeHNOCRefId("BNAM", mSleepList); @@ -104,8 +103,7 @@ namespace ESM void Region::blank() { mRecordFlags = 0; - mData.mClear = mData.mCloudy = mData.mFoggy = mData.mOvercast = mData.mRain = mData.mThunder = mData.mAsh - = mData.mBlight = mData.mSnow = mData.mBlizzard = 0; + mData.mProbabilities.fill(0); mMapColor = 0; diff --git a/components/esm3/loadregn.hpp b/components/esm3/loadregn.hpp index 00c5546e2f..757566a360 100644 --- a/components/esm3/loadregn.hpp +++ b/components/esm3/loadregn.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_REGN_H #define OPENMW_ESM_REGN_H +#include #include #include @@ -24,26 +25,24 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Region"; } -#pragma pack(push) -#pragma pack(1) struct WEATstruct { // These are probabilities that add up to 100 - unsigned char mClear, mCloudy, mFoggy, mOvercast, mRain, mThunder, mAsh, mBlight, mSnow, mBlizzard; + // Clear, Cloudy, Foggy, Overcast, Rain, Thunder, Ash, Blight, Snow, Blizzard + std::array mProbabilities; }; // 10 bytes -#pragma pack(pop) // Reference to a sound that is played randomly in this region struct SoundRef { ESM::RefId mSound; - unsigned char mChance; + uint8_t mChance; }; WEATstruct mData; - int mMapColor; // RGBA + int32_t mMapColor; // RGBA - unsigned int mRecordFlags; + uint32_t mRecordFlags; // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. RefId mId, mSleepList; diff --git a/components/esm3/weatherstate.cpp b/components/esm3/weatherstate.cpp index 4c6896d37e..a7a10fe624 100644 --- a/components/esm3/weatherstate.cpp +++ b/components/esm3/weatherstate.cpp @@ -41,7 +41,7 @@ namespace ESM esm.getHNT(region.mWeather, regionWeatherRecord); while (esm.isNextSub(regionChanceRecord)) { - char chance; + uint8_t chance; esm.getHT(chance); region.mChances.push_back(chance); } @@ -66,9 +66,9 @@ namespace ESM { esm.writeHNCRefId(regionNameRecord, it->first); esm.writeHNT(regionWeatherRecord, it->second.mWeather); - for (size_t i = 0; i < it->second.mChances.size(); ++i) + for (const uint8_t& chance : it->second.mChances) { - esm.writeHNT(regionChanceRecord, it->second.mChances[i]); + esm.writeHNT(regionChanceRecord, chance); } } } diff --git a/components/esm3/weatherstate.hpp b/components/esm3/weatherstate.hpp index 3fad90ddb4..625ba0ba6e 100644 --- a/components/esm3/weatherstate.hpp +++ b/components/esm3/weatherstate.hpp @@ -13,8 +13,8 @@ namespace ESM struct RegionWeatherState { - int mWeather; - std::vector mChances; + int32_t mWeather; + std::vector mChances; }; struct WeatherState @@ -24,9 +24,9 @@ namespace ESM bool mFastForward; float mWeatherUpdateTime; float mTransitionFactor; - int mCurrentWeather; - int mNextWeather; - int mQueuedWeather; + int32_t mCurrentWeather; + int32_t mNextWeather; + int32_t mQueuedWeather; std::map mRegions; void load(ESMReader& esm); From fbafa13b3d3579ddf92481dd458ce0cea660529e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 14 Oct 2023 17:01:55 +0200 Subject: [PATCH 0284/2167] Appease the compiler --- apps/opencs/model/world/nestedcoladapterimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 1f9958e84e..b96cf46465 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -1076,7 +1076,7 @@ namespace CSMWorld } else if (subColIndex == 1) { - if (subRowIndex >= 0 && subRowIndex < region.mData.mProbabilities.size()) + if (subRowIndex >= 0 && static_cast(subRowIndex) < region.mData.mProbabilities.size()) return region.mData.mProbabilities[subRowIndex]; } From 40313019efe722635f968dd8e685fe8dfc752825 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 15 Oct 2023 14:14:04 +0300 Subject: [PATCH 0285/2167] BulletNifLoader updates Refactor root node, visual collision type and filename handling Only handle BSXFlags for the root, handle BSXFlags collision flag and absence Properly distinguish collision node and autogenerated flag --- components/nifbullet/bulletnifloader.cpp | 119 ++++++++++++++--------- components/nifbullet/bulletnifloader.hpp | 9 +- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index a72ec36cad..81b68c36dd 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -162,18 +163,17 @@ namespace NifBullet if (node) roots.emplace_back(node); } - const std::string filename = Files::pathToUnicodeString(nif.getFilename()); - mShape->mFileName = filename; + mShape->mFileName = Files::pathToUnicodeString(nif.getFilename()); if (roots.empty()) { - warn("Found no root nodes in NIF file " + filename); + warn("Found no root nodes in NIF file " + mShape->mFileName); return mShape; } - // Try to find a valid bounding box first. If one's found for any root node, use that. for (const Nif::NiAVObject* node : roots) { - if (findBoundingBox(*node, filename)) + // Try to find a valid bounding box first. If one's found for any root node, use that. + if (findBoundingBox(*node)) { const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); @@ -188,29 +188,18 @@ namespace NifBullet return mShape; } } + + HandleNodeArgs args; + // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see // Animation::addAnimSource). assume all nodes in the file will be animated // TODO: investigate whether this should and could be optimized. - const bool isAnimated = pathFileNameStartsWithX(filename); + args.mAnimated = pathFileNameStartsWithX(mShape->mFileName); // If there's no bounding box, we'll have to generate a Bullet collision shape // from the collision data present in every root node. for (const Nif::NiAVObject* node : roots) - { - const Nif::NiNode* colNode = findRootCollisionNode(*node); - bool hasCollisionShape = false; - if (colNode != nullptr) - { - if (colNode->mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) - hasCollisionShape = true; - else - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - } - HandleNodeArgs args; - args.mAutogenerated = args.mIsCollisionNode = !hasCollisionShape; - args.mAnimated = isAnimated; - handleNode(filename, *node, nullptr, args, mShape->mVisualCollisionType); - } + handleRoot(nif, *node, args); if (mCompoundShape) mShape->mCollisionShape = std::move(mCompoundShape); @@ -223,7 +212,7 @@ namespace NifBullet // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? - bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node, const std::string& filename) + bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { unsigned int type = node.mBounds.mType; switch (type) @@ -238,7 +227,7 @@ namespace NifBullet { std::stringstream warning; warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex; - warning << " in file " << filename; + warning << " in file " << mShape->mFileName; warn(warning.str()); } } @@ -249,27 +238,70 @@ namespace NifBullet if (const Nif::NiNode* ninode = dynamic_cast(&node)) { for (const auto& child : ninode->mChildren) - if (!child.empty() && findBoundingBox(child.get(), filename)) + if (!child.empty() && findBoundingBox(child.get())) return true; } return false; } - const Nif::NiNode* BulletNifLoader::findRootCollisionNode(const Nif::NiAVObject& rootNode) const + void BulletNifLoader::handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args) { - if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) + // Gamebryo/Bethbryo meshes + if (nif.getVersion() >= Nif::NIFStream::generateVersion(10, 0, 1, 0)) { - for (const auto& child : ninode->mChildren) + // Handle BSXFlags + const Nif::NiIntegerExtraData* bsxFlags = nullptr; + for (const auto& e : node.getExtraList()) { - if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode) - return static_cast(child.getPtr()); + if (e->recType == Nif::RC_BSXFlags) + { + bsxFlags = static_cast(e.getPtr()); + break; + } + } + + // Collision flag + if (!bsxFlags || !(bsxFlags->mData & 2)) + return; + + // Editor marker flag + if (bsxFlags->mData & 32) + args.mHasMarkers = true; + + // FIXME: hack, using rendered geometry instead of Bethesda Havok data + args.mAutogenerated = true; + } + // Pre-Gamebryo meshes + else + { + // Handle RootCollisionNode + const Nif::NiNode* colNode = nullptr; + if (const Nif::NiNode* ninode = dynamic_cast(&node)) + { + for (const auto& child : ninode->mChildren) + { + if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode) + { + colNode = static_cast(child.getPtr()); + break; + } + } + } + + args.mAutogenerated = colNode == nullptr; + + // FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision + if (colNode && colNode->mChildren.empty()) + { + args.mAutogenerated = true; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; } } - return nullptr; + + handleNode(node, nullptr, args); } - void BulletNifLoader::handleNode(const std::string& fileName, const Nif::NiAVObject& node, - const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType) + void BulletNifLoader::handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) @@ -295,20 +327,23 @@ namespace NifBullet if (node.recType == Nif::RC_RootCollisionNode) { - args.mIsCollisionNode = true; if (args.mAutogenerated) { // Encountered a RootCollisionNode inside an autogenerated mesh. // We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`) // and generate the camera collision shape based on rendered geometry. - if (visualCollisionType == Resource::VisualCollisionType::Camera) + if (mShape->mVisualCollisionType == Resource::VisualCollisionType::Camera) return; // Otherwise we'll want to notify the user. - Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName + Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << mShape->mFileName << ". Treating it as a common NiTriShape."; } + else + { + args.mIsCollisionNode = true; + } } // Don't collide with AvoidNode shapes @@ -330,10 +365,10 @@ namespace NifBullet // uppercase if (sd->mData.length() > 2 && sd->mData[2] == 'C') // Collide only with camera. - visualCollisionType = Resource::VisualCollisionType::Camera; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; else // No collision. - visualCollisionType = Resource::VisualCollisionType::Default; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; } // Don't autogenerate collision if MRK is set. // FIXME: verify if this covers the entire subtree @@ -342,15 +377,9 @@ namespace NifBullet return; } } - else if (e->recType == Nif::RC_BSXFlags) - { - auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->mData & 32) // Editor marker flag - args.mHasMarkers = true; - } } - if (args.mIsCollisionNode) + if (args.mAutogenerated || args.mIsCollisionNode) { // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. @@ -369,7 +398,7 @@ namespace NifBullet continue; assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); - handleNode(fileName, child.get(), ¤tParent, args, visualCollisionType); + handleNode(child.get(), ¤tParent, args); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 819ba34b34..521bbe91dd 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -48,7 +48,7 @@ namespace NifBullet osg::ref_ptr load(Nif::FileView file); private: - bool findBoundingBox(const Nif::NiAVObject& node, const std::string& filename); + bool findBoundingBox(const Nif::NiAVObject& node); struct HandleNodeArgs { @@ -59,11 +59,8 @@ namespace NifBullet bool mAvoid{ false }; }; - void handleNode(const std::string& fileName, const Nif::NiAVObject& node, const Nif::Parent* parent, - HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType); - - const Nif::NiNode* findRootCollisionNode(const Nif::NiAVObject& rootNode) const; - + void handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args); + void handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args); void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args); std::unique_ptr mCompoundShape; From 8db631c6b66c83a43e591ad744a4941aa5bec005 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 15 Oct 2023 15:32:59 +0300 Subject: [PATCH 0286/2167] Update BSXFlags test --- apps/openmw_test_suite/nifloader/testbulletnifloader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 8e8d04d93d..306853f87d 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -1144,7 +1144,7 @@ namespace TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" + mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" mNiIntegerExtraData.recType = Nif::RC_BSXFlags; mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.mParents.push_back(&mNiNode); @@ -1154,6 +1154,7 @@ namespace Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); file.mHash = mHash; + file.mVersion = Nif::NIFStream::generateVersion(10, 0, 1, 0); const auto result = mLoader.load(file); From 7e087707cd4ac4d998117149135a348fccacb3ac Mon Sep 17 00:00:00 2001 From: Kindi Date: Sun, 15 Oct 2023 21:26:47 +0800 Subject: [PATCH 0287/2167] fix potential miscalculation --- apps/openmw/mwlua/itemdata.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 0741b5ebe2..f2bd32703f 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -90,13 +90,9 @@ namespace MWLua ptr.getClass().setRemainingUsageTime(ptr, cond); else if (ptr.getClass().hasItemHealth(ptr)) { - const float lastChargeRemainder = ptr.getCellRef().getChargeIntRemainder(); // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); ptr.getCellRef().setCharge(std::max(0.f, cond)); - // resolve the remaining charge remainder to be subtracted - if (lastChargeRemainder < 0) - ptr.getCellRef().applyChargeRemainderToBeSubtracted(lastChargeRemainder); } else invalidPropErr(prop, ptr); From 2ed584428c27399391214c64d036c138921e3751 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 15 Oct 2023 15:44:24 +0100 Subject: [PATCH 0288/2167] Add missing headers to components/CMakeLists.txt Otherwise, they won't show up as part of the project in IDEs. --- apps/opencs/editor.cpp | 2 +- apps/openmw/main.cpp | 2 +- components/CMakeLists.txt | 48 +++++++++++++++---- .../crashcatcher/windows_crashcatcher.hpp | 2 +- .../crashcatcher/windows_crashmonitor.cpp | 2 +- .../crashcatcher/windows_crashmonitor.hpp | 2 +- components/crashcatcher/windows_crashshm.hpp | 2 +- components/debug/debugging.cpp | 2 +- components/misc/thread.cpp | 2 +- components/{ => misc}/windows.hpp | 0 components/platform/file.win32.cpp | 2 +- 11 files changed, 48 insertions(+), 18 deletions(-) rename components/{ => misc}/windows.hpp (100%) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 416d059735..c21fc12a05 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -21,7 +21,7 @@ #include #ifdef _WIN32 -#include +#include #endif #include diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index a78bd22d42..b0b49f3acd 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -14,7 +14,7 @@ #include #if defined(_WIN32) -#include +#include // makes __argc and __argv available on windows #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dd680dc98d..b0169cc2c2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -77,6 +77,9 @@ add_component_dir (settings categories/video categories/water categories/windows + gyroscopeaxis + hrtfmode + navmeshrendermode parser sanitizer sanitizerimpl @@ -90,8 +93,16 @@ add_component_dir (bsa bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream ) +add_component_dir (bullethelpers + collisionobject + heightfield + operators + processtrianglecallback + transformboundingbox + ) + add_component_dir (vfs - manager archive bsaarchive filesystemarchive registerarchives + manager archive bsaarchive filesystemarchive pathutil registerarchives ) add_component_dir (resource @@ -107,11 +118,12 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt - screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod + screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod clearcolor + cullsafeboundsvisitor keyframe nodecallback textkeymap ) add_component_dir (nif - base controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr texture + base controller data effect extra niffile nifkey nifstream niftypes node parent particle physics property record recordptr texture ) add_component_dir (nifosg @@ -123,11 +135,13 @@ add_component_dir (nifbullet ) add_component_dir (to_utf8 + tables_gen to_utf8 ) add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge esmterrain formid + fourcc stringrefid generatedrefid indexrefid @@ -135,7 +149,7 @@ add_component_dir(esm attr common defs esmcommon records util luascripts format esm3exteriorcellrefid ) -add_component_dir(fx pass technique lexer widgets stateupdater) +add_component_dir(fx pass technique lexer lexer_types parse_constants widgets stateupdater) add_component_dir(std140 ubo) @@ -149,10 +163,11 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp + infoorder timestamp formatversion landrecorddata ) add_component_dir (esmterrain + gridsampling storage ) @@ -255,8 +270,13 @@ add_component_dir (esm4 ) add_component_dir (misc - constants utf8stream resourcehelpers rng messageformatparser weakcache thread - compression osguservalues color tuplemeta tuplehelpers + barrier budgetmeasurement color compression constants convert coordinateconverter endianness float16 frameratelimiter + guarded math mathutil messageformatparser notnullptr objectpool osguservalues progressreporter resourcehelpers rng + strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows + ) + +add_component_dir (misc/strings + algorithm conversion format lower ) add_component_dir (stereo @@ -264,7 +284,7 @@ add_component_dir (stereo ) add_component_dir (debug - debugging debuglog gldebug debugdraw + debugging debuglog gldebug debugdraw writeflags ) IF(NOT WIN32 AND NOT APPLE) @@ -274,6 +294,7 @@ ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager constrainedfilestream memorystream hash configfileparser openfile constrainedfilestreambuf conversion + istreamptr streamwithbuffer ) add_component_dir (compiler @@ -285,7 +306,7 @@ add_component_dir (compiler add_component_dir (interpreter context controlopcodes genericopcodes installopcodes interpreter localopcodes mathopcodes - miscopcodes opcodes runtime types defines + miscopcodes opcodes program runtime types defines ) add_component_dir (translation @@ -414,13 +435,22 @@ add_component_dir(loadinglistener reporter ) +add_component_dir(serialization + binaryreader + binarywriter + osgyaml + sizeaccumulator + ) + add_component_dir(sqlite3 db + request statement transaction ) add_component_dir(esmloader + lessbyid load esmdata ) diff --git a/components/crashcatcher/windows_crashcatcher.hpp b/components/crashcatcher/windows_crashcatcher.hpp index 385567545f..89678c9ada 100644 --- a/components/crashcatcher/windows_crashcatcher.hpp +++ b/components/crashcatcher/windows_crashcatcher.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace Crash { diff --git a/components/crashcatcher/windows_crashmonitor.cpp b/components/crashcatcher/windows_crashmonitor.cpp index dc76ce3456..10a9166384 100644 --- a/components/crashcatcher/windows_crashmonitor.cpp +++ b/components/crashcatcher/windows_crashmonitor.cpp @@ -1,7 +1,7 @@ #include "windows_crashmonitor.hpp" #include -#include +#include #include diff --git a/components/crashcatcher/windows_crashmonitor.hpp b/components/crashcatcher/windows_crashmonitor.hpp index 3e6d3adb98..16e173169e 100644 --- a/components/crashcatcher/windows_crashmonitor.hpp +++ b/components/crashcatcher/windows_crashmonitor.hpp @@ -1,7 +1,7 @@ #ifndef WINDOWS_CRASHMONITOR_HPP #define WINDOWS_CRASHMONITOR_HPP -#include +#include #include #include diff --git a/components/crashcatcher/windows_crashshm.hpp b/components/crashcatcher/windows_crashshm.hpp index b3ba38fb2a..b919757890 100644 --- a/components/crashcatcher/windows_crashshm.hpp +++ b/components/crashcatcher/windows_crashshm.hpp @@ -1,7 +1,7 @@ #ifndef WINDOWS_CRASHSHM_HPP #define WINDOWS_CRASHSHM_HPP -#include +#include namespace Crash { diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 0bec74ce73..3be977657a 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -16,7 +16,7 @@ #ifdef _WIN32 #include #include -#include +#include #include diff --git a/components/misc/thread.cpp b/components/misc/thread.cpp index 7ac17d46d2..cece5ccbea 100644 --- a/components/misc/thread.cpp +++ b/components/misc/thread.cpp @@ -26,7 +26,7 @@ namespace Misc #elif defined(WIN32) -#include +#include namespace Misc { diff --git a/components/windows.hpp b/components/misc/windows.hpp similarity index 100% rename from components/windows.hpp rename to components/misc/windows.hpp diff --git a/components/platform/file.win32.cpp b/components/platform/file.win32.cpp index efcb233fc0..f5ef60478b 100644 --- a/components/platform/file.win32.cpp +++ b/components/platform/file.win32.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include From 7ac402390a80f04afbcaf54d35bb9676403ae2ea Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 13 Oct 2023 13:11:04 +0200 Subject: [PATCH 0289/2167] Use settings values for Water settings --- apps/openmw/mwgui/settingswindow.cpp | 14 ++++++-------- apps/openmw/mwrender/water.cpp | 21 +++++++++------------ components/settings/categories/water.hpp | 5 ++--- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index ac25b5984a..2c405dcbd3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -380,7 +380,7 @@ namespace MWGui const std::string& tmip = Settings::general().mTextureMipmap; mTextureFilteringButton->setCaptionWithReplacing(textureMipmappingToStr(tmip)); - int waterTextureSize = Settings::Manager::getInt("rtt size", "Water"); + int waterTextureSize = Settings::water().mRttSize; if (waterTextureSize >= 512) mWaterTextureSize->setIndexSelected(0); if (waterTextureSize >= 1024) @@ -388,10 +388,10 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - int waterReflectionDetail = std::clamp(Settings::Manager::getInt("reflection detail", "Water"), 0, 5); + const int waterReflectionDetail = Settings::water().mReflectionDetail; mWaterReflectionDetail->setIndexSelected(waterReflectionDetail); - int waterRainRippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + const int waterRainRippleDetail = Settings::water().mRainRippleDetail; mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); updateMaxLightsComboBox(mMaxLights); @@ -531,21 +531,19 @@ namespace MWGui size = 1024; else if (pos == 2) size = 2048; - Settings::Manager::setInt("rtt size", "Water", size); + Settings::water().mRttSize.set(size); apply(); } void SettingsWindow::onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = static_cast(std::min(pos, 5)); - Settings::Manager::setInt("reflection detail", "Water", level); + Settings::water().mReflectionDetail.set(static_cast(pos)); apply(); } void SettingsWindow::onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos) { - unsigned int level = static_cast(std::min(pos, 2)); - Settings::Manager::setInt("rain ripple detail", "Water", level); + Settings::water().mRainRippleDetail.set(static_cast(pos)); apply(); } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index f823378e46..091ab99821 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -247,8 +247,7 @@ namespace MWRender void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); - camera->setSmallFeatureCullingPixelSize( - Settings::Manager::getInt("small feature culling pixel size", "Water")); + camera->setSmallFeatureCullingPixelSize(Settings::water().mSmallFeatureCullingPixelSize); camera->setName("RefractionCamera"); camera->addCullCallback(new InheritViewPointCallback); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); @@ -265,7 +264,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - if (Settings::Manager::getFloat("refraction scale", "Water") != 1) // TODO: to be removed with issue #5709 + if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); } @@ -285,8 +284,7 @@ namespace MWRender void setWaterLevel(float waterLevel) { - const float refractionScale - = std::clamp(Settings::Manager::getFloat("refraction scale", "Water"), 0.f, 1.f); + const float refractionScale = Settings::water().mRefractionScale; mViewMatrix = osg::Matrix::scale(1, 1, refractionScale) * osg::Matrix::translate(0, 0, (1.0 - refractionScale) * waterLevel); @@ -328,8 +326,7 @@ namespace MWRender void setDefaults(osg::Camera* camera) override { camera->setReferenceFrame(osg::Camera::RELATIVE_RF); - camera->setSmallFeatureCullingPixelSize( - Settings::Manager::getInt("small feature culling pixel size", "Water")); + camera->setSmallFeatureCullingPixelSize(Settings::water().mSmallFeatureCullingPixelSize); camera->setName("ReflectionCamera"); camera->addCullCallback(new InheritViewPointCallback); @@ -384,7 +381,7 @@ namespace MWRender private: unsigned int calcNodeMask() { - int reflectionDetail = Settings::Manager::getInt("reflection detail", "Water"); + int reflectionDetail = Settings::water().mReflectionDetail; reflectionDetail = std::clamp(reflectionDetail, mInterior ? 2 : 0, 5); unsigned int extraMask = 0; if (reflectionDetail >= 1) @@ -525,9 +522,9 @@ namespace MWRender mWaterGeom->setStateSet(nullptr); mWaterGeom->setUpdateCallback(nullptr); - if (Settings::Manager::getBool("shader", "Water")) + if (Settings::water().mShader) { - unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); + const unsigned int rttSize = Settings::water().mRttSize; mReflection = new Reflection(rttSize, mInterior); mReflection->setWaterLevel(mTop); @@ -536,7 +533,7 @@ namespace MWRender mReflection->addCullCallback(mCullCallback); mParent->addChild(mReflection); - if (Settings::Manager::getBool("refraction", "Water")) + if (Settings::water().mRefraction) { mRefraction = new Refraction(rttSize); mRefraction->setWaterLevel(mTop); @@ -693,7 +690,7 @@ namespace MWRender // use a define map to conditionally compile the shader std::map defineMap; defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); - const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); + const int rippleDetail = Settings::water().mRainRippleDetail; defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index 02b5f0c5dd..b88d38b023 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -21,9 +21,8 @@ namespace Settings SettingValue mShader{ mIndex, "Water", "shader" }; SettingValue mRttSize{ mIndex, "Water", "rtt size", makeMaxSanitizerInt(1) }; SettingValue mRefraction{ mIndex, "Water", "refraction" }; - SettingValue mReflectionDetail{ mIndex, "Water", "reflection detail", - makeEnumSanitizerInt({ 0, 1, 2, 3, 4, 5 }) }; - SettingValue mRainRippleDetail{ mIndex, "Water", "rain ripple detail", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mReflectionDetail{ mIndex, "Water", "reflection detail", makeClampSanitizerInt(0, 5) }; + SettingValue mRainRippleDetail{ mIndex, "Water", "rain ripple detail", makeClampSanitizerInt(0, 2) }; SettingValue mSmallFeatureCullingPixelSize{ mIndex, "Water", "small feature culling pixel size", makeMaxStrictSanitizerFloat(0) }; SettingValue mRefractionScale{ mIndex, "Water", "refraction scale", makeClampSanitizerFloat(0, 1) }; From ed2e96980240871163f9f696b067ccd1ba645e6d Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 17 Oct 2023 20:10:19 +0000 Subject: [PATCH 0290/2167] Fix build --- apps/openmw/mwgui/waitdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 8f2c720d3e..568f05abc3 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "../mwbase/environment.hpp" From 43a931d3c43ab147d32bd4b453c656915edd10fe Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 12:43:30 +0200 Subject: [PATCH 0291/2167] [#7528] Fix MyGUI 3.4.3 issues --- apps/openmw/mwgui/cursor.cpp | 4 ++-- apps/openmw/mwgui/resourceskin.cpp | 8 ++++---- apps/openmw/mwgui/settingswindow.cpp | 6 +++--- apps/openmw/mwgui/tooltips.cpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- components/fontloader/fontloader.cpp | 4 ++-- components/myguiplatform/myguidatamanager.cpp | 2 +- components/myguiplatform/myguidatamanager.hpp | 2 +- components/myguiplatform/myguiloglistener.hpp | 2 +- components/myguiplatform/scalinglayer.cpp | 4 ++-- components/widgets/box.cpp | 13 +++++++------ components/widgets/box.hpp | 12 ++++++------ components/widgets/imagebutton.cpp | 3 ++- components/widgets/imagebutton.hpp | 2 +- components/widgets/list.cpp | 2 +- components/widgets/list.hpp | 2 +- 16 files changed, 36 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index 7c95e2fd11..1b6431f0fb 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -23,8 +23,8 @@ namespace MWGui MyGUI::xml::ElementEnumerator info = _node->getElementEnumerator(); while (info.next("Property")) { - const std::string& key = info->findAttribute("key"); - const std::string& value = info->findAttribute("value"); + auto key = info->findAttribute("key"); + auto value = info->findAttribute("value"); if (key == "Point") mPoint = MyGUI::IntPoint::parse(value); diff --git a/apps/openmw/mwgui/resourceskin.cpp b/apps/openmw/mwgui/resourceskin.cpp index ea081dd17a..e527b15796 100644 --- a/apps/openmw/mwgui/resourceskin.cpp +++ b/apps/openmw/mwgui/resourceskin.cpp @@ -9,15 +9,15 @@ namespace MWGui void resizeSkin(MyGUI::xml::ElementPtr _node) { _node->setAttribute("type", "ResourceSkin"); - const std::string size = _node->findAttribute("size"); + auto size = _node->findAttribute("size"); if (!size.empty()) return; - const std::string textureName = _node->findAttribute("texture"); + auto textureName = _node->findAttribute("texture"); if (textureName.empty()) return; - MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(textureName); + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(std::string(textureName)); if (!texture) return; @@ -30,7 +30,7 @@ namespace MWGui if (basis->getName() != "BasisSkin") continue; - const std::string basisSkinType = basis->findAttribute("type"); + auto basisSkinType = basis->findAttribute("type"); if (Misc::StringUtils::ciEqual(basisSkinType, "SimpleText")) continue; bool isTileRect = Misc::StringUtils::ciEqual(basisSkinType, "TileRect"); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 2c405dcbd3..3c15826eae 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -242,13 +242,13 @@ namespace MWGui void SettingsWindow::updateSliderLabel(MyGUI::ScrollBar* scroller, const std::string& value) { - std::string labelWidgetName = scroller->getUserString("SettingLabelWidget"); + auto labelWidgetName = scroller->getUserString("SettingLabelWidget"); if (!labelWidgetName.empty()) { MyGUI::TextBox* textBox; getWidget(textBox, labelWidgetName); - std::string labelCaption = scroller->getUserString("SettingLabelCaption"); - labelCaption = Misc::StringUtils::format(labelCaption, value); + auto labelCaption = scroller->getUserString("SettingLabelCaption"); + labelCaption = Misc::StringUtils::format(std::string(labelCaption), value); textBox->setCaptionWithReplacing(labelCaption); } } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 6beee8d07b..323579317a 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -244,7 +244,7 @@ namespace MWGui = store->get().find(MWMechanics::getSpellSchool(spell, player))->mSchool; info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); } - const std::string& cost = focus->getUserString("SpellCost"); + auto cost = focus->getUserString("SpellCost"); if (!cost.empty() && cost != "0") info.text += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 9bcfb3e158..1da19cf64e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2186,7 +2186,7 @@ namespace MWGui ResourceImageSetPointerFix* imgSetPointer = resource->castType(false); if (!imgSetPointer) continue; - std::string tex_name = imgSetPointer->getImageSet()->getIndexInfo(0, 0).texture; + auto tex_name = imgSetPointer->getImageSet()->getIndexInfo(0, 0).texture; osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(tex_name); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 9434578220..da20413333 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -58,7 +58,7 @@ namespace MyGUI::xml::ElementPtr sizeProperty = getProperty(layersIterator.current(), "Size"); if (sizeProperty != nullptr) { - std::string sizeValue = sizeProperty->findAttribute("value"); + auto sizeValue = sizeProperty->findAttribute("value"); if (!sizeValue.empty()) return MyGUI::IntSize::parse(sizeValue); } @@ -614,7 +614,7 @@ namespace Gui MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); while (resourceNode.next("Resource")) { - std::string type = resourceNode->findAttribute("type"); + auto type = resourceNode->findAttribute("type"); if (Misc::StringUtils::ciEqual(type, "ResourceLayout")) { diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index 8e34e0c2fe..76ad3ac66a 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -58,7 +58,7 @@ namespace osgMyGUI throw std::runtime_error("DataManager::getDataListNames is not implemented - VFS is used"); } - const std::string& DataManager::getDataPath(const std::string& name) const + std::string DataManager::getDataPath(const std::string& name) const { static std::string result; result.clear(); diff --git a/components/myguiplatform/myguidatamanager.hpp b/components/myguiplatform/myguidatamanager.hpp index 40dc6c3705..5b392177b7 100644 --- a/components/myguiplatform/myguidatamanager.hpp +++ b/components/myguiplatform/myguidatamanager.hpp @@ -45,7 +45,7 @@ namespace osgMyGUI @param _name Resource name. @return Return full path to specified data. */ - const std::string& getDataPath(const std::string& _name) const override; + std::string getDataPath(const std::string& _name) const override; private: std::filesystem::path mResourcePath; diff --git a/components/myguiplatform/myguiloglistener.hpp b/components/myguiplatform/myguiloglistener.hpp index 1af63a397f..3aeda9747b 100644 --- a/components/myguiplatform/myguiloglistener.hpp +++ b/components/myguiplatform/myguiloglistener.hpp @@ -31,7 +31,7 @@ namespace osgMyGUI void flush() override; void log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, - const std::string& _message, const char* _file, int _line) override; + const std::string& _message, const char* _file, int _line); private: std::ofstream mStream; diff --git a/components/myguiplatform/scalinglayer.cpp b/components/myguiplatform/scalinglayer.cpp index fbd2f594c7..c04134bfad 100644 --- a/components/myguiplatform/scalinglayer.cpp +++ b/components/myguiplatform/scalinglayer.cpp @@ -123,8 +123,8 @@ namespace osgMyGUI { if (info->getName() == "Property") { - const std::string& key = info->findAttribute("key"); - const std::string& value = info->findAttribute("value"); + auto key = info->findAttribute("key"); + auto value = info->findAttribute("value"); if (key == "Size") { diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 642721b807..2d81152b3a 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -40,7 +40,7 @@ namespace Gui notifySizeChange(this); } - void AutoSizedTextBox::setPropertyOverride(const std::string& _key, const std::string& _value) + void AutoSizedTextBox::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (_key == "ExpandDirection") { @@ -103,7 +103,7 @@ namespace Gui setEditStatic(true); } - void AutoSizedEditBox::setPropertyOverride(const std::string& _key, const std::string& _value) + void AutoSizedEditBox::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (_key == "ExpandDirection") { @@ -136,7 +136,7 @@ namespace Gui notifySizeChange(this); } - void AutoSizedButton::setPropertyOverride(const std::string& _key, const std::string& _value) + void AutoSizedButton::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (_key == "ExpandDirection") { @@ -147,6 +147,7 @@ namespace Gui Gui::Button::setPropertyOverride(_key, _value); } } + Box::Box() : mSpacing(4) , mPadding(0) @@ -159,7 +160,7 @@ namespace Gui align(); } - bool Box::_setPropertyImpl(const std::string& _key, const std::string& _value) + bool Box::_setPropertyImpl(std::string_view _key, const std::string_view _value) { if (_key == "Spacing") mSpacing = MyGUI::utility::parseValue(_value); @@ -260,7 +261,7 @@ namespace Gui } } - void HBox::setPropertyOverride(const std::string& _key, const std::string& _value) + void HBox::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (!Box::_setPropertyImpl(_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); @@ -415,7 +416,7 @@ namespace Gui } } - void VBox::setPropertyOverride(const std::string& _key, const std::string& _value) + void VBox::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (!Box::_setPropertyImpl(_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index b5fcc8e9b6..699009c21f 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -53,7 +53,7 @@ namespace Gui void setCaption(const MyGUI::UString& _value) override; protected: - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; std::string mFontSize; }; @@ -68,7 +68,7 @@ namespace Gui void initialiseOverride() override; protected: - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, const std::string_view _value) override; int getWidth(); std::string mFontSize; bool mShrink = false; @@ -85,7 +85,7 @@ namespace Gui void setCaption(const MyGUI::UString& _value) override; protected: - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, const std::string_view _value) override; std::string mFontSize; }; @@ -105,7 +105,7 @@ namespace Gui protected: virtual void align() = 0; - virtual bool _setPropertyImpl(const std::string& _key, const std::string& _value); + virtual bool _setPropertyImpl(std::string_view _key, std::string_view _value); int mSpacing; // how much space to put between elements @@ -137,7 +137,7 @@ namespace Gui void align() override; MyGUI::IntSize getRequestedSize() override; - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; void onWidgetCreated(MyGUI::Widget* _widget) override; }; @@ -156,7 +156,7 @@ namespace Gui void align() override; MyGUI::IntSize getRequestedSize() override; - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value); void onWidgetCreated(MyGUI::Widget* _widget) override; }; diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 0eb193aea3..79df471733 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -34,7 +34,7 @@ namespace Gui updateImage(); } - void ImageButton::setPropertyOverride(const std::string& _key, const std::string& _value) + void ImageButton::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (_key == "ImageHighlighted") mImageHighlighted = _value; @@ -56,6 +56,7 @@ namespace Gui else ImageBox::setPropertyOverride(_key, _value); } + void ImageButton::onMouseSetFocus(Widget* _old) { mMouseFocus = true; diff --git a/components/widgets/imagebutton.hpp b/components/widgets/imagebutton.hpp index 923f168ba4..90b5e6339e 100644 --- a/components/widgets/imagebutton.hpp +++ b/components/widgets/imagebutton.hpp @@ -32,7 +32,7 @@ namespace Gui static bool sDefaultNeedKeyFocus; protected: - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; void onMouseLostFocus(MyGUI::Widget* _new) override; void onMouseSetFocus(MyGUI::Widget* _old) override; void onMouseButtonPressed(int _left, int _top, MyGUI::MouseButton _id) override; diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index b4784299a5..7ccd3e6313 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -107,7 +107,7 @@ namespace Gui mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); } - void MWList::setPropertyOverride(const std::string& _key, const std::string& _value) + void MWList::setPropertyOverride(std::string_view _key, const std::string_view _value) { if (_key == "ListItemSkin") mListItemSkin = _value; diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index 88368e0794..3d5e320cf7 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -49,7 +49,7 @@ namespace Gui void scrollToTop(); - void setPropertyOverride(const std::string& _key, const std::string& _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; protected: void initialiseOverride() override; From 8885519953a59e59e087b756784ff0af3d53d34a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 13:36:24 +0200 Subject: [PATCH 0292/2167] bump macos deps for arm64 --- CI/before_install.osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 5b305d66b6..41e6707c45 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -34,7 +34,7 @@ qmake --version if [[ "${MACOS_AMD64}" ]]; then curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip else - curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20230920_arm64.zip -o ~/openmw-deps.zip + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20231017_arm64.zip -o ~/openmw-deps.zip fi unzip -o ~/openmw-deps.zip -d /tmp > /dev/null From 2a8976d4e9072d7ba3ac99fe6a27b82c3cb898eb Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 13:39:53 +0200 Subject: [PATCH 0293/2167] bump windows mygui to 3.4.3 --- CI/before_script.msvc.sh | 20 ++++++++++---------- CI/org.openmw.OpenMW.devel.yaml | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index abedc7f965..338ca1ee9e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -597,14 +597,14 @@ if [ -z $SKIP_DOWNLOAD ]; then "ffmpeg-${FFMPEG_VER}-dev-win${BITS}.zip" # MyGUI - download "MyGUI 3.4.2" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" \ - "MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" + download "MyGUI 3.4.3" \ + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" \ + "MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" if [ -n "$PDBS" ]; then download "MyGUI symbols" \ - "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" \ - "MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" + "https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" \ + "MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" fi # OpenAL @@ -768,20 +768,20 @@ printf "FFmpeg ${FFMPEG_VER}... " } cd $DEPS echo -printf "MyGUI 3.4.2... " +printf "MyGUI 3.4.3... " { cd $DEPS_INSTALL if [ -d MyGUI ] && \ grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ grep "MYGUI_VERSION_MINOR 4" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \ - grep "MYGUI_VERSION_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null + grep "MYGUI_VERSION_PATCH 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf MyGUI - eval 7z x -y "${DEPS}/MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" $STRIP - [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" $STRIP - mv "MyGUI-3.4.2-msvc${MYGUI_MSVC_YEAR}-win${BITS}" MyGUI + eval 7z x -y "${DEPS}/MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}.7z" $STRIP + [ -n "$PDBS" ] && eval 7z x -y "${DEPS}/MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}-sym.7z" $STRIP + mv "MyGUI-3.4.3-msvc${MYGUI_MSVC_YEAR}-win${BITS}" MyGUI fi export MYGUI_HOME="$(real_pwd)/MyGUI" for CONFIGURATION in ${CONFIGURATIONS[@]}; do diff --git a/CI/org.openmw.OpenMW.devel.yaml b/CI/org.openmw.OpenMW.devel.yaml index 9e8fb9eeb9..9f4d921cf1 100644 --- a/CI/org.openmw.OpenMW.devel.yaml +++ b/CI/org.openmw.OpenMW.devel.yaml @@ -125,8 +125,8 @@ modules: - "-DMYGUI_BUILD_PLUGINS=0" sources: - type: archive - url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.2.tar.gz - sha256: 1cc45fb96c9438e3476778449af0378443d84794a458978a29c75306e45dd45a + url: https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.3.tar.gz + sha256: 33c91b531993047e77cace36d6fea73634b8c17bd0ed193d4cd12ac7c6328abd - name: libunshield buildsystem: cmake-ninja From 1df448f59b8b2b4bbe7eca77adc72b082bd80960 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 13:44:41 +0200 Subject: [PATCH 0294/2167] remove hack from 3.4.2 --- components/myguiplatform/myguirendermanager.hpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index 9a53b06c06..a940b31050 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -74,11 +74,7 @@ namespace osgMyGUI return static_cast(MyGUI::RenderManager::getInstancePtr()); } - bool checkTexture(MyGUI::ITexture* _texture) -#if MYGUI_DEBUG_MODE == 1 /* needed workaround for MyGUI 3.4.2 */ - override -#endif - ; + bool checkTexture(MyGUI::ITexture* _texture) override; /** @see RenderManager::getViewSize */ const MyGUI::IntSize& getViewSize() const override From e97b07b6a495aca1ee46492fdf42e1a227a41c30 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 14:02:37 +0200 Subject: [PATCH 0295/2167] additional cleanup --- CMakeLists.txt | 2 +- apps/openmw/mwgui/formatting.cpp | 2 +- apps/openmw/mwgui/formatting.hpp | 2 +- apps/openmw/mwgui/spellview.cpp | 4 +-- apps/openmw/mwgui/tooltips.cpp | 6 ++-- components/lua_ui/widget.cpp | 4 --- components/lua_ui/window.cpp | 4 --- components/widgets/box.cpp | 6 ++-- components/widgets/box.hpp | 24 +++----------- components/widgets/fontwrapper.hpp | 40 ------------------------ components/widgets/numericeditbox.hpp | 3 +- components/widgets/sharedstatebutton.hpp | 4 +-- components/widgets/widgets.cpp | 3 -- extern/CMakeLists.txt | 4 +-- 14 files changed, 19 insertions(+), 89 deletions(-) delete mode 100644 components/widgets/fontwrapper.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ba0cd672cd..cad6290274 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,7 +454,7 @@ set(Boost_NO_WARN_NEW_VERSIONS ON) # ignore warnings about new releases of boos find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS}) if(OPENMW_USE_SYSTEM_MYGUI) - find_package(MyGUI 3.4.2 REQUIRED) + find_package(MyGUI 3.4.3 REQUIRED) endif() find_package(SDL2 2.0.9 REQUIRED) find_package(OpenAL REQUIRED) diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 7f62bbf49c..c4f0f804a6 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -475,7 +475,7 @@ namespace MWGui::Formatting : GraphicElement(parent, pag, blockStyle) , mTextStyle(textStyle) { - Gui::EditBox* box = parent->createWidget("NormalText", + MyGUI::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); box->setEditStatic(true); diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index 9a215b200b..f093a36dfe 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -161,7 +161,7 @@ namespace MWGui private: int currentFontHeight() const; TextStyle mTextStyle; - Gui::EditBox* mEditBox; + MyGUI::EditBox* mEditBox; }; class ImageElement : public GraphicElement diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 678f6ffe1f..97de7dbc06 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -238,7 +238,7 @@ namespace MWGui mLines.emplace_back(separator, (MyGUI::Widget*)nullptr, NoSpellIndex); } - MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget->setCaptionWithReplacing(label); groupWidget->setTextAlign(MyGUI::Align::Left); @@ -246,7 +246,7 @@ namespace MWGui if (!label2.empty()) { - MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget2->setCaptionWithReplacing(label2); groupWidget2->setTextAlign(MyGUI::Align::Right); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 323579317a..929d78f3b1 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -443,7 +443,7 @@ namespace MWGui const std::string realImage = Misc::ResourceHelpers::correctIconPath(image, MWBase::Environment::get().getResourceSystem()->getVFS()); - Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget( + MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget( "NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setEditStatic(true); captionWidget->setNeedKeyFocus(false); @@ -452,7 +452,7 @@ namespace MWGui int captionHeight = std::max(!caption.empty() ? captionSize.height : 0, imageSize); - Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", + MyGUI::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(0, captionHeight + imageCaptionVPadding, 300, 300 - captionHeight - imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); textWidget->setEditStatic(true); @@ -474,7 +474,7 @@ namespace MWGui MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", MyGUI::IntCoord(padding.left, totalSize.height + padding.top, 8, 8), MyGUI::Align::Default); icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); - Gui::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", + MyGUI::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(padding.left + 8 + 4, totalSize.height + padding.top, 300 - padding.left - 8 - 4, 300 - totalSize.height), MyGUI::Align::Default); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index ff9f4d90a2..eacaec37bd 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -73,11 +73,7 @@ namespace LuaUi w->eventMouseButtonPressed.clear(); w->eventMouseButtonReleased.clear(); w->eventMouseMove.clear(); -#if MYGUI_VERSION <= MYGUI_DEFINE_VERSION(3, 4, 2) - w->eventMouseDrag.m_event.clear(); -#else w->eventMouseDrag.clear(); -#endif w->eventMouseSetFocus.clear(); w->eventMouseLostFocus.clear(); diff --git a/components/lua_ui/window.cpp b/components/lua_ui/window.cpp index 3ee7336a78..5da2ae4ca4 100644 --- a/components/lua_ui/window.cpp +++ b/components/lua_ui/window.cpp @@ -16,11 +16,7 @@ namespace LuaUi for (auto& [w, _] : mActionWidgets) { w->eventMouseButtonPressed.clear(); -#if MYGUI_VERSION <= MYGUI_DEFINE_VERSION(3, 4, 2) - w->eventMouseDrag.m_event.clear(); -#else w->eventMouseDrag.clear(); -#endif } mActionWidgets.clear(); diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 2d81152b3a..ce32939315 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -48,7 +48,7 @@ namespace Gui } else { - Gui::TextBox::setPropertyOverride(_key, _value); + TextBox::setPropertyOverride(_key, _value); } } @@ -115,7 +115,7 @@ namespace Gui } else { - Gui::EditBox::setPropertyOverride(_key, _value); + EditBox::setPropertyOverride(_key, _value); } } @@ -144,7 +144,7 @@ namespace Gui } else { - Gui::Button::setPropertyOverride(_key, _value); + Button::setPropertyOverride(_key, _value); } } diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index 699009c21f..0029bc938a 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -7,25 +7,9 @@ #include #include -#include "fontwrapper.hpp" namespace Gui { - class Button : public FontWrapper - { - MYGUI_RTTI_DERIVED(Button) - }; - - class TextBox : public FontWrapper - { - MYGUI_RTTI_DERIVED(TextBox) - }; - - class EditBox : public FontWrapper - { - MYGUI_RTTI_DERIVED(EditBox) - }; - class AutoSizedWidget { public: @@ -44,7 +28,7 @@ namespace Gui MyGUI::Align mExpandDirection; }; - class AutoSizedTextBox : public AutoSizedWidget, public TextBox + class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox { MYGUI_RTTI_DERIVED(AutoSizedTextBox) @@ -57,7 +41,7 @@ namespace Gui std::string mFontSize; }; - class AutoSizedEditBox : public AutoSizedWidget, public EditBox + class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox { MYGUI_RTTI_DERIVED(AutoSizedEditBox) @@ -76,7 +60,7 @@ namespace Gui int mMaxWidth = 0; }; - class AutoSizedButton : public AutoSizedWidget, public Button + class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button { MYGUI_RTTI_DERIVED(AutoSizedButton) @@ -156,7 +140,7 @@ namespace Gui void align() override; MyGUI::IntSize getRequestedSize() override; - void setPropertyOverride(std::string_view _key, std::string_view _value); + void setPropertyOverride(std::string_view _key, std::string_view _value) override; void onWidgetCreated(MyGUI::Widget* _widget) override; }; diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp deleted file mode 100644 index f2c30376b9..0000000000 --- a/components/widgets/fontwrapper.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef OPENMW_WIDGETS_WRAPPER_H -#define OPENMW_WIDGETS_WRAPPER_H - -#include - -#include "components/settings/values.hpp" - -#include - -namespace Gui -{ - template - class FontWrapper : public T - { -#if MYGUI_VERSION <= MYGUI_DEFINE_VERSION(3, 4, 2) - public: - void setFontName(const std::string& name) override - { - T::setFontName(name); - T::setPropertyOverride("FontHeight", std::to_string(Settings::gui().mFontSize)); - } - - protected: - void setPropertyOverride(const std::string& _key, const std::string& _value) override - { - T::setPropertyOverride(_key, _value); - - // https://github.com/MyGUI/mygui/issues/113 - // There is a bug in MyGUI: when it initializes the FontName property, it reset the font height. - // We should restore it. - if (_key == "FontName") - { - T::setPropertyOverride("FontHeight", std::to_string(Settings::gui().mFontSize)); - } - } -#endif - }; -} - -#endif diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index ee8ef39a59..8e5de984c8 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -3,7 +3,6 @@ #include -#include "fontwrapper.hpp" namespace Gui { @@ -11,7 +10,7 @@ namespace Gui /** * @brief A variant of the EditBox that only allows integer inputs */ - class NumericEditBox final : public FontWrapper + class NumericEditBox final : public MyGUI::EditBox { MYGUI_RTTI_DERIVED(NumericEditBox) diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 688d949f6e..99f597360c 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -3,8 +3,6 @@ #include -#include "fontwrapper.hpp" - namespace Gui { @@ -14,7 +12,7 @@ namespace Gui /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a /// ButtonGroup. - class SharedStateButton final : public FontWrapper + class SharedStateButton final : public MyGUI::Button { MYGUI_RTTI_DERIVED(SharedStateButton) diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp index d27d7e5fc9..097f84c62f 100644 --- a/components/widgets/widgets.cpp +++ b/components/widgets/widgets.cpp @@ -18,12 +18,9 @@ namespace Gui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 9a536069e6..cf7a3bcf70 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -77,8 +77,8 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) include(FetchContent) FetchContent_Declare(mygui - URL https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.2.zip - URL_HASH SHA512=d15de716102237ca55b952c2ab52f84b91766332a0357a50b17c20cf2f168666ddaab52d088d7bb8f713ad0fc27e19d74e6ae2673f310a8f60a3b5754f0a0ba7 + URL https://github.com/MyGUI/mygui/archive/refs/tags/MyGUI3.4.3.zip + URL_HASH SHA512=c804ef665e786076582367f171082cd2181fdbd214300ddcca4a4245c5a0f45e62e72778ee2d96ec46b393e22477dd729f9bb3006e6eecbf536674e34a057721 SOURCE_DIR fetched/mygui ) FetchContent_MakeAvailableExcludeFromAll(mygui) From 589a27d09cb51cfaa75292c18e93e4a060b2f3e5 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 14:09:32 +0200 Subject: [PATCH 0296/2167] additional clean up for clang --- components/myguiplatform/myguirendermanager.hpp | 17 +++-------------- components/widgets/box.hpp | 1 - components/widgets/numericeditbox.hpp | 1 - 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/components/myguiplatform/myguirendermanager.hpp b/components/myguiplatform/myguirendermanager.hpp index a940b31050..7f1582203a 100644 --- a/components/myguiplatform/myguirendermanager.hpp +++ b/components/myguiplatform/myguirendermanager.hpp @@ -66,8 +66,6 @@ namespace osgMyGUI void enableShaders(Shader::ShaderManager& shaderManager); - void setScalingFactor(float factor); - static RenderManager& getInstance() { return *getInstancePtr(); } static RenderManager* getInstancePtr() { @@ -77,16 +75,10 @@ namespace osgMyGUI bool checkTexture(MyGUI::ITexture* _texture) override; /** @see RenderManager::getViewSize */ - const MyGUI::IntSize& getViewSize() const override - { - return mViewSize; - } + const MyGUI::IntSize& getViewSize() const override { return mViewSize; } /** @see RenderManager::getVertexFormat */ - MyGUI::VertexColourType getVertexFormat() const override - { - return mVertexFormat; - } + MyGUI::VertexColourType getVertexFormat() const override { return mVertexFormat; } /** @see RenderManager::isFormatSupported */ bool isFormatSupported(MyGUI::PixelFormat format, MyGUI::TextureUsage usage) override; @@ -119,10 +111,7 @@ namespace osgMyGUI void setInjectState(osg::StateSet* stateSet); /** @see IRenderTarget::getInfo */ - const MyGUI::RenderTargetInfo& getInfo() const override - { - return mInfo; - } + const MyGUI::RenderTargetInfo& getInfo() const override { return mInfo; } void setViewSize(int width, int height) override; diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index 0029bc938a..ac8e632767 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -7,7 +7,6 @@ #include #include - namespace Gui { class AutoSizedWidget diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index 8e5de984c8..39605b67d9 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -3,7 +3,6 @@ #include - namespace Gui { From 97009f1e23b948ebfaf59dfd53f579810234cfff Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 7 Aug 2023 17:40:38 +0200 Subject: [PATCH 0297/2167] Merge Assumeru/mystringvui --- apps/openmw/mwgui/resourceskin.cpp | 5 ++--- apps/openmw/mwgui/settingswindow.cpp | 6 +++--- apps/openmw/mwgui/windowmanagerimp.cpp | 6 +++--- apps/openmw/mwgui/windowmanagerimp.hpp | 6 +++--- components/fontloader/fontloader.cpp | 2 +- components/fontloader/fontloader.hpp | 2 +- components/myguiplatform/myguidatamanager.cpp | 11 +++-------- components/myguiplatform/myguiloglistener.cpp | 6 +++--- components/myguiplatform/myguiloglistener.hpp | 4 ++-- components/widgets/box.cpp | 12 ++++++------ components/widgets/box.hpp | 4 ++-- components/widgets/imagebutton.cpp | 2 +- components/widgets/list.cpp | 2 +- 13 files changed, 31 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwgui/resourceskin.cpp b/apps/openmw/mwgui/resourceskin.cpp index e527b15796..3d9f09952e 100644 --- a/apps/openmw/mwgui/resourceskin.cpp +++ b/apps/openmw/mwgui/resourceskin.cpp @@ -9,15 +9,14 @@ namespace MWGui void resizeSkin(MyGUI::xml::ElementPtr _node) { _node->setAttribute("type", "ResourceSkin"); - auto size = _node->findAttribute("size"); - if (!size.empty()) + if (!_node->findAttribute("size").empty()) return; auto textureName = _node->findAttribute("texture"); if (textureName.empty()) return; - MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(std::string(textureName)); + MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(std::string{ textureName }); if (!texture) return; diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 3c15826eae..1a41f9bb55 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -168,7 +168,7 @@ namespace MWGui std::string_view type = getSettingType(current); if (type == checkButtonType) { - const std::string initialValue + std::string_view initialValue = Settings::get(getSettingCategory(current), getSettingName(current)) ? "#{Interface:On}" : "#{Interface:Off}"; current->castType()->setCaptionWithReplacing(initialValue); @@ -247,8 +247,8 @@ namespace MWGui { MyGUI::TextBox* textBox; getWidget(textBox, labelWidgetName); - auto labelCaption = scroller->getUserString("SettingLabelCaption"); - labelCaption = Misc::StringUtils::format(std::string(labelCaption), value); + std::string labelCaption{ scroller->getUserString("SettingLabelCaption") }; + labelCaption = Misc::StringUtils::format(labelCaption, value); textBox->setCaptionWithReplacing(labelCaption); } } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1da19cf64e..6f27a2cdbf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1227,7 +1227,7 @@ namespace MWGui MWBase::Environment::get().getStateManager()->requestQuit(); } - void WindowManager::onCursorChange(const std::string& name) + void WindowManager::onCursorChange(std::string_view name) { mCursorManager->cursorChanged(name); } @@ -2071,13 +2071,13 @@ namespace MWGui mWerewolfFader->notifyAlphaChanged(set ? 1.0f : 0.0f); } - void WindowManager::onClipboardChanged(const std::string& _type, const std::string& _data) + void WindowManager::onClipboardChanged(std::string_view _type, std::string_view _data) { if (_type == "Text") SDL_SetClipboardText(MyGUI::TextIterator::getOnlyText(MyGUI::UString(_data)).asUTF8().c_str()); } - void WindowManager::onClipboardRequested(const std::string& _type, std::string& _data) + void WindowManager::onClipboardRequested(std::string_view _type, std::string& _data) { if (_type != "Text") return; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index fae3bb1ec9..444a6f6942 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -560,7 +560,7 @@ namespace MWGui */ void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); - void onCursorChange(const std::string& name); + void onCursorChange(std::string_view name); void onKeyFocusChanged(MyGUI::Widget* widget); // Key pressed while playing a video @@ -568,8 +568,8 @@ namespace MWGui void sizeVideo(int screenWidth, int screenHeight); - void onClipboardChanged(const std::string& _type, const std::string& _data); - void onClipboardRequested(const std::string& _type, std::string& _data); + void onClipboardChanged(std::string_view _type, std::string_view _data); + void onClipboardRequested(std::string_view _type, std::string& _data); void createTextures(); void createCursors(); diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index da20413333..64aa32310b 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -608,7 +608,7 @@ namespace Gui MyGUI::ResourceManager::getInstance().addResource(bookFont); } - void FontLoader::overrideLineHeight(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version) + void FontLoader::overrideLineHeight(MyGUI::xml::ElementPtr _node, std::string_view _file, MyGUI::Version _version) { // We should adjust line height for MyGUI widgets depending on font size MyGUI::xml::ElementEnumerator resourceNode = _node->getElementEnumerator(); diff --git a/components/fontloader/fontloader.hpp b/components/fontloader/fontloader.hpp index 8c1312ff97..7e9220d58d 100644 --- a/components/fontloader/fontloader.hpp +++ b/components/fontloader/fontloader.hpp @@ -27,7 +27,7 @@ namespace Gui public: FontLoader(ToUTF8::FromType encoding, const VFS::Manager* vfs, float scalingFactor); - void overrideLineHeight(MyGUI::xml::ElementPtr _node, const std::string& _file, MyGUI::Version _version); + void overrideLineHeight(MyGUI::xml::ElementPtr _node, std::string_view _file, MyGUI::Version _version); static std::string_view getFontForFace(std::string_view face); diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index 76ad3ac66a..49dba3634b 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -60,20 +60,15 @@ namespace osgMyGUI std::string DataManager::getDataPath(const std::string& name) const { - static std::string result; - result.clear(); - if (name.empty()) { - result = Files::pathToUnicodeString(mResourcePath); - return result; + return Files::pathToUnicodeString(mResourcePath); } if (!isDataExist(name)) - return result; + return {}; - result = Files::pathToUnicodeString(mResourcePath / name); - return result; + return Files::pathToUnicodeString(mResourcePath / name); } } diff --git a/components/myguiplatform/myguiloglistener.cpp b/components/myguiplatform/myguiloglistener.cpp index 42dc9c0415..89de63c46f 100644 --- a/components/myguiplatform/myguiloglistener.cpp +++ b/components/myguiplatform/myguiloglistener.cpp @@ -25,12 +25,12 @@ namespace osgMyGUI mStream.flush(); } - void CustomLogListener::log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, - const std::string& _message, const char* _file, int _line) + void CustomLogListener::log(std::string_view _section, MyGUI::LogLevel _level, const struct tm* _time, + std::string_view _message, std::string_view _file, int _line) { if (mStream.is_open()) { - const char* separator = " | "; + std::string_view separator = " | "; mStream << std::setw(2) << std::setfill('0') << _time->tm_hour << ":" << std::setw(2) << std::setfill('0') << _time->tm_min << ":" << std::setw(2) << std::setfill('0') << _time->tm_sec << separator << _section << separator << _level.print() << separator << _message << separator << _file diff --git a/components/myguiplatform/myguiloglistener.hpp b/components/myguiplatform/myguiloglistener.hpp index 3aeda9747b..15cea0effd 100644 --- a/components/myguiplatform/myguiloglistener.hpp +++ b/components/myguiplatform/myguiloglistener.hpp @@ -30,8 +30,8 @@ namespace osgMyGUI void close() override; void flush() override; - void log(const std::string& _section, MyGUI::LogLevel _level, const struct tm* _time, - const std::string& _message, const char* _file, int _line); + void log(std::string_view _section, MyGUI::LogLevel _level, const struct tm* _time, std::string_view _message, + std::string_view _file, int _line) override; private: std::ofstream mStream; diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index ce32939315..89f92b7bf1 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -40,7 +40,7 @@ namespace Gui notifySizeChange(this); } - void AutoSizedTextBox::setPropertyOverride(std::string_view _key, const std::string_view _value) + void AutoSizedTextBox::setPropertyOverride(std::string_view _key, std::string_view _value) { if (_key == "ExpandDirection") { @@ -103,7 +103,7 @@ namespace Gui setEditStatic(true); } - void AutoSizedEditBox::setPropertyOverride(std::string_view _key, const std::string_view _value) + void AutoSizedEditBox::setPropertyOverride(std::string_view _key, std::string_view _value) { if (_key == "ExpandDirection") { @@ -136,7 +136,7 @@ namespace Gui notifySizeChange(this); } - void AutoSizedButton::setPropertyOverride(std::string_view _key, const std::string_view _value) + void AutoSizedButton::setPropertyOverride(std::string_view _key, std::string_view _value) { if (_key == "ExpandDirection") { @@ -160,7 +160,7 @@ namespace Gui align(); } - bool Box::_setPropertyImpl(std::string_view _key, const std::string_view _value) + bool Box::_setPropertyImpl(std::string_view _key, std::string_view _value) { if (_key == "Spacing") mSpacing = MyGUI::utility::parseValue(_value); @@ -261,7 +261,7 @@ namespace Gui } } - void HBox::setPropertyOverride(std::string_view _key, const std::string_view _value) + void HBox::setPropertyOverride(std::string_view _key, std::string_view _value) { if (!Box::_setPropertyImpl(_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); @@ -416,7 +416,7 @@ namespace Gui } } - void VBox::setPropertyOverride(std::string_view _key, const std::string_view _value) + void VBox::setPropertyOverride(std::string_view _key, std::string_view _value) { if (!Box::_setPropertyImpl(_key, _value)) MyGUI::Widget::setPropertyOverride(_key, _value); diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index ac8e632767..b7543f1f05 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -51,7 +51,7 @@ namespace Gui void initialiseOverride() override; protected: - void setPropertyOverride(std::string_view _key, const std::string_view _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; int getWidth(); std::string mFontSize; bool mShrink = false; @@ -68,7 +68,7 @@ namespace Gui void setCaption(const MyGUI::UString& _value) override; protected: - void setPropertyOverride(std::string_view _key, const std::string_view _value) override; + void setPropertyOverride(std::string_view _key, std::string_view _value) override; std::string mFontSize; }; diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index 79df471733..01fbd66cd5 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -34,7 +34,7 @@ namespace Gui updateImage(); } - void ImageButton::setPropertyOverride(std::string_view _key, const std::string_view _value) + void ImageButton::setPropertyOverride(std::string_view _key, std::string_view _value) { if (_key == "ImageHighlighted") mImageHighlighted = _value; diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 7ccd3e6313..896057443c 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -107,7 +107,7 @@ namespace Gui mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); } - void MWList::setPropertyOverride(std::string_view _key, const std::string_view _value) + void MWList::setPropertyOverride(std::string_view _key, std::string_view _value) { if (_key == "ListItemSkin") mListItemSkin = _value; From 6e530ebd656fb4d2407231853ccb866324093ce1 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 7 Aug 2023 21:47:45 +0200 Subject: [PATCH 0298/2167] make sure we are using our latest libs from daily or staging if doing a transition --- CI/install_debian_deps.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 4417a04317..bd767bb173 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -122,4 +122,6 @@ mkdir -pv "$APT_CACHE_DIR" apt-get update -yqq apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends software-properties-common gnupg >/dev/null add-apt-repository -y ppa:openmw/openmw +add-apt-repository -y ppa:openmw/openmw-daily +add-apt-repository -y ppa:openmw/staging apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null From edc3994384cb2a6dd926e7c30fd27d8dd5e81b85 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 17 Oct 2023 17:42:51 +0200 Subject: [PATCH 0299/2167] turn on MYGUI_DONT_USE_OBSOLETE --- extern/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index cf7a3bcf70..6f55e4f1c6 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -68,6 +68,7 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI) set(MYGUI_BUILD_DEMOS OFF CACHE BOOL "") set(MYGUI_BUILD_PLUGINS OFF CACHE BOOL "") set(MYGUI_BUILD_TOOLS OFF CACHE BOOL "") + set(MYGUI_DONT_USE_OBSOLETE ON CACHE BOOL "") if(MYGUI_STATIC) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) From 5fc46b166ac69c2c575df3490b01afe35fc896ed Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 18 Oct 2023 09:04:57 -0700 Subject: [PATCH 0300/2167] track debug window size and dimensions between sessions --- apps/openmw/mwgui/settings.cpp | 19 +++++++++++++++++++ apps/openmw/mwgui/settings.hpp | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 1 + components/settings/categories/windows.hpp | 9 +++++++++ files/settings-default.cfg | 11 +++++++++++ 5 files changed, 41 insertions(+) diff --git a/apps/openmw/mwgui/settings.cpp b/apps/openmw/mwgui/settings.cpp index fb1068ca41..5005876d55 100644 --- a/apps/openmw/mwgui/settings.cpp +++ b/apps/openmw/mwgui/settings.cpp @@ -99,6 +99,25 @@ namespace MWGui }; } + WindowSettingValues makeDebugWindowSettingValues() + { + return WindowSettingValues{ + .mRegular = WindowRectSettingValues { + .mX = Settings::windows().mDebugX, + .mY = Settings::windows().mDebugY, + .mW = Settings::windows().mDebugW, + .mH = Settings::windows().mDebugH, + }, + .mMaximized = WindowRectSettingValues { + .mX = Settings::windows().mDebugMaximizedX, + .mY = Settings::windows().mDebugMaximizedY, + .mW = Settings::windows().mDebugMaximizedW, + .mH = Settings::windows().mDebugMaximizedH, + }, + .mIsMaximized = Settings::windows().mDebugMaximized, + }; + } + WindowSettingValues makeDialogueWindowSettingValues() { return WindowSettingValues{ diff --git a/apps/openmw/mwgui/settings.hpp b/apps/openmw/mwgui/settings.hpp index 8d1cda37dd..b51ac29ce5 100644 --- a/apps/openmw/mwgui/settings.hpp +++ b/apps/openmw/mwgui/settings.hpp @@ -25,6 +25,7 @@ namespace MWGui WindowSettingValues makeCompanionWindowSettingValues(); WindowSettingValues makeConsoleWindowSettingValues(); WindowSettingValues makeContainerWindowSettingValues(); + WindowSettingValues makeDebugWindowSettingValues(); WindowSettingValues makeDialogueWindowSettingValues(); WindowSettingValues makeInventoryWindowSettingValues(); WindowSettingValues makeInventoryBarterWindowSettingValues(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 9bcfb3e158..94bac9e10e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -498,6 +498,7 @@ namespace MWGui auto debugWindow = std::make_unique(); mDebugWindow = debugWindow.get(); mWindows.push_back(std::move(debugWindow)); + trackWindow(mDebugWindow, makeDebugWindowSettingValues()); auto postProcessorHud = std::make_unique(); mPostProcessorHud = postProcessorHud.get(); diff --git a/components/settings/categories/windows.hpp b/components/settings/categories/windows.hpp index 77927c1204..1fc249cc97 100644 --- a/components/settings/categories/windows.hpp +++ b/components/settings/categories/windows.hpp @@ -161,6 +161,15 @@ namespace Settings SettingValue mPostprocessorMaximizedW{ mIndex, "Windows", "postprocessor maximized w" }; SettingValue mPostprocessorMaximizedH{ mIndex, "Windows", "postprocessor maximized h" }; SettingValue mPostprocessorMaximized{ mIndex, "Windows", "postprocessor maximized" }; + SettingValue mDebugX{ mIndex, "Windows", "debug x" }; + SettingValue mDebugY{ mIndex, "Windows", "debug y" }; + SettingValue mDebugW{ mIndex, "Windows", "debug w" }; + SettingValue mDebugH{ mIndex, "Windows", "debug h" }; + SettingValue mDebugMaximizedX{ mIndex, "Windows", "debug maximized x" }; + SettingValue mDebugMaximizedY{ mIndex, "Windows", "debug maximized y" }; + SettingValue mDebugMaximizedW{ mIndex, "Windows", "debug maximized w" }; + SettingValue mDebugMaximizedH{ mIndex, "Windows", "debug maximized h" }; + SettingValue mDebugMaximized{ mIndex, "Windows", "debug maximized" }; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5c473bea81..d8a101a7a5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -847,6 +847,17 @@ postprocessor maximized w = 0.97 postprocessor maximized h = 0.875 postprocessor maximized = false +# Debug window for viewing logs and profiling +debug x = 0.70 +debug y = 0.00 +debug w = 0.30 +debug h = 1.00 +debug maximized x = 0.015 +debug maximized y = 0.02 +debug maximized w = 0.97 +debug maximized h = 0.875 +debug maximized = false + [Navigator] # Enable navigator (true, false). When enabled background threads are started to build navmesh for world geometry. From 9405e5cb3c23a683d7448854730d41088e1ed05e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 19 Oct 2023 02:13:32 +0300 Subject: [PATCH 0301/2167] BulletNifLoader: Replicate node bounds handling more closely --- .../nifloader/testbulletnifloader.cpp | 41 +++++++++---------- components/nifbullet/bulletnifloader.cpp | 38 ++++++----------- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 306853f87d..72959f8591 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -411,10 +411,9 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F( - TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_compound_shape_and_box_inside) { - mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -436,9 +435,9 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) + TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_compound_shape_with_box_inside) { - mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -462,10 +461,9 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) + TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_but_should_use_bounding_box) { - mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -493,10 +491,10 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) + TEST_F( + TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_one_is_bounding_box_use_bounding_box) { - mNode.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); @@ -530,14 +528,14 @@ namespace } TEST_F(TestBulletNifLoader, - for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) + for_root_and_two_children_where_both_with_bounds_but_second_is_bounding_box_use_bounding_box) { mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); mNode.mBounds.mBox.mCenter = osg::Vec3f(-1, -2, -3); mNode.mParents.push_back(&mNiNode); - mNode2.mFlags |= Nif::NiAVObject::Flag_BBoxCollision; + mNode2.mName = "Bounding Box"; mNode2.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode2.mBounds.mBox.mExtents = osg::Vec3f(4, 5, 6); mNode2.mBounds.mBox.mCenter = osg::Vec3f(-4, -5, -6); @@ -565,8 +563,7 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_should_return_shape_with_null_collision_shape) { mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); @@ -579,8 +576,6 @@ namespace const auto result = mLoader.load(file); Resource::BulletShape expected; - expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); EXPECT_EQ(*result, expected); } @@ -605,8 +600,7 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_root_node_with_bounds_should_return_static_shape_with_bounds_but_with_null_collision_shape) + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_static_shape) { mNiTriShape.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNiTriShape.mBounds.mBox.mExtents = osg::Vec3f(1, 2, 3); @@ -618,9 +612,14 @@ namespace const auto result = mLoader.load(file); + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + Resource::BulletShape expected; - expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); - expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); + expected.mCollisionShape.reset(compound.release()); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 81b68c36dd..95744a8cfe 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -214,33 +214,25 @@ namespace NifBullet // Return: use bounding box for collision? bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { - unsigned int type = node.mBounds.mType; - switch (type) + if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) { - case Nif::BoundingVolume::Type::BASE_BV: - break; - case Nif::BoundingVolume::Type::BOX_BV: + if (node.mBounds.mType == Nif::BoundingVolume::Type::BOX_BV) + { mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents; mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter; - break; - default: - { - std::stringstream warning; - warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex; - warning << " in file " << mShape->mFileName; - warn(warning.str()); } + else + { + warn("Invalid Bounding Box node bounds in file " + mShape->mFileName); + } + return true; } - if (type != Nif::BoundingVolume::Type::BASE_BV && node.hasBBoxCollision()) - return true; - - if (const Nif::NiNode* ninode = dynamic_cast(&node)) - { + if (auto ninode = dynamic_cast(&node)) for (const auto& child : ninode->mChildren) if (!child.empty() && findBoundingBox(child.get())) return true; - } + return false; } @@ -379,14 +371,8 @@ namespace NifBullet } } - if (args.mAutogenerated || args.mIsCollisionNode) - { - // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! - // It must be ignored completely. - // (occurs in tr_ex_imp_wall_arch_04.nif) - if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && isTypeNiGeometry(node.recType)) - handleNiTriShape(static_cast(node), parent, args); - } + if ((args.mAutogenerated || args.mIsCollisionNode) && isTypeNiGeometry(node.recType)) + handleNiTriShape(static_cast(node), parent, args); // For NiNodes, loop through children if (const Nif::NiNode* ninode = dynamic_cast(&node)) From 64080b0c25b3b67dec81a596345e3867ce27cabe Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 19 Oct 2023 12:03:06 +0200 Subject: [PATCH 0302/2167] bump key for windows --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27c57fff62..e7dabca6fb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -590,7 +590,7 @@ macOS13_Xcode14_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v7 + key: ninja-v8 paths: - ccache - deps @@ -710,7 +710,7 @@ macOS13_Xcode14_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v7 + key: msbuild-v8 paths: - ccache - deps From 981e4821710c1e7ffc19d0a4eaaf0bb7ddb5b0a4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 20 Oct 2023 16:29:45 +0300 Subject: [PATCH 0303/2167] Read BSSubIndexTriShape It's currently handled exactly like BSTriShape, which works ok enough for our purposes --- components/nif/niffile.cpp | 1 + components/nif/node.cpp | 75 ++++++++++++++++++++++++++++++--- components/nif/node.hpp | 57 +++++++++++++++++++++++++ components/nif/record.hpp | 1 + components/nifosg/nifloader.cpp | 1 + 5 files changed, 130 insertions(+), 5 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 89559a9149..e92d4e74bc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -280,6 +280,7 @@ namespace Nif { "BSLODTriShape", &construct }, { "BSMeshLODTriShape", &construct }, { "BSSegmentedTriShape", &construct }, + { "BSSubIndexTriShape", &construct }, // PARTICLES diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 8f5443b28e..28cd2cdbfe 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -218,17 +218,20 @@ namespace Nif } } + void BSSegmentedTriShape::SegmentData::read(NIFStream* nif) + { + nif->read(mFlags); + nif->read(mStartIndex); + nif->read(mNumTriangles); + } + void BSSegmentedTriShape::read(NIFStream* nif) { NiTriShape::read(nif); mSegments.resize(nif->get()); for (SegmentData& segment : mSegments) - { - nif->read(segment.mFlags); - nif->read(segment.mStartIndex); - nif->read(segment.mNumTriangles); - } + segment.read(nif); } void BSLODTriShape::read(NIFStream* nif) @@ -447,6 +450,68 @@ namespace Nif nif->readArray(mLOD); } + void BSSubIndexTriShape::SubSegment::read(NIFStream* nif) + { + nif->read(mStartIndex); + nif->read(mNumPrimitives); + nif->read(mArrayIndex); + nif->skip(4); // Unknown + } + + void BSSubIndexTriShape::Segment::read(NIFStream* nif) + { + nif->read(mStartIndex); + nif->read(mNumPrimitives); + nif->read(mParentArrayIndex); + mSubSegments.resize(nif->get()); + for (SubSegment& subsegment : mSubSegments) + subsegment.read(nif); + } + + void BSSubIndexTriShape::SubSegmentDataRecord::read(NIFStream* nif) + { + nif->read(mUserSlotID); + nif->read(mMaterial); + nif->readVector(mExtraData, nif->get()); + } + + void BSSubIndexTriShape::SubSegmentData::read(NIFStream* nif) + { + uint32_t numArrayIndices; + nif->read(numArrayIndices); + mDataRecords.resize(nif->get()); + nif->readVector(mArrayIndices, numArrayIndices); + for (SubSegmentDataRecord& dataRecord : mDataRecords) + dataRecord.read(nif); + mSSFFile = nif->getSizedString(nif->get()); + } + + void BSSubIndexTriShape::Segmentation::read(NIFStream* nif) + { + nif->read(mNumPrimitives); + mSegments.resize(nif->get()); + nif->read(mNumTotalSegments); + for (Segment& segment : mSegments) + segment.read(nif); + + if (mSegments.size() < mNumTotalSegments) + mSubSegmentData.read(nif); + } + + void BSSubIndexTriShape::read(NIFStream* nif) + { + BSTriShape::read(nif); + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_SSE) + { + mSegments.resize(nif->get()); + for (BSSegmentedTriShape::SegmentData& segment : mSegments) + segment.read(nif); + } + else if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4 && mDataSize > 0) + mSegmentation.read(nif); + } + void BSVertexDesc::read(NIFStream* nif) { uint64_t data; diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e873fc27dc..0aaad40ed4 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -164,6 +164,8 @@ namespace Nif uint8_t mFlags; uint32_t mStartIndex; uint32_t mNumTriangles; + + void read(NIFStream* nif); }; std::vector mSegments; @@ -396,6 +398,61 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSSubIndexTriShape : BSTriShape + { + struct SubSegment + { + uint32_t mStartIndex; + uint32_t mNumPrimitives; + uint32_t mArrayIndex; + + void read(NIFStream* nif); + }; + + struct Segment + { + uint32_t mStartIndex; + uint32_t mNumPrimitives; + uint32_t mParentArrayIndex; + std::vector mSubSegments; + + void read(NIFStream* nif); + }; + + struct SubSegmentDataRecord + { + uint32_t mUserSlotID; + uint32_t mMaterial; + std::vector mExtraData; + + void read(NIFStream* nif); + }; + + struct SubSegmentData + { + std::vector mArrayIndices; + std::vector mDataRecords; + std::string mSSFFile; + + void read(NIFStream* nif); + }; + + struct Segmentation + { + uint32_t mNumPrimitives; + uint32_t mNumTotalSegments; + std::vector mSegments; + SubSegmentData mSubSegmentData; + + void read(NIFStream* nif); + }; + + std::vector mSegments; // SSE + Segmentation mSegmentation; // FO4 + + void read(NIFStream* nif) override; + }; + struct BSValueNode : NiNode { enum Flags diff --git a/components/nif/record.hpp b/components/nif/record.hpp index a06dcfe2a9..07c3842113 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -132,6 +132,7 @@ namespace Nif RC_BSSkinBoneData, RC_BSSkinInstance, RC_BSSkyShaderProperty, + RC_BSSubIndexTriShape, RC_BSTriShape, RC_BSWArray, RC_BSWaterShaderProperty, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index fdf51486ed..3b8c47e071 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -109,6 +109,7 @@ namespace case Nif::RC_BSTriShape: case Nif::RC_BSDynamicTriShape: case Nif::RC_BSMeshLODTriShape: + case Nif::RC_BSSubIndexTriShape: return true; } return false; From 23eb6289b3bb643005f422fc9eee0446e4e80089 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 20 Oct 2023 17:09:42 +0300 Subject: [PATCH 0304/2167] Read BSLightingShaderPropertyUShortController --- components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index e92d4e74bc..202c551a05 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -156,6 +156,8 @@ namespace Nif &construct }, { "BSLightingShaderPropertyFloatController", &construct }, + { "BSLightingShaderPropertyUShortController", + &construct }, { "bhkBlendController", &construct }, { "NiBSBoneLODController", &construct }, { "NiLightRadiusController", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 07c3842113..0679bd862a 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -98,6 +98,7 @@ namespace Nif RC_BSLightingShaderProperty, RC_BSLightingShaderPropertyColorController, RC_BSLightingShaderPropertyFloatController, + RC_BSLightingShaderPropertyUShortController, RC_BSLODTriShape, RC_BSMaterialEmittanceMultController, RC_BSMeshLODTriShape, From 8ce9f7b9cf8b63498aa3cb95bdb6dd454e4b8340 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 20 Oct 2023 17:39:58 +0300 Subject: [PATCH 0305/2167] Read BSPackedCombinedSharedGeomDataExtra --- components/nif/extra.cpp | 46 +++++++++++++++++++++++++++++++++++++ components/nif/extra.hpp | 47 ++++++++++++++++++++++++++++++++++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 1 + 4 files changed, 96 insertions(+) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 1fc24e5f89..2d222f5a54 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -162,4 +162,50 @@ namespace Nif nif->getSizedStrings(mPointNames, nif->get()); } + void BSPackedGeomDataCombined::read(NIFStream* nif) + { + nif->read(mGrayscaleToPaletteScale); + nif->read(mTransform); + nif->read(mBoundingSphere); + } + + void BSPackedGeomObject::read(NIFStream* nif) + { + nif->read(mFileHash); + nif->read(mDataOffset); + } + + void BSPackedSharedGeomData::read(NIFStream* nif) + { + nif->read(mNumVertices); + nif->read(mLODLevels); + nif->read(mLOD0TriCount); + nif->read(mLOD0TriOffset); + nif->read(mLOD1TriCount); + nif->read(mLOD1TriOffset); + nif->read(mLOD2TriCount); + nif->read(mLOD2TriOffset); + mCombined.resize(nif->get()); + for (BSPackedGeomDataCombined& data : mCombined) + data.read(nif); + mVertexDesc.read(nif); + } + + void BSPackedCombinedSharedGeomDataExtra::read(NIFStream* nif) + { + NiExtraData::read(nif); + + mVertexDesc.read(nif); + nif->read(mNumVertices); + nif->read(mNumTriangles); + nif->read(mFlags1); + nif->read(mFlags2); + mObjects.resize(nif->get()); + for (BSPackedGeomObject& object : mObjects) + object.read(nif); + mObjectData.resize(mObjects.size()); + for (BSPackedSharedGeomData& objectData : mObjectData) + objectData.read(nif); + } + } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 0808e91b16..1efa4ae7bb 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_NIF_EXTRA_HPP #include "base.hpp" +#include "node.hpp" namespace Nif { @@ -199,5 +200,51 @@ namespace Nif }; }; + struct BSPackedGeomDataCombined + { + float mGrayscaleToPaletteScale; + NiTransform mTransform; + osg::BoundingSpheref mBoundingSphere; + + void read(NIFStream* nif); + }; + + struct BSPackedGeomObject + { + uint32_t mFileHash; + uint32_t mDataOffset; + + void read(NIFStream* nif); + }; + + struct BSPackedSharedGeomData + { + uint32_t mNumVertices; + uint32_t mLODLevels; + uint32_t mLOD0TriCount; + uint32_t mLOD0TriOffset; + uint32_t mLOD1TriCount; + uint32_t mLOD1TriOffset; + uint32_t mLOD2TriCount; + uint32_t mLOD2TriOffset; + std::vector mCombined; + BSVertexDesc mVertexDesc; + + void read(NIFStream* nif); + }; + + struct BSPackedCombinedSharedGeomDataExtra : NiExtraData + { + BSVertexDesc mVertexDesc; + uint32_t mNumVertices; + uint32_t mNumTriangles; + uint32_t mFlags1; + uint32_t mFlags2; + std::vector mObjects; + std::vector mObjectData; + + void read(NIFStream* nif) override; + }; + } #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 202c551a05..35a9eee787 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -256,6 +256,8 @@ namespace Nif { "BSDistantObjectLargeRefExtraData", &construct }, { "BSEyeCenterExtraData", &construct }, + { "BSPackedCombinedSharedGeomDataExtra", + &construct }, { "BSPositionData", &construct }, { "BSWArray", &construct }, { "BSXFlags", &construct }, diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0679bd862a..79958b361d 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -108,6 +108,7 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSNiAlphaPropertyTestRefController, RC_BSPackedAdditionalGeometryData, + RC_BSPackedCombinedSharedGeomDataExtra, RC_BSParentVelocityModifier, RC_BSPositionData, RC_BSProceduralLightningController, From d0ffe6e2f95734dd7a5045220b970862b4c5a723 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 21 Oct 2023 07:10:16 +0300 Subject: [PATCH 0306/2167] Don't apply Charm to creatures (bug #7630) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bbc5b5b1b..2c01901ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves + Bug #7630: Charm can be cast on creatures Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index d9137dcc3d..2e28aaa1f3 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -559,6 +559,10 @@ namespace MWMechanics modifyAiSetting( target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude, invalid); break; + case ESM::MagicEffect::Charm: + if (!target.getClass().isNpc()) + invalid = true; + break; case ESM::MagicEffect::Sound: if (target == getPlayer()) { From 4d6667920493fb3711ac275dbd281efd75166215 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 21 Oct 2023 13:04:02 +0200 Subject: [PATCH 0307/2167] Queue Lua handler `uiModeChanged` --- apps/openmw/mwlua/luamanagerimp.cpp | 16 +++++++++++++++- apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/playerscripts.hpp | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index fc686dcbb3..63a2838250 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -246,6 +246,13 @@ namespace MWLua reloadAllScriptsImpl(); mReloadAllScriptsRequested = false; } + + if (mDelayedUiModeChangedArg) + { + if (playerScripts) + playerScripts->uiModeChanged(*mDelayedUiModeChangedArg, true); + mDelayedUiModeChangedArg = std::nullopt; + } } void LuaManager::applyDelayedActions() @@ -275,6 +282,7 @@ namespace MWLua mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; mNewGameStarted = false; + mDelayedUiModeChangedArg = std::nullopt; if (!mPlayer.isEmpty()) { mPlayer.getCellRef().unsetRefNum(); @@ -325,9 +333,15 @@ namespace MWLua { if (mPlayer.isEmpty()) return; + ObjectId argId = arg.isEmpty() ? ObjectId() : getId(arg); + if (mApplyingDelayedActions) + { + mDelayedUiModeChangedArg = argId; + return; + } PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) - playerScripts->uiModeChanged(arg, mApplyingDelayedActions); + playerScripts->uiModeChanged(argId, false); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 9f4c0096b5..a725761dbd 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -204,6 +204,7 @@ namespace MWLua std::optional mTeleportPlayerAction; std::vector mUIMessages; std::vector> mInGameConsoleMessages; + std::optional mDelayedUiModeChangedArg; LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index e8cad4968c..2d3aa9bc78 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -66,9 +66,9 @@ namespace MWLua } // `arg` is either forwarded from MWGui::pushGuiMode or empty - void uiModeChanged(const MWWorld::Ptr& arg, bool byLuaAction) + void uiModeChanged(ObjectId arg, bool byLuaAction) { - if (arg.isEmpty()) + if (arg.isZeroOrUnset()) callEngineHandlers(mUiModeChanged, byLuaAction); else callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg)); From 0748e2094d4af3afa12f2f8ec756449bc3e22228 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 22 Oct 2023 10:12:31 +0300 Subject: [PATCH 0308/2167] Read NiParticleBomb --- components/nif/niffile.cpp | 1 + components/nif/particle.cpp | 22 ++++++++++++++++++---- components/nif/particle.hpp | 36 ++++++++++++++++++++++++++++++++---- components/nif/record.hpp | 1 + 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 35a9eee787..81a223e095 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -308,6 +308,7 @@ namespace Nif // Modifiers, 4.0.0.2 { "NiGravity", &construct }, + { "NiParticleBomb", &construct }, { "NiParticleColorModifier", &construct }, { "NiParticleGrowFade", &construct }, { "NiParticleRotation", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index d74473f468..0581c5a1d1 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -51,6 +51,20 @@ namespace Nif nif->read(mDirection); } + void NiParticleBomb::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mRange); + nif->read(mDuration); + nif->read(mStrength); + nif->read(mStartTime); + mDecayType = static_cast(nif->get()); + mSymmetryType = static_cast(nif->get()); + nif->read(mPosition); + nif->read(mDirection); + } + void NiParticleCollider::read(NIFStream* nif) { NiParticleModifier::read(nif); @@ -294,10 +308,10 @@ namespace Nif mBombObject.read(nif); nif->read(mBombAxis); - nif->read(mDecay); - nif->read(mDeltaV); - nif->read(mDecayType); - nif->read(mSymmetryType); + nif->read(mRange); + nif->read(mStrength); + mDecayType = static_cast(nif->get()); + mSymmetryType = static_cast(nif->get()); } void NiPSysBombModifier::post(Reader& nif) diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 45b6296891..1bdbd3a94a 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -40,6 +40,20 @@ namespace Nif Point = 1, // Fixed origin }; + enum class DecayType : uint32_t + { + None = 0, // f(Distance) = 1.0 + Linear = 1, // f(Distance) = (Range - Distance) / Range + Exponential = 2, // f(Distance) = exp(-Distance / Range) + }; + + enum class SymmetryType : uint32_t + { + Spherical = 0, + Cylindrical = 1, // Perpendicular to direction axis + Planar = 2, // Parallel to direction axis + }; + struct NiGravity : NiParticleModifier { float mDecay{ 0.f }; @@ -51,6 +65,20 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiParticleBomb : NiParticleModifier + { + float mRange; + float mDuration; + float mStrength; + float mStartTime; + DecayType mDecayType; + SymmetryType mSymmetryType; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + + void read(NIFStream* nif); + }; + struct NiParticleCollider : NiParticleModifier { float mBounceFactor; @@ -210,10 +238,10 @@ namespace Nif { NiAVObjectPtr mBombObject; osg::Vec3f mBombAxis; - float mDecay; - float mDeltaV; - uint32_t mDecayType; - uint32_t mSymmetryType; + float mRange; + float mStrength; + DecayType mDecayType; + SymmetryType mSymmetryType; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 79958b361d..d2a30b1317 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -206,6 +206,7 @@ namespace Nif RC_NiMultiTargetTransformController, RC_NiNode, RC_NiPalette, + RC_NiParticleBomb, RC_NiParticleColorModifier, RC_NiParticleGrowFade, RC_NiParticleRotation, From 1bd4860026539941957a5f68a8e68f7104058bb6 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 22 Oct 2023 11:25:39 +0200 Subject: [PATCH 0309/2167] no more obsolete mygui shizzle for macos --- CI/before_install.osx.sh | 2 +- apps/openmw/CMakeLists.txt | 2 ++ components/CMakeLists.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 41e6707c45..40210428d0 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -34,7 +34,7 @@ qmake --version if [[ "${MACOS_AMD64}" ]]; then curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20221113.zip -o ~/openmw-deps.zip else - curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20231017_arm64.zip -o ~/openmw-deps.zip + curl -fSL -R -J https://gitlab.com/OpenMW/openmw-deps/-/raw/main/macos/openmw-deps-20231022_arm64.zip -o ~/openmw-deps.zip fi unzip -o ~/openmw-deps.zip -d /tmp > /dev/null diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 57e2b9d708..a05d20af73 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -183,6 +183,8 @@ if (MSVC) ) endif() +add_definitions(-DMYGUI_DONT_USE_OBSOLETE=ON) + if (ANDROID) target_link_libraries(openmw EGL android log z) endif (ANDROID) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b0169cc2c2..36bd74d217 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -287,6 +287,7 @@ add_component_dir (debug debugging debuglog gldebug debugdraw writeflags ) +add_definitions(-DMYGUI_DONT_USE_OBSOLETE=ON) IF(NOT WIN32 AND NOT APPLE) add_definitions(-DGLOBAL_DATA_PATH="${GLOBAL_DATA_PATH}") add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") From 6081dcc43cd274f308658ff53a02cbd5bb212d23 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 21 Oct 2023 21:40:02 +0200 Subject: [PATCH 0310/2167] Work around MyGUI bug in a less destructive way --- components/lua_ui/widget.cpp | 20 ++++++++++++-------- components/lua_ui/widget.hpp | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index ff9f4d90a2..767d425453 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -92,15 +92,21 @@ namespace LuaUi w->widget()->detachFromWidget(); } + void WidgetExtension::updateVisible() + { + // workaround for MyGUI bug + // parent visibility doesn't affect added children + MyGUI::Widget* widget = this->widget(); + MyGUI::Widget* parent = widget->getParent(); + bool inheritedVisible = widget->getVisible() && (parent == nullptr || parent->getInheritedVisible()); + widget->setVisible(inheritedVisible); + } + void WidgetExtension::attach(WidgetExtension* ext) { ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); - // workaround for MyGUI bug - // parent visibility doesn't affect added children - ext->widget()->setVisible(!ext->widget()->getVisible()); - ext->widget()->setVisible(!ext->widget()->getVisible()); } void WidgetExtension::attachTemplate(WidgetExtension* ext) @@ -108,10 +114,6 @@ namespace LuaUi ext->mParent = this; ext->mTemplateChild = true; ext->widget()->attachToWidget(widget()); - // workaround for MyGUI bug - // parent visibility doesn't affect added children - ext->widget()->setVisible(!ext->widget()->getVisible()); - ext->widget()->setVisible(!ext->widget()->getVisible()); } WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) @@ -256,6 +258,8 @@ namespace LuaUi void WidgetExtension::updateCoord() { + updateVisible(); + MyGUI::IntCoord oldCoord = mWidget->getCoord(); MyGUI::IntCoord newCoord = calculateCoord(); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 3dbc14b6c3..81698b0479 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -173,6 +173,8 @@ namespace LuaUi void focusLoss(MyGUI::Widget*, MyGUI::Widget*); std::optional> mOnCoordChange; + + void updateVisible(); }; class LuaWidget : public MyGUI::Widget, public WidgetExtension From ee5ca066fd5fb2346ab63d66bb366282830b80b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 21 Oct 2023 13:56:13 +0300 Subject: [PATCH 0311/2167] Allow talking with fleeing creatures (bug #7631) --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 9 ++++++--- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 9 +++++++++ apps/openmw/mwmechanics/aisequence.hpp | 3 +++ apps/openmw/mwmechanics/aistate.hpp | 7 +++++++ 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..0874cf1a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7630: Charm can be cast on creatures + Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4950a8c40b..eebcb99512 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -464,18 +464,20 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isInCombat = aiSequence.isInCombat(); if (stats.isDead()) { // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) + else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback @@ -570,7 +572,8 @@ namespace MWClass if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) return true; - return !customData.mCreatureStats.getAiSequence().isInCombat(); + const MWMechanics::AiSequence& aiSeq = customData.mCreatureStats.getAiSequence(); + return !aiSeq.isInCombat() || aiSeq.isFleeing(); } MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4c6ea42d36..5285fb31dd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -708,7 +708,7 @@ namespace MWMechanics mFleeDest = ESM::Pathgrid::Point(0, 0, 0); } - bool AiCombatStorage::isFleeing() + bool AiCombatStorage::isFleeing() const { return mFleeState != FleeState_None; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 92d380dbd8..494f038d6e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -70,7 +70,7 @@ namespace MWMechanics void startFleeing(); void stopFleeing(); - bool isFleeing(); + bool isFleeing() const; }; /// \brief Causes the actor to fight another actor diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b58927c993..13602877ed 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -135,6 +135,15 @@ namespace MWMechanics return mNumPursuitPackages > 0; } + bool AiSequence::isFleeing() const + { + if (!isInCombat()) + return false; + + const AiCombatStorage* storage = mAiState.getPtr(); + return storage && storage->isFleeing(); + } + bool AiSequence::isEngagedWithActor() const { if (!isInCombat()) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index ab3cc11e2c..92c1724ea6 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -117,6 +117,9 @@ namespace MWMechanics /// Is there any pursuit package. bool isInPursuit() const; + /// Is the actor fleeing? + bool isFleeing() const; + /// Removes all packages using the specified id. void removePackagesById(AiPackageTypeId id); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index d79469a9a0..f2ce17fd9c 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -38,6 +38,13 @@ namespace MWMechanics return *result; } + /// \brief returns pointer to stored object in the desired type + template + Derived* getPtr() const + { + return dynamic_cast(mStorage.get()); + } + template void copy(DerivedClassStorage& destination) const { From 3330907d7bf7c68a1145b535341f00770850f262 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 21 Oct 2023 16:22:15 +0300 Subject: [PATCH 0312/2167] Combat AI: Prefer picking reachable/visible targets (bug #6932) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..e5a6dcb4b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack + Bug #6932: Creatures flee from my followers and we have to chase after them Bug #6939: OpenMW-CS: ID columns are too short Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6964: Nerasa Dralor Won't Follow diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b58927c993..b058e61c92 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -272,7 +272,9 @@ namespace MWMechanics } else { - float rating = MWMechanics::getBestActionRating(actor, target); + float rating = 0.f; + if (MWMechanics::canFight(actor, target)) + rating = MWMechanics::getBestActionRating(actor, target); const ESM::Position& targetPos = target.getRefData().getPosition(); From f68bd3ba971dee1ed3b1dbdefa555a0ecb510508 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 12 Oct 2023 01:12:50 +0300 Subject: [PATCH 0313/2167] Launcher: Improve directory appending UX and heuristics (bug #7502) - Recognize more asset directories and omwgame files - Always let the user append the selected directory - Automatically pick the user-selected directory if it's the only valid directory - Add a caption to directory selection dialog, properly handle cancellation --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 75 +++++++++++++++++++++------------ apps/launcher/datafilespage.hpp | 1 - 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..be460568ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles + Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 756aaba131..b6192d3c02 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -40,8 +40,35 @@ namespace { void contentSubdirs(const QString& path, QStringList& dirs) { - QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.ba2", "*.omwscripts" }; - QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" }; + static const QStringList fileFilter{ + "*.esm", + "*.esp", + "*.bsa", + "*.ba2", + "*.omwgame", + "*.omwaddon", + "*.omwscripts", + }; + + static const QStringList dirFilter{ + "animations", + "bookart", + "fonts", + "icons", + "interface", + "l10n", + "meshes", + "music", + "mygui", + "scripts", + "shaders", + "sound", + "splash", + "strings", + "textures", + "trees", + "video", + }; QDir currentDir(path); if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() @@ -587,18 +614,6 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString& text) mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } -QString Launcher::DataFilesPage::selectDirectory() -{ - QFileDialog fileDialog(this); - fileDialog.setFileMode(QFileDialog::Directory); - fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); - - if (fileDialog.exec() == QDialog::Rejected) - return {}; - - return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); -} - void Launcher::DataFilesPage::addSubdirectories(bool append) { int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); @@ -606,22 +621,30 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (selectedRow == -1) return; - const auto rootDir = selectDirectory(); - if (rootDir.isEmpty()) + QString rootPath = QFileDialog::getExistingDirectory( + this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); + + if (rootPath.isEmpty()) return; - QStringList subdirs; - contentSubdirs(rootDir, subdirs); + const QDir rootDir(rootPath); + rootPath = rootDir.canonicalPath(); - if (subdirs.empty()) + QStringList subdirs; + contentSubdirs(rootPath, subdirs); + + // Always offer to append the root directory just in case + if (subdirs.isEmpty() || subdirs[0] != rootPath) + subdirs.prepend(rootPath); + else if (subdirs.size() == 1) { - // we didn't find anything that looks like a content directory, add directory selected by user - if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) - { - ui.directoryListWidget->addItem(rootDir); - mNewDataDirs.push_back(rootDir); - refreshDataFilesView(); - } + // We didn't find anything else that looks like a content directory + // Automatically add the directory selected by user + if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) + return; + ui.directoryListWidget->addItem(rootPath); + mNewDataDirs.push_back(rootPath); + refreshDataFilesView(); return; } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 033c91f9c7..dc3aeaef6f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -131,7 +131,6 @@ namespace Launcher void reloadCells(QStringList selectedFiles); void refreshDataFilesView(); void updateNavMeshProgress(int minDataSize); - QString selectDirectory(); /** * Returns the file paths of all selected content files From 76939aae45a72aba52bbcb2237f2c3d186a527c1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 3 Oct 2023 01:27:14 +0300 Subject: [PATCH 0314/2167] Refurbish RigGeometry Restructure/untangle influence data Don't store the input influence data Overall cleanup --- components/sceneutil/riggeometry.cpp | 139 ++++++++------------------- components/sceneutil/riggeometry.hpp | 35 +++---- 2 files changed, 53 insertions(+), 121 deletions(-) diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index b940fb60ec..1572fab338 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -1,5 +1,7 @@ #include "riggeometry.hpp" +#include + #include #include @@ -10,39 +12,10 @@ #include "skeleton.hpp" #include "util.hpp" -namespace -{ - inline void accumulateMatrix( - const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, const float weight, osg::Matrixf& result) - { - osg::Matrixf m = invBindMatrix * matrix; - float* ptr = m.ptr(); - float* ptrresult = result.ptr(); - ptrresult[0] += ptr[0] * weight; - ptrresult[1] += ptr[1] * weight; - ptrresult[2] += ptr[2] * weight; - - ptrresult[4] += ptr[4] * weight; - ptrresult[5] += ptr[5] * weight; - ptrresult[6] += ptr[6] * weight; - - ptrresult[8] += ptr[8] * weight; - ptrresult[9] += ptr[9] * weight; - ptrresult[10] += ptr[10] * weight; - - ptrresult[12] += ptr[12] * weight; - ptrresult[13] += ptr[13] * weight; - ptrresult[14] += ptr[14] * weight; - } -} - namespace SceneUtil { RigGeometry::RigGeometry() - : mSkeleton(nullptr) - , mLastFrameNumber(0) - , mBoundsFirstFrame(true) { setNumChildrenRequiringUpdateTraversal(1); // update done in accept(NodeVisitor&) @@ -50,12 +23,7 @@ namespace SceneUtil RigGeometry::RigGeometry(const RigGeometry& copy, const osg::CopyOp& copyop) : Drawable(copy, copyop) - , mSkeleton(nullptr) - , mInfluenceMap(copy.mInfluenceMap) - , mBone2VertexVector(copy.mBone2VertexVector) - , mBoneSphereVector(copy.mBoneSphereVector) - , mLastFrameNumber(0) - , mBoundsFirstFrame(true) + , mData(copy.mData) { setSourceGeometry(copy.mSourceGeometry); setNumChildrenRequiringUpdateTraversal(1); @@ -151,42 +119,18 @@ namespace SceneUtil return false; } - if (!mInfluenceMap) + if (!mData) { - Log(Debug::Error) << "Error: No InfluenceMap set on RigGeometry"; + Log(Debug::Error) << "Error: No influence data set on RigGeometry"; return false; } - mBoneNodesVector.clear(); - for (auto& bonePair : mBoneSphereVector->mData) + mNodes.clear(); + for (const BoneInfo& info : mData->mBones) { - const std::string& boneName = bonePair.first; - Bone* bone = mSkeleton->getBone(boneName); - if (!bone) - { - mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; - continue; - } - - mBoneNodesVector.push_back(bone); - } - - for (auto& pair : mBone2VertexVector->mData) - { - for (auto& weight : pair.first) - { - const std::string& boneName = weight.first.first; - Bone* bone = mSkeleton->getBone(boneName); - if (!bone) - { - mBoneNodesVector.push_back(nullptr); - Log(Debug::Error) << "Error: RigGeometry did not find bone " << boneName; - continue; - } - - mBoneNodesVector.push_back(bone); - } + mNodes.push_back(mSkeleton->getBone(info.mName)); + if (!mNodes.back()) + Log(Debug::Error) << "Error: RigGeometry did not find bone " << info.mName; } return true; @@ -226,25 +170,28 @@ namespace SceneUtil osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); - int index = mBoneSphereVector->mData.size(); - for (auto& pair : mBone2VertexVector->mData) + for (const auto& [influences, vertices] : mData->mInfluences) { osg::Matrixf resultMat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); - for (auto& weight : pair.first) + for (const auto& [index, weight] : influences) { - Bone* bone = mBoneNodesVector[index]; + const Bone* bone = mNodes[index]; if (bone == nullptr) continue; - accumulateMatrix(weight.first.second, bone->mMatrixInSkeletonSpace, weight.second, resultMat); - index++; + osg::Matrixf boneMat = mData->mBones[index].mInvBindMatrix * bone->mMatrixInSkeletonSpace; + float* boneMatPtr = boneMat.ptr(); + float* resultMatPtr = resultMat.ptr(); + for (int i = 0; i < 16; ++i, ++resultMatPtr, ++boneMatPtr) + if (i % 4 != 3) + *resultMatPtr += *boneMatPtr * weight; } if (mGeomToSkelMatrix) resultMat *= (*mGeomToSkelMatrix); - for (auto& vertex : pair.second) + for (unsigned short vertex : vertices) { (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); if (normalDst) @@ -291,15 +238,14 @@ namespace SceneUtil osg::BoundingBox box; - int index = 0; - for (auto& boundPair : mBoneSphereVector->mData) + size_t index = 0; + for (const BoneInfo& info : mData->mBones) { - Bone* bone = mBoneNodesVector[index]; + const Bone* bone = mNodes[index++]; if (bone == nullptr) continue; - index++; - osg::BoundingSpheref bs = boundPair.second; + osg::BoundingSpheref bs = info.mBoundSphere; if (mGeomToSkelMatrix) transformBoundingSphere(bone->mMatrixInSkeletonSpace * (*mGeomToSkelMatrix), bs); else @@ -357,35 +303,26 @@ namespace SceneUtil void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) { - mInfluenceMap = influenceMap; + mData = new InfluenceData; + mData->mBones.reserve(influenceMap->mData.size()); - typedef std::map> Vertex2BoneMap; - Vertex2BoneMap vertex2BoneMap; - mBoneSphereVector = new BoneSphereVector; - mBoneSphereVector->mData.reserve(mInfluenceMap->mData.size()); - mBone2VertexVector = new Bone2VertexVector; - for (auto& influencePair : mInfluenceMap->mData) + std::unordered_map> vertexToInfluences; + size_t index = 0; + for (const auto& [boneName, bi] : influenceMap->mData) { - const std::string& boneName = influencePair.first; - const BoneInfluence& bi = influencePair.second; - mBoneSphereVector->mData.emplace_back(boneName, bi.mBoundSphere); + mData->mBones.push_back({ boneName, bi.mBoundSphere, bi.mInvBindMatrix }); - for (auto& weightPair : bi.mWeights) - { - std::vector& vec = vertex2BoneMap[weightPair.first]; - - vec.emplace_back(std::make_pair(boneName, bi.mInvBindMatrix), weightPair.second); - } + for (const auto& [vertex, weight] : bi.mWeights) + vertexToInfluences[vertex].emplace_back(index, weight); + index++; } - Bone2VertexMap bone2VertexMap; - for (auto& vertexPair : vertex2BoneMap) - { - bone2VertexMap[vertexPair.second].emplace_back(vertexPair.first); - } + std::map, VertexList> influencesToVertices; + for (const auto& [vertex, weights] : vertexToInfluences) + influencesToVertices[weights].emplace_back(vertex); - mBone2VertexVector->mData.reserve(bone2VertexMap.size()); - mBone2VertexVector->mData.assign(bone2VertexMap.begin(), bone2VertexMap.end()); + mData->mInfluences.reserve(influencesToVertices.size()); + mData->mInfluences.assign(influencesToVertices.begin(), influencesToVertices.end()); } void RigGeometry::accept(osg::NodeVisitor& nv) diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index 72e8fcd032..d1c077288d 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -36,6 +36,7 @@ namespace SceneUtil // static parts of the model. void compileGLObjects(osg::RenderInfo& renderInfo) const override {} + // TODO: Make InfluenceMap more similar to InfluenceData struct BoneInfluence { osg::Matrixf mInvBindMatrix; @@ -84,35 +85,29 @@ namespace SceneUtil osg::ref_ptr mSourceGeometry; osg::ref_ptr mSourceTangents; - Skeleton* mSkeleton; + Skeleton* mSkeleton{ nullptr }; osg::ref_ptr mGeomToSkelMatrix; - osg::ref_ptr mInfluenceMap; - - typedef std::pair BoneBindMatrixPair; - - typedef std::pair BoneWeight; - - typedef std::vector VertexList; - - typedef std::map, VertexList> Bone2VertexMap; - - struct Bone2VertexVector : public osg::Referenced + struct BoneInfo { - std::vector, VertexList>> mData; + std::string mName; + osg::BoundingSpheref mBoundSphere; + osg::Matrixf mInvBindMatrix; }; - osg::ref_ptr mBone2VertexVector; - struct BoneSphereVector : public osg::Referenced + using BoneWeight = std::pair; + using VertexList = std::vector; + struct InfluenceData : public osg::Referenced { - std::vector> mData; + std::vector mBones; + std::vector, VertexList>> mInfluences; }; - osg::ref_ptr mBoneSphereVector; - std::vector mBoneNodesVector; + osg::ref_ptr mData; + std::vector mNodes; - unsigned int mLastFrameNumber; - bool mBoundsFirstFrame; + unsigned int mLastFrameNumber{ 0 }; + bool mBoundsFirstFrame{ true }; bool initFromParentSkeleton(osg::NodeVisitor* nv); From 715efe6cb19e24ebb3d33d8b235748cc66bd3f14 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 22 Oct 2023 11:03:13 +0300 Subject: [PATCH 0315/2167] Handle NiParticleBomb (feature #7634) --- CHANGELOG.md | 1 + components/nifosg/nifloader.cpp | 12 +++++ components/nifosg/particle.cpp | 78 ++++++++++++++++++++++++++++++ components/nifosg/particle.hpp | 25 ++++++++++ components/sceneutil/serialize.cpp | 2 +- 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bbc5b5b1b..0f6ed2de6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,7 @@ Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music Feature #7618: Show the player character's health in the save details + Feature #7634: Support NiParticleBomb Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3b8c47e071..a5f1032c69 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1092,6 +1092,18 @@ namespace NifOsg const Nif::NiGravity* gr = static_cast(modifier.getPtr()); program->addOperator(new GravityAffector(gr)); } + else if (modifier->recType == Nif::RC_NiParticleBomb) + { + auto bomb = static_cast(modifier.getPtr()); + osg::ref_ptr bombProgram(new osgParticle::ModularProgram); + attachTo->addChild(bombProgram); + bombProgram->setParticleSystem(partsys); + bombProgram->setReferenceFrame(rf); + bombProgram->setStartTime(bomb->mStartTime); + bombProgram->setLifeTime(bomb->mDuration); + bombProgram->setEndless(false); + bombProgram->addOperator(new ParticleBomb(bomb)); + } else if (modifier->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier* cl diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 551cbfeae8..f15678c879 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -346,6 +346,84 @@ namespace NifOsg } } + ParticleBomb::ParticleBomb(const Nif::NiParticleBomb* bomb) + : mRange(bomb->mRange) + , mStrength(bomb->mStrength) + , mDecayType(bomb->mDecayType) + , mSymmetryType(bomb->mSymmetryType) + , mPosition(bomb->mPosition) + , mDirection(bomb->mDirection) + { + } + + ParticleBomb::ParticleBomb(const ParticleBomb& copy, const osg::CopyOp& copyop) + : osgParticle::Operator(copy, copyop) + { + mRange = copy.mRange; + mStrength = copy.mStrength; + mDecayType = copy.mDecayType; + mSymmetryType = copy.mSymmetryType; + mCachedWorldPosition = copy.mCachedWorldPosition; + mCachedWorldDirection = copy.mCachedWorldDirection; + } + + void ParticleBomb::beginOperate(osgParticle::Program* program) + { + bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); + + mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; + + // We don't need the direction for Spherical bomb + if (mSymmetryType != Nif::SymmetryType::Spherical) + { + mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; + mCachedWorldDirection.normalize(); + } + } + + void ParticleBomb::operate(osgParticle::Particle* particle, double dt) + { + float decay = 1.f; + osg::Vec3f explosionDir; + + osg::Vec3f particleDir = particle->getPosition() - mCachedWorldPosition; + float distance = particleDir.length(); + particleDir.normalize(); + + switch (mDecayType) + { + case Nif::DecayType::None: + break; + case Nif::DecayType::Linear: + decay = 1.f - distance / mRange; + break; + case Nif::DecayType::Exponential: + decay = std::exp(-distance / mRange); + break; + } + + if (decay <= 0.f) + return; + + switch (mSymmetryType) + { + case Nif::SymmetryType::Spherical: + explosionDir = particleDir; + break; + case Nif::SymmetryType::Cylindrical: + explosionDir = particleDir - mCachedWorldDirection * (mCachedWorldDirection * particleDir); + explosionDir.normalize(); + break; + case Nif::SymmetryType::Planar: + explosionDir = mCachedWorldDirection; + if (explosionDir * particleDir < 0) + explosionDir = -explosionDir; + break; + } + + particle->addVelocity(explosionDir * mStrength * decay * dt); + } + Emitter::Emitter() : osgParticle::Emitter() , mFlags(0) diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 967531013a..272bc5baed 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -199,6 +199,31 @@ namespace NifOsg osg::Vec3f mCachedWorldDirection; }; + class ParticleBomb : public osgParticle::Operator + { + public: + ParticleBomb(const Nif::NiParticleBomb* bomb); + ParticleBomb() = default; + ParticleBomb(const ParticleBomb& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + ParticleBomb& operator=(const ParticleBomb&) = delete; + + META_Object(NifOsg, ParticleBomb) + + void operate(osgParticle::Particle* particle, double dt) override; + void beginOperate(osgParticle::Program*) override; + + private: + float mRange{ 0.f }; + float mStrength{ 0.f }; + Nif::DecayType mDecayType{ Nif::DecayType::None }; + Nif::SymmetryType mSymmetryType{ Nif::SymmetryType::Spherical }; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + osg::Vec3f mCachedWorldPosition; + osg::Vec3f mCachedWorldDirection; + }; + // NodeVisitor to find a Group node with the given record index, stored in the node's user data container. // Alternatively, returns the node's parent Group if that node is not a Group (i.e. a leaf node). class FindGroupByRecIndex : public osg::NodeVisitor diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 066f43d123..784dafafa5 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -167,7 +167,7 @@ namespace SceneUtil "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::GrowFadeAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", From dcf6a1fc3c6f986caf6a825912d0d52ce5606a01 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 8 Oct 2023 20:10:53 +0200 Subject: [PATCH 0316/2167] Refresh mMovementAnimationControlled when refreshing idle animations. --- apps/openmw/mwmechanics/character.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 92bc2d143e..3e242586a0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -821,6 +821,9 @@ namespace MWMechanics mCurrentIdle = idleGroup; mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); + + // May still be false after recent turn or jump animations + mMovementAnimationControlled = true; } void CharacterController::refreshCurrentAnims( From 7c280662684465fae747a2c74fc6ff19993ed040 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 8 Oct 2023 22:45:03 +0200 Subject: [PATCH 0317/2167] Changelog entry for 7611 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..3033eb83f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves + Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics From 45a2b8042df8d3c2d1d269ca6601e00bb86c5409 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 11 Oct 2023 11:50:06 +0200 Subject: [PATCH 0318/2167] Derive the value of MovementAnimationControlled instead of storing it. --- apps/openmw/mwmechanics/character.cpp | 27 ++++++++++++++++----------- apps/openmw/mwmechanics/character.hpp | 3 ++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3e242586a0..99c82f2bf7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -353,6 +353,7 @@ namespace MWMechanics { clearStateAnimation(mCurrentMovement); mMovementState = CharState_None; + mMovementAnimationHasMovement = false; } void CharacterController::resetCurrentIdleState() @@ -705,7 +706,7 @@ namespace MWMechanics if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement) mAnimation->getInfo(mCurrentMovement, &startpoint); - mMovementAnimationControlled = true; + mMovementAnimationHasMovement = true; clearStateAnimation(mCurrentMovement); mCurrentMovement = movementAnimName; @@ -743,7 +744,7 @@ namespace MWMechanics bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); - mMovementAnimationControlled = false; + mMovementAnimationHasMovement = false; } } @@ -821,9 +822,6 @@ namespace MWMechanics mCurrentIdle = idleGroup; mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); - - // May still be false after recent turn or jump animations - mMovementAnimationControlled = true; } void CharacterController::refreshCurrentAnims( @@ -855,7 +853,6 @@ namespace MWMechanics resetCurrentHitState(); resetCurrentIdleState(); resetCurrentJumpState(); - mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0); @@ -2312,9 +2309,6 @@ namespace MWMechanics updateIdleStormState(inwater); } - if (mInJump) - mMovementAnimationControlled = false; - if (isTurning()) { // Adjust animation speed from 1.0 to 1.5 multiplier @@ -2350,7 +2344,7 @@ namespace MWMechanics } } - if (!mMovementAnimationControlled) + if (!isMovementAnimationControlled()) world->queueMovement(mPtr, vec); } @@ -2419,7 +2413,7 @@ namespace MWMechanics } // Update movement - if (mMovementAnimationControlled && mPtr.getClass().isActor()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2580,6 +2574,17 @@ namespace MWMechanics return mAnimation->isPlaying(groupName); } + bool CharacterController::isMovementAnimationControlled() const + { + bool movementAnimationControlled = true; + movementAnimationControlled = mIdleState != CharState_None; + if (mMovementState != CharState_None) + movementAnimationControlled = mMovementAnimationHasMovement; + if (mInJump) + movementAnimationControlled = false; + return movementAnimationControlled; + } + void CharacterController::clearAnimQueue(bool clearPersistAnims) { // Do not interrupt scripted animations, if we want to keep them diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5a676e3f6d..e4d2d32fb8 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -147,7 +147,7 @@ namespace MWMechanics std::string mCurrentMovement; float mMovementAnimSpeed{ 0.f }; bool mAdjustMovementAnimSpeed{ false }; - bool mMovementAnimationControlled{ true }; + bool mMovementAnimationHasMovement{ false }; CharacterState mDeathState{ CharState_None }; std::string mCurrentDeath; @@ -272,6 +272,7 @@ namespace MWMechanics bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; + bool isMovementAnimationControlled() const; enum KillResult { From e893767ed055ced9dc20d5c3c25bdbb5b32b1fed Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 14 Oct 2023 13:56:44 +0200 Subject: [PATCH 0319/2167] Redundant line --- apps/openmw/mwmechanics/character.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 99c82f2bf7..48abac8b06 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2576,8 +2576,7 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { - bool movementAnimationControlled = true; - movementAnimationControlled = mIdleState != CharState_None; + bool movementAnimationControlled = mIdleState != CharState_None; if (mMovementState != CharState_None) movementAnimationControlled = mMovementAnimationHasMovement; if (mInJump) From 354b028072ff817ea1f7083126d8ba6a455c5bc9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 14 Oct 2023 13:57:29 +0200 Subject: [PATCH 0320/2167] isMovementAnimationControlled should be private. --- apps/openmw/mwmechanics/character.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index e4d2d32fb8..316a1cff0e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -216,6 +216,7 @@ namespace MWMechanics static bool isRandomAttackAnimation(std::string_view group); bool isPersistentAnimPlaying() const; + bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -272,7 +273,6 @@ namespace MWMechanics bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; - bool isMovementAnimationControlled() const; enum KillResult { From ee80f889b74e1fcad05fd62fcf95471e2f74e9a5 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Mon, 23 Oct 2023 12:35:57 +0000 Subject: [PATCH 0321/2167] Make Per-Pixel Lighting option description a bit more neutral --- docs/source/reference/modding/settings/shaders.rst | 4 +--- files/settings-default.cfg | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b21444b9ef..9ee1cbfaa5 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -23,9 +23,7 @@ force per pixel lighting Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. Has no effect if the 'force shaders' option is false. -Enabling per-pixel lighting results in visual differences to the original MW engine. -It is not recommended to enable this option when using vanilla Morrowind files, -because certain lights in Morrowind rely on vertex lighting to look as intended. +Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders ignore this setting. clamp lighting diff --git a/files/settings-default.cfg b/files/settings-default.cfg index d8a101a7a5..ece773a677 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -407,9 +407,8 @@ force shaders = false # Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. # Has no effect if the 'force shaders' option is false. -# Enabling per-pixel lighting can result in visual differences to the original MW engine. It is not -# recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind -# rely on vertex lighting to look as intended. +# Enabling per-pixel lighting can result in visual differences to the original MW engine as +# certain lights in Morrowind rely on vertex lighting to look as intended. force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). From 606a0eea8fd8fc234a99eebff64d19f7b5e43d0b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 23 Oct 2023 19:28:54 +0000 Subject: [PATCH 0322/2167] Get rid of obsolete comment The matrix in question was removed by !2682 --- files/shaders/compatibility/shadows_vertex.glsl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/files/shaders/compatibility/shadows_vertex.glsl b/files/shaders/compatibility/shadows_vertex.glsl index 08daf620bf..a99a4a10e6 100644 --- a/files/shaders/compatibility/shadows_vertex.glsl +++ b/files/shaders/compatibility/shadows_vertex.glsl @@ -19,7 +19,6 @@ void setupShadowCoords(vec4 viewPos, vec3 viewNormal) { #if SHADOWS - // This matrix has the opposite handedness to the others used here, so multiplication must have the vector to the left. Alternatively it could be transposed after construction, but that's extra work for the GPU just to make the code look a tiny bit cleaner. vec4 shadowOffset; @foreach shadow_texture_unit_index @shadow_texture_unit_list #if @perspectiveShadowMaps @@ -46,4 +45,4 @@ void setupShadowCoords(vec4 viewPos, vec3 viewNormal) #endif @endforeach #endif // SHADOWS -} \ No newline at end of file +} From 611f96ce6545bec790e633f4899c9167a6ee39bc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 23 Oct 2023 22:19:16 -0500 Subject: [PATCH 0323/2167] Add class bindings --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/classbindings.cpp | 106 ++++++++++++++++++++++++++++ apps/openmw/mwlua/classbindings.hpp | 13 ++++ apps/openmw/mwlua/luabindings.cpp | 5 ++ files/lua_api/openmw/core.lua | 16 +++++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwlua/classbindings.cpp create mode 100644 apps/openmw/mwlua/classbindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 57e2b9d708..5394f1ac30 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal - worker magicbindings factionbindings + worker magicbindings factionbindings classbindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp new file mode 100644 index 0000000000..5a9b3969d2 --- /dev/null +++ b/apps/openmw/mwlua/classbindings.cpp @@ -0,0 +1,106 @@ +#include "classbindings.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "luamanagerimp.hpp" + +namespace +{ +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + using classStore = MWWorld::Store; + + void initCoreClassBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::usertype classStoreT = lua.new_usertype("ESM3_classStore"); + classStoreT[sol::meta_function::to_string] = [](const classStore& store) { + return "ESM3_classStore{" + std::to_string(store.getSize()) + " classes}"; + }; + classStoreT[sol::meta_function::length] = [](const classStore& store) { return store.getSize(); }; + classStoreT[sol::meta_function::index] = sol::overload( + [](const classStore& store, size_t index) -> const ESM::Class* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); + }, + [](const classStore& store, std::string_view classId) -> const ESM::Class* { + return store.search(ESM::RefId::deserializeText(classId)); + }); + classStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + classStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + // class record + auto classT = lua.new_usertype("ESM3_Class"); + classT[sol::meta_function::to_string] + = [](const ESM::Class& rec) -> std::string { return "ESM3_Class[" + rec.mId.toDebugString() + "]"; }; + classT["id"] = sol::readonly_property([](const ESM::Class& rec) { return rec.mId.serializeText(); }); + classT["name"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mName; }); + classT["description"] + = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); + classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { + sol::table res(lua, sol::create); + auto skills = rec.mData.mSkills; + + for (size_t i = 0; i < skills.size(); ++i) + { + ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); + res[i + 1] = skillId.serializeText(); + } + + return res; + }); + classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { + sol::table res(lua, sol::create); + auto attribute = rec.mData.mAttribute; + + for (size_t i = 0; i < attribute.size(); ++i) + { + ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]); + res[i + 1] = attributeId.serializeText(); + } + + return res; + }); + classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { + sol::table res(lua, sol::create); + auto skills = rec.mData.mSkills; + + for (size_t i = 0; i < skills.size(); ++i) + { + ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]); + res[i + 1] = skillId.serializeText(); + } + + return res; + }); + classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { + if (rec.mData.mSpecialization == ESM::Class::Stealth) + return "stealth"; + else if (rec.mData.mSpecialization == ESM::Class::Magic) + return "magic"; + else + return "combat"; + }); + classT["isPlayable"] + = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); + } +} diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp new file mode 100644 index 0000000000..c907e5ea94 --- /dev/null +++ b/apps/openmw/mwlua/classbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_CLASSBINDINGS_H +#define MWLUA_CLASSBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + void initCoreClassBindings(const Context& context); +} + +#endif // MWLUA_CLASSBINDINGS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index a50459502b..f506ecbc80 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include "camerabindings.hpp" #include "cellbindings.hpp" +#include "classbindings.hpp" #include "debugbindings.hpp" #include "factionbindings.hpp" #include "inputbindings.hpp" @@ -159,6 +161,9 @@ namespace MWLua initCoreFactionBindings(context); api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); + initCoreClassBindings(context); + api["class"] = &MWBase::Environment::get().getWorld()->getStore().get(); + api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 2c815d6dfc..304f0642f2 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -14,6 +14,10 @@ -- A read-only list of all @{#FactionRecord}s in the world database. -- @field [parent=#core] #list<#FactionRecord> factions +--- +-- A read-only list of all @{#ClassRecord}s in the world database. +-- @field [parent=#core] #list<#ClassRecord> class + --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit @@ -868,6 +872,18 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound +--- +-- Class data record +-- @type ClassRecord +-- @field #string id Class id +-- @field #string name Class name +-- @field #list<#string> attributes A read-only list containing the specialized attributes of the class. +-- @field #list<#string> majorSkills A read-only list containing the major skills of the class. +-- @field #list<#string> minorSkills A read-only list containing the minor skills of the class. +-- @field #string description Class description +-- @field #string specialization Class specialization. Either combat, magic, or stealth. + + --- -- Faction data record -- @type FactionRecord From 254bf7c5d8f200b841c3ed8b1f6ac8ce7149d2d5 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 23 Oct 2023 22:23:30 -0500 Subject: [PATCH 0324/2167] class -> classes --- apps/openmw/mwlua/luabindings.cpp | 2 +- files/lua_api/openmw/core.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index f506ecbc80..86ce4083df 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -162,7 +162,7 @@ namespace MWLua api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); initCoreClassBindings(context); - api["class"] = &MWBase::Environment::get().getWorld()->getStore().get(); + api["classes"] = &MWBase::Environment::get().getWorld()->getStore().get(); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 304f0642f2..68e614d864 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -16,7 +16,7 @@ --- -- A read-only list of all @{#ClassRecord}s in the world database. --- @field [parent=#core] #list<#ClassRecord> class +-- @field [parent=#core] #list<#ClassRecord> classes --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. From bd90ac2ed87b391934cb33cfa634b40375c11806 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 23 Oct 2023 23:02:01 -0500 Subject: [PATCH 0325/2167] Add missing field in docs --- files/lua_api/openmw/core.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 68e614d864..19bbb2a750 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -881,6 +881,7 @@ -- @field #list<#string> majorSkills A read-only list containing the major skills of the class. -- @field #list<#string> minorSkills A read-only list containing the minor skills of the class. -- @field #string description Class description +-- @field #boolean isPlayable True if the player can play as this class -- @field #string specialization Class specialization. Either combat, magic, or stealth. From 5dd1f0332fcdba651ff959d2df9a6019ee717065 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 23 Oct 2023 23:44:54 -0500 Subject: [PATCH 0326/2167] Add notes about active effects --- files/lua_api/openmw/core.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 2c815d6dfc..9cfbb3228f 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -674,11 +674,13 @@ --- -- @type ActiveEffect +-- Magic effect that is currently active on an actor. +-- Note that when this effect expires or is removed, it will remain temporarily. Magnitude will be set to 0 for effects that expire. -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #string id Effect id string -- @field #string name Localized name of the effect --- @field #number magnitude +-- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitudeBase -- @field #number magnitudeModifier From db42a91867e61e64bc4f58c3597ead99e4f1db9b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 09:23:25 +0000 Subject: [PATCH 0327/2167] Add global variable access to world.mwscript (#7597) --- apps/openmw/mwlua/mwscriptbindings.cpp | 51 ++++++++++++++++++++++++++ files/lua_api/openmw/world.lua | 6 +++ 2 files changed, 57 insertions(+) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index dbe02a9fed..92957efb71 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -7,6 +7,7 @@ #include "../mwbase/scriptmanager.hpp" #include "../mwbase/world.hpp" #include "../mwscript/globalscripts.hpp" +#include "../mwworld/esmstore.hpp" #include "object.hpp" @@ -43,6 +44,10 @@ namespace sol struct is_automagical : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -76,6 +81,7 @@ namespace MWLua // api["getGlobalScripts"] = [](std::string_view recordId) -> list of scripts // api["getLocalScripts"] = [](const GObject& obj) -> list of scripts + sol::state_view& lua = context.mLua->sol(); sol::usertype mwscript = context.mLua->sol().new_usertype("MWScript"); sol::usertype mwscriptVars = context.mLua->sol().new_usertype("MWScriptVariables"); @@ -108,6 +114,51 @@ namespace MWLua "No variable \"" + std::string(var) + "\" in mwscript " + s.mRef.mId.toDebugString()); }; + using GlobalStore = MWWorld::Store; + sol::usertype globalStoreT = lua.new_usertype("ESM3_GlobalStore"); + const GlobalStore* globalStore = &MWBase::Environment::get().getWorld()->getStore().get(); + globalStoreT[sol::meta_function::to_string] = [](const GlobalStore& store) { + return "ESM3_GlobalStore{" + std::to_string(store.getSize()) + " globals}"; + }; + globalStoreT[sol::meta_function::length] = [](const GlobalStore& store) { return store.getSize(); }; + globalStoreT[sol::meta_function::index] + = sol::overload([](const GlobalStore& store, std::string_view globalId) -> sol::optional { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + return sol::nullopt; + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + }); + globalStoreT[sol::meta_function::new_index] + = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + return; + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + MWBase::Environment::get().getWorld()->setGlobalInt(globalId, static_cast(val)); + } + else + { + MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); + } + }); + globalStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + globalStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + api["getGlobalVariables"] = [globalStore](sol::optional player) { + if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("First argument must either be a player or be missing"); + + return globalStore; + }; return LuaUtil::makeReadOnly(api); } diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 90868387e5..13fa75e0ad 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -29,6 +29,12 @@ -- @param openmw.core#GameObject player (optional) Will be used in multiplayer mode to get the script if there is a separate instance for each player. Currently has no effect. -- @return #MWScript, #nil +--- +-- Returns mutable global variables. In multiplayer, these may be specific to the provided player. +-- @function [parent=#MWScriptFunctions] getGlobalVariables +-- @param openmw.core#GameObject player (optional) Will be used in multiplayer mode to get the globals if there is a separate instance for each player. Currently has no effect. +-- @return #list<#number> + --- -- Returns global mwscript with given recordId. Returns `nil` if the script doesn't exist or is not started. -- Currently there can be only one instance of each mwscript, but in multiplayer it will be possible to have a separate instance per player. From bc742e9d862b6074994b46e3204aac6925af44de Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 08:36:23 -0500 Subject: [PATCH 0328/2167] Add build.os --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e0b39ec495..544fe56904 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,3 +8,5 @@ python: install: - requirements: docs/requirements.txt +build: + os: ubuntu-22.04 \ No newline at end of file From b3eec4ae3266d53c8270b3d0f265921d8eeeaa0b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 08:39:16 -0500 Subject: [PATCH 0329/2167] Add tools --- .readthedocs.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 544fe56904..ad3758e26d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,9 +4,10 @@ sphinx: configuration: docs/source/conf.py python: - version: 3.8 install: - requirements: docs/requirements.txt build: - os: ubuntu-22.04 \ No newline at end of file + os: ubuntu-22.04 + tools: + python: "3.8" \ No newline at end of file From 22142668c7192b0c1bf90293edfd279411ada0b6 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 08:46:27 -0500 Subject: [PATCH 0330/2167] Add Newline --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ad3758e26d..962b34f516 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ python: build: os: ubuntu-22.04 tools: - python: "3.8" \ No newline at end of file + python: "3.8" From 78e9a1753e449f5680db536bc32d9637a2e2fa0b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 13:55:50 +0000 Subject: [PATCH 0331/2167] Add lua binding for gameObject globalVariable --- apps/openmw/mwlua/objectbindings.cpp | 7 +++++++ files/lua_api/openmw/core.lua | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ef25dadfab..7db7877245 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -211,6 +211,13 @@ namespace MWLua objectT["isValid"] = [](const ObjectT& o) { return !o.ptrOrEmpty().isEmpty(); }; objectT["recordId"] = sol::readonly_property( [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); + objectT["globalVariable"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { + std::string globalVariable = o.ptr().getCellRef().getGlobalVariable(); + if (globalVariable.empty()) + return sol::nullopt; + else + return ESM::RefId::stringRefId(globalVariable).serializeText(); + }); objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { const MWWorld::Ptr& ptr = o.ptr(); MWWorld::WorldModel* wm = MWBase::Environment::get().getWorldModel(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 2c815d6dfc..acfa6a1fc2 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -167,6 +167,7 @@ -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). -- @field #number count Count (>1 means a stack of objects). -- @field #string recordId Returns record ID of the object in lowercase. +-- @field #string globalVariable Global Variable associated with this object(read only). --- -- Does the object still exist and is available. From 15bee21286a852248c1ef0148c9040c379be2539 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 09:30:00 -0500 Subject: [PATCH 0332/2167] Fix caps --- apps/openmw/mwlua/classbindings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 5a9b3969d2..8eb11a9056 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -32,9 +32,9 @@ namespace MWLua void initCoreClassBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); - sol::usertype classStoreT = lua.new_usertype("ESM3_classStore"); + sol::usertype classStoreT = lua.new_usertype("ESM3_ClassStore"); classStoreT[sol::meta_function::to_string] = [](const classStore& store) { - return "ESM3_classStore{" + std::to_string(store.getSize()) + " classes}"; + return "ESM3_ClassStore{" + std::to_string(store.getSize()) + " classes}"; }; classStoreT[sol::meta_function::length] = [](const classStore& store) { return store.getSize(); }; classStoreT[sol::meta_function::index] = sol::overload( From 77c978c2266f9b7f08e3e57d7b37dd83b99d834a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 17:23:54 +0200 Subject: [PATCH 0333/2167] Use more fixed size integers --- components/esm3/aipackage.hpp | 10 +++++----- components/esm3/creaturelevliststate.hpp | 2 +- components/esm3/creaturestats.hpp | 5 ++--- components/esm3/debugprofile.hpp | 4 ++-- components/esm3/doorstate.hpp | 2 +- components/esm3/filter.hpp | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 61aea2750a..7346a4af36 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -16,10 +16,10 @@ namespace ESM struct AIData { - unsigned short mHello; // This is the base value for greeting distance [0, 65535] + uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] char mU1, mU2, mU3; // Unknown values - int mServices; // See the Services enum + int32_t mServices; // See the Services enum void blank(); ///< Set record to default state (does not touch the ID). @@ -27,8 +27,8 @@ namespace ESM struct AIWander { - short mDistance; - short mDuration; + int16_t mDistance; + int16_t mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; unsigned char mShouldRepeat; @@ -44,7 +44,7 @@ namespace ESM struct AITarget { float mX, mY, mZ; - short mDuration; + int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; unsigned char mPadding; diff --git a/components/esm3/creaturelevliststate.hpp b/components/esm3/creaturelevliststate.hpp index f8fb7162ff..e7121cf8ac 100644 --- a/components/esm3/creaturelevliststate.hpp +++ b/components/esm3/creaturelevliststate.hpp @@ -9,7 +9,7 @@ namespace ESM struct CreatureLevListState final : public ObjectState { - int mSpawnActorId; + int32_t mSpawnActorId; bool mSpawn; void load(ESMReader& esm) override; diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index 63ba49cdaa..6e65a52354 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -47,9 +47,8 @@ namespace ESM std::vector mSummonGraveyard; TimeStamp mTradeTime; - int mGoldPool; - int mActorId; - // int mHitAttemptActorId; + int32_t mGoldPool; + int32_t mActorId; enum Flags { diff --git a/components/esm3/debugprofile.hpp b/components/esm3/debugprofile.hpp index fc48fb23f6..a86e84bfd5 100644 --- a/components/esm3/debugprofile.hpp +++ b/components/esm3/debugprofile.hpp @@ -24,14 +24,14 @@ namespace ESM Flag_Global = 4 // make available from main menu (i.e. not location specific) }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; std::string mScriptText; - unsigned int mFlags; + uint32_t mFlags; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/doorstate.hpp b/components/esm3/doorstate.hpp index 5298327707..c23ffd5ad2 100644 --- a/components/esm3/doorstate.hpp +++ b/components/esm3/doorstate.hpp @@ -9,7 +9,7 @@ namespace ESM struct DoorState final : public ObjectState { - int mDoorState = 0; + int32_t mDoorState = 0; void load(ESMReader& esm) override; void save(ESMWriter& esm, bool inInventory = false) const override; diff --git a/components/esm3/filter.hpp b/components/esm3/filter.hpp index c4642285af..6a978a2596 100644 --- a/components/esm3/filter.hpp +++ b/components/esm3/filter.hpp @@ -17,7 +17,7 @@ namespace ESM static constexpr std::string_view getRecordType() { return "Filter"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; From dc781bad5d83fbb00f040902058a94e5cddb575e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 17:51:12 +0200 Subject: [PATCH 0334/2167] Use fixed size unsigned ints for inventory offsets --- apps/essimporter/convertinventory.cpp | 5 +- apps/openmw/mwworld/containerstore.cpp | 12 ++--- apps/openmw/mwworld/containerstore.hpp | 8 ++-- apps/openmw/mwworld/inventorystore.cpp | 16 +++---- apps/openmw/mwworld/inventorystore.hpp | 4 +- components/esm3/inventorystate.cpp | 63 ++++++++++++++------------ components/esm3/inventorystate.hpp | 16 +++---- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 2f03cfaf41..69a2ea4120 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -9,15 +9,14 @@ namespace ESSImport void convertInventory(const Inventory& inventory, ESM::InventoryState& state) { - int index = 0; + uint32_t index = 0; for (const auto& item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); - objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile - // openmw handles them differently, so no need to set any flags + objstate.mCount = item.mCount; state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d82125a5ce..b55a524a48 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -111,12 +111,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( } void MWWorld::ContainerStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { } void MWWorld::ContainerStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { } @@ -128,7 +128,7 @@ void MWWorld::ContainerStore::storeState(const LiveCellRef& ref, ESM::ObjectS template void MWWorld::ContainerStore::storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const + const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, bool equipable) const { for (const LiveCellRef& liveCellRef : collection.mList) { @@ -926,7 +926,7 @@ void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const { state.mItems.clear(); - int index = 0; + size_t index = 0; storeStates(potions, state, index); storeStates(appas, state, index); storeStates(armors, state, index, true); @@ -947,12 +947,12 @@ void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory) mModified = true; mResolved = true; - int index = 0; + size_t index = 0; for (const ESM::ObjectState& state : inventory.mItems) { int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID); - int thisIndex = index++; + size_t thisIndex = index++; switch (type) { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 889fcf7463..fb2722dde8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -161,16 +161,16 @@ namespace MWWorld void storeState(const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; + void storeStates(const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, + bool equipable = false) const; void updateRechargingItems(); virtual void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const; virtual void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory); public: ContainerStore(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9b3470a835..095f5d3cc1 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -46,32 +46,32 @@ void MWWorld::InventoryStore::initSlots(TSlots& slots_) } void MWWorld::InventoryStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { - for (int i = 0; i < static_cast(mSlots.size()); ++i) + for (int32_t i = 0; i < MWWorld::InventoryStore::Slots; ++i) + { if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref) - { - inventory.mEquipmentSlots[index] = i; - } + inventory.mEquipmentSlots[static_cast(index)] = i; + } if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref) inventory.mSelectedEnchantItem = index; } void MWWorld::InventoryStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { if (index == inventory.mSelectedEnchantItem) mSelectedEnchantItem = iter; - std::map::const_iterator found = inventory.mEquipmentSlots.find(index); + auto found = inventory.mEquipmentSlots.find(index); if (found != inventory.mEquipmentSlots.end()) { if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots) throw std::runtime_error("Invalid slot index in inventory state"); // make sure the item can actually be equipped in this slot - int slot = found->second; + int32_t slot = found->second; std::pair, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); if (!allowedSlots.first.size()) return; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6df5fa1e5a..0af6ee2b28 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -81,9 +81,9 @@ namespace MWWorld void fireEquipmentChangedEvent(); void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const override; void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) override; ContainerStoreIterator findSlot(int slot) const; diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index e58d0335bf..c3af101edc 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -7,22 +7,25 @@ namespace ESM { + namespace + { + constexpr uint32_t sInvalidSlot = -1; + } void InventoryState::load(ESMReader& esm) { // obsolete - int index = 0; + uint32_t index = 0; while (esm.isNextSub("IOBJ")) { - int unused; // no longer used - esm.getHT(unused); + esm.skip(4); ObjectState state; // obsolete if (esm.isNextSub("SLOT")) { - int slot; + int32_t slot; esm.getHT(slot); mEquipmentSlots[index] = slot; } @@ -38,9 +41,9 @@ namespace ESM ++index; } - int itemsCount = 0; + uint32_t itemsCount = 0; esm.getHNOT(itemsCount, "ICNT"); - for (int i = 0; i < itemsCount; i++) + for (; itemsCount > 0; --itemsCount) { ObjectState state; @@ -62,7 +65,7 @@ namespace ESM { // Get its name ESM::RefId id = esm.getRefId(); - int count; + int32_t count; std::string parentGroup; // Then get its count esm.getHNT(count, "COUN"); @@ -91,9 +94,9 @@ namespace ESM while (esm.isNextSub("EQUI")) { esm.getSubHeader(); - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } @@ -101,20 +104,24 @@ namespace ESM if (esm.isNextSub("EQIP")) { esm.getSubHeader(); - int slotsCount = 0; + uint32_t slotsCount = 0; esm.getT(slotsCount); - for (int i = 0; i < slotsCount; i++) + for (; slotsCount > 0; --slotsCount) { - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } } - mSelectedEnchantItem = -1; - esm.getHNOT(mSelectedEnchantItem, "SELE"); + uint32_t selectedEnchantItem = sInvalidSlot; + esm.getHNOT(selectedEnchantItem, "SELE"); + if (selectedEnchantItem == sInvalidSlot) + mSelectedEnchantItem.reset(); + else + mSelectedEnchantItem = selectedEnchantItem; // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities @@ -132,7 +139,7 @@ namespace ESM void InventoryState::save(ESMWriter& esm) const { - int itemsCount = static_cast(mItems.size()); + uint32_t itemsCount = static_cast(mItems.size()); if (itemsCount > 0) { esm.writeHNT("ICNT", itemsCount); @@ -149,34 +156,32 @@ namespace ESM esm.writeHNString("LGRP", it->first.second); } - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end(); ++it) + for (const auto& [id, params] : mPermanentMagicEffectMagnitudes) { - esm.writeHNRefId("MAGI", it->first); + esm.writeHNRefId("MAGI", id); - const std::vector>& params = it->second; - for (std::vector>::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt) + for (const auto& [rand, mult] : params) { - esm.writeHNT("RAND", pIt->first); - esm.writeHNT("MULT", pIt->second); + esm.writeHNT("RAND", rand); + esm.writeHNT("MULT", mult); } } - int slotsCount = static_cast(mEquipmentSlots.size()); + uint32_t slotsCount = static_cast(mEquipmentSlots.size()); if (slotsCount > 0) { esm.startSubRecord("EQIP"); esm.writeT(slotsCount); - for (std::map::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it) + for (const auto& [index, slot] : mEquipmentSlots) { - esm.writeT(it->first); - esm.writeT(it->second); + esm.writeT(index); + esm.writeT(slot); } esm.endRecord("EQIP"); } - if (mSelectedEnchantItem != -1) - esm.writeHNT("SELE", mSelectedEnchantItem); + if (mSelectedEnchantItem) + esm.writeHNT("SELE", *mSelectedEnchantItem); } } diff --git a/components/esm3/inventorystate.hpp b/components/esm3/inventorystate.hpp index a0fd948951..050d1eb92f 100644 --- a/components/esm3/inventorystate.hpp +++ b/components/esm3/inventorystate.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESM_INVENTORYSTATE_H #include +#include #include "objectstate.hpp" #include @@ -19,20 +20,15 @@ namespace ESM std::vector mItems; // - std::map mEquipmentSlots; + std::map mEquipmentSlots; - std::map, int> mLevelledItemMap; + std::map, int32_t> mLevelledItemMap; - typedef std::map>> TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; + std::map>> mPermanentMagicEffectMagnitudes; - int mSelectedEnchantItem; // For inventories only + std::optional mSelectedEnchantItem; // For inventories only - InventoryState() - : mSelectedEnchantItem(-1) - { - } - virtual ~InventoryState() {} + virtual ~InventoryState() = default; virtual void load(ESMReader& esm); virtual void save(ESMWriter& esm) const; From 77aaa6177e2bbb7f2925bd8b67225f0750e5f081 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 19:25:52 +0200 Subject: [PATCH 0335/2167] Use more fixed size ints --- components/esm3/aisequence.cpp | 2 +- components/esm3/loadcell.cpp | 12 ++++++------ components/esm3/loadcell.hpp | 10 +++++----- components/esm3/loadcont.hpp | 6 +++--- components/esm3/loadglob.hpp | 2 +- components/esm3/loadgmst.hpp | 2 +- components/esm3/loadlevlist.cpp | 10 +++++----- components/esm3/loadlevlist.hpp | 6 +++--- components/esm3/loadltex.hpp | 2 +- components/esm3/loadmgef.cpp | 2 +- components/esm3/loadmgef.hpp | 8 ++++---- components/esm3/loadnpc.cpp | 2 +- components/esm3/loadnpc.hpp | 14 +++++++------- components/esm3/loadrace.hpp | 10 +++++----- components/esm3/loadsndg.hpp | 4 ++-- components/esm3/loadsscr.hpp | 2 +- components/esm3/loadstat.hpp | 2 +- components/esm3/loadtes3.hpp | 6 +++--- components/esm3/magiceffects.cpp | 4 ++-- components/esm3/magiceffects.hpp | 8 ++++---- components/esm3/npcstats.cpp | 26 +++++++++++++------------- components/esm3/npcstats.hpp | 24 ++++++++++++------------ components/esm3/objectstate.cpp | 4 ++-- components/esm3/objectstate.hpp | 4 ++-- components/esm3/player.hpp | 4 ++-- components/esm3/projectilestate.hpp | 2 +- components/esm3/queststate.hpp | 2 +- components/esm3/savedgame.hpp | 4 ++-- components/esm3/statstate.cpp | 8 ++++---- components/esm3/variant.cpp | 16 ++++++++-------- components/esm3/variant.hpp | 8 ++++---- components/esm3/variantimp.cpp | 12 ++++++------ components/esm3/variantimp.hpp | 4 ++-- 33 files changed, 116 insertions(+), 116 deletions(-) diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 668b36c871..21973acd1d 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -196,7 +196,7 @@ namespace ESM int count = 0; while (esm.isNextSub("AIPK")) { - int type; + int32_t type; esm.getHT(type); mPackages.emplace_back(); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index b966338ae5..829cf9e916 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -17,7 +17,7 @@ namespace ESM ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum void adjustRefNum(RefNum& refNum, const ESMReader& reader) { - unsigned int local = (refNum.mIndex & 0xff000000) >> 24; + uint32_t local = (refNum.mIndex & 0xff000000) >> 24; // If we have an index value that does not make sense, assume that it was an addition // by the present plugin (but a faulty one) @@ -124,7 +124,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - int waterl; + int32_t waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; @@ -192,7 +192,7 @@ namespace ESM { if (mWaterInt) { - int water = (mWater >= 0) ? (int)(mWater + 0.5) : (int)(mWater - 0.5); + int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); esm.writeHNT("INTV", water); } else @@ -218,13 +218,13 @@ namespace ESM } } - void Cell::saveTempMarker(ESMWriter& esm, int tempCount) const + void Cell::saveTempMarker(ESMWriter& esm, int32_t tempCount) const { if (tempCount != 0) esm.writeHNT("NAM0", tempCount); } - void Cell::restore(ESMReader& esm, int iCtx) const + void Cell::restore(ESMReader& esm, size_t iCtx) const { esm.restoreContext(mContextList.at(iCtx)); } @@ -321,7 +321,7 @@ namespace ESM void Cell::blank() { - mName = ""; + mName.clear(); mRegion = ESM::RefId(); mWater = 0; mWaterInt = false; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 0ba0777e7c..bfabdd58f9 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -34,7 +34,7 @@ namespace ESM RefNum mRefNum; // Coordinates of target exterior cell - int mTarget[2]; + int32_t mTarget[2]; // The content file format does not support moving objects to an interior cell. // The save game format does support moving to interior cells, but uses a different mechanism @@ -153,13 +153,13 @@ namespace ESM ESMReader& esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references void save(ESMWriter& esm, bool isDeleted = false) const; - void saveTempMarker(ESMWriter& esm, int tempCount) const; + void saveTempMarker(ESMWriter& esm, int32_t tempCount) const; bool isExterior() const { return !(mData.mFlags & Interior); } - int getGridX() const { return mData.mX; } + int32_t getGridX() const { return mData.mX; } - int getGridY() const { return mData.mY; } + int32_t getGridY() const { return mData.mY; } bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } @@ -172,7 +172,7 @@ namespace ESM // somewhere other than the file system, you need to pre-open the // ESMReader, and the filename must match the stored filename // exactly. - void restore(ESMReader& esm, int iCtx) const; + void restore(ESMReader& esm, size_t iCtx) const; std::string getDescription() const; ///< Return a short string describing the cell (mostly used for debugging/logging purpose) diff --git a/components/esm3/loadcont.hpp b/components/esm3/loadcont.hpp index 3921821da0..3c1fb6468e 100644 --- a/components/esm3/loadcont.hpp +++ b/components/esm3/loadcont.hpp @@ -19,7 +19,7 @@ namespace ESM struct ContItem { - int mCount{ 0 }; + int32_t mCount{ 0 }; ESM::RefId mItem; }; @@ -48,12 +48,12 @@ namespace ESM Unknown = 8 }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel; float mWeight; // Not sure, might be max total weight allowed? - int mFlags; + int32_t mFlags; InventoryList mInventory; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadglob.hpp b/components/esm3/loadglob.hpp index eed53a5f7b..c78bc83917 100644 --- a/components/esm3/loadglob.hpp +++ b/components/esm3/loadglob.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Global"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; ESM::RefId mId; Variant mValue; diff --git a/components/esm3/loadgmst.hpp b/components/esm3/loadgmst.hpp index 72d30b9ea9..c827be5b6b 100644 --- a/components/esm3/loadgmst.hpp +++ b/components/esm3/loadgmst.hpp @@ -26,7 +26,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "GameSetting"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; Variant mValue; diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 970174ada2..627edbadce 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -30,7 +30,7 @@ namespace ESM break; case fourCC("INDX"): { - int length = 0; + uint32_t length = 0; esm.getHT(length); mList.resize(length); @@ -87,12 +87,12 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", mList.size()); - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const auto& item : mList) { - esm.writeHNCRefId(recName, it->mId); - esm.writeHNT("INTV", it->mLevel); + esm.writeHNCRefId(recName, item.mId); + esm.writeHNT("INTV", item.mLevel); } } diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 809c89102e..536b865d90 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -24,15 +24,15 @@ namespace ESM struct LevelledListBase { - int mFlags; + int32_t mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; struct LevelItem { RefId mId; - short mLevel; + uint16_t mLevel; }; std::vector mList; diff --git a/components/esm3/loadltex.hpp b/components/esm3/loadltex.hpp index e6dc2de73e..fb95e8b9ed 100644 --- a/components/esm3/loadltex.hpp +++ b/components/esm3/loadltex.hpp @@ -29,7 +29,7 @@ namespace ESM // mId is merely a user friendly name for the texture in the editor. std::string mTexture; RefId mId; - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 7d3879a341..686afbc34a 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -36,7 +36,7 @@ namespace ESM esm.getSubNameIs("MEDT"); esm.getSubHeader(); - int school; + int32_t school; esm.getT(school); mData.mSchool = MagicSchool::indexToSkillRefId(school); esm.getT(mData.mBaseCost); diff --git a/components/esm3/loadmgef.hpp b/components/esm3/loadmgef.hpp index 9d68bad609..25ec7d0655 100644 --- a/components/esm3/loadmgef.hpp +++ b/components/esm3/loadmgef.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "MagicEffect"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; enum Flags @@ -74,9 +74,9 @@ namespace ESM { RefId mSchool; // Skill id float mBaseCost; - int mFlags; + int32_t mFlags; // Glow color for enchanted items with this effect - int mRed, mGreen, mBlue; + int32_t mRed, mGreen, mBlue; float mUnknown1; // Called "Size X" in CS float mSpeed; // Speed of fired projectile @@ -107,7 +107,7 @@ namespace ESM // there. They can be redefined in mods by setting the name in GMST // sEffectSummonCreature04/05 creature id in // sMagicCreature04ID/05ID. - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 24a79d2e4c..d844f7d2bc 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -82,7 +82,7 @@ namespace ESM break; case fourCC("FLAG"): hasFlags = true; - int flags; + int32_t flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index f0d726434b..af8c2a8574 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -79,28 +79,28 @@ namespace ESM struct NPDTstruct52 { - short mLevel; + int16_t mLevel; unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; // mSkill can grow up to 200, it must be unsigned std::array mSkills; char mUnknown1; - unsigned short mHealth, mMana, mFatigue; + uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; char mUnknown2; - int mGold; + int32_t mGold; }; // 52 bytes // Structure for autocalculated characters. // This is only used for load and save operations. struct NPDTstruct12 { - short mLevel; + int16_t mLevel; // see above unsigned char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; - int mGold; + int32_t mGold; }; // 12 bytes #pragma pack(pop) @@ -111,7 +111,7 @@ namespace ESM int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank - int mBloodType; + int32_t mBloodType; unsigned char mFlags; InventoryList mInventory; @@ -125,7 +125,7 @@ namespace ESM AIPackageList mAiPackage; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mRace, mClass, mFaction, mScript; std::string mModel, mName; diff --git a/components/esm3/loadrace.hpp b/components/esm3/loadrace.hpp index 8dd60bdef1..8cb9d76118 100644 --- a/components/esm3/loadrace.hpp +++ b/components/esm3/loadrace.hpp @@ -27,13 +27,13 @@ namespace ESM struct SkillBonus { - int mSkill; // SkillEnum - int mBonus; + int32_t mSkill; // SkillEnum + int32_t mBonus; }; struct MaleFemale { - int mMale, mFemale; + int32_t mMale, mFemale; int getValue(bool male) const; }; @@ -63,13 +63,13 @@ namespace ESM // as 'height' times 128. This has not been tested yet. MaleFemaleF mHeight, mWeight; - int mFlags; // 0x1 - playable, 0x2 - beast race + int32_t mFlags; // 0x1 - playable, 0x2 - beast race }; // Size = 140 bytes RADTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mName, mDescription; RefId mId; SpellList mPowers; diff --git a/components/esm3/loadsndg.hpp b/components/esm3/loadsndg.hpp index fff4b98439..3337220d9d 100644 --- a/components/esm3/loadsndg.hpp +++ b/components/esm3/loadsndg.hpp @@ -36,9 +36,9 @@ namespace ESM }; // Type - int mType; + int32_t mType; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mCreature, mSound; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadsscr.hpp b/components/esm3/loadsscr.hpp index 6c9163e4e6..34349d8ea4 100644 --- a/components/esm3/loadsscr.hpp +++ b/components/esm3/loadsscr.hpp @@ -28,7 +28,7 @@ namespace ESM static std::string_view getRecordType() { return "StartScript"; } std::string mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; // Load a record and add it to the list diff --git a/components/esm3/loadstat.hpp b/components/esm3/loadstat.hpp index abe589563b..4c0341f4ea 100644 --- a/components/esm3/loadstat.hpp +++ b/components/esm3/loadstat.hpp @@ -31,7 +31,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Static"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mModel; diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 7927b35cee..8b14d41645 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -20,11 +20,11 @@ namespace ESM versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ - unsigned int version; - int type; // 0=esp, 1=esm, 32=ess (unused) + uint32_t version; + int32_t type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description - int records; // Number of records + int32_t records; // Number of records }; struct GMDT diff --git a/components/esm3/magiceffects.cpp b/components/esm3/magiceffects.cpp index 346cc2c568..a8a759949b 100644 --- a/components/esm3/magiceffects.cpp +++ b/components/esm3/magiceffects.cpp @@ -20,8 +20,8 @@ namespace ESM { while (esm.isNextSub("EFID")) { - int id; - std::pair params; + int32_t id; + std::pair params; esm.getHT(id); esm.getHNT(params.first, "BASE"); if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion) diff --git a/components/esm3/magiceffects.hpp b/components/esm3/magiceffects.hpp index 3f141355c8..74a6e34743 100644 --- a/components/esm3/magiceffects.hpp +++ b/components/esm3/magiceffects.hpp @@ -16,7 +16,7 @@ namespace ESM struct MagicEffects { // - std::map> mEffects; + std::map> mEffects; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -24,16 +24,16 @@ namespace ESM struct SummonKey { - SummonKey(int effectId, const ESM::RefId& sourceId, int index) + SummonKey(int32_t effectId, const ESM::RefId& sourceId, int32_t index) : mEffectId(effectId) , mSourceId(sourceId) , mEffectIndex(index) { } - int mEffectId; + int32_t mEffectId; ESM::RefId mSourceId; - int mEffectIndex; + int32_t mEffectIndex; }; inline auto makeTupleRef(const SummonKey& value) noexcept diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index c34205f6c2..a21ba807e4 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -21,7 +21,7 @@ namespace ESM Faction faction; - int expelled = 0; + int32_t expelled = 0; esm.getHNOT(expelled, "FAEX"); if (expelled) @@ -75,7 +75,7 @@ namespace ESM esm.getHNOT(hasWerewolfAttributes, "HWAT"); if (hasWerewolfAttributes) { - StatState dummy; + StatState dummy; for (int i = 0; i < ESM::Attribute::Length; ++i) dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; @@ -130,21 +130,21 @@ namespace ESM void NpcStats::save(ESMWriter& esm) const { - for (auto iter(mFactions.begin()); iter != mFactions.end(); ++iter) + for (const auto& [id, faction] : mFactions) { - esm.writeHNRefId("FACT", iter->first); + esm.writeHNRefId("FACT", id); - if (iter->second.mExpelled) + if (faction.mExpelled) { - int expelled = 1; + int32_t expelled = 1; esm.writeHNT("FAEX", expelled); } - if (iter->second.mRank >= 0) - esm.writeHNT("FARA", iter->second.mRank); + if (faction.mRank >= 0) + esm.writeHNT("FARA", faction.mRank); - if (iter->second.mReputation) - esm.writeHNT("FARE", iter->second.mReputation); + if (faction.mReputation) + esm.writeHNT("FARE", faction.mReputation); } if (mDisposition) @@ -169,7 +169,7 @@ namespace ESM esm.writeHNT("LPRO", mLevelProgress); bool saveSkillIncreases = false; - for (int increase : mSkillIncrease) + for (int32_t increase : mSkillIncrease) { if (increase != 0) { @@ -183,8 +183,8 @@ namespace ESM if (mSpecIncreases[0] != 0 || mSpecIncreases[1] != 0 || mSpecIncreases[2] != 0) esm.writeHNT("SPEC", mSpecIncreases); - for (auto iter(mUsedIds.begin()); iter != mUsedIds.end(); ++iter) - esm.writeHNRefId("USED", *iter); + for (const RefId& id : mUsedIds) + esm.writeHNRefId("USED", id); if (mTimeToStartDrowning) esm.writeHNT("DRTI", mTimeToStartDrowning); diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index e80ec04c25..ccb58a12ad 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -23,8 +23,8 @@ namespace ESM struct Faction { bool mExpelled; - int mRank; - int mReputation; + int32_t mRank; + int32_t mReputation; Faction(); }; @@ -33,18 +33,18 @@ namespace ESM bool mWerewolfDeprecatedData; - std::map mFactions; // lower case IDs - int mDisposition; + std::map mFactions; + int32_t mDisposition; std::array, ESM::Skill::Length> mSkills; - int mBounty; - int mReputation; - int mWerewolfKills; - int mLevelProgress; - std::array mSkillIncrease; - std::array mSpecIncreases; - std::vector mUsedIds; // lower case IDs + int32_t mBounty; + int32_t mReputation; + int32_t mWerewolfKills; + int32_t mLevelProgress; + std::array mSkillIncrease; + std::array mSpecIncreases; + std::vector mUsedIds; float mTimeToStartDrowning; - int mCrimeId; + int32_t mCrimeId; /// Initialize to default state void blank(); diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a56988843a..a7fe41d66c 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -49,7 +49,7 @@ namespace ESM esm.getHNOT(mFlags, "FLAG"); // obsolete - int unused; + int32_t unused; esm.getHNOT(unused, "LTIM"); mAnimationState.load(esm); @@ -179,6 +179,6 @@ namespace ESM throw std::logic_error(error.str()); } - ObjectState::~ObjectState() {} + ObjectState::~ObjectState() = default; } diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index 67f4d27706..4c09d16d18 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -32,9 +32,9 @@ namespace ESM Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; - int mCount; + int32_t mCount; Position mPosition; - unsigned int mFlags; + uint32_t mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index f691f22e86..7f9309765c 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -27,8 +27,8 @@ namespace ESM RefId mMarkedCell; ESM::RefId mBirthsign; - int mCurrentCrimeId; - int mPaidCrimeId; + int32_t mCurrentCrimeId; + int32_t mPaidCrimeId; float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; diff --git a/components/esm3/projectilestate.hpp b/components/esm3/projectilestate.hpp index 7a7651f364..cab550b114 100644 --- a/components/esm3/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -23,7 +23,7 @@ namespace ESM Vector3 mPosition; Quaternion mOrientation; - int mActorId; + int32_t mActorId; void load(ESMReader& esm); void save(ESMWriter& esm) const; diff --git a/components/esm3/queststate.hpp b/components/esm3/queststate.hpp index 5858714df0..6d9fd6c4fb 100644 --- a/components/esm3/queststate.hpp +++ b/components/esm3/queststate.hpp @@ -14,7 +14,7 @@ namespace ESM struct QuestState { ESM::RefId mTopic; // lower case id - int mState; + int32_t mState; unsigned char mFinished; void load(ESMReader& esm); diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 2048244ac2..4632e98927 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -20,7 +20,7 @@ namespace ESM std::vector mContentFiles; std::string mPlayerName; - int mPlayerLevel; + int32_t mPlayerLevel; // ID of class ESM::RefId mPlayerClassId; @@ -34,7 +34,7 @@ namespace ESM std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data - int mCurrentDay = 0; + int32_t mCurrentDay = 0; float mCurrentHealth = 0; float mMaximumHealth = 0; diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index b5ddc54985..7477d83e2d 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -21,19 +21,19 @@ namespace ESM // We changed stats values from integers to floats; ensure backwards compatibility if (intFallback) { - int base = 0; + int32_t base = 0; esm.getHNT(base, "STBA"); mBase = static_cast(base); - int mod = 0; + int32_t mod = 0; esm.getHNOT(mod, "STMO"); mMod = static_cast(mod); - int current = 0; + int32_t current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); - int oldDamage = 0; + int32_t oldDamage = 0; esm.getHNOT(oldDamage, "STDA"); mDamage = static_cast(oldDamage); } diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index 3d5daa2cb3..48621818eb 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -17,7 +17,7 @@ namespace ESM template struct GetValue { - constexpr T operator()(int value) const { return static_cast(value); } + constexpr T operator()(int32_t value) const { return static_cast(value); } constexpr T operator()(float value) const { return static_cast(value); } @@ -41,7 +41,7 @@ namespace ESM { } - void operator()(int& value) const { value = static_cast(mValue); } + void operator()(int32_t& value) const { value = static_cast(mValue); } void operator()(float& value) const { value = static_cast(mValue); } @@ -58,9 +58,9 @@ namespace ESM return std::get(mData); } - int Variant::getInteger() const + int32_t Variant::getInteger() const { - return std::visit(GetValue{}, mData); + return std::visit(GetValue{}, mData); } float Variant::getFloat() const @@ -194,17 +194,17 @@ namespace ESM case VT_Short: - stream << "variant short: " << std::get(mData); + stream << "variant short: " << std::get(mData); break; case VT_Int: - stream << "variant int: " << std::get(mData); + stream << "variant int: " << std::get(mData); break; case VT_Long: - stream << "variant long: " << std::get(mData); + stream << "variant long: " << std::get(mData); break; case VT_Float: @@ -259,7 +259,7 @@ namespace ESM std::get(mData) = std::move(value); } - void Variant::setInteger(int value) + void Variant::setInteger(int32_t value) { std::visit(SetValue(value), mData); } diff --git a/components/esm3/variant.hpp b/components/esm3/variant.hpp index d00ccb2746..ed72b1dc05 100644 --- a/components/esm3/variant.hpp +++ b/components/esm3/variant.hpp @@ -25,7 +25,7 @@ namespace ESM class Variant { VarType mType; - std::variant mData; + std::variant mData; public: enum Format @@ -54,7 +54,7 @@ namespace ESM { } - explicit Variant(int value) + explicit Variant(int32_t value) : mType(VT_Long) , mData(value) { @@ -71,7 +71,7 @@ namespace ESM const std::string& getString() const; ///< Will throw an exception, if value can not be represented as a string. - int getInteger() const; + int32_t getInteger() const; ///< Will throw an exception, if value can not be represented as an integer (implicit /// casting of float values is permitted). @@ -93,7 +93,7 @@ namespace ESM void setString(std::string&& value); ///< Will throw an exception, if type is not compatible with string. - void setInteger(int value); + void setInteger(int32_t value); ///< Will throw an exception, if type is not compatible with integer. void setFloat(float value); diff --git a/components/esm3/variantimp.cpp b/components/esm3/variantimp.cpp index 410f4ade8d..31248556ec 100644 --- a/components/esm3/variantimp.cpp +++ b/components/esm3/variantimp.cpp @@ -46,7 +46,7 @@ namespace ESM esm.writeHNString("STRV", in); } - void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) + void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int32_t& out) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -60,9 +60,9 @@ namespace ESM if (std::isnan(value)) out = 0; else - out = static_cast(value); + out = static_cast(value); else if (type == VT_Long) - out = static_cast(value); + out = static_cast(value); else esm.fail("unsupported global variable integer type"); } @@ -82,7 +82,7 @@ namespace ESM { if (type == VT_Short) { - short value; + int16_t value; esm.getHT(value); out = value; } @@ -95,7 +95,7 @@ namespace ESM } } - void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) + void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int32_t in) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -126,7 +126,7 @@ namespace ESM else if (format == Variant::Format_Local) { if (type == VT_Short) - esm.writeHNT("STTV", static_cast(in)); + esm.writeHNT("STTV", static_cast(in)); else if (type == VT_Int) esm.writeHNT("INTV", in); else diff --git a/components/esm3/variantimp.hpp b/components/esm3/variantimp.hpp index 90f45a420f..365cff988a 100644 --- a/components/esm3/variantimp.hpp +++ b/components/esm3/variantimp.hpp @@ -12,13 +12,13 @@ namespace ESM void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value); - void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value); + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int32_t& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value); - void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value); + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int32_t value); struct ReadESMVariantValue { From f4d349070c99443cdf5f4607047a48093804c32b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 13:51:12 -0500 Subject: [PATCH 0336/2167] Add isTeleportingEnabled and setTeleportingEnabled --- apps/openmw/mwlua/types/player.cpp | 13 +++++++++++++ files/lua_api/openmw/types.lua | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index ab15385f08..52170f7075 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -138,6 +138,19 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); return input->getControlSwitch(key); }; + player["isTeleportingEnabled"] = [](const Object& player) -> bool { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); + }; + player["setTeleportingEnabled"] = [context](const Object& player, bool state) { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + if (dynamic_cast(&player) && !dynamic_cast(&player)) + throw std::runtime_error("Only player and global scripts can toggle teleportation."); + context.mLuaManager->addAction([state] { MWBase::Environment::get().getWorld()->enableTeleporting(state); }, + "toggleTeleportingAction"); + }; player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) throw std::runtime_error("The argument must be a player."); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index c65a7323cb..3df689ce0e 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -919,13 +919,26 @@ -- @function [parent=#Player] getCrimeLevel -- @param openmw.core#GameObject player -- @return #number - + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished -- @param openmw.core#GameObject player -- @return #boolean +--- +-- Whether teleportation for this player is enabled. +-- @function [parent=#Player] isTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean player +-- @return #boolean + +--- +-- Enables or disables teleportation for this player. +-- @function [parent=#Player] setTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean state True to enable teleporting, false to disable. + --- -- Returns a list containing quests @{#PlayerQuest} for the specified player, indexed by quest ID. -- @function [parent=#Player] quests From 212f6bae569f2fed240ec259240e3ca17fa095e6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 20:59:20 +0200 Subject: [PATCH 0337/2167] Use correct skip and fix MSVC --- components/esm3/inventorystate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index c3af101edc..84a52ff518 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -9,7 +9,7 @@ namespace ESM { namespace { - constexpr uint32_t sInvalidSlot = -1; + constexpr uint32_t sInvalidSlot = static_cast(-1); } void InventoryState::load(ESMReader& esm) @@ -18,7 +18,7 @@ namespace ESM uint32_t index = 0; while (esm.isNextSub("IOBJ")) { - esm.skip(4); + esm.skipHT(); ObjectState state; From 23bf6ed878fb7ce2b11e3023ddd9851a46728fce Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 16 Oct 2023 18:16:12 +0200 Subject: [PATCH 0338/2167] Remove missing scripts from actors much like is done for items --- CHANGELOG.md | 1 + apps/openmw/mwworld/esmstore.cpp | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac1067da..9ea5c748e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7204: Missing actor scripts freeze the game Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f9b53cf21f..ad3d1f8d43 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -84,7 +84,8 @@ namespace } std::vector getNPCsToReplace(const MWWorld::Store& factions, - const MWWorld::Store& classes, const std::unordered_map& npcs) + const MWWorld::Store& classes, const MWWorld::Store& scripts, + const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found const ESM::RefId& defaultCls = getDefaultClass(classes); @@ -122,6 +123,14 @@ namespace changed = true; } + if (!npc.mScript.empty() && !scripts.search(npc.mScript)) + { + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script " + << npc.mScript << ", ignoring it."; + npc.mScript = ESM::RefId(); + changed = true; + } + if (changed) npcsToReplace.push_back(npc); } @@ -138,9 +147,9 @@ namespace { if (!item.mScript.empty() && !scripts.search(item.mScript)) { + Log(Debug::Verbose) << MapT::mapped_type::getRecordType() << ' ' << id << " (" << item.mName + << ") has nonexistent script " << item.mScript << ", ignoring it."; item.mScript = ESM::RefId(); - Log(Debug::Verbose) << "Item " << id << " (" << item.mName << ") has nonexistent script " - << item.mScript << ", ignoring it."; } } } @@ -517,8 +526,8 @@ namespace MWWorld void ESMStore::validate() { auto& npcs = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mStatic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mStatic); for (const ESM::NPC& npc : npcsToReplace) { @@ -526,6 +535,8 @@ namespace MWWorld npcs.insertStatic(npc); } + removeMissingScripts(getWritable(), getWritable().mStatic); + // Validate spell effects for invalid arguments std::vector spellsToReplace; auto& spells = getWritable(); @@ -605,8 +616,8 @@ namespace MWWorld auto& npcs = getWritable(); auto& scripts = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mDynamic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mDynamic); for (const ESM::NPC& npc : npcsToReplace) npcs.insert(npc); @@ -614,6 +625,7 @@ namespace MWWorld removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingObjects(getWritable()); From e6c02efbb7eb0fa70b3c445ed84f98d54c3a8d0e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 16 Oct 2023 02:34:07 +0300 Subject: [PATCH 0339/2167] Add record presence early-outs for various script instructions (feature #7625) AddItem, RemoveItem, StartScript, StopScript, AddTopic --- CHANGELOG.md | 1 + apps/openmw/mwscript/containerextensions.cpp | 13 +++++++++++++ apps/openmw/mwscript/dialogueextensions.cpp | 8 ++++++++ apps/openmw/mwscript/miscextensions.cpp | 17 +++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac1067da..2ea1c33513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music Feature #7618: Show the player character's health in the save details + Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index edee3963e7..4a0d302cd8 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/levelledlist.hpp" +#include "interpretercontext.hpp" #include "ref.hpp" namespace @@ -94,6 +95,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to add item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); @@ -210,6 +217,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to remove item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 1f0a9a37cf..6511fbdb01 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "ref.hpp" @@ -89,6 +90,13 @@ namespace MWScript ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->get().search(topic)) + { + runtime.getContext().report( + "Failed to add topic '" + topic.getRefIdString() + "': topic record not found"); + return; + } + MWBase::Environment::get().getDialogueManager()->addTopic(topic); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 43da00afe3..687b512106 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,14 @@ namespace MWScript MWWorld::Ptr target = R()(runtime, false); ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to start global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript(name, target); } }; @@ -206,6 +215,14 @@ namespace MWScript { const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to stop global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name); } }; From ac9cfc782a613663d6f8440a4a06bc3119fcabc9 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 12:34:56 +0000 Subject: [PATCH 0340/2167] Add functions to lua ui library to toggle HUD visibility, and check current status. --- apps/openmw/mwbase/windowmanager.hpp | 3 ++- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 3 ++- apps/openmw/mwinput/actionmanager.cpp | 2 +- apps/openmw/mwlua/uibindings.cpp | 5 ++++- apps/openmw/mwscript/guiextensions.cpp | 3 ++- files/data/scripts/omw/ui.lua | 15 ++++++++++++--- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index df6893c1ba..f225ebf24e 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -229,7 +229,8 @@ namespace MWBase virtual void unsetSelectedWeapon() = 0; virtual void showCrosshair(bool show) = 0; - virtual bool toggleHud() = 0; + virtual bool setHudVisibility(bool show) = 0; + virtual bool isHudVisible() const = 0; virtual void disallowMouse() = 0; virtual void allowMouse() = 0; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 94bac9e10e..adb5b186ce 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1605,9 +1605,9 @@ namespace MWGui mQuickKeysMenu->activateQuickKey(index); } - bool WindowManager::toggleHud() + bool WindowManager::setHudVisibility(bool show) { - mHudEnabled = !mHudEnabled; + mHudEnabled = show; updateVisible(); mMessageBoxManager->setVisible(mHudEnabled); return mHudEnabled; diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index fae3bb1ec9..5cd1aa23e0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -247,7 +247,8 @@ namespace MWGui void showCrosshair(bool show) override; /// Turn visibility of HUD on or off - bool toggleHud() override; + bool setHudVisibility(bool show) override; + bool isHudVisible() const override { return mHudEnabled; } void disallowMouse() override; void allowMouse() override; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 3fac540f5e..eb82c160f9 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -118,7 +118,7 @@ namespace MWInput quickKey(10); break; case A_ToggleHUD: - windowManager->toggleHud(); + windowManager->setHudVisibility(!windowManager->isHudVisible()); break; case A_ToggleDebug: windowManager->toggleDebugWindow(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 04914dd881..79f5fac9a1 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -111,6 +111,10 @@ namespace MWLua }; sol::table api = context.mLua->newTable(); + api["_setHudVisibility"] = [luaManager = context.mLuaManager](bool state) { + luaManager->addAction([state] { MWBase::Environment::get().getWindowManager()->setHudVisibility(state); }); + }; + api["_isHudVisible"] = []() -> bool { return MWBase::Environment::get().getWindowManager()->isHudVisible(); }; api["showMessage"] = [luaManager = context.mLuaManager](std::string_view message) { luaManager->addUIMessage(message); }; api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ @@ -296,7 +300,6 @@ namespace MWLua }; // TODO - // api["_showHUD"] = [](bool) {}; // api["_showMouseCursor"] = [](bool) {}; return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index c1590941e6..07855f18ef 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -192,7 +192,8 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - bool state = MWBase::Environment::get().getWindowManager()->toggleHud(); + bool state = MWBase::Environment::get().getWindowManager()->setHudVisibility( + !MWBase::Environment::get().getWindowManager()->isHudVisible()); runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); if (!state) diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 6fd80bf394..48412f6a0f 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -226,12 +226,21 @@ return { -- @function [parent=#UI] setPauseOnMode -- @param #string mode Mode to configure -- @param #boolean shouldPause - setPauseOnMode = function(mode, shouldPause) modePause[mode] = shouldPause end + setPauseOnMode = function(mode, shouldPause) modePause[mode] = shouldPause end, + + --- Set whether the UI should be visible. + -- @function [parent=#UI] setHudVisibility + -- @param #boolean showHud + setHudVisibility = function(showHud) ui._setHudVisibility(showHud) end, + + --- + -- Returns if the player HUD is visible or not + -- @function [parent=#UI] isHudVisible + -- @return #bool + isHudVisible = function() return ui._isHudVisible() end, -- TODO -- registerHudElement = function(name, showFn, hideFn) end, - -- showHud = function(bool) end, - -- isHudVisible = function() end, -- showHudElement = function(name, bool) end, -- hudElements, -- map from element name to its visibility }, From 10dbfe66b3421ff460908393a791ae237acf27d9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Oct 2023 17:55:24 +0100 Subject: [PATCH 0341/2167] Revert "Ditch python in Windows CI - we don't need it" This reverts commit 00c13b8dcd9026ee0200a07380acd5da7075313c. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7dabca6fb..44655466ea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -531,6 +531,7 @@ macOS13_Xcode14_arm64: - choco install ccache -y - choco install vswhere -y - choco install ninja -y + - choco install python -y - choco install awscli -y - refreshenv - | @@ -652,6 +653,7 @@ macOS13_Xcode14_arm64: - choco install 7zip -y - choco install ccache -y - choco install vswhere -y + - choco install python -y - choco install awscli -y - refreshenv - | From 5cb5d2e166d830126808db8f380f0aa856074eac Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 25 Oct 2023 18:07:26 +0100 Subject: [PATCH 0342/2167] Add error handling to Store-Symbols.ps1 --- CI/Store-Symbols.ps1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CI/Store-Symbols.ps1 b/CI/Store-Symbols.ps1 index 8634181432..11938fa4b4 100644 --- a/CI/Store-Symbols.ps1 +++ b/CI/Store-Symbols.ps1 @@ -1,3 +1,5 @@ +$ErrorActionPreference = "Stop" + if (-Not (Test-Path CMakeCache.txt)) { Write-Error "This script must be run from the build directory." @@ -8,6 +10,9 @@ if (-Not (Test-Path .cmake\api\v1\reply\index-*.json) -Or -Not ((Get-Content -Ra Write-Output "Running CMake query..." New-Item -Type File -Force .cmake\api\v1\query\codemodel-v2 cmake . + if ($LASTEXITCODE -ne 0) { + Write-Error "Command exited with code $LASTEXITCODE" + } Write-Output "Done." } @@ -45,13 +50,26 @@ finally if (-not (Test-Path symstore-venv)) { python -m venv symstore-venv + if ($LASTEXITCODE -ne 0) { + Write-Error "Command exited with code $LASTEXITCODE" + } } $symstoreVersion = "0.3.4" if (-not (Test-Path symstore-venv\Scripts\symstore.exe) -or -not ((symstore-venv\Scripts\pip show symstore | Select-String '(?<=Version: ).*').Matches.Value -eq $symstoreVersion)) { symstore-venv\Scripts\pip install symstore==$symstoreVersion + if ($LASTEXITCODE -ne 0) { + Write-Error "Command exited with code $LASTEXITCODE" + } } + $artifacts = $artifacts | Where-Object { Test-Path $_ } + Write-Output "Storing symbols..." + symstore-venv\Scripts\symstore --compress --skip-published .\SymStore @artifacts +if ($LASTEXITCODE -ne 0) { + Write-Error "Command exited with code $LASTEXITCODE" +} + Write-Output "Done." From 1bff02e3b00e615ead11c487593c03711b203795 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 26 Oct 2023 03:32:15 +0800 Subject: [PATCH 0343/2167] add docs --- apps/openmw/mwlua/itemdata.cpp | 6 +----- apps/openmw/mwworld/cellref.hpp | 2 +- files/lua_api/openmw/types.lua | 9 +++++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index f2bd32703f..6dea2360e7 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -17,7 +17,7 @@ namespace void invalidPropErr(std::string_view prop, const MWWorld::Ptr& ptr) { - throw std::runtime_error(std::string(prop) + " does not exist for item " + throw std::runtime_error("'" + std::string(prop) + "'" + " property does not exist for item " + std::string(ptr.getClass().getName(ptr)) + "(" + std::string(ptr.getTypeDescription()) + ")"); } } @@ -96,11 +96,7 @@ namespace MWLua } else invalidPropErr(prop, ptr); - return; } - - /*ignore or error?*/ - invalidPropErr(prop, ptr); } }; } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index a1ddd8d0f0..9595a4cc4c 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -130,7 +130,7 @@ namespace MWWorld } void setCharge(int charge); void setChargeFloat(float charge); - void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if > 1 + void applyChargeRemainderToBeSubtracted(float chargeRemainder); // Stores remainders and applies if <= -1 // Stores fractional part of mChargeInt void setChargeIntRemainder(float chargeRemainder); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 03efe885b5..b9a6958654 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -658,6 +658,15 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Set of properties that differentiates one item from another of the same record type. +-- @function [parent=#Item] itemData +-- @param openmw.core#GameObject item +-- @return #ItemData + +--- +-- @type ItemData +-- @field #number condition The item's current condition. Time remaining for lights. Uses left for lockpicks and probes. Current health for weapons and armor. -------------------------------------------------------------------------------- -- @{#Creature} functions From 3e31142c0bba38f0f116d43e9c28f371ffa753f9 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 17:24:18 -0500 Subject: [PATCH 0344/2167] Remove lua action --- apps/openmw/mwlua/types/player.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 52170f7075..fc4c9858cc 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -148,8 +148,7 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); if (dynamic_cast(&player) && !dynamic_cast(&player)) throw std::runtime_error("Only player and global scripts can toggle teleportation."); - context.mLuaManager->addAction([state] { MWBase::Environment::get().getWorld()->enableTeleporting(state); }, - "toggleTeleportingAction"); + MWBase::Environment::get().getWorld()->enableTeleporting(state); }; player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) From f0640da21eac87cc196af0b5dc0cda5ef2a99be2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 21:05:14 -0500 Subject: [PATCH 0345/2167] Add character to core --- apps/openmw/mwlua/luabindings.cpp | 7 ++++--- files/lua_api/openmw/core.lua | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 86ce4083df..448a57da61 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -158,12 +158,13 @@ namespace MWLua api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); + sol::table character(lua->sol(), sol::create); initCoreFactionBindings(context); - api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); + character["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); initCoreClassBindings(context); - api["classes"] = &MWBase::Environment::get().getWorld()->getStore().get(); - + character["classes"] = &MWBase::Environment::get().getWorld()->getStore().get(); + api["character"] = character; api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 19bbb2a750..05daf424c8 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -14,9 +14,12 @@ -- A read-only list of all @{#FactionRecord}s in the world database. -- @field [parent=#core] #list<#FactionRecord> factions +--- @{#Character}: Class and Character Data +-- @field [parent=#core] #Character character + --- -- A read-only list of all @{#ClassRecord}s in the world database. --- @field [parent=#core] #list<#ClassRecord> classes +-- @field [parent=#Character] #list<#ClassRecord> classes --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. From 2f16a104dce2c23a8e6b34f929e1d9bef062e34e Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 21:38:42 -0500 Subject: [PATCH 0346/2167] Revise record store, add specialization function --- apps/openmw/mwlua/classbindings.cpp | 35 +++++++---------------------- apps/openmw/mwlua/classbindings.hpp | 2 +- apps/openmw/mwlua/luabindings.cpp | 3 +-- apps/openmw/mwlua/stats.cpp | 12 ++++++++++ apps/openmw/mwlua/stats.hpp | 2 +- files/lua_api/openmw/core.lua | 8 +++++-- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 8eb11a9056..237aaae291 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,5 +1,6 @@ #include "classbindings.hpp" - +#include "stats.hpp" +#include "types/types.hpp" #include #include @@ -27,27 +28,12 @@ namespace sol namespace MWLua { - using classStore = MWWorld::Store; - void initCoreClassBindings(const Context& context) + sol::table initCoreClassBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); - sol::usertype classStoreT = lua.new_usertype("ESM3_ClassStore"); - classStoreT[sol::meta_function::to_string] = [](const classStore& store) { - return "ESM3_ClassStore{" + std::to_string(store.getSize()) + " classes}"; - }; - classStoreT[sol::meta_function::length] = [](const classStore& store) { return store.getSize(); }; - classStoreT[sol::meta_function::index] = sol::overload( - [](const classStore& store, size_t index) -> const ESM::Class* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const classStore& store, std::string_view classId) -> const ESM::Class* { - return store.search(ESM::RefId::deserializeText(classId)); - }); - classStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - classStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + sol::table classes(context.mLua->sol(), sol::create); + addRecordFunctionBinding(classes, context); // class record auto classT = lua.new_usertype("ESM3_Class"); classT[sol::meta_function::to_string] @@ -92,15 +78,10 @@ namespace MWLua return res; }); - classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { - if (rec.mData.mSpecialization == ESM::Class::Stealth) - return "stealth"; - else if (rec.mData.mSpecialization == ESM::Class::Magic) - return "magic"; - else - return "combat"; - }); + classT["specialization"] = sol::readonly_property( + [](const ESM::Class& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); + return classes; } } diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index c907e5ea94..9dd9befae4 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - void initCoreClassBindings(const Context& context); + sol::table initCoreClassBindings(const Context& context); } #endif // MWLUA_CLASSBINDINGS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 448a57da61..b55bf3ca27 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -162,8 +162,7 @@ namespace MWLua initCoreFactionBindings(context); character["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); - initCoreClassBindings(context); - character["classes"] = &MWBase::Environment::get().getWorld()->getStore().get(); + character["classes"] = initCoreClassBindings(context); api["character"] = character; api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 5c1e536dd6..1cc1902c49 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -60,6 +60,16 @@ namespace namespace MWLua { + std::string_view getSpecialization(const int var) + { + if (var == ESM::Class::Stealth) + return "stealth"; + else if (var == ESM::Class::Magic) + return "magic"; + else + return "combat"; + } + static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) { if (!obj.mStatsCache.empty()) @@ -446,6 +456,8 @@ namespace MWLua skillT["name"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mName; }); skillT["description"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); + skillT["specialization"] = sol::readonly_property( + [](const ESM::Skill& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index 4ce2f6b5eb..6cbf486c7b 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -6,7 +6,7 @@ namespace MWLua { struct Context; - + std::string_view getSpecialization(const int val); void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); sol::table initCoreStatsBindings(const Context& context); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 280d5f07bd..c1d9be3169 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -12,14 +12,18 @@ --- -- A read-only list of all @{#FactionRecord}s in the world database. --- @field [parent=#core] #list<#FactionRecord> factions +-- @field [parent=#Character] #list<#FactionRecord> factions --- @{#Character}: Class and Character Data -- @field [parent=#core] #Character character + +--- @{#Classes}: Class Data +-- @field [parent=#Character] #Classes classes + --- -- A read-only list of all @{#ClassRecord}s in the world database. --- @field [parent=#Character] #list<#ClassRecord> classes +-- @field [parent=#Classes] #list<#ClassRecord> records --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. From c2943e2fd0bfbb87b38941abe966166c6969fb5b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 21:47:00 -0500 Subject: [PATCH 0347/2167] Use int32_t --- apps/openmw/mwlua/stats.cpp | 8 ++++---- apps/openmw/mwlua/stats.hpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 1cc1902c49..c4440c954c 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -60,16 +60,16 @@ namespace namespace MWLua { - std::string_view getSpecialization(const int var) + std::string_view getSpecialization(const int32_t val) { - if (var == ESM::Class::Stealth) + if (val == ESM::Class::Stealth) return "stealth"; - else if (var == ESM::Class::Magic) + else if (val == ESM::Class::Magic) return "magic"; else return "combat"; } - + static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) { if (!obj.mStatsCache.empty()) diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index 6cbf486c7b..35031ff95f 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -6,7 +6,7 @@ namespace MWLua { struct Context; - std::string_view getSpecialization(const int val); + std::string_view getSpecialization(const int32_t val); void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); sol::table initCoreStatsBindings(const Context& context); From 6a671186ee1089cde407c422d7c27471a2f88a81 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 26 Oct 2023 11:50:50 -0500 Subject: [PATCH 0348/2167] Removed unused var --- apps/openmw/mwlua/types/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index fc4c9858cc..cef0753817 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -143,7 +143,7 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); }; - player["setTeleportingEnabled"] = [context](const Object& player, bool state) { + player["setTeleportingEnabled"] = [](const Object& player, bool state) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) throw std::runtime_error("The argument must be a player."); if (dynamic_cast(&player) && !dynamic_cast(&player)) From f9888230afe7f1d5c6c6cb95a8aa8c4d53d84aff Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 26 Oct 2023 20:46:34 +0200 Subject: [PATCH 0349/2167] Fix Lua UI atlasing --- components/lua_ui/image.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 0454dd19b4..ffe93a8d2d 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -8,9 +8,7 @@ namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) { - mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); - mAlign = MyGUI::Align::Stretch; - MyGUI::TileRect::_setAlign(_oldsize); + mCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); mTileSize = mSetTileSize; // zero tilesize stands for not tiling @@ -25,6 +23,8 @@ namespace LuaUi mTileSize.width = 1e7; if (mTileSize.height <= 0) mTileSize.height = 1e7; + + MyGUI::TileRect::_updateView(); } void LuaImage::initialize() @@ -55,13 +55,13 @@ namespace LuaUi if (texture != nullptr) textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); - mTileRect->updateSize(MyGUI::IntSize(tileH ? textureSize.width : 0, tileV ? textureSize.height : 0)); - setImageTile(textureSize); - if (atlasCoord.width == 0) atlasCoord.width = textureSize.width; if (atlasCoord.height == 0) atlasCoord.height = textureSize.height; + + mTileRect->updateSize(MyGUI::IntSize(tileH ? atlasCoord.width : 0, tileV ? atlasCoord.height : 0)); + setImageTile(atlasCoord.size()); setImageCoord(atlasCoord); setColour(propertyValue("color", MyGUI::Colour(1, 1, 1, 1))); From dcd81d026f87c7242ea33f185bdeb33ae671561b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 12:24:42 +0200 Subject: [PATCH 0350/2167] Use settings values for Video settings * Convert window mode, vsync mode into enums, screenshot type into a struct. * Add missing doc for screenshot type. --- apps/launcher/graphicspage.cpp | 76 +++++++----------- apps/launcher/graphicspage.hpp | 3 +- apps/launcher/maindialog.cpp | 13 ++- apps/openmw/engine.cpp | 24 +++--- apps/openmw/mwgui/settingswindow.cpp | 58 ++++++-------- apps/openmw/mwgui/windowmanagerimp.cpp | 18 ++--- apps/openmw/mwinput/actionmanager.cpp | 7 +- apps/openmw/mwinput/sensormanager.cpp | 3 +- apps/openmw/mwlua/camerabindings.cpp | 8 +- apps/openmw/mwlua/uibindings.cpp | 5 +- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/postprocessor.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/screenshotmanager.cpp | 79 ++++++++----------- apps/openmw/mwrender/util.cpp | 2 +- components/CMakeLists.txt | 12 ++- components/sdlutil/sdlgraphicswindow.cpp | 10 +-- components/sdlutil/sdlgraphicswindow.hpp | 10 +-- components/sdlutil/sdlinputwrapper.cpp | 6 +- components/sdlutil/sdlvideowrapper.cpp | 10 +-- components/sdlutil/sdlvideowrapper.hpp | 4 +- components/sdlutil/vsyncmode.hpp | 14 ++++ components/settings/categories/video.hpp | 14 ++-- components/settings/screenshotsettings.hpp | 29 +++++++ components/settings/settings.cpp | 51 ++++++++++++ components/settings/settings.hpp | 38 +++++++-- components/settings/settingvalue.hpp | 38 +++++++++ components/settings/windowmode.hpp | 14 ++++ components/stereo/stereomanager.cpp | 2 +- .../reference/modding/settings/video.rst | 9 +++ files/settings-default.cfg | 2 +- 31 files changed, 346 insertions(+), 221 deletions(-) create mode 100644 components/sdlutil/vsyncmode.hpp create mode 100644 components/settings/screenshotsettings.hpp create mode 100644 components/settings/windowmode.hpp diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 9a10bf6d9c..18fb57805f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -96,32 +96,29 @@ bool Launcher::GraphicsPage::loadSettings() // Visuals - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - if (vsync < 0 || vsync > 2) - vsync = 0; + const int vsync = Settings::video().mVsyncMode; vSyncComboBox->setCurrentIndex(vsync); - size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); - if (windowMode > static_cast(Settings::WindowMode::Windowed)) - windowMode = 0; - windowModeComboBox->setCurrentIndex(windowMode); - slotFullScreenChanged(windowMode); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; - if (Settings::Manager::getBool("window border", "Video")) + windowModeComboBox->setCurrentIndex(static_cast(windowMode)); + handleWindowModeChange(windowMode); + + if (Settings::video().mWindowBorder) windowBorderCheckBox->setCheckState(Qt::Checked); // aaValue is the actual value (0, 1, 2, 4, 8, 16) - int aaValue = Settings::Manager::getInt("antialiasing", "Video"); + const int aaValue = Settings::video().mAntialiasing; // aaIndex is the index into the allowed values in the pull down. - int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); + const int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; QString resolution = QString::number(width) + QString(" x ") + QString::number(height); - screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video")); + screenComboBox->setCurrentIndex(Settings::video().mScreen); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -137,7 +134,7 @@ bool Launcher::GraphicsPage::loadSettings() customHeightSpinBox->setValue(height); } - float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); + const float fpsLimit = Settings::video().mFramerateLimit; if (fpsLimit != 0) { framerateLimitCheckBox->setCheckState(Qt::Checked); @@ -198,23 +195,10 @@ void Launcher::GraphicsPage::saveSettings() { // Visuals - // Ensure we only set the new settings if they changed. This is to avoid cluttering the - // user settings file (which by definition should only contain settings the user has touched) - int cVSync = vSyncComboBox->currentIndex(); - if (cVSync != Settings::Manager::getInt("vsync mode", "Video")) - Settings::Manager::setInt("vsync mode", "Video", cVSync); - - int cWindowMode = windowModeComboBox->currentIndex(); - if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) - Settings::Manager::setInt("window mode", "Video", cWindowMode); - - bool cWindowBorder = windowBorderCheckBox->checkState(); - if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) - Settings::Manager::setBool("window border", "Video", cWindowBorder); - - int cAAValue = antiAliasingComboBox->currentText().toInt(); - if (cAAValue != Settings::Manager::getInt("antialiasing", "Video")) - Settings::Manager::setInt("antialiasing", "Video", cAAValue); + Settings::video().mVsyncMode.set(static_cast(vSyncComboBox->currentIndex())); + Settings::video().mWindowMode.set(static_cast(windowModeComboBox->currentIndex())); + Settings::video().mWindowBorder.set(windowBorderCheckBox->checkState() == Qt::Checked); + Settings::video().mAntialiasing.set(antiAliasingComboBox->currentText().toInt()); int cWidth = 0; int cHeight = 0; @@ -234,25 +218,17 @@ void Launcher::GraphicsPage::saveSettings() cHeight = customHeightSpinBox->value(); } - if (cWidth != Settings::Manager::getInt("resolution x", "Video")) - Settings::Manager::setInt("resolution x", "Video", cWidth); - - if (cHeight != Settings::Manager::getInt("resolution y", "Video")) - Settings::Manager::setInt("resolution y", "Video", cHeight); - - int cScreen = screenComboBox->currentIndex(); - if (cScreen != Settings::Manager::getInt("screen", "Video")) - Settings::Manager::setInt("screen", "Video", cScreen); + Settings::video().mResolutionX.set(cWidth); + Settings::video().mResolutionY.set(cHeight); + Settings::video().mScreen.set(screenComboBox->currentIndex()); if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { - float cFpsLimit = framerateLimitSpinBox->value(); - if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video")) - Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit); + Settings::video().mFramerateLimit.set(framerateLimitSpinBox->value()); } - else if (Settings::Manager::getFloat("framerate limit", "Video") != 0) + else if (Settings::video().mFramerateLimit != 0) { - Settings::Manager::setFloat("framerate limit", "Video", 0); + Settings::video().mFramerateLimit.set(0); } // Lighting @@ -392,8 +368,12 @@ void Launcher::GraphicsPage::screenChanged(int screen) void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (mode == static_cast(Settings::WindowMode::Fullscreen) - || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) + handleWindowModeChange(static_cast(mode)); +} + +void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) +{ + if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 92bdf35ac4..85f91d1ff1 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -3,7 +3,7 @@ #include "ui_graphicspage.h" -#include +#include namespace Files { @@ -40,6 +40,7 @@ namespace Launcher static QRect getMaximumResolution(); bool setupSDL(); + void handleWindowModeChange(Settings::WindowMode state); }; } #endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 023d6b729a..bba3bbe5e1 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,13 +1,5 @@ #include "maindialog.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -15,10 +7,15 @@ #include #include +#include +#include #include #include #include +#include #include +#include +#include #include "datafilespage.hpp" #include "graphicspage.hpp" diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7393562cfb..3cac85984b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -452,14 +452,13 @@ void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame) void OMW::Engine::createWindow() { - int screen = Settings::Manager::getInt("screen", "Video"); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); - bool windowBorder = Settings::Manager::getBool("window border", "Video"); - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); + const int screen = Settings::video().mScreen; + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const bool windowBorder = Settings::video().mWindowBorder; + const SDLUtil::VSyncMode vsync = Settings::video().mVsyncMode; + unsigned antialiasing = static_cast(Settings::video().mAntialiasing); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); @@ -482,8 +481,7 @@ void OMW::Engine::createWindow() if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::video().mMinimizeOnFocusLoss ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); @@ -513,7 +511,7 @@ void OMW::Engine::createWindow() Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing / 2; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -560,7 +558,7 @@ void OMW::Engine::createWindow() SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -866,7 +864,7 @@ void OMW::Engine::go() // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); - mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); + mEnvironment.setFrameRateLimit(Settings::video().mFramerateLimit); prepareEngine(); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1a41f9bb55..fbcdb963fc 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -354,7 +354,7 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list - int screen = Settings::Manager::getInt("screen", "Video"); + const int screen = Settings::video().mScreen; int numDisplayModes = SDL_GetNumDisplayModes(screen); std::vector> resolutions; for (int i = 0; i < numDisplayModes; i++) @@ -396,8 +396,7 @@ namespace MWGui updateMaxLightsComboBox(mMaxLights); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; mWindowBorderButton->setEnabled( windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); @@ -491,8 +490,8 @@ namespace MWGui int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); apply(); } @@ -506,8 +505,8 @@ namespace MWGui { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); - int currentX = Settings::Manager::getInt("resolution x", "Video"); - int currentY = Settings::Manager::getInt("resolution y", "Video"); + const int currentX = Settings::video().mResolutionX; + const int currentY = Settings::video().mResolutionY; for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) { @@ -591,23 +590,22 @@ namespace MWGui "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); } - void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - Settings::Manager::setInt("vsync mode", "Video", index); + Settings::video().mVsyncMode.set(static_cast(sender->getIndexSelected())); apply(); } - void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + const Settings::WindowMode windowMode = static_cast(sender->getIndexSelected()); + if (windowMode == Settings::WindowMode::WindowedFullscreen) { mResolutionList->setEnabled(false); mWindowModeHint->setVisible(true); @@ -618,12 +616,12 @@ namespace MWGui mWindowModeHint->setVisible(false); } - if (index == static_cast(Settings::WindowMode::Windowed)) + if (windowMode == Settings::WindowMode::Windowed) mWindowBorderButton->setEnabled(true); else mWindowBorderButton->setEnabled(false); - Settings::Manager::setInt("window mode", "Video", index); + Settings::video().mWindowMode.set(windowMode); apply(); } @@ -849,14 +847,12 @@ namespace MWGui void SettingsWindow::updateWindowModeSettings() { - size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const std::size_t windowModeIndex = static_cast(windowMode); - if (index > static_cast(Settings::WindowMode::Windowed)) - index = MyGUI::ITEM_NONE; + mWindowModeList->setIndexSelected(windowModeIndex); - mWindowModeList->setIndexSelected(index); - - if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) + if (windowMode != Settings::WindowMode::Windowed && windowModeIndex != MyGUI::ITEM_NONE) { // check if this resolution is supported in fullscreen if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) @@ -864,8 +860,8 @@ namespace MWGui const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); } bool supported = false; @@ -882,8 +878,7 @@ namespace MWGui fallbackY = resY; } - if (resX == Settings::Manager::getInt("resolution x", "Video") - && resY == Settings::Manager::getInt("resolution y", "Video")) + if (resX == Settings::video().mResolutionX && resY == Settings::video().mResolutionY) supported = true; } @@ -891,26 +886,21 @@ namespace MWGui { if (fallbackX != 0 && fallbackY != 0) { - Settings::Manager::setInt("resolution x", "Video", fallbackX); - Settings::Manager::setInt("resolution y", "Video", fallbackY); + Settings::video().mResolutionX.set(fallbackX); + Settings::video().mResolutionY.set(fallbackY); } } mWindowBorderButton->setEnabled(false); } - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + if (windowMode == Settings::WindowMode::WindowedFullscreen) mResolutionList->setEnabled(false); } void SettingsWindow::updateVSyncModeSettings() { - int index = static_cast(Settings::Manager::getInt("vsync mode", "Video")); - - if (index < 0 || index > 2) - index = 0; - - mVSyncModeList->setIndexSelected(index); + mVSyncModeList->setIndexSelected(static_cast(Settings::video().mVsyncMode)); } void SettingsWindow::layoutControlsBox() diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 43b37623d5..1d41bab34f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -294,8 +294,7 @@ namespace MWGui += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); mVideoWrapper = std::make_unique(window, viewer); - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); if (useShaders) mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); @@ -1157,25 +1156,22 @@ namespace MWGui changeRes = true; else if (setting.first == "Video" && setting.second == "vsync mode") - mVideoWrapper->setSyncToVBlank(Settings::Manager::getInt("vsync mode", "Video")); + mVideoWrapper->setSyncToVBlank(Settings::video().mVsyncMode); else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast")) - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); } if (changeRes) { - mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), - Settings::Manager::getInt("resolution y", "Video"), - static_cast(Settings::Manager::getInt("window mode", "Video")), - Settings::Manager::getBool("window border", "Video")); + mVideoWrapper->setVideoMode(Settings::video().mResolutionX, Settings::video().mResolutionY, + Settings::video().mWindowMode, Settings::video().mWindowBorder); } } void WindowManager::windowResized(int x, int y) { - Settings::Manager::setInt("resolution x", "Video", x); - Settings::Manager::setInt("resolution y", "Video", y); + Settings::video().mResolutionX.set(x); + Settings::video().mResolutionY.set(y); // We only want to process changes to window-size related settings. Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } }; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index eb82c160f9..5f9a3fde85 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -170,10 +170,9 @@ namespace MWInput void ActionManager::screenshot() { - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - bool regularScreenshot = settingStr.empty() || settingStr == "regular"; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - if (regularScreenshot) + if (settings.mType == Settings::ScreenshotType::Regular) { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 32e48a008e..298006030a 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -42,8 +42,7 @@ namespace MWInput float angle = 0; - SDL_DisplayOrientation currentOrientation - = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); + SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::video().mScreen); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index bbdba00ee2..e3470eb853 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -94,8 +94,8 @@ namespace MWLua api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); osg::Matrixf invertedViewMatrix; @@ -106,8 +106,8 @@ namespace MWLua }; api["worldToViewportVector"] = [camera](osg::Vec3f pos) { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; osg::Matrix windowMatrix = osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 79f5fac9a1..d42f7b0637 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -239,10 +239,7 @@ namespace MWLua return luaManager->uiResourceManager()->registerTexture(data); }; - api["screenSize"] = []() { - return osg::Vec2f( - Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); - }; + api["screenSize"] = []() { return osg::Vec2f(Settings::video().mResolutionX, Settings::video().mResolutionY); }; api["_getAllUiModes"] = [](sol::this_state lua) { sol::table res(lua, sol::create); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index fbf5bf112a..86371dc0bf 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -154,7 +154,7 @@ namespace MWRender public: CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) - : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, + : RTTNode(sizeX, sizeY, Settings::video().mAntialiasing, false, 0, StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget()) , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) { diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 9162657e30..a6945de299 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -112,7 +112,7 @@ namespace MWRender : osg::Group() , mEnableLiveReload(false) , mRootNode(rootNode) - , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mSamples(Settings::video().mAntialiasing) , mDirty(false) , mDirtyFrameId(0) , mRendering(rendering) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..c5d268f561 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1230,8 +1230,8 @@ namespace MWRender if (mViewDistance < mNearClip) throw std::runtime_error("Viewing distance is less than near clip"); - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 336a321cf0..b4bdda0a28 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,7 +21,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwgui/loadingscreen.hpp" #include "postprocessor.hpp" #include "util.hpp" @@ -29,7 +29,7 @@ namespace MWRender { - enum Screenshot360Type + enum class Screenshot360Type { Spherical, Cylindrical, @@ -161,59 +161,46 @@ namespace MWRender bool ScreenshotManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - Screenshot360Type screenshotMapping = Spherical; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); + Screenshot360Type screenshotMapping = Screenshot360Type::Spherical; - if (settingArgs.size() > 0) + switch (settings.mType) { - std::string_view typeStrings[4] = { "spherical", "cylindrical", "planet", "cubemap" }; - bool found = false; - - for (int i = 0; i < 4; ++i) - { - if (settingArgs[0] == typeStrings[i]) - { - screenshotMapping = static_cast(i); - found = true; - break; - } - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + case Settings::ScreenshotType::Regular: + Log(Debug::Warning) << "Wrong screenshot 360 type: regular."; return false; - } + case Settings::ScreenshotType::Cylindrical: + screenshotMapping = Screenshot360Type::Cylindrical; + break; + case Settings::ScreenshotType::Spherical: + screenshotMapping = Screenshot360Type::Spherical; + break; + case Settings::ScreenshotType::Planet: + screenshotMapping = Screenshot360Type::Planet; + break; + case Settings::ScreenshotType::Cubemap: + screenshotMapping = Screenshot360Type::RawCubemap; + break; } + int screenshotW = mViewer->getCamera()->getViewport()->width(); + + if (settings.mWidth.has_value()) + screenshotW = *settings.mWidth; + + int screenshotH = mViewer->getCamera()->getViewport()->height(); + + if (settings.mHeight.has_value()) + screenshotH = *settings.mHeight; + // planet mapping needs higher resolution - int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - { - screenshotW = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); - } - - if (settingArgs.size() > 2) - { - screenshotH = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); - } - - if (settingArgs.size() > 3) - { - cubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); - } - - bool rawCubemap = screenshotMapping == RawCubemap; + const int cubeSize = screenshotMapping == Screenshot360Type::Planet ? screenshotW : screenshotW / 2; + const bool rawCubemap = screenshotMapping == Screenshot360Type::RawCubemap; if (rawCubemap) screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == Planet) + else if (screenshotMapping == Screenshot360Type::Planet) screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; @@ -276,7 +263,7 @@ namespace MWRender stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); - stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); screenshotCamera->addChild(quad); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 234b022f5d..cd03784e8c 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -70,7 +70,7 @@ namespace MWRender bool shouldAddMSAAIntermediateTarget() { - return Settings::shaders().mAntialiasAlphaTest && Settings::Manager::getInt("antialiasing", "Video") > 1; + return Settings::shaders().mAntialiasAlphaTest && Settings::video().mAntialiasing > 1; } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 36bd74d217..b2fc46f358 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -87,6 +87,8 @@ add_component_dir (settings settingvalue shadermanager values + screenshotsettings + windowmode ) add_component_dir (bsa @@ -336,7 +338,15 @@ add_component_dir (fontloader ) add_component_dir (sdlutil - gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager sdlmappings + events + gl4es_init + imagetosurface + sdlcursormanager + sdlgraphicswindow + sdlinputwrapper + sdlmappings + sdlvideowrapper + vsyncmode ) add_component_dir (version diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 3c2efc3728..36947460df 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -14,22 +14,16 @@ namespace SDLUtil close(true); } - GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync) + GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode) : mWindow(nullptr) , mContext(nullptr) , mValid(false) , mRealized(false) , mOwnsWindow(false) + , mVSyncMode(vsyncMode) { _traits = traits; - if (vsync == 2) - mVSyncMode = VSyncMode::Adaptive; - else if (vsync == 1) - mVSyncMode = VSyncMode::Enabled; - else - mVSyncMode = VSyncMode::Disabled; - init(); if (GraphicsWindowSDL2::valid()) { diff --git a/components/sdlutil/sdlgraphicswindow.hpp b/components/sdlutil/sdlgraphicswindow.hpp index 3af6ef9276..238c872fb9 100644 --- a/components/sdlutil/sdlgraphicswindow.hpp +++ b/components/sdlutil/sdlgraphicswindow.hpp @@ -5,14 +5,10 @@ #include +#include "vsyncmode.hpp" + namespace SDLUtil { - enum VSyncMode - { - Disabled = 0, - Enabled = 1, - Adaptive = 2 - }; class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow { @@ -29,7 +25,7 @@ namespace SDLUtil virtual ~GraphicsWindowSDL2(); public: - GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync); + GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode); bool isSameKindAs(const Object* object) const override { diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 07cab33ad3..cc9706732e 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,7 +1,7 @@ #include "sdlinputwrapper.hpp" #include -#include +#include #include @@ -187,8 +187,10 @@ namespace SDLUtil { case SDL_DISPLAYEVENT_ORIENTATION: if (mSensorListener - && evt.display.display == (unsigned int)Settings::Manager::getInt("screen", "Video")) + && evt.display.display == static_cast(Settings::video().mScreen)) + { mSensorListener->displayOrientationChanged(); + } break; default: break; diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index 3b612214b8..d93c16aace 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -30,14 +30,8 @@ namespace SDLUtil SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); } - void VideoWrapper::setSyncToVBlank(int mode) + void VideoWrapper::setSyncToVBlank(VSyncMode vsyncMode) { - VSyncMode vsyncMode = VSyncMode::Disabled; - if (mode == 1) - vsyncMode = VSyncMode::Enabled; - else if (mode == 2) - vsyncMode = VSyncMode::Adaptive; - osgViewer::Viewer::Windows windows; mViewer->getWindows(windows); mViewer->stopThreading(); @@ -47,7 +41,7 @@ namespace SDLUtil if (GraphicsWindowSDL2* sdl2win = dynamic_cast(win)) sdl2win->setSyncToVBlank(vsyncMode); else - win->setSyncToVBlank(static_cast(mode)); + win->setSyncToVBlank(vsyncMode != VSyncMode::Disabled); } mViewer->startThreading(); } diff --git a/components/sdlutil/sdlvideowrapper.hpp b/components/sdlutil/sdlvideowrapper.hpp index 9ed6ff1252..7977de40a7 100644 --- a/components/sdlutil/sdlvideowrapper.hpp +++ b/components/sdlutil/sdlvideowrapper.hpp @@ -5,6 +5,8 @@ #include +#include "vsyncmode.hpp" + struct SDL_Window; namespace osgViewer @@ -26,7 +28,7 @@ namespace SDLUtil VideoWrapper(SDL_Window* window, osg::ref_ptr viewer); ~VideoWrapper(); - void setSyncToVBlank(int mode); + void setSyncToVBlank(VSyncMode vsyncMode); void setGammaContrast(float gamma, float contrast); diff --git a/components/sdlutil/vsyncmode.hpp b/components/sdlutil/vsyncmode.hpp new file mode 100644 index 0000000000..5156addc40 --- /dev/null +++ b/components/sdlutil/vsyncmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H +#define OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H + +namespace SDLUtil +{ + enum VSyncMode + { + Disabled = 0, + Enabled = 1, + Adaptive = 2 + }; +} + +#endif diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index fd6bb0018a..0e0f7a75bb 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,8 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include +#include + +#include #include #include @@ -20,16 +24,16 @@ namespace Settings SettingValue mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) }; SettingValue mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) }; - SettingValue mWindowMode{ mIndex, "Video", "window mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mWindowMode{ mIndex, "Video", "window mode" }; SettingValue mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) }; SettingValue mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" }; SettingValue mWindowBorder{ mIndex, "Video", "window border" }; SettingValue mAntialiasing{ mIndex, "Video", "antialiasing", makeMaxSanitizerInt(0) }; - SettingValue mVsyncMode{ mIndex, "Video", "vsync mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mVsyncMode{ mIndex, "Video", "vsync mode" }; SettingValue mFramerateLimit{ mIndex, "Video", "framerate limit", makeMaxSanitizerFloat(0) }; SettingValue mContrast{ mIndex, "Video", "contrast", makeMaxStrictSanitizerFloat(0) }; SettingValue mGamma{ mIndex, "Video", "gamma", makeMaxStrictSanitizerFloat(0) }; - SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; + SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; }; } diff --git a/components/settings/screenshotsettings.hpp b/components/settings/screenshotsettings.hpp new file mode 100644 index 0000000000..6475ace005 --- /dev/null +++ b/components/settings/screenshotsettings.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H +#define OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H + +#include +#include + +namespace Settings +{ + enum class ScreenshotType + { + Regular, + Cylindrical, + Spherical, + Planet, + Cubemap, + }; + + struct ScreenshotSettings + { + ScreenshotType mType; + std::optional mWidth; + std::optional mHeight; + std::optional mCubeSize; + + auto operator<=>(const ScreenshotSettings& value) const = default; + }; +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7b31bf6aad..c64e179efd 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -119,6 +119,23 @@ namespace Settings Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast(value) << ", fallback to auto (-1)"; return -1; } + + ScreenshotType parseScreenshotType(std::string_view value) + { + if (value == "regular") + return ScreenshotType::Regular; + if (value == "spherical") + return ScreenshotType::Spherical; + if (value == "cylindrical") + return ScreenshotType::Cylindrical; + if (value == "planet") + return ScreenshotType::Planet; + if (value == "cubemap") + return ScreenshotType::Cubemap; + + Log(Debug::Warning) << "Invalid screenshot type: " << value << ", fallback to regular"; + return ScreenshotType::Regular; + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); @@ -501,6 +518,16 @@ namespace Settings setInt(setting, category, toInt(value)); } + void Manager::set(std::string_view setting, std::string_view category, WindowMode value) + { + setInt(setting, category, static_cast(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value) + { + setInt(setting, category, static_cast(value)); + } + void Manager::recordInit(std::string_view setting, std::string_view category) { sInitialized.emplace(category, setting); @@ -547,4 +574,28 @@ namespace Settings Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; return SceneUtil::LightingMethod::PerObjectUniform; } + + ScreenshotSettings parseScreenshotSettings(std::string_view value) + { + std::vector settingArgs; + Misc::StringUtils::split(value, settingArgs); + + ScreenshotSettings result; + + if (settingArgs.size() > 0) + result.mType = parseScreenshotType(settingArgs[0]); + else + result.mType = ScreenshotType::Regular; + + if (settingArgs.size() > 1) + result.mWidth = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); + + if (settingArgs.size() > 2) + result.mHeight = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); + + if (settingArgs.size() > 3) + result.mCubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); + + return result; + } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index a5b75fd445..c061755bc1 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -5,9 +5,12 @@ #include "gyroscopeaxis.hpp" #include "hrtfmode.hpp" #include "navmeshrendermode.hpp" +#include "screenshotsettings.hpp" +#include "windowmode.hpp" #include #include +#include #include #include @@ -27,13 +30,6 @@ namespace Files namespace Settings { - enum class WindowMode - { - Fullscreen = 0, - WindowedFullscreen, - Windowed - }; - /// /// \brief Settings management (can change during runtime) /// @@ -114,6 +110,8 @@ namespace Settings static void set(std::string_view setting, std::string_view category, const MyGUI::Colour& value); static void set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value); static void set(std::string_view setting, std::string_view category, HrtfMode value); + static void set(std::string_view setting, std::string_view category, WindowMode value); + static void set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value); private: static std::set> sInitialized; @@ -239,6 +237,32 @@ namespace Settings return HrtfMode::Enable; return HrtfMode::Disable; } + + template <> + inline WindowMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return WindowMode::Fullscreen; + return static_cast(value); + } + + template <> + inline SDLUtil::VSyncMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return SDLUtil::VSyncMode::Disabled; + return static_cast(value); + } + + ScreenshotSettings parseScreenshotSettings(std::string_view value); + + template <> + inline ScreenshotSettings Manager::getImpl(std::string_view setting, std::string_view category) + { + return parseScreenshotSettings(getString(setting, category)); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 7b49c9bda2..2f239a4ebd 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -42,6 +42,9 @@ namespace Settings NavMeshRenderMode, LightingMethod, HrtfMode, + WindowMode, + VSyncMode, + ScreenshotSettings, }; template @@ -161,6 +164,24 @@ namespace Settings return SettingValueType::HrtfMode; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::WindowMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::VSyncMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::ScreenshotSettings; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -203,6 +224,12 @@ namespace Settings return "lighting method"; case SettingValueType::HrtfMode: return "hrtf mode"; + case SettingValueType::WindowMode: + return "window mode"; + case SettingValueType::VSyncMode: + return "vsync mode"; + case SettingValueType::ScreenshotSettings: + return "screenshot settings"; } return "unsupported"; } @@ -361,6 +388,17 @@ namespace Settings } return stream; } + else if constexpr (std::is_same_v) + { + stream << "ScreenshotSettings{ .mType = " << static_cast(value.mValue.mType); + if (value.mValue.mWidth.has_value()) + stream << ", .mWidth = " << *value.mValue.mWidth; + if (value.mValue.mHeight.has_value()) + stream << ", .mHeight = " << *value.mValue.mHeight; + if (value.mValue.mCubeSize.has_value()) + stream << ", .mCubeSize = " << *value.mValue.mCubeSize; + return stream << " }"; + } else return stream << value.mValue; } diff --git a/components/settings/windowmode.hpp b/components/settings/windowmode.hpp new file mode 100644 index 0000000000..96a81059f4 --- /dev/null +++ b/components/settings/windowmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H +#define OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H + +namespace Settings +{ + enum class WindowMode + { + Fullscreen = 0, + WindowedFullscreen = 1, + Windowed = 2, + }; +} + +#endif diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bf82d52078..29660e3580 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -273,7 +273,7 @@ namespace Stereo void Manager::updateStereoFramebuffer() { // VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created - // auto samples = Settings::Manager::getInt("antialiasing", "Video"); + // auto samples = /*do not use Settings here*/; // auto eyeRes = eyeResolution(); // if (mMultiviewFramebuffer) diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 4cb5ba1842..801cf63d5b 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -194,3 +194,12 @@ Gamma is an exponent that makes colors brighter if greater than 1.0 and darker i This setting can be changed in the Detail tab of the Video panel of the Options menu. It has been reported to not work on some Linux systems, and therefore the in-game setting in the Options menu has been disabled on Linux systems. + +screenshot type +--------------- + +:Type: screenshot settings +:Default: regular + +Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by +screenshot width, height and cubemap resolution in pixels (e.g. spherical 1600 1000 1200). diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ece773a677..bbc6b4d1c8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -640,7 +640,7 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 -# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by # screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) screenshot type = regular From 3f4591eb3b3e4e6ec0e27cf51f8df07c6ea19607 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 27 Oct 2023 21:22:43 +0300 Subject: [PATCH 0351/2167] Add movable static and ESM4 land texture stores --- apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwlua/cellbindings.cpp | 4 ++++ apps/openmw/mwlua/types/types.cpp | 3 +++ apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/cellstore.hpp | 6 ++++-- apps/openmw/mwworld/esmstore.cpp | 1 + apps/openmw/mwworld/esmstore.hpp | 10 ++++++---- apps/openmw/mwworld/store.cpp | 2 ++ components/esm/records.hpp | 2 ++ files/lua_api/openmw/types.lua | 3 +++ 10 files changed, 28 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 1668799eba..5017b1b272 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Light::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Npc::registerSelf(); ESM4Named::registerSelf(); ESM4Static::registerSelf(); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 46a72c0848..7c3b31c7b6 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -240,6 +241,9 @@ namespace MWLua case ESM::REC_MISC4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_MSTT4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_ALCH4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index eeb7061cc3..bd8b592f7a 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -47,6 +47,7 @@ namespace MWLua constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; + constexpr std::string_view ESM4MovableStatic = "ESM4MovableStatic"; constexpr std::string_view ESM4Potion = "ESM4Potion"; constexpr std::string_view ESM4Static = "ESM4Static"; constexpr std::string_view ESM4Terminal = "ESM4Terminal"; @@ -91,6 +92,7 @@ namespace MWLua { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, + { ESM::REC_MSTT4, ObjectTypeName::ESM4MovableStatic }, { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, { ESM::REC_STAT4, ObjectTypeName::ESM4Static }, { ESM::REC_TERM4, ObjectTypeName::ESM4Terminal }, @@ -230,6 +232,7 @@ namespace MWLua addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); + addType(ObjectTypeName::ESM4MovableStatic, { ESM::REC_MSTT4 }); addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); addESM4TerminalBindings(addType(ObjectTypeName::ESM4Terminal, { ESM::REC_TERM4 }), context); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index bad2181a06..ff79940540 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 13409ab169..debd80a97b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -73,6 +73,7 @@ namespace ESM4 struct Flora; struct Ingredient; struct MiscItem; + struct MovableStatic; struct Terminal; struct Tree; struct Weapon; @@ -95,8 +96,9 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList>; + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index ad3d1f8d43..12fbc0d4df 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -301,6 +301,7 @@ namespace MWWorld case ESM::REC_LVLC4: case ESM::REC_LVLN4: case ESM::REC_MISC4: + case ESM::REC_MSTT4: case ESM::REC_NPC_4: case ESM::REC_STAT4: case ESM::REC_TERM4: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 821ca6f488..ceba4a26ef 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -92,11 +92,13 @@ namespace ESM4 struct HeadPart; struct Ingredient; struct Land; + struct LandTexture; struct LevelledCreature; struct LevelledItem; struct LevelledNpc; struct Light; struct MiscItem; + struct MovableStatic; struct Npc; struct Outfit; struct Potion; @@ -139,10 +141,10 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store>; + Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 22f6857a24..e42675c66e 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1363,11 +1363,13 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; 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/components/esm/records.hpp b/components/esm/records.hpp index ccd03a05b9..4473e0290f 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -62,10 +62,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 3df689ce0e..69ce5fbaf2 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1881,6 +1881,9 @@ --- Functions for @{#ESM4Miscellaneous} objects -- @field [parent=#types] #ESM4Miscellaneous ESM4Miscellaneous +--- Functions for @{#ESM4MovableStatic} objects +-- @field [parent=#types] #ESM4MovableStatic ESM4MovableStatic + --- Functions for @{#ESM4Potion} objects -- @field [parent=#types] #ESM4Potion ESM4Potion From 84cad3de01340c97d9abf2f52c4a3b6f752addcc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 27 Oct 2023 22:03:08 +0300 Subject: [PATCH 0352/2167] Don't show tooltips for unnamed ESM4Named --- apps/openmw/mwclass/esm4base.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 5c05d81692..9c02af963d 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -138,17 +138,17 @@ namespace MWClass { } - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } - - MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override - { - return ESM4Impl::getToolTipInfo(ptr.get()->mBase->mFullName, count); - } - std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return ptr.get()->mBase->mFullName; } + + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override + { + return ESM4Impl::getToolTipInfo(getName(ptr), count); + } + + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return !getName(ptr).empty(); } }; } From 7e5a1cec04dc86bc6e05f4391980b0fb1740494c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 22:29:34 +0200 Subject: [PATCH 0353/2167] Use settings values for Terrain settings --- apps/openmw/mwgui/mapwindow.cpp | 2 +- apps/openmw/mwgui/settingswindow.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 12 ++++++------ apps/openmw/mwrender/renderingmanager.cpp | 20 +++++++++----------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 088024ac63..6d79b5f9d7 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -89,7 +89,7 @@ namespace { if (!Settings::map().mAllowZooming) return Constants::CellGridRadius; - if (!Settings::Manager::getBool("distant terrain", "Terrain")) + if (!Settings::terrain().mDistantTerrain) return Constants::CellGridRadius; const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits; return std::clamp( diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1a41f9bb55..71bf276a34 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -258,7 +258,7 @@ namespace MWGui , mKeyboardMode(true) , mCurrentPage(-1) { - bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool terrain = Settings::terrain().mDistantTerrain; const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; MyGUI::Widget* unusedSlider; getWidget(unusedSlider, widgetName); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 385ce46fff..99ebb94647 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -457,14 +457,14 @@ namespace MWRender : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , Terrain::QuadTreeWorld::ChunkManager(worldspace) , mSceneManager(sceneManager) + , mActiveGrid(Settings::terrain().mObjectPagingActiveGrid) + , mDebugBatches(Settings::terrain().mDebugChunks) + , mMergeFactor(Settings::terrain().mObjectPagingMergeFactor) + , mMinSize(Settings::terrain().mObjectPagingMinSize) + , mMinSizeMergeFactor(Settings::terrain().mObjectPagingMinSizeMergeFactor) + , mMinSizeCostMultiplier(Settings::terrain().mObjectPagingMinSizeCostMultiplier) , mRefTrackerLocked(false) { - mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); - mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); - mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); - mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); - mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } std::map ObjectPaging::collectESM3References( diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..23508e70bd 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1316,23 +1316,21 @@ namespace MWRender return existingChunkMgr->second; RenderingManager::WorldspaceChunkMgr newChunkMgr; - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; - bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool distantTerrain = Settings::terrain().mDistantTerrain; if (distantTerrain || groundcover) { - const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); - int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); - compMapPower = std::max(-3, compMapPower); - float compMapLevel = pow(2, compMapPower); - const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); - float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); - maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); - bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); + const int compMapResolution = Settings::terrain().mCompositeMapResolution; + const int compMapPower = Settings::terrain().mCompositeMapLevel; + const float compMapLevel = std::pow(2, compMapPower); + const int vertexLodMod = Settings::terrain().mVertexLodMod; + const float maxCompGeometrySize = Settings::terrain().mMaxCompositeGeometrySize; + const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); - if (Settings::Manager::getBool("object paging", "Terrain")) + if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging = std::make_unique(mResourceSystem->getSceneManager(), worldspace); From 561a6bf854d9b5b9486eab1bb471fbf928f220a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 23:26:50 +0200 Subject: [PATCH 0354/2167] Avoid using camera settings from stereo manager --- apps/openmw/engine.cpp | 5 ++++- components/stereo/stereomanager.cpp | 17 ++++++++--------- components/stereo/stereomanager.hpp | 12 +++++++++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7393562cfb..06f180310e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -313,6 +313,8 @@ bool OMW::Engine::frame(float frametime) mLuaManager->reportStats(frameNumber, *stats); } + mStereoManager->updateSettings(Settings::camera().mNearClip, Settings::camera().mViewingDistance); + mViewer->eventTraversal(); mViewer->updateTraversal(); @@ -634,7 +636,8 @@ void OMW::Engine::prepareEngine() bool stereoEnabled = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); - mStereoManager = std::make_unique(mViewer, stereoEnabled); + mStereoManager = std::make_unique( + mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bf82d52078..b7023095d2 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -114,13 +114,15 @@ namespace Stereo return *sInstance; } - Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo) + Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far) : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0, 0) + , mNear(near) + , mFar(far) , mFrustumManager(nullptr) , mUpdateViewCallback(nullptr) { @@ -289,20 +291,17 @@ namespace Stereo void Manager::update() { - const double near_ = Settings::camera().mNearClip; - const double far_ = Settings::camera().mViewingDistance; - if (mUpdateViewCallback) { mUpdateViewCallback->updateView(mView[0], mView[1]); mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true); - mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); - mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); + mProjectionMatrix[0] = mView[0].perspectiveMatrix(mNear, mFar, false); + mProjectionMatrix[1] = mView[1].perspectiveMatrix(mNear, mFar, false); if (SceneUtil::AutoDepth::isReversed()) { - mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); - mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); + mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(mNear, mFar, true); + mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(mNear, mFar, true); } View masterView; @@ -310,7 +309,7 @@ namespace Stereo masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); - auto projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); + auto projectionMatrix = masterView.perspectiveMatrix(mNear, mFar, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 6f4e971718..40bb9fd10c 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -76,12 +76,20 @@ namespace Stereo //! @Param viewer the osg viewer whose stereo should be managed. //! @Param enableStereo whether or not stereo should be enabled. //! @Param enableMultiview whether or not to make use of the GL_OVR_Multiview extension, if supported. - Manager(osgViewer::Viewer* viewer, bool enableStereo); + //! @Param near defines distance to near camera clipping plane from view point. + //! @Param far defines distance to far camera clipping plane from view point. + explicit Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far); ~Manager(); //! Called during update traversal void update(); + void updateSettings(double near, double far) + { + mNear = near; + mFar = far; + } + //! Initializes all details of stereo if applicable. If the constructor was called with enableMultiview=true, //! and the GL_OVR_Multiview extension is supported, Stereo::getMultiview() will return true after this call. void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview); @@ -138,6 +146,8 @@ namespace Stereo std::shared_ptr mMultiviewFramebuffer; bool mEyeResolutionOverriden; osg::Vec2i mEyeResolutionOverride; + double mNear; + double mFar; std::array mView; std::array mViewOffsetMatrix; From bb7ac64f194b0807f475330ac24a0357375605b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 22:42:43 +0200 Subject: [PATCH 0355/2167] Use settings values for Stereo and Stereo View settings --- apps/openmw/engine.cpp | 61 ++++++++++++++++++- components/settings/categories/stereoview.hpp | 32 +++++----- components/stereo/frustum.cpp | 4 +- components/stereo/frustum.hpp | 2 +- components/stereo/multiview.cpp | 4 +- components/stereo/multiview.hpp | 2 +- components/stereo/stereomanager.cpp | 58 +++++------------- components/stereo/stereomanager.hpp | 25 +++++++- 8 files changed, 117 insertions(+), 71 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 06f180310e..990db6ac38 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -595,7 +595,63 @@ void OMW::Engine::createWindow() realizeOperations->add(mSelectColorFormatOperation); if (Stereo::getStereo()) - realizeOperations->add(new Stereo::InitializeStereoOperation()); + { + Stereo::Settings settings; + + settings.mMultiview = Settings::stereo().mMultiview; + settings.mAllowDisplayListsForMultiview = Settings::stereo().mAllowDisplayListsForMultiview; + settings.mSharedShadowMaps = Settings::stereo().mSharedShadowMaps; + + if (Settings::stereo().mUseCustomView) + { + const osg::Vec3 leftEyeOffset(Settings::stereoView().mLeftEyeOffsetX, + Settings::stereoView().mLeftEyeOffsetY, Settings::stereoView().mLeftEyeOffsetZ); + + const osg::Quat leftEyeOrientation(Settings::stereoView().mLeftEyeOrientationX, + Settings::stereoView().mLeftEyeOrientationY, Settings::stereoView().mLeftEyeOrientationZ, + Settings::stereoView().mLeftEyeOrientationW); + + const osg::Vec3 rightEyeOffset(Settings::stereoView().mRightEyeOffsetX, + Settings::stereoView().mRightEyeOffsetY, Settings::stereoView().mRightEyeOffsetZ); + + const osg::Quat rightEyeOrientation(Settings::stereoView().mRightEyeOrientationX, + Settings::stereoView().mRightEyeOrientationY, Settings::stereoView().mRightEyeOrientationZ, + Settings::stereoView().mRightEyeOrientationW); + + settings.mCustomView = Stereo::CustomView{ + .mLeft = Stereo::View{ + .pose = Stereo::Pose{ + .position = leftEyeOffset, + .orientation = leftEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mLeftEyeFovLeft, + .angleRight = Settings::stereoView().mLeftEyeFovRight, + .angleUp = Settings::stereoView().mLeftEyeFovUp, + .angleDown = Settings::stereoView().mLeftEyeFovDown, + }, + }, + .mRight = Stereo::View{ + .pose = Stereo::Pose{ + .position = rightEyeOffset, + .orientation = rightEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mRightEyeFovLeft, + .angleRight = Settings::stereoView().mRightEyeFovRight, + .angleUp = Settings::stereoView().mRightEyeFovUp, + .angleDown = Settings::stereoView().mRightEyeFovDown, + }, + }, + }; + } + + if (Settings::stereo().mUseCustomEyeResolution) + settings.mEyeResolution + = osg::Vec2i(Settings::stereoView().mEyeResolutionX, Settings::stereoView().mEyeResolutionY); + + realizeOperations->add(new Stereo::InitializeStereoOperation(settings)); + } mViewer->realize(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); @@ -634,8 +690,7 @@ void OMW::Engine::prepareEngine() mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mEnvironment.setStateManager(*mStateManager); - bool stereoEnabled - = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); + const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo(); mStereoManager = std::make_unique( mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index 1e9d35ace8..bcd0f57abc 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -31,14 +31,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mLeftEyeOrientationW{ mIndex, "Stereo View", "left eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; SettingValue mRightEyeOffsetX{ mIndex, "Stereo View", "right eye offset x" }; SettingValue mRightEyeOffsetY{ mIndex, "Stereo View", "right eye offset y" }; SettingValue mRightEyeOffsetZ{ mIndex, "Stereo View", "right eye offset z" }; @@ -50,14 +50,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mRightEyeOrientationW{ mIndex, "Stereo View", "right eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; }; } diff --git a/components/stereo/frustum.cpp b/components/stereo/frustum.cpp index cf8a7e8c30..a35beba7dd 100644 --- a/components/stereo/frustum.cpp +++ b/components/stereo/frustum.cpp @@ -85,7 +85,7 @@ namespace Stereo } } - StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) + StereoFrustumManager::StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera) : mCamera(camera) , mShadowTechnique(nullptr) , mShadowFrustumCallback(nullptr) @@ -95,7 +95,7 @@ namespace Stereo mMultiviewFrustumCallback = std::make_unique(this, camera); } - if (Settings::Manager::getBool("shared shadow maps", "Stereo")) + if (sharedShadowMaps) { mShadowFrustumCallback = new ShadowFrustumCallback(this); auto* renderer = static_cast(mCamera->getRenderer()); diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp index c3abdd87d2..35e3adf95a 100644 --- a/components/stereo/frustum.hpp +++ b/components/stereo/frustum.hpp @@ -45,7 +45,7 @@ namespace Stereo class StereoFrustumManager { public: - StereoFrustumManager(osg::Camera* camera); + StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera); ~StereoFrustumManager(); void update(std::array projections); diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index 047a52747b..a2f6cfd626 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -124,12 +124,12 @@ namespace Stereo } } - void setVertexBufferHint(bool enableMultiview) + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview) { if (getStereo() && enableMultiview) { auto* ds = osg::DisplaySettings::instance().get(); - if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") + if (!allowDisplayListsForMultiview && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) { // Note that this only works if this code is executed before realize() is called on the viewer. diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index fa69afc7a1..a9d84eae85 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -37,7 +37,7 @@ namespace Stereo void configureExtensions(unsigned int contextID, bool enableMultiview); //! Sets the appropriate vertex buffer hint on OSG's display settings if needed - void setVertexBufferHint(bool enableMultiview); + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview); //! Creates a Texture2D as a texture view into a Texture2DArray osg::ref_ptr createTextureView_Texture2DFromTexture2DArray( diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index b7023095d2..29de9d0bb0 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -134,13 +134,13 @@ namespace Stereo Manager::~Manager() {} - void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview) + void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps) { auto ci = gc->getState()->getContextID(); configureExtensions(ci, enableMultiview); mMainCamera->addUpdateCallback(mUpdateCallback); - mFrustumManager = std::make_unique(mViewer->getCamera()); + mFrustumManager = std::make_unique(sharedShadowMaps, mViewer->getCamera()); if (getMultiview()) setupOVRMultiView2Technique(); @@ -393,60 +393,30 @@ namespace Stereo right = mRight; } - InitializeStereoOperation::InitializeStereoOperation() + InitializeStereoOperation::InitializeStereoOperation(const Settings& settings) : GraphicsOperation("InitializeStereoOperation", false) + , mMultiview(settings.mMultiview) + , mSharedShadowMaps(settings.mSharedShadowMaps) + , mCustomView(settings.mCustomView) + , mEyeResolution(settings.mEyeResolution) { // Ideally, this would have belonged to the operator(). But the vertex buffer // hint has to be set before realize is called on the osg viewer, and so has to // be done here instead. - Stereo::setVertexBufferHint(Settings::Manager::getBool("multiview", "Stereo")); + Stereo::setVertexBufferHint(settings.mMultiview, settings.mAllowDisplayListsForMultiview); } void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext) { auto& sm = Stereo::Manager::instance(); - if (Settings::Manager::getBool("use custom view", "Stereo")) - { - Stereo::View left; - Stereo::View right; + if (mCustomView.has_value()) + sm.setUpdateViewCallback( + std::make_shared(mCustomView->mLeft, mCustomView->mRight)); - left.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); - left.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); - left.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); - left.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); - left.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); - left.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); - left.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); - left.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); - left.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); - left.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); - left.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); + if (mEyeResolution.has_value()) + sm.overrideEyeResolution(*mEyeResolution); - right.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); - right.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); - right.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); - right.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); - right.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); - right.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); - right.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); - right.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); - right.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); - right.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); - right.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); - - auto customViewCallback = std::make_shared(left, right); - sm.setUpdateViewCallback(customViewCallback); - } - - if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) - { - osg::Vec2i eyeResolution = osg::Vec2i(); - eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); - eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); - sm.overrideEyeResolution(eyeResolution); - } - - sm.initializeStereo(graphicsContext, Settings::Manager::getBool("multiview", "Stereo")); + sm.initializeStereo(graphicsContext, mMultiview, mSharedShadowMaps); } } diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 40bb9fd10c..8ed88da550 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -92,7 +92,7 @@ namespace Stereo //! Initializes all details of stereo if applicable. If the constructor was called with enableMultiview=true, //! and the GL_OVR_Multiview extension is supported, Stereo::getMultiview() will return true after this call. - void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview); + void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps); //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); @@ -163,13 +163,34 @@ namespace Stereo osg::ref_ptr mIdentifierRight = new Identifier(); }; + struct CustomView + { + Stereo::View mLeft; + Stereo::View mRight; + }; + + struct Settings + { + bool mMultiview; + bool mAllowDisplayListsForMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; + }; + //! Performs stereo-specific initialization operations. class InitializeStereoOperation final : public osg::GraphicsOperation { public: - InitializeStereoOperation(); + explicit InitializeStereoOperation(const Settings& settings); void operator()(osg::GraphicsContext* graphicsContext) override; + + private: + bool mMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; }; } From 6cd5734fb33abfc37746dc5de44184836265e632 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 21:29:37 +0200 Subject: [PATCH 0356/2167] Rate weapons as useless if the actor's skill is 0 --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/weaponpriority.cpp | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97693047b..7e9d31a19e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index e0584afcd4..dd83db286f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -118,13 +118,17 @@ namespace MWMechanics } int value = 50.f; - if (actor.getClass().isNpc()) - { - ESM::RefId skill = item.getClass().getEquipmentSkill(item); - if (!skill.empty()) - value = actor.getClass().getSkill(actor, skill); - } - else + ESM::RefId skill = item.getClass().getEquipmentSkill(item); + if (!skill.empty()) + value = actor.getClass().getSkill(actor, skill); + // Prefer hand-to-hand if our skill is 0 (presumably due to magic) + if (value <= 0.f) + return 0.f; + // Note that a creature with a dagger and 0 Stealth will forgo the weapon despite using Combat for hit chance. + // The same creature will use a sword provided its Combat stat isn't 0. We're using the "skill" value here to + // decide whether to use the weapon at all, but adjusting the final rating based on actual hit chance - i.e. the + // Combat stat. + if (!actor.getClass().isNpc()) { MWWorld::LiveCellRef* ref = actor.get(); value = ref->mBase->mData.mCombat; From 09928ba2653a1e6a7726d1068c7362328cc90550 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sat, 28 Oct 2023 10:23:48 -0700 Subject: [PATCH 0357/2167] use a dynamic falloff range for soft effect and use shader flags --- components/nifosg/nifloader.cpp | 3 ++- components/sceneutil/extradata.cpp | 11 ++++++++--- components/sceneutil/extradata.hpp | 2 +- .../reference/modding/custom-shader-effects.rst | 17 ++++++++++------- files/shaders/compatibility/bs/nolighting.frag | 4 +++- files/shaders/compatibility/objects.frag | 4 +++- files/shaders/lib/particle/soft.glsl | 6 +++--- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5f1032c69..9945a8c40e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2708,7 +2708,8 @@ namespace NifOsg stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); } if (shaderprop->softEffect()) - SceneUtil::setupSoftEffect(*node, shaderprop->mFalloffDepth, true); + SceneUtil::setupSoftEffect( + *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); break; } default: diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index d8d3e871dc..720e032a61 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -15,7 +15,7 @@ namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff) + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth) { static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); @@ -23,6 +23,7 @@ namespace SceneUtil stateset->addUniform(new osg::Uniform("particleSize", size)); stateset->addUniform(new osg::Uniform("particleFade", falloff)); + stateset->addUniform(new osg::Uniform("softFalloffDepth", falloffDepth)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); @@ -35,6 +36,8 @@ namespace SceneUtil std::string source; + constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases + if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) { YAML::Node root = YAML::Load(source); @@ -47,8 +50,9 @@ namespace SceneUtil { auto size = it.second["size"].as(45.f); auto falloff = it.second["falloff"].as(false); + auto falloffDepth = it.second["falloffDepth"].as(defaultFalloffDepth); - setupSoftEffect(node, size, falloff); + setupSoftEffect(node, size, falloff, falloffDepth); } } @@ -56,7 +60,8 @@ namespace SceneUtil } else if (osgParticle::ParticleSystem* partsys = dynamic_cast(&node)) { - setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); + setupSoftEffect( + node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false, defaultFalloffDepth); } traverse(node); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index ddcb9b6c5c..9b1563b78a 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -15,7 +15,7 @@ namespace osg namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff); + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); class ProcessExtraDataVisitor : public osg::NodeVisitor { diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 340ed5ffed..5ea711953d 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -31,13 +31,15 @@ This setting can either be activated in the OpenMW launcher or changed in `setti Variables. -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| Name | Description | Type | Default | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloffDepth | The units at which geometry starts to fade. | float | 300 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ Example usage. @@ -48,6 +50,7 @@ Example usage. "soft_effect" : { "size": 250, "falloff" : false, + "falloffDepth": 5, } } } diff --git a/files/shaders/compatibility/bs/nolighting.frag b/files/shaders/compatibility/bs/nolighting.frag index c5393d4732..c9e3ca4e13 100644 --- a/files/shaders/compatibility/bs/nolighting.frag +++ b/files/shaders/compatibility/bs/nolighting.frag @@ -38,6 +38,7 @@ uniform float alphaRef; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif void main() @@ -71,7 +72,8 @@ void main() far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4ef463cb34..4caf6c97e2 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -98,6 +98,7 @@ varying vec3 passNormal; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif #if @particleOcclusion @@ -256,7 +257,8 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/lib/particle/soft.glsl b/files/shaders/lib/particle/soft.glsl index c415d8402b..99336bc039 100644 --- a/files/shaders/lib/particle/soft.glsl +++ b/files/shaders/lib/particle/soft.glsl @@ -19,7 +19,8 @@ float calcSoftParticleFade( float far, float depth, float size, - bool fade + bool fade, + float softFalloffDepth ) { float euclidianDepth = length(viewPos); @@ -32,13 +33,12 @@ float calcSoftParticleFade( float falloff = size * falloffMultiplier; float delta = particleDepth - sceneDepth; - const float nearMult = 300.0; float viewBias = 1.0; if (fade) { float VdotN = dot(viewDir, viewNormal); - viewBias = abs(VdotN) * quickstep(euclidianDepth / nearMult) * (1.0 - pow(1.0 + VdotN, 1.3)); + viewBias = abs(VdotN) * quickstep(euclidianDepth / softFalloffDepth) * (1.0 - pow(1.0 - abs(VdotN), 1.3)); } const float shift = 0.845; From 24bac3ebd4d2fc24874fd4b393f43f4d7e6d7fb7 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 27 Oct 2023 18:22:13 +0200 Subject: [PATCH 0358/2167] AiWander must update mIsWanderingManually when resuming wander. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9d31a19e..fd1e0ce39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 30756ade35..30aad2e89a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -229,7 +229,7 @@ namespace MWMechanics } if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); } if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) @@ -499,7 +499,7 @@ namespace MWMechanics if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); else storage.setState(AiWanderStorage::Wander_ChooseAction); } From ec81d99f210ce480dc09527025866f7f55e0047a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 27 Oct 2023 18:31:02 +0200 Subject: [PATCH 0359/2167] Add me to authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 99080fdebd..9791171b9c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -140,6 +140,7 @@ Programmers Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) + Mads Sandvei (Foal) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) From 12abd30e9ff94d1ebd0b6b38d34d5837cdb6663b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 29 Oct 2023 04:11:31 +0300 Subject: [PATCH 0360/2167] Make sun specularity behavior more intuitive (bug #6190) Remove sun visibility influence on object specularity Subdue sun visibility influence on water specularity --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwworld/weather.cpp | 3 +-- files/shaders/compatibility/water.frag | 11 +++++++---- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9d31a19e..c5cb8b8ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile Bug #6427: Enemy health bar disappears before damaging effect ends diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..7e593431d0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -702,7 +702,7 @@ namespace MWRender { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); - mSunLight->setSpecular(specular); + mSunLight->setSpecular(osg::Vec4f(specular.x(), specular.y(), specular.z(), specular.w() * sunVis)); mPostProcessor->getStateUpdater()->setSunColor(diffuse); mPostProcessor->getStateUpdater()->setSunVis(sunVis); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 0da70bdd48..5d739a9161 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -789,8 +789,7 @@ namespace MWWorld mRendering.configureFog( mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset / 100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour( - mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 83fb8a53e0..987dab10a6 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -43,6 +43,7 @@ const float SCATTER_AMOUNT = 0.3; // amount of sunlight scatter const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction +const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade const float SPEC_HARDNESS = 256.0; // specular highlights hardness @@ -172,6 +173,8 @@ void main(void) vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); + // alpha component is sun visibility; we want to start fading specularity when visibility is low + sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); @@ -179,7 +182,7 @@ void main(void) #if REFRACTION // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); + rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; @@ -200,7 +203,7 @@ void main(void) vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + rainSpecular; + gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -213,8 +216,8 @@ void main(void) shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; + gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); #endif gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); From cdb325f19ac55f66109769c6fa64aae8567f3b34 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:00:26 +0200 Subject: [PATCH 0361/2167] Rewrite CharacterController::updateAnimQueue() to properly maintain the animation queue and clean up finished animations. --- apps/openmw/mwmechanics/character.cpp | 30 +++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 48abac8b06..42611e1f9f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1852,17 +1852,39 @@ namespace MWMechanics void CharacterController::updateAnimQueue() { - if (mAnimQueue.size() > 1) - { + if (mAnimQueue.empty()) + return; + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + { + // The last animation stopped playing when it shouldn't have. + // This is caused by a rebuild of the animation object, so we should restart the animation here + // Subtract 1 from mLoopCount to consider the current loop done. + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + } + else + { + // Animation is done, remove it from the queue. + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + } + } + else + { + mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); } if (!mAnimQueue.empty()) From 8792e76f5d06b6c19c498e58ee17a0a40228f581 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:35:00 +0200 Subject: [PATCH 0362/2167] explicitly prevent movement whenever playing scripted animations --- apps/openmw/mwmechanics/character.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 42611e1f9f..c83e72cef8 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2366,7 +2366,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled()) + if (!isMovementAnimationControlled() && mAnimQueue.empty()) world->queueMovement(mPtr, vec); } @@ -2435,7 +2435,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) world->queueMovement(mPtr, moved); mSkipAnim = false; From 24890a729bfa736d806154f40f62d859ba06e72f Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:59:05 +0200 Subject: [PATCH 0363/2167] clang'd --- apps/openmw/mwmechanics/character.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c83e72cef8..9c39b5b60d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,16 +1855,16 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) { @@ -1881,7 +1881,7 @@ namespace MWMechanics mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); } - } + } else { mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); From 92a5e524070a88d2cf972c4191b2cee9993914ea Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:53:34 +0200 Subject: [PATCH 0364/2167] rename "persisted" animations to "scripted", because that is what they actually are. And begin teaching the animation system to distinguish between a scripted and an unscripted animation. --- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 59 +++++++++++-------- apps/openmw/mwmechanics/character.hpp | 8 +-- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/objects.cpp | 4 +- apps/openmw/mwmechanics/objects.hpp | 2 +- apps/openmw/mwrender/animation.cpp | 12 +++- apps/openmw/mwrender/animation.hpp | 3 + 10 files changed, 60 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 743c5d5ab5..3e7b075e62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1998,12 +1998,12 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); + return iter->second->getCharacterController().playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 98c64397ab..15a39136a6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -113,7 +113,7 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) const; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; void persistAnimationStates() const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9c39b5b60d..0d9cda9263 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -538,7 +538,7 @@ namespace MWMechanics if (mAnimation->isPlaying("containerclose")) return false; - mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; @@ -559,7 +559,7 @@ namespace MWMechanics if (animPlaying) startPoint = 1.f - complete; - mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); } } @@ -828,7 +828,7 @@ namespace MWMechanics CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // If the current animation is persistent, do not touch it - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; refreshHitRecoilAnims(); @@ -882,7 +882,7 @@ namespace MWMechanics mDeathState = chooseRandomDeathState(); // Do not interrupt scripted animation by death - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; playDeath(startpoint, mDeathState); @@ -1482,7 +1482,7 @@ namespace MWMechanics } // Combat for actors with persistent animations obviously will be buggy - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return forcestateupdate; float complete = 0.f; @@ -1855,10 +1855,18 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + bool isFrontAnimPlaying = false; + if (mAnimQueue.front().mScripted) + isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); + else + isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); + + + if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) { + // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); @@ -1866,14 +1874,13 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) { - // The last animation stopped playing when it shouldn't have. - // This is caused by a rebuild of the animation object, so we should restart the animation here - // Subtract 1 from mLoopCount to consider the current loop done. + // A scripted animation stopped playing before time. + // This happens when the animation object is rebuilt, so we should restart the animation here. bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else { @@ -2393,7 +2400,7 @@ namespace MWMechanics } } - bool isPersist = isPersistentAnimPlaying(); + bool isPersist = isScriptedAnimPlaying(); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if (duration > 0.0f) moved /= duration; @@ -2450,7 +2457,7 @@ namespace MWMechanics state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { - if (!iter->mPersist) + if (!iter->mScripted) continue; ESM::AnimationState::ScriptedAnimation anim; @@ -2486,7 +2493,7 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; + entry.mScripted = true; mAnimQueue.push_back(entry); } @@ -2505,18 +2512,18 @@ namespace MWMechanics mIdleState = CharState_SpecialIdle; bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", complete, anim.mLoopCount, loopfallback); } } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool persist) + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; // We should not interrupt persistent animations by non-persistent ones - if (isPersistentAnimPlaying() && !persist) + if (isScriptedAnimPlaying() && !scripted) return true; // If this animation is a looped animation (has a "loop start" key) that is already playing @@ -2545,17 +2552,17 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = groupname; entry.mLoopCount = count - 1; - entry.mPersist = persist; + entry.mScripted = scripted; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(persist); + clearAnimQueue(scripted); clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, + mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, count - 1, loopfallback); } @@ -2566,7 +2573,7 @@ namespace MWMechanics // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing if (groupname == "idle") - entry.mPersist = false; + entry.mScripted = false; mAnimQueue.push_back(entry); @@ -2578,12 +2585,12 @@ namespace MWMechanics mSkipAnim = true; } - bool CharacterController::isPersistentAnimPlaying() const + bool CharacterController::isScriptedAnimPlaying() const { if (!mAnimQueue.empty()) { const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mPersist && isAnimPlaying(first.mGroup); + return first.mScripted && isAnimPlaying(first.mGroup); } return false; @@ -2609,7 +2616,7 @@ namespace MWMechanics void CharacterController::clearAnimQueue(bool clearPersistAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); if (clearPersistAnims) @@ -2620,7 +2627,7 @@ namespace MWMechanics for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { - if (!it->mPersist) + if (!it->mScripted) it = mAnimQueue.erase(it); else ++it; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 316a1cff0e..79bbe73538 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -40,7 +40,7 @@ namespace MWMechanics Priority_Torch, Priority_Storm, Priority_Death, - Priority_Persistent, + Priority_Scripted, Num_Priorities }; @@ -135,7 +135,7 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; - bool mPersist; + bool mScripted; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; @@ -215,7 +215,7 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isPersistentAnimPlaying() const; + bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -270,7 +270,7 @@ namespace MWMechanics void persistAnimationState() const; void unpersistAnimationState(); - bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); + bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 071ac164f3..f95df16855 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -760,12 +760,12 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { if (ptr.getClass().isActor()) - return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); + return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else - return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); + return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 36bb18e022..997636522e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -142,7 +142,7 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) override; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; void persistAnimationStates() override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index ab981dd459..5bdfc91ac7 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,12 +99,12 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->playGroup(groupName, mode, number, persist); + return iter->second->playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 8b5962109c..296f454e4f 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,7 +46,7 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false); + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b49f382f66..cae3687afb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,6 +1020,14 @@ namespace MWRender return false; } + bool Animation::isPlayingScripted(std::string_view groupname) const + { + AnimStateMap::const_iterator state(mStates.find(groupname)); + if (state != mStates.end()) + return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); + return false; + } + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); @@ -1145,7 +1153,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; @@ -1158,7 +1166,7 @@ namespace MWRender while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) { ++stateiter; continue; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8615811cc3..9a55c91fc4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,6 +432,9 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; + /** Returns true if the named, scripted animation group is playing. */ + bool isPlayingScripted(std::string_view groupname) const; + /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From a9ade184d8f2dda41db4bb51fc2df4d5cdfead60 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:55:10 +0200 Subject: [PATCH 0365/2167] mLoopCount is the number of *remaining* loops. So we should be checking for >0, not >1. --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0d9cda9263..ad201997bd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1874,7 +1874,7 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) + else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) { // A scripted animation stopped playing before time. // This happens when the animation object is rebuilt, so we should restart the animation here. From 426f5952a5bfbdf3c6d2114164a4176b85c6db3e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:02:17 +0200 Subject: [PATCH 0366/2167] Prevent movement only when *scripted* animations are playing. Otherwise idle animations of wandering NPCs will slide. --- apps/openmw/mwmechanics/character.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ad201997bd..08c035ce31 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1861,7 +1861,6 @@ namespace MWMechanics else isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) @@ -2373,7 +2372,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && mAnimQueue.empty()) + if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, vec); } @@ -2442,7 +2441,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2522,7 +2521,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-persistent ones + // We should not interrupt persistent animations by non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; From cf5b782c761a0cbded399f3ef97b451f166bece0 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:05:07 +0200 Subject: [PATCH 0367/2167] More persist -> script language. --- apps/openmw/mwmechanics/character.cpp | 15 +++++++-------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08c035ce31..b3661c8f96 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -827,7 +827,7 @@ namespace MWMechanics void CharacterController::refreshCurrentAnims( CharacterState idle, CharacterState movement, JumpingState jump, bool force) { - // If the current animation is persistent, do not touch it + // If the current animation is scripted, do not touch it if (isScriptedAnimPlaying()) return; @@ -1481,7 +1481,7 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with persistent animations obviously will be buggy + // Combat for actors with scripted animations obviously will be buggy if (isScriptedAnimPlaying()) return forcestateupdate; @@ -2399,8 +2399,7 @@ namespace MWMechanics } } - bool isPersist = isScriptedAnimPlaying(); - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (duration > 0.0f) moved /= duration; else @@ -2521,7 +2520,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-scripted ones + // We should not interrupt scripted animations with non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; @@ -2612,13 +2611,13 @@ namespace MWMechanics return movementAnimationControlled; } - void CharacterController::clearAnimQueue(bool clearPersistAnims) + void CharacterController::clearAnimQueue(bool clearScriptedAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); - if (clearPersistAnims) + if (clearScriptedAnims) { mAnimQueue.clear(); return; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 79bbe73538..c3d45fe0fb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -207,7 +207,7 @@ namespace MWMechanics void refreshMovementAnims(CharacterState movement, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false); - void clearAnimQueue(bool clearPersistAnims = false); + void clearAnimQueue(bool clearScriptedAnims = false); bool updateWeaponState(); void updateIdleStormState(bool inwater) const; From 70f41b22b657aba178e747320535e251efdaa684 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:25:12 +0200 Subject: [PATCH 0368/2167] Based on the reason for closing !1845, we *should* let a scripted animation get stuck forever if the animation was already playing. --- apps/openmw/mwmechanics/character.cpp | 8 +------- apps/openmw/mwrender/animation.cpp | 8 -------- apps/openmw/mwrender/animation.hpp | 3 --- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b3661c8f96..aa045edff6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,13 +1855,7 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - bool isFrontAnimPlaying = false; - if (mAnimQueue.front().mScripted) - isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); - else - isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - - if (!isFrontAnimPlaying) + if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { if (mAnimQueue.size() > 1) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cae3687afb..4ce589f8b9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,14 +1020,6 @@ namespace MWRender return false; } - bool Animation::isPlayingScripted(std::string_view groupname) const - { - AnimStateMap::const_iterator state(mStates.find(groupname)); - if (state != mStates.end()) - return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); - return false; - } - bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9a55c91fc4..8615811cc3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,9 +432,6 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; - /** Returns true if the named, scripted animation group is playing. */ - bool isPlayingScripted(std::string_view groupname) const; - /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From c25c6872b03b4ba0c0df3ded2779b01cc1ca5d67 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:20:37 +0200 Subject: [PATCH 0369/2167] changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..6641f41ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation + Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples From afb9fa06d7279ce90890f3ceba4b9be555412ddf Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:23:29 +0200 Subject: [PATCH 0370/2167] clang format --- apps/openmw/mwrender/animation.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4ce589f8b9..0741e24a69 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1145,8 +1145,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) - && stateiter->second.mPlaying) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; break; From 85f104fefe0f4bf4a29bca465ae05621f3531e6a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 15:33:07 +0100 Subject: [PATCH 0371/2167] Comments from Capo --- apps/openmw/mwmechanics/character.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index aa045edff6..a21e5fbf36 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1857,30 +1857,22 @@ namespace MWMechanics if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - if (mAnimQueue.size() > 1) + // Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of + // the animation object. + if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0) { - // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); + } - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) + if (!mAnimQueue.empty()) { - // A scripted animation stopped playing before time. - // This happens when the animation object is rebuilt, so we should restart the animation here. + // Move on to the remaining items of the queue bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + mAnimation->play(mAnimQueue.front().mGroup, mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else - { - // Animation is done, remove it from the queue. - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - } } else { From 58e3fdac3661d2f796bfd11e388301eea524435d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 16:32:35 +0100 Subject: [PATCH 0372/2167] Clang format --- apps/openmw/mwmechanics/character.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a21e5fbf36..068d91ab42 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1869,9 +1869,10 @@ namespace MWMechanics { // Move on to the remaining items of the queue bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, + mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, + mAnimQueue.front().mLoopCount, loopfallback); } } else From d2836748fd708feba645def1b9f7c43ee98672e9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 20:27:02 +0100 Subject: [PATCH 0373/2167] Changelog entry for #4742 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6641f41ac5..c7756fd437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 Bug #4382: Sound output device does not change when it should Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses From e51dfca4883890dbdef21fa3a1368e5263480d72 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 29 Oct 2023 22:41:53 +0100 Subject: [PATCH 0374/2167] Stop trying to save ESM4 objects in omwsave files because they currently can not be saved and only lead to errors in logs. --- apps/openmw/mwworld/cellstore.cpp | 45 ++++++++++++++++--------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ff79940540..d3c9d73c7c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -161,31 +161,32 @@ namespace template void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { - if (!collection.mList.empty()) + // references + for (const MWWorld::LiveCellRef& liveCellRef : collection.mList) { - // references - for (typename MWWorld::CellRefList::List::const_iterator iter(collection.mList.begin()); - iter != collection.mList.end(); ++iter) + if (ESM::isESM4Rec(T::sRecordId)) { - if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) - { - // Reference that came from a content file and has not been changed -> ignore - continue; - } - if (iter->mData.getCount() == 0 && !iter->mRef.hasContentFile()) - { - // Deleted reference that did not come from a content file -> ignore - continue; - } - using StateType = typename RecordToState::StateType; - StateType state; - iter->save(state); - - // recordId currently unused - writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); - - state.save(writer); + // TODO: Implement loading/saving of REFR4 and ACHR4 with ESM3 reader/writer. + continue; } + if (!liveCellRef.mData.hasChanged() && !liveCellRef.mRef.hasChanged() && liveCellRef.mRef.hasContentFile()) + { + // Reference that came from a content file and has not been changed -> ignore + continue; + } + if (liveCellRef.mData.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) + { + // Deleted reference that did not come from a content file -> ignore + continue; + } + using StateType = typename RecordToState::StateType; + StateType state; + liveCellRef.save(state); + + // recordId currently unused + writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); + + state.save(writer); } } From f4b27a521aacf29297d2f1d408012a1a0cc8ce44 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 30 Oct 2023 02:08:59 +0300 Subject: [PATCH 0375/2167] Read LTEX::INAM --- components/esm4/loadltex.cpp | 3 +++ components/esm4/loadltex.hpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 955ac938e3..9b2d12034f 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -76,6 +76,9 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5, FO4 + case ESM4::SUB_INAM: + reader.get(mMaterialFlags); + break; // SSE default: throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp index bed5887f5c..33c1683ac0 100644 --- a/components/esm4/loadltex.hpp +++ b/components/esm4/loadltex.hpp @@ -63,6 +63,17 @@ namespace ESM4 // ---------------------- + // ------ SSE ----------- + + enum MaterialFlags + { + Flag_IsSnow = 0x1, + }; + + std::uint32_t mMaterialFlags; + + // ---------------------- + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; From 37617974670bfa66fd4d82844bdd5c14ae9ae489 Mon Sep 17 00:00:00 2001 From: Kindi Date: Tue, 12 Sep 2023 03:03:59 +0800 Subject: [PATCH 0376/2167] reimplement partial equipping --- apps/openmw/mwgui/inventorywindow.cpp | 36 ++++++++++++++++----------- apps/openmw/mwgui/inventorywindow.hpp | 1 + 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 610e34e3cd..16f135df17 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -556,6 +556,20 @@ namespace MWGui std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); + // Handles partial equipping (final part) + if (mEquippedStackableCount.has_value()) + { + // the count to unequip + int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); + if (count > 0) + { + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + invStore.unequipItemQuantity(ptr, count); + updateItemView(); + mEquippedStackableCount.reset(); + } + } + if (isVisible()) { mItemView->update(); @@ -581,27 +595,21 @@ namespace MWGui } // Handles partial equipping - const std::pair, bool> slots = ptr.getClass().getEquipmentSlots(ptr); + mEquippedStackableCount.reset(); + const auto slots = ptr.getClass().getEquipmentSlots(ptr); if (!slots.first.empty() && slots.second) { - int equippedStackableCount = 0; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front()); - // Get the count before useItem() + // Save the currently equipped count before useItem() if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) - equippedStackableCount = slotIt->getRefData().getCount(); - - useItem(ptr); - int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount; - if (unequipCount > 0) - { - invStore.unequipItemQuantity(ptr, unequipCount); - updateItemView(); - } + mEquippedStackableCount = slotIt->getRefData().getCount(); + else + mEquippedStackableCount = 0; } - else - MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); + + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index f3d8e3dcd6..9fc77ceec5 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -74,6 +74,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; int mSelectedItem; + std::optional mEquippedStackableCount; MWWorld::Ptr mPtr; From 2d38ca4c35362c75d3dde7227615a720813b32bf Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 30 Oct 2023 15:30:01 +0300 Subject: [PATCH 0377/2167] Improve invisibility breaking consistency (bug #7660) - Break on recharge attempts - Break on repair attempts - Break on potion creation attempts - Don't break on failed ingredient consuming --- CHANGELOG.md | 1 + apps/openmw/mwclass/actor.cpp | 8 ++++++-- apps/openmw/mwmechanics/alchemy.cpp | 2 ++ apps/openmw/mwmechanics/recharge.cpp | 2 ++ apps/openmw/mwmechanics/repair.cpp | 2 ++ 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..8eeea365f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7647: NPC walk cycle bugs after greeting player + Bug #7660: Some inconsistencies regarding Invisibility breaking Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 7f60ae5c2c..9c197a70d2 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -95,12 +95,16 @@ namespace MWClass bool Actor::consume(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) const { - MWBase::Environment::get().getWorld()->breakInvisibility(actor); MWMechanics::CastSpell cast(actor, actor); const ESM::RefId& recordId = consumable.getCellRef().getRefId(); MWBase::Environment::get().getWorldModel()->registerPtr(consumable); MWBase::Environment::get().getLuaManager()->itemConsumed(consumable, actor); actor.getClass().getContainerStore(actor).remove(consumable, 1); - return cast.cast(recordId); + if (cast.cast(recordId)) + { + MWBase::Environment::get().getWorld()->breakInvisibility(actor); + return true; + } + return false; } } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 914de6da2b..995d55759b 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -510,6 +510,8 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create(const std::string& nam if (readyStatus != Result_Success) return readyStatus; + MWBase::Environment::get().getWorld()->breakInvisibility(mAlchemist); + Result result = Result_RandomFailure; int brewedCount = 0; for (int i = 0; i < count; ++i) diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index fc8a0e8a72..9c42e4088c 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -44,6 +44,8 @@ namespace MWMechanics MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWBase::Environment::get().getWorld()->breakInvisibility(player); + float luckTerm = 0.1f * stats.getAttribute(ESM::Attribute::Luck).getModified(); if (luckTerm < 1 || luckTerm > 10) luckTerm = 1; diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 4894b93e7f..3011004244 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -22,6 +22,8 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); MWWorld::LiveCellRef* ref = mTool.get(); + MWBase::Environment::get().getWorld()->breakInvisibility(player); + // unstack tool if required player.getClass().getContainerStore(player).unstack(mTool); From 9a6c2fd2ccab30ee6e9b14643562cd4e7de5233f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 30 Oct 2023 23:23:39 +0300 Subject: [PATCH 0378/2167] Fill out ESM4::ItemMod --- components/esm4/loadimod.cpp | 42 ++++++++++++++++++++++++++++++++---- components/esm4/loadimod.hpp | 19 ++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 7382946e2c..0359f6d23b 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -46,16 +46,50 @@ void ESM4::ItemMod::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; - case ESM4::SUB_OBND: case ESM4::SUB_FULL: + reader.getLocalizedString(mFullName); + break; case ESM4::SUB_MODL: - case ESM4::SUB_ICON: - case ESM4::SUB_MICO: - case ESM4::SUB_SCRI: + reader.getZString(mModel); + break; + case ESM4::SUB_MODB: + reader.get(mBoundRadius); + break; case ESM4::SUB_DESC: + reader.getLocalizedString(mText); + break; + case ESM4::SUB_ICON: + reader.getZString(mIcon); + break; + case ESM4::SUB_MICO: + reader.getZString(mMiniIcon); + break; + case ESM4::SUB_SCRI: + reader.getFormId(mScriptId); + break; case ESM4::SUB_YNAM: + reader.getFormId(mPickUpSound); + break; case ESM4::SUB_ZNAM: + reader.getFormId(mDropSound); + break; case ESM4::SUB_DATA: + reader.get(mData.mValue); + reader.get(mData.mWeight); + break; + case ESM4::SUB_OBND: + case ESM4::SUB_MODT: // Model data + case ESM4::SUB_MODS: + case ESM4::SUB_MODD: // Model data end + case ESM4::SUB_DAMC: // Destructible + case ESM4::SUB_DEST: + case ESM4::SUB_DMDC: + case ESM4::SUB_DMDL: + case ESM4::SUB_DMDT: + case ESM4::SUB_DMDS: + case ESM4::SUB_DSTA: + case ESM4::SUB_DSTD: + case ESM4::SUB_DSTF: // Destructible end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadimod.hpp b/components/esm4/loadimod.hpp index f1faed978c..1f6250ae72 100644 --- a/components/esm4/loadimod.hpp +++ b/components/esm4/loadimod.hpp @@ -42,10 +42,29 @@ namespace ESM4 struct ItemMod { + struct Data + { + std::uint32_t mValue{ 0 }; + float mWeight{ 0.f }; + }; + ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; + std::string mFullName; + std::string mModel; + std::string mText; + std::string mIcon; + std::string mMiniIcon; + + ESM::FormId mScriptId; + ESM::FormId mPickUpSound; + ESM::FormId mDropSound; + + float mBoundRadius; + + Data mData; void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; From 3d5b06a78e0dd85276137baf7ad1052f02ae5bcf Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 29 Oct 2023 03:13:24 +0300 Subject: [PATCH 0379/2167] Sort inactive shader list (#7652) --- CHANGELOG.md | 1 + apps/openmw/mwgui/postprocessorhud.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..39da12eac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb + Feature #7652: Sort inactive post processing shaders list properly Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index e54704d6d4..ab5bdf791d 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -421,7 +422,12 @@ namespace MWGui auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + std::vector techniques; for (const auto& [name, _] : processor->getTechniqueMap()) + techniques.push_back(name); + std::sort(techniques.begin(), techniques.end(), Misc::StringUtils::ciLess); + + for (const std::string& name : techniques) { auto technique = processor->loadTechnique(name); From 03c38182290843af1ce78380bd5d1f2798440d75 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 30 Oct 2023 23:33:11 +0300 Subject: [PATCH 0380/2167] Place ESM4::ItemMod into the scene --- apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwlua/cellbindings.cpp | 4 ++++ apps/openmw/mwlua/types/types.cpp | 3 +++ apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/cellstore.hpp | 3 ++- apps/openmw/mwworld/esmstore.hpp | 5 +++-- apps/openmw/mwworld/store.cpp | 1 + components/esm/records.hpp | 1 + files/lua_api/openmw/types.lua | 3 +++ 9 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 5017b1b272..f96c73e529 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,7 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Light::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 7c3b31c7b6..e43354bfaf 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,9 @@ namespace MWLua case ESM::REC_FURN4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_IMOD4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_INGR4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index bd8b592f7a..694b63ddf2 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -45,6 +45,7 @@ namespace MWLua constexpr std::string_view ESM4Flora = "ESM4Flora"; constexpr std::string_view ESM4Furniture = "ESM4Furniture"; constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; + constexpr std::string_view ESM4ItemMod = "ESM4ItemMod"; constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; constexpr std::string_view ESM4MovableStatic = "ESM4MovableStatic"; @@ -90,6 +91,7 @@ namespace MWLua { ESM::REC_FLOR4, ObjectTypeName::ESM4Flora }, { ESM::REC_FURN4, ObjectTypeName::ESM4Furniture }, { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, + { ESM::REC_IMOD4, ObjectTypeName::ESM4ItemMod }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, { ESM::REC_MSTT4, ObjectTypeName::ESM4MovableStatic }, @@ -230,6 +232,7 @@ namespace MWLua addType(ObjectTypeName::ESM4Flora, { ESM::REC_FLOR4 }); addType(ObjectTypeName::ESM4Furniture, { ESM::REC_FURN4 }); addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); + addType(ObjectTypeName::ESM4ItemMod, { ESM::REC_IMOD4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); addType(ObjectTypeName::ESM4MovableStatic, { ESM::REC_MSTT4 }); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ff79940540..6ee638f233 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index debd80a97b..c80cf56d6a 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -72,6 +72,7 @@ namespace ESM4 struct Furniture; struct Flora; struct Ingredient; + struct ItemMod; struct MiscItem; struct MovableStatic; struct Terminal; @@ -96,7 +97,7 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList>; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index ceba4a26ef..16062c97db 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -91,6 +91,7 @@ namespace ESM4 struct Hair; struct HeadPart; struct Ingredient; + struct ItemMod; struct Land; struct LandTexture; struct LevelledCreature; @@ -140,8 +141,8 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store>; diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index e42675c66e..ac3ee72a94 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1362,6 +1362,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 4473e0290f..ded4b92a92 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..56cfd538f2 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1875,6 +1875,9 @@ --- Functions for @{#ESM4Ingredient} objects -- @field [parent=#types] #ESM4Ingredient ESM4Ingredient +--- Functions for @{#ESM4ItemMod} objects +-- @field [parent=#types] #ESM4ItemMod ESM4ItemMod + --- Functions for @{#ESM4Light} objects -- @field [parent=#types] #ESM4Light ESM4Light From 377d8c905ce5d5b3d9e7013091c6620e9667ab6a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 31 Oct 2023 10:18:09 +0400 Subject: [PATCH 0381/2167] Fix TrueType fonts scaling --- apps/openmw/mwgui/formatting.cpp | 2 +- apps/openmw/mwgui/formatting.hpp | 2 +- apps/openmw/mwgui/spellview.cpp | 4 ++-- apps/openmw/mwgui/tooltips.cpp | 6 +++--- components/widgets/box.cpp | 6 +++--- components/widgets/box.hpp | 23 ++++++++++++++++++++--- components/widgets/fontwrapper.hpp | 24 ++++++++++++++++++++++++ components/widgets/numericeditbox.hpp | 4 +++- components/widgets/sharedstatebutton.hpp | 4 +++- components/widgets/widgets.cpp | 3 +++ 10 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 components/widgets/fontwrapper.hpp diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index c4f0f804a6..7f62bbf49c 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -475,7 +475,7 @@ namespace MWGui::Formatting : GraphicElement(parent, pag, blockStyle) , mTextStyle(textStyle) { - MyGUI::EditBox* box = parent->createWidget("NormalText", + Gui::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, parent->getName() + MyGUI::utility::toString(parent->getChildCount())); box->setEditStatic(true); diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index f093a36dfe..9a215b200b 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -161,7 +161,7 @@ namespace MWGui private: int currentFontHeight() const; TextStyle mTextStyle; - MyGUI::EditBox* mEditBox; + Gui::EditBox* mEditBox; }; class ImageElement : public GraphicElement diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp index 97de7dbc06..678f6ffe1f 100644 --- a/apps/openmw/mwgui/spellview.cpp +++ b/apps/openmw/mwgui/spellview.cpp @@ -238,7 +238,7 @@ namespace MWGui mLines.emplace_back(separator, (MyGUI::Widget*)nullptr, NoSpellIndex); } - MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget->setCaptionWithReplacing(label); groupWidget->setTextAlign(MyGUI::Align::Left); @@ -246,7 +246,7 @@ namespace MWGui if (!label2.empty()) { - MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", + MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), MyGUI::Align::Left | MyGUI::Align::Top); groupWidget2->setCaptionWithReplacing(label2); groupWidget2->setTextAlign(MyGUI::Align::Right); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 929d78f3b1..323579317a 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -443,7 +443,7 @@ namespace MWGui const std::string realImage = Misc::ResourceHelpers::correctIconPath(image, MWBase::Environment::get().getResourceSystem()->getVFS()); - MyGUI::EditBox* captionWidget = mDynamicToolTipBox->createWidget( + Gui::EditBox* captionWidget = mDynamicToolTipBox->createWidget( "NormalText", MyGUI::IntCoord(0, 0, 300, 300), MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipCaption"); captionWidget->setEditStatic(true); captionWidget->setNeedKeyFocus(false); @@ -452,7 +452,7 @@ namespace MWGui int captionHeight = std::max(!caption.empty() ? captionSize.height : 0, imageSize); - MyGUI::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", + Gui::EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(0, captionHeight + imageCaptionVPadding, 300, 300 - captionHeight - imageCaptionVPadding), MyGUI::Align::Stretch, "ToolTipText"); textWidget->setEditStatic(true); @@ -474,7 +474,7 @@ namespace MWGui MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", MyGUI::IntCoord(padding.left, totalSize.height + padding.top, 8, 8), MyGUI::Align::Default); icon->setColour(MyGUI::Colour(1.0f, 0.3f, 0.3f)); - MyGUI::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", + Gui::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", MyGUI::IntCoord(padding.left + 8 + 4, totalSize.height + padding.top, 300 - padding.left - 8 - 4, 300 - totalSize.height), MyGUI::Align::Default); diff --git a/components/widgets/box.cpp b/components/widgets/box.cpp index 89f92b7bf1..d711925656 100644 --- a/components/widgets/box.cpp +++ b/components/widgets/box.cpp @@ -48,7 +48,7 @@ namespace Gui } else { - TextBox::setPropertyOverride(_key, _value); + Gui::TextBox::setPropertyOverride(_key, _value); } } @@ -115,7 +115,7 @@ namespace Gui } else { - EditBox::setPropertyOverride(_key, _value); + Gui::EditBox::setPropertyOverride(_key, _value); } } @@ -144,7 +144,7 @@ namespace Gui } else { - Button::setPropertyOverride(_key, _value); + Gui::Button::setPropertyOverride(_key, _value); } } diff --git a/components/widgets/box.hpp b/components/widgets/box.hpp index b7543f1f05..83cbddb3b0 100644 --- a/components/widgets/box.hpp +++ b/components/widgets/box.hpp @@ -7,8 +7,25 @@ #include #include +#include "fontwrapper.hpp" + namespace Gui { + class Button : public FontWrapper + { + MYGUI_RTTI_DERIVED(Button) + }; + + class TextBox : public FontWrapper + { + MYGUI_RTTI_DERIVED(TextBox) + }; + + class EditBox : public FontWrapper + { + MYGUI_RTTI_DERIVED(EditBox) + }; + class AutoSizedWidget { public: @@ -27,7 +44,7 @@ namespace Gui MyGUI::Align mExpandDirection; }; - class AutoSizedTextBox : public AutoSizedWidget, public MyGUI::TextBox + class AutoSizedTextBox : public AutoSizedWidget, public TextBox { MYGUI_RTTI_DERIVED(AutoSizedTextBox) @@ -40,7 +57,7 @@ namespace Gui std::string mFontSize; }; - class AutoSizedEditBox : public AutoSizedWidget, public MyGUI::EditBox + class AutoSizedEditBox : public AutoSizedWidget, public EditBox { MYGUI_RTTI_DERIVED(AutoSizedEditBox) @@ -59,7 +76,7 @@ namespace Gui int mMaxWidth = 0; }; - class AutoSizedButton : public AutoSizedWidget, public MyGUI::Button + class AutoSizedButton : public AutoSizedWidget, public Button { MYGUI_RTTI_DERIVED(AutoSizedButton) diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp new file mode 100644 index 0000000000..f6a57923a9 --- /dev/null +++ b/components/widgets/fontwrapper.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_WIDGETS_WRAPPER_H +#define OPENMW_WIDGETS_WRAPPER_H + +#include + +#include "components/settings/values.hpp" + +#include + +namespace Gui +{ + template + class FontWrapper : public T + { + public: + void setFontName(std::string_view name) override + { + T::setFontName(name); + T::setPropertyOverride("FontHeight", std::to_string(Settings::gui().mFontSize)); + } + }; +} + +#endif diff --git a/components/widgets/numericeditbox.hpp b/components/widgets/numericeditbox.hpp index 39605b67d9..ee8ef39a59 100644 --- a/components/widgets/numericeditbox.hpp +++ b/components/widgets/numericeditbox.hpp @@ -3,13 +3,15 @@ #include +#include "fontwrapper.hpp" + namespace Gui { /** * @brief A variant of the EditBox that only allows integer inputs */ - class NumericEditBox final : public MyGUI::EditBox + class NumericEditBox final : public FontWrapper { MYGUI_RTTI_DERIVED(NumericEditBox) diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 99f597360c..688d949f6e 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -3,6 +3,8 @@ #include +#include "fontwrapper.hpp" + namespace Gui { @@ -12,7 +14,7 @@ namespace Gui /// @brief A button that applies its own state changes to other widgets, to do this you define it as part of a /// ButtonGroup. - class SharedStateButton final : public MyGUI::Button + class SharedStateButton final : public FontWrapper { MYGUI_RTTI_DERIVED(SharedStateButton) diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp index 097f84c62f..d27d7e5fc9 100644 --- a/components/widgets/widgets.cpp +++ b/components/widgets/widgets.cpp @@ -18,9 +18,12 @@ namespace Gui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); From 1dd7a1525558b733fccc10ba1cca1a46b8e30d4a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 5 Sep 2023 22:45:38 +0200 Subject: [PATCH 0382/2167] Draft: add new type of Lua scripts - menu scripts --- apps/openmw/CMakeLists.txt | 11 +- apps/openmw/mwbase/statemanager.hpp | 3 + apps/openmw/mwbase/windowmanager.hpp | 3 +- apps/openmw/mwgui/windowmanagerimp.cpp | 7 +- apps/openmw/mwgui/windowmanagerimp.hpp | 3 +- apps/openmw/mwlua/corebindings.cpp | 136 +++++++++ apps/openmw/mwlua/corebindings.hpp | 19 ++ apps/openmw/mwlua/globalscripts.hpp | 4 - apps/openmw/mwlua/luabindings.cpp | 328 ++-------------------- apps/openmw/mwlua/luabindings.hpp | 10 +- apps/openmw/mwlua/luamanagerimp.cpp | 38 ++- apps/openmw/mwlua/luamanagerimp.hpp | 2 + apps/openmw/mwlua/menuscripts.cpp | 114 ++++++++ apps/openmw/mwlua/menuscripts.hpp | 46 +++ apps/openmw/mwlua/soundbindings.cpp | 9 +- apps/openmw/mwlua/types/types.cpp | 7 +- apps/openmw/mwlua/uibindings.cpp | 11 +- apps/openmw/mwlua/worldbindings.cpp | 215 ++++++++++++++ apps/openmw/mwlua/worldbindings.hpp | 13 + apps/openmw/mwstate/statemanagerimp.cpp | 12 + apps/openmw/mwstate/statemanagerimp.hpp | 5 + components/esm/luascripts.hpp | 6 +- components/lua/configuration.cpp | 1 + components/lua/configuration.hpp | 1 + components/lua/storage.cpp | 26 +- components/lua/storage.hpp | 10 +- files/data/CMakeLists.txt | 3 +- files/data/builtin.omwscripts | 1 + files/data/scripts/omw/console/menu.lua | 114 ++++++++ files/data/scripts/omw/console/player.lua | 7 +- 30 files changed, 818 insertions(+), 347 deletions(-) create mode 100644 apps/openmw/mwlua/corebindings.cpp create mode 100644 apps/openmw/mwlua/corebindings.hpp create mode 100644 apps/openmw/mwlua/menuscripts.cpp create mode 100644 apps/openmw/mwlua/menuscripts.hpp create mode 100644 apps/openmw/mwlua/worldbindings.cpp create mode 100644 apps/openmw/mwlua/worldbindings.hpp create mode 100644 files/data/scripts/omw/console/menu.lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a05d20af73..b987b1d124 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,10 +61,13 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant - context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings - types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal - worker magicbindings factionbindings + context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings + mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings + postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings + types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc + types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus + types/potion types/ingredient types/misc types/repair types/armor types/light types/static + types/clothing types/levelledlist types/terminal ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 1a25da32b0..35435e1430 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -44,6 +44,9 @@ namespace MWBase virtual void askLoadRecent() = 0; + virtual void requestNewGame() = 0; + virtual void requestLoad(const std::filesystem::path& filepath) = 0; + virtual State getState() const = 0; virtual void newGame(bool bypass = false) = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f225ebf24e..92ad28647b 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -164,7 +164,8 @@ namespace MWBase virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; virtual MWWorld::Ptr getConsoleSelectedObject() const = 0; - virtual void setConsoleMode(const std::string& mode) = 0; + virtual void setConsoleMode(std::string_view mode) = 0; + virtual const std::string& getConsoleMode() = 0; static constexpr std::string_view sConsoleColor_Default = "#FFFFFF"; static constexpr std::string_view sConsoleColor_Error = "#FF2222"; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 43b37623d5..d832a25023 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -2173,11 +2173,16 @@ namespace MWGui mConsole->print(msg, color); } - void WindowManager::setConsoleMode(const std::string& mode) + void WindowManager::setConsoleMode(std::string_view mode) { mConsole->setConsoleMode(mode); } + const std::string& WindowManager::getConsoleMode() + { + return mConsole->getConsoleMode(); + } + void WindowManager::createCursors() { MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5f6b12b7e5..c9eced4e94 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -191,7 +191,8 @@ namespace MWGui void setConsoleSelectedObject(const MWWorld::Ptr& object) override; MWWorld::Ptr getConsoleSelectedObject() const override; void printToConsole(const std::string& msg, std::string_view color) override; - void setConsoleMode(const std::string& mode) override; + void setConsoleMode(std::string_view mode) override; + const std::string& getConsoleMode() override; /// Set time left for the player to start drowning (update the drowning bar) /// @param time time left to start drowning diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp new file mode 100644 index 0000000000..b9ff991406 --- /dev/null +++ b/apps/openmw/mwlua/corebindings.cpp @@ -0,0 +1,136 @@ +#include "corebindings.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/datetimemanager.hpp" +#include "../mwworld/esmstore.hpp" + +#include "factionbindings.hpp" +#include "luaevents.hpp" +#include "magicbindings.hpp" +#include "soundbindings.hpp" +#include "stats.hpp" + +namespace MWLua +{ + static sol::table initContentFilesBindings(sol::state_view& lua) + { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + sol::table list(lua, sol::create); + for (size_t i = 0; i < contentList.size(); ++i) + list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]); + sol::table res(lua, sol::create); + res["list"] = LuaUtil::makeReadOnly(list); + res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return i + 1; + return sol::nullopt; + }; + res["has"] = [&contentList](std::string_view contentFile) -> bool { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return true; + return false; + }; + return LuaUtil::makeReadOnly(res); + } + + void addCoreTimeBindings(sol::table& api, const Context& context) + { + MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); + + api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); }; + api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); }; + api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); }; + api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); }; + api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); }; + api["getRealTime"] = []() { + return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); + }; + } + + sol::table initCorePackage(const Context& context) + { + auto* lua = context.mLua; + + if (lua->sol()["openmw_core"] != sol::nil) + return lua->sol()["openmw_core"]; + + sol::table api(lua->sol(), sol::create); + api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt + api["quit"] = [lua]() { + Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); + MWBase::Environment::get().getStateManager()->requestQuit(); + }; + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { + context.mLuaEvents->addGlobalEvent( + { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; + api["contentFiles"] = initContentFilesBindings(lua->sol()); + api["sound"] = initCoreSoundBindings(context); + api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText(); + throw std::runtime_error("Content file not found: " + std::string(contentFile)); + }; + addCoreTimeBindings(api, context); + api["magic"] = initCoreMagicBindings(context); + api["stats"] = initCoreStatsBindings(context); + + initCoreFactionBindings(context); + api["factions"] = &MWBase::Environment::get().getESMStore()->get(); + + api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); + const MWWorld::Store* gmstStore + = &MWBase::Environment::get().getESMStore()->get(); + api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object { + const ESM::GameSetting* gmst = gmstStore->search(setting); + if (gmst == nullptr) + return sol::nil; + const ESM::Variant& value = gmst->mValue; + switch (value.getType()) + { + case ESM::VT_Float: + return sol::make_object(lua->sol(), value.getFloat()); + case ESM::VT_Short: + case ESM::VT_Long: + case ESM::VT_Int: + return sol::make_object(lua->sol(), value.getInteger()); + case ESM::VT_String: + return sol::make_object(lua->sol(), value.getString()); + case ESM::VT_Unknown: + case ESM::VT_None: + break; + } + return sol::nil; + }; + + lua->sol()["openmw_core"] = LuaUtil::makeReadOnly(api); + return lua->sol()["openmw_core"]; + } + + sol::table initCorePackageForMenuScripts(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + for (auto& [k, v] : LuaUtil::getMutableFromReadOnly(initCorePackage(context))) + api[k] = v; + api["sendGlobalEvent"] = sol::nil; + api["sound"] = sol::nil; + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/corebindings.hpp b/apps/openmw/mwlua/corebindings.hpp new file mode 100644 index 0000000000..d086d3884c --- /dev/null +++ b/apps/openmw/mwlua/corebindings.hpp @@ -0,0 +1,19 @@ +#ifndef MWLUA_COREBINDINGS_H +#define MWLUA_COREBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + void addCoreTimeBindings(sol::table& api, const Context& context); + + sol::table initCorePackage(const Context&); + + // Returns `openmw.core`, but disables the functionality that shouldn't + // be availabe in menu scripts (to prevent cheating in mutiplayer via menu console). + sol::table initCorePackageForMenuScripts(const Context&); +} + +#endif // MWLUA_COREBINDINGS_H diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp index afaadb9d0a..37fff22f99 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -1,10 +1,6 @@ #ifndef MWLUA_GLOBALSCRIPTS_H #define MWLUA_GLOBALSCRIPTS_H -#include -#include -#include - #include #include diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index a50459502b..931cd43296 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -1,328 +1,30 @@ #include "luabindings.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include #include "../mwbase/environment.hpp" -#include "../mwbase/statemanager.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwworld/action.hpp" -#include "../mwworld/class.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/datetimemanager.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/manualref.hpp" -#include "../mwworld/store.hpp" -#include "../mwworld/worldmodel.hpp" - -#include "luaevents.hpp" -#include "luamanagerimp.hpp" -#include "mwscriptbindings.hpp" -#include "objectlists.hpp" #include "camerabindings.hpp" #include "cellbindings.hpp" +#include "corebindings.hpp" #include "debugbindings.hpp" -#include "factionbindings.hpp" #include "inputbindings.hpp" -#include "magicbindings.hpp" +#include "localscripts.hpp" +#include "menuscripts.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" #include "postprocessingbindings.hpp" #include "soundbindings.hpp" -#include "stats.hpp" #include "types/types.hpp" #include "uibindings.hpp" #include "vfsbindings.hpp" +#include "worldbindings.hpp" namespace MWLua { - struct CellsStore - { - }; -} - -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; -} - -namespace MWLua -{ - - static void checkGameInitialized(LuaUtil::LuaState* lua) - { - if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) - throw std::runtime_error( - "This function cannot be used until the game is fully initialized.\n" + lua->debugTraceback()); - } - - static void addTimeBindings(sol::table& api, const Context& context, bool global) - { - MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); - - api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); }; - api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); }; - api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); }; - api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); }; - api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); }; - api["getRealTime"] = []() { - return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); - }; - - if (!global) - return; - - api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); }; - api["setSimulationTimeScale"] = [context, timeManager](float scale) { - context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); - }; - - api["pause"] - = [timeManager](sol::optional tag) { timeManager->pause(tag.value_or("paused")); }; - api["unpause"] - = [timeManager](sol::optional tag) { timeManager->unpause(tag.value_or("paused")); }; - api["getPausedTags"] = [timeManager](sol::this_state lua) { - sol::table res(lua, sol::create); - for (const std::string& tag : timeManager->getPausedTags()) - res[tag] = tag; - return res; - }; - } - - static sol::table initContentFilesBindings(sol::state_view& lua) - { - const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); - sol::table list(lua, sol::create); - for (size_t i = 0; i < contentList.size(); ++i) - list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]); - sol::table res(lua, sol::create); - res["list"] = LuaUtil::makeReadOnly(list); - res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional { - for (size_t i = 0; i < contentList.size(); ++i) - if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) - return i + 1; - return sol::nullopt; - }; - res["has"] = [&contentList](std::string_view contentFile) -> bool { - for (size_t i = 0; i < contentList.size(); ++i) - if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) - return true; - return false; - }; - return LuaUtil::makeReadOnly(res); - } - - static sol::table initCorePackage(const Context& context) - { - auto* lua = context.mLua; - sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt - api["quit"] = [lua]() { - Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); - MWBase::Environment::get().getStateManager()->requestQuit(); - }; - api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { - context.mLuaEvents->addGlobalEvent( - { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); - }; - api["contentFiles"] = initContentFilesBindings(lua->sol()); - api["sound"] = initCoreSoundBindings(context); - api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { - const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); - for (size_t i = 0; i < contentList.size(); ++i) - if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) - return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText(); - throw std::runtime_error("Content file not found: " + std::string(contentFile)); - }; - addTimeBindings(api, context, false); - api["magic"] = initCoreMagicBindings(context); - api["stats"] = initCoreStatsBindings(context); - - initCoreFactionBindings(context); - api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); - - api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); - const MWWorld::Store* gmstStore - = &MWBase::Environment::get().getESMStore()->get(); - api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object { - const ESM::GameSetting* gmst = gmstStore->search(setting); - if (gmst == nullptr) - return sol::nil; - const ESM::Variant& value = gmst->mValue; - switch (value.getType()) - { - case ESM::VT_Float: - return sol::make_object(lua->sol(), value.getFloat()); - case ESM::VT_Short: - case ESM::VT_Long: - case ESM::VT_Int: - return sol::make_object(lua->sol(), value.getInteger()); - case ESM::VT_String: - return sol::make_object(lua->sol(), value.getString()); - case ESM::VT_Unknown: - case ESM::VT_None: - break; - } - return sol::nil; - }; - - return LuaUtil::makeReadOnly(api); - } - - static void addCellGetters(sol::table& api, const Context& context) - { - api["getCellByName"] = [](std::string_view name) { - return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; - }; - api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; - return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( - ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; - }; - - const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); - const MWWorld::Store* cells4Store = &MWBase::Environment::get().getESMStore()->get(); - sol::usertype cells = context.mLua->sol().new_usertype("Cells"); - cells[sol::meta_function::length] - = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; - cells[sol::meta_function::index] - = [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional { - if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0) - return sol::nullopt; - - index--; // Translate from Lua's 1-based indexing. - if (index < cells3Store->getSize()) - { - const ESM::Cell* cellRecord = cells3Store->at(index); - return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( - cellRecord->mId, /*forceLoad=*/false) }; - } - else - { - const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); - return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( - cellRecord->mId, /*forceLoad=*/false) }; - } - }; - cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); - cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); - api["cells"] = CellsStore{}; - } - - static sol::table initWorldPackage(const Context& context) - { - sol::table api(context.mLua->sol(), sol::create); - ObjectLists* objectLists = context.mObjectLists; - addTimeBindings(api, context, true); - addCellGetters(api, context); - api["mwscript"] = initMWScriptBindings(context); - api["activeActors"] = GObjectList{ objectLists->getActorsInScene() }; - api["players"] = GObjectList{ objectLists->getPlayers() }; - api["createObject"] = [lua = context.mLua](std::string_view recordId, sol::optional count) -> GObject { - checkGameInitialized(lua); - MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId)); - const MWWorld::Ptr& ptr = mref.getPtr(); - ptr.getRefData().disable(); - MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getDraftCell(); - MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, cell, count.value_or(1)); - return GObject(newPtr); - }; - api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject { - ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); - if (!refId.is()) - throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); - return GObject(*refId.getIf()); - }; - - // Creates a new record in the world database. - api["createRecord"] = sol::overload( - [lua = context.mLua](const ESM::Activator& activator) -> const ESM::Activator* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(activator); - }, - [lua = context.mLua](const ESM::Armor& armor) -> const ESM::Armor* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(armor); - }, - [lua = context.mLua](const ESM::Clothing& clothing) -> const ESM::Clothing* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(clothing); - }, - [lua = context.mLua](const ESM::Book& book) -> const ESM::Book* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(book); - }, - [lua = context.mLua](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(misc); - }, - [lua = context.mLua](const ESM::Potion& potion) -> const ESM::Potion* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(potion); - }, - [lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* { - checkGameInitialized(lua); - return MWBase::Environment::get().getESMStore()->insert(weapon); - }); - - api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { - if (!object.ptr().getRefData().activate()) - return; - context.mLuaManager->addAction( - [object, actor] { - const MWWorld::Ptr& objPtr = object.ptr(); - const MWWorld::Ptr& actorPtr = actor.ptr(); - objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr); - }, - "_runStandardActivationAction"); - }; - api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor, bool force) { - context.mLuaManager->addAction( - [object, actor, force] { - const MWWorld::Ptr& actorPtr = actor.ptr(); - const MWWorld::Ptr& objectPtr = object.ptr(); - if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - MWBase::Environment::get().getWindowManager()->useItem(objectPtr, force); - else - { - std::unique_ptr action = objectPtr.getClass().use(objectPtr, force); - action->execute(actorPtr, true); - } - }, - "_runStandardUseAction"); - }; - - return LuaUtil::makeReadOnly(api); - } - std::map initCommonPackages(const Context& context) { sol::state_view lua = context.mLua->sol(); @@ -331,8 +33,6 @@ namespace MWLua { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, - { "openmw.core", initCorePackage(context) }, - { "openmw.types", initTypesPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.vfs", initVFSPackage(context) }, }; @@ -343,6 +43,8 @@ namespace MWLua initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); return { + { "openmw.core", initCorePackage(context) }, + { "openmw.types", initTypesPackage(context) }, { "openmw.world", initWorldPackage(context) }, }; } @@ -353,6 +55,8 @@ namespace MWLua initCellBindingsForLocalScripts(context); LocalScripts::initializeSelfPackage(context); return { + { "openmw.core", initCorePackage(context) }, + { "openmw.types", initTypesPackage(context) }, { "openmw.nearby", initNearbyPackage(context) }, }; } @@ -369,4 +73,16 @@ namespace MWLua }; } + std::map initMenuPackages(const Context& context) + { + return { + { "openmw.core", initCorePackageForMenuScripts(context) }, // + { "openmw.ambient", initAmbientPackage(context) }, // + { "openmw.ui", initUserInterfacePackage(context) }, // + { "openmw.menu", initMenuPackage(context) }, + // TODO: Maybe add: + // { "openmw.input", initInputPackage(context) }, + // { "openmw.postprocessing", initPostprocessingPackage(context) }, + }; + } } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index e5d481d1eb..749987e5b2 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -12,14 +12,18 @@ namespace MWLua // Initialize Lua packages that are available for all scripts. std::map initCommonPackages(const Context&); - // Initialize Lua packages that are available only for global scripts. + // Initialize Lua packages that are available for global scripts (additionally to common packages). std::map initGlobalPackages(const Context&); - // Initialize Lua packages that are available only for local scripts (including player scripts). + // Initialize Lua packages that are available for local scripts (additionally to common packages). std::map initLocalPackages(const Context&); - // Initialize Lua packages that are available only for local scripts on the player. + // Initialize Lua packages that are available only for local scripts on the player (additionally to common and local + // packages). std::map initPlayerPackages(const Context&); + + // Initialize Lua packages that are available only for menu scripts (additionally to common packages). + std::map initMenuPackages(const Context&); } #endif // MWLUA_LUABINDINGS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 63a2838250..77ddfcc4a7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -68,6 +68,7 @@ namespace MWLua Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; for (size_t i = 0; i < mConfiguration.size(); ++i) Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); + mMenuScripts.setAutoStartConf(mConfiguration.getMenuConf()); mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf()); } @@ -89,20 +90,25 @@ namespace MWLua mLua.addCommonPackage(name, package); for (const auto& [name, package] : initGlobalPackages(context)) mGlobalScripts.addPackage(name, package); + for (const auto& [name, package] : initMenuPackages(context)) + mMenuScripts.addPackage(name, package); mLocalPackages = initLocalPackages(localContext); + mPlayerPackages = initPlayerPackages(localContext); mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); mGlobalScripts.addPackage( "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); + mMenuScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mPlayerStorage)); mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); mPlayerPackages["openmw.storage"] = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); initConfiguration(); mInitialized = true; + mMenuScripts.addAutoStartedScripts(); } void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath) @@ -204,9 +210,6 @@ namespace MWLua void LuaManager::synchronizedUpdate() { - if (mPlayer.isEmpty()) - return; // The game is not started yet. - if (mNewGameStarted) { mNewGameStarted = false; @@ -217,7 +220,8 @@ namespace MWLua // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. mProcessingInputEvents = true; - PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); + PlayerScripts* playerScripts + = mPlayer.isEmpty() ? nullptr : dynamic_cast(mPlayer.getRefData().getLuaScripts()); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu)) { @@ -225,6 +229,7 @@ namespace MWLua playerScripts->processInputEvent(event); } mInputEvents.clear(); + mMenuScripts.update(0); if (playerScripts) playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() ? 0.0 @@ -272,7 +277,6 @@ namespace MWLua { LuaUi::clearUserInterface(); mUiResourceManager.clear(); - MWBase::Environment::get().getWindowManager()->setConsoleMode(""); MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); mActiveLocalScripts.clear(); mLuaEvents.clear(); @@ -320,6 +324,7 @@ namespace MWLua mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; mNewGameStarted = true; + mMenuScripts.stateChanged(); } void LuaManager::gameLoaded() @@ -327,6 +332,7 @@ namespace MWLua if (!mGlobalScriptsStarted) mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; + mMenuScripts.stateChanged(); } void LuaManager::uiModeChanged(const MWWorld::Ptr& arg) @@ -529,6 +535,9 @@ namespace MWLua } for (LocalScripts* scripts : mActiveLocalScripts) scripts->setActive(true); + + mMenuScripts.removeAllScripts(); + mMenuScripts.addAutoStartedScripts(); } void LuaManager::handleConsoleCommand( @@ -537,16 +546,16 @@ namespace MWLua PlayerScripts* playerScripts = nullptr; if (!mPlayer.isEmpty()) playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); - if (!playerScripts) + bool processed = mMenuScripts.consoleCommand(consoleMode, command); + if (playerScripts) { - MWBase::Environment::get().getWindowManager()->printToConsole( - "You must enter a game session to run Lua commands\n", MWBase::WindowManager::sConsoleColor_Error); - return; + sol::object selected = sol::nil; + if (!selectedPtr.isEmpty()) + selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr))); + if (playerScripts->consoleCommand(consoleMode, command, selected)) + processed = true; } - sol::object selected = sol::nil; - if (!selectedPtr.isEmpty()) - selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr))); - if (!playerScripts->consoleCommand(consoleMode, command, selected)) + if (!processed) MWBase::Environment::get().getWindowManager()->printToConsole( "No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error); } @@ -680,6 +689,7 @@ namespace MWLua for (size_t i = 0; i < mConfiguration.size(); ++i) { bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal; + bool isMenu = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sMenu; out << std::left; out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath; @@ -692,6 +702,8 @@ namespace MWLua if (isGlobal) out << std::setw(valueW * 2) << "NA (global script)"; + else if (isMenu && (!selectedScripts || !selectedScripts->hasScript(i))) + out << std::setw(valueW * 2) << "NA (menu script)"; else if (selectedPtr.isEmpty()) out << std::setw(valueW * 2) << "NA (not selected) "; else if (!selectedScripts || !selectedScripts->hasScript(i)) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a725761dbd..b16e81082b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -17,6 +17,7 @@ #include "globalscripts.hpp" #include "localscripts.hpp" #include "luaevents.hpp" +#include "menuscripts.hpp" #include "object.hpp" #include "objectlists.hpp" @@ -164,6 +165,7 @@ namespace MWLua std::map mLocalPackages; std::map mPlayerPackages; + MenuScripts mMenuScripts{ &mLua }; GlobalScripts mGlobalScripts{ &mLua }; std::set mActiveLocalScripts; ObjectLists mObjectLists; diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp new file mode 100644 index 0000000000..300f5ad489 --- /dev/null +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -0,0 +1,114 @@ +#include "menuscripts.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" +#include "../mwstate/character.hpp" + +namespace MWLua +{ + static const MWState::Character* findCharacter(std::string_view characterDir) + { + MWBase::StateManager* manager = MWBase::Environment::get().getStateManager(); + for (auto it = manager->characterBegin(); it != manager->characterEnd(); ++it) + if (it->getPath().filename() == characterDir) + return &*it; + return nullptr; + } + + static const MWState::Slot* findSlot(const MWState::Character* character, std::string_view slotName) + { + if (!character) + return nullptr; + for (const MWState::Slot& slot : *character) + if (slot.mPath.filename() == slotName) + return &slot; + return nullptr; + } + + sol::table initMenuPackage(const Context& context) + { + sol::state_view lua = context.mLua->sol(); + sol::table api(lua, sol::create); + + api["STATE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "NoGame", MWBase::StateManager::State_NoGame }, + { "Running", MWBase::StateManager::State_Running }, + { "Ended", MWBase::StateManager::State_Ended }, + })); + + api["getState"] = []() -> int { return MWBase::Environment::get().getStateManager()->getState(); }; + + api["newGame"] = []() { MWBase::Environment::get().getStateManager()->requestNewGame(); }; + + api["loadGame"] = [](std::string_view dir, std::string_view slotName) { + const MWState::Character* character = findCharacter(dir); + const MWState::Slot* slot = findSlot(character, slotName); + if (!slot) + throw std::runtime_error("Save game slot not found: " + std::string(dir) + "/" + std::string(slotName)); + MWBase::Environment::get().getStateManager()->requestLoad(slot->mPath); + }; + + api["deleteGame"] = [](std::string_view dir, std::string_view slotName) { + const MWState::Character* character = findCharacter(dir); + const MWState::Slot* slot = findSlot(character, slotName); + if (!slot) + throw std::runtime_error("Save game slot not found: " + std::string(dir) + "/" + std::string(slotName)); + MWBase::Environment::get().getStateManager()->deleteGame(character, slot); + }; + + api["getCurrentSaveDir"] = []() -> sol::optional { + MWBase::StateManager* manager = MWBase::Environment::get().getStateManager(); + const MWState::Character* character = manager->getCurrentCharacter(); + if (character) + return character->getPath().filename().string(); + else + return sol::nullopt; + }; + + api["saveGame"] = [](std::string_view description, sol::optional slotName) { + MWBase::StateManager* manager = MWBase::Environment::get().getStateManager(); + const MWState::Character* character = manager->getCurrentCharacter(); + const MWState::Slot* slot = nullptr; + if (slotName) + slot = findSlot(character, *slotName); + manager->saveGame(description, slot); + }; + + auto getSaves = [](sol::state_view lua, const MWState::Character& character) { + sol::table saves(lua, sol::create); + for (const MWState::Slot& slot : character) + { + sol::table slotInfo(lua, sol::create); + slotInfo["description"] = slot.mProfile.mDescription; + slotInfo["playerName"] = slot.mProfile.mPlayerName; + slotInfo["playerLevel"] = slot.mProfile.mPlayerLevel; + sol::table contentFiles(lua, sol::create); + for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i) + contentFiles[i + 1] = slot.mProfile.mContentFiles[i]; + slotInfo["contentFiles"] = contentFiles; + saves[slot.mPath.filename().string()] = slotInfo; + } + return saves; + }; + + api["getSaves"] = [getSaves](sol::this_state lua, std::string_view dir) -> sol::table { + const MWState::Character* character = findCharacter(dir); + if (!character) + throw std::runtime_error("Saves not found: " + std::string(dir)); + return getSaves(lua, *character); + }; + + api["getAllSaves"] = [getSaves](sol::this_state lua) -> sol::table { + sol::table saves(lua, sol::create); + MWBase::StateManager* manager = MWBase::Environment::get().getStateManager(); + for (auto it = manager->characterBegin(); it != manager->characterEnd(); ++it) + saves[it->getPath().filename().string()] = getSaves(lua, *it); + return saves; + }; + + api["quit"] = []() { MWBase::Environment::get().getStateManager()->requestQuit(); }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp new file mode 100644 index 0000000000..3fd1bce186 --- /dev/null +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -0,0 +1,46 @@ +#ifndef MWLUA_MENUSCRIPTS_H +#define MWLUA_MENUSCRIPTS_H + +#include + +#include +#include +#include + +#include "../mwbase/luamanager.hpp" + +#include "context.hpp" + +namespace MWLua +{ + + sol::table initMenuPackage(const Context& context); + + class MenuScripts : public LuaUtil::ScriptsContainer + { + public: + MenuScripts(LuaUtil::LuaState* lua) + : LuaUtil::ScriptsContainer(lua, "Menu") + { + registerEngineHandlers({ &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged }); + } + + void stateChanged() { callEngineHandlers(mStateChanged); } + + bool consoleCommand(const std::string& consoleMode, const std::string& command) + { + callEngineHandlers(mConsoleCommandHandlers, consoleMode, command); + return !mConsoleCommandHandlers.mList.empty(); + } + + void uiModeChanged() { callEngineHandlers(mUiModeChanged); } + + private: + EngineHandlerList mStateChanged{ "onStateChanged" }; + EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; + EngineHandlerList mUiModeChanged{ "_onUiModeChanged" }; + }; + +} + +#endif // MWLUA_GLOBALSCRIPTS_H diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index dc45a672b4..55071ea374 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -61,7 +61,11 @@ namespace MWLua { sol::table initAmbientPackage(const Context& context) { - sol::table api(context.mLua->sol(), sol::create); + sol::state_view& lua = context.mLua->sol(); + if (lua["openmw_ambient"] != sol::nil) + return lua["openmw_ambient"]; + + sol::table api(lua, sol::create); api["playSound"] = [](std::string_view soundId, const sol::optional& options) { auto args = getPlaySoundArgs(options); @@ -104,7 +108,8 @@ namespace MWLua api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; - return LuaUtil::makeReadOnly(api); + lua["openmw_ambient"] = LuaUtil::makeReadOnly(api); + return lua["openmw_ambient"]; } sol::table initCoreSoundBindings(const Context& context) diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index bd8b592f7a..cf03d1baef 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -162,6 +162,10 @@ namespace MWLua sol::table initTypesPackage(const Context& context) { auto* lua = context.mLua; + + if (lua->sol()["openmw_types"] != sol::nil) + return lua->sol()["openmw_types"]; + sol::table types(lua->sol(), sol::create); auto addType = [&](std::string_view name, std::vector recTypes, std::optional base = std::nullopt) -> sol::table { @@ -250,6 +254,7 @@ namespace MWLua packageToType[t] = type; } - return LuaUtil::makeReadOnly(types); + lua->sol()["openmw_types"] = LuaUtil::makeReadOnly(types); + return lua->sol()["openmw_types"]; } } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 79f5fac9a1..96f3246ebe 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -92,6 +92,12 @@ namespace MWLua sol::table initUserInterfacePackage(const Context& context) { + { + sol::state_view& lua = context.mLua->sol(); + if (lua["openmw_ui"] != sol::nil) + return lua["openmw_ui"]; + } + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); auto element = context.mLua->sol().new_usertype("Element"); @@ -130,6 +136,7 @@ namespace MWLua api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view mode) { luaManager->addAction([mode = std::string(mode), windowManager] { windowManager->setConsoleMode(mode); }); }; + api["getConsoleMode"] = [windowManager]() -> std::string_view { return windowManager->getConsoleMode(); }; api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager, windowManager](const sol::object& obj) { if (obj == sol::nil) luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); }); @@ -302,6 +309,8 @@ namespace MWLua // TODO // api["_showMouseCursor"] = [](bool) {}; - return LuaUtil::makeReadOnly(api); + sol::state_view& lua = context.mLua->sol(); + lua["openmw_ui"] = LuaUtil::makeReadOnly(api); + return lua["openmw_ui"]; } } diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp new file mode 100644 index 0000000000..c5ff7c89ca --- /dev/null +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -0,0 +1,215 @@ +#include "worldbindings.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/store.hpp" +#include "../mwworld/worldmodel.hpp" + +#include "luamanagerimp.hpp" + +#include "corebindings.hpp" +#include "mwscriptbindings.hpp" + +namespace MWLua +{ + struct CellsStore + { + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + + static void checkGameInitialized(LuaUtil::LuaState* lua) + { + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + throw std::runtime_error( + "This function cannot be used until the game is fully initialized.\n" + lua->debugTraceback()); + } + + static void addWorldTimeBindings(sol::table& api, const Context& context) + { + MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); + + api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); }; + api["setSimulationTimeScale"] = [context, timeManager](float scale) { + context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); + }; + + api["pause"] + = [timeManager](sol::optional tag) { timeManager->pause(tag.value_or("paused")); }; + api["unpause"] + = [timeManager](sol::optional tag) { timeManager->unpause(tag.value_or("paused")); }; + api["getPausedTags"] = [timeManager](sol::this_state lua) { + sol::table res(lua, sol::create); + for (const std::string& tag : timeManager->getPausedTags()) + res[tag] = tag; + return res; + }; + } + + static void addCellGetters(sol::table& api, const Context& context) + { + api["getCellByName"] = [](std::string_view name) { + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) }; + }; + api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior( + ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) }; + }; + + const MWWorld::Store* cells3Store = &MWBase::Environment::get().getESMStore()->get(); + const MWWorld::Store* cells4Store = &MWBase::Environment::get().getESMStore()->get(); + sol::usertype cells = context.mLua->sol().new_usertype("Cells"); + cells[sol::meta_function::length] + = [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); }; + cells[sol::meta_function::index] + = [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional { + if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0) + return sol::nullopt; + + index--; // Translate from Lua's 1-based indexing. + if (index < cells3Store->getSize()) + { + const ESM::Cell* cellRecord = cells3Store->at(index); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + else + { + const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize()); + return GCell{ &MWBase::Environment::get().getWorldModel()->getCell( + cellRecord->mId, /*forceLoad=*/false) }; + } + }; + cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get(); + cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get(); + api["cells"] = CellsStore{}; + } + + sol::table initWorldPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + addCoreTimeBindings(api, context); + addWorldTimeBindings(api, context); + addCellGetters(api, context); + api["mwscript"] = initMWScriptBindings(context); + + ObjectLists* objectLists = context.mObjectLists; + api["activeActors"] = GObjectList{ objectLists->getActorsInScene() }; + api["players"] = GObjectList{ objectLists->getPlayers() }; + + api["createObject"] = [lua = context.mLua](std::string_view recordId, sol::optional count) -> GObject { + checkGameInitialized(lua); + MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId)); + const MWWorld::Ptr& ptr = mref.getPtr(); + ptr.getRefData().disable(); + MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getDraftCell(); + MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, cell, count.value_or(1)); + return GObject(newPtr); + }; + api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject { + ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); + if (!refId.is()) + throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); + return GObject(*refId.getIf()); + }; + + // Creates a new record in the world database. + api["createRecord"] = sol::overload( + [lua = context.mLua](const ESM::Activator& activator) -> const ESM::Activator* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(activator); + }, + [lua = context.mLua](const ESM::Armor& armor) -> const ESM::Armor* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(armor); + }, + [lua = context.mLua](const ESM::Clothing& clothing) -> const ESM::Clothing* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(clothing); + }, + [lua = context.mLua](const ESM::Book& book) -> const ESM::Book* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(book); + }, + [lua = context.mLua](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(misc); + }, + [lua = context.mLua](const ESM::Potion& potion) -> const ESM::Potion* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(potion); + }, + [lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(weapon); + }); + + api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { + if (!object.ptr().getRefData().activate()) + return; + context.mLuaManager->addAction( + [object, actor] { + const MWWorld::Ptr& objPtr = object.ptr(); + const MWWorld::Ptr& actorPtr = actor.ptr(); + objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr); + }, + "_runStandardActivationAction"); + }; + api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor, bool force) { + context.mLuaManager->addAction( + [object, actor, force] { + const MWWorld::Ptr& actorPtr = actor.ptr(); + const MWWorld::Ptr& objectPtr = object.ptr(); + if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->useItem(objectPtr, force); + else + { + std::unique_ptr action = objectPtr.getClass().use(objectPtr, force); + action->execute(actorPtr, true); + } + }, + "_runStandardUseAction"); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/worldbindings.hpp b/apps/openmw/mwlua/worldbindings.hpp new file mode 100644 index 0000000000..4bd2318b68 --- /dev/null +++ b/apps/openmw/mwlua/worldbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_WORLDBINDINGS_H +#define MWLUA_WORLDBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initWorldPackage(const Context&); +} + +#endif // MWLUA_WORLDBINDINGS_H diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index fb3590a3f0..8819aaa29c 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -666,6 +666,18 @@ void MWState::StateManager::update(float duration) MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); } } + + if (mNewGameRequest) + { + newGame(); + mNewGameRequest = false; + } + + if (mLoadRequest) + { + loadGame(*mLoadRequest); + mLoadRequest = std::nullopt; + } } bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index df62ca7ebf..dfd4dd12f0 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -14,6 +14,8 @@ namespace MWState { bool mQuitRequest; bool mAskLoadRecent; + bool mNewGameRequest = false; + std::optional mLoadRequest; State mState; CharacterManager mCharacterManager; double mTimePlayed; @@ -36,6 +38,9 @@ namespace MWState void askLoadRecent() override; + void requestNewGame() override { mNewGameRequest = true; } + void requestLoad(const std::filesystem::path& filepath) override { mLoadRequest = filepath; } + State getState() const override; void newGame(bool bypass = false) override; diff --git a/components/esm/luascripts.hpp b/components/esm/luascripts.hpp index 2a6bf0dbb1..01d322285f 100644 --- a/components/esm/luascripts.hpp +++ b/components/esm/luascripts.hpp @@ -20,8 +20,10 @@ namespace ESM static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script static constexpr Flags sPlayer = 1ull << 2; // auto attach to players - static constexpr Flags sMerge = 1ull - << 3; // merge with configuration for this script from previous content files. + // merge with configuration for this script from previous content files. + static constexpr Flags sMerge = 1ull << 3; + + static constexpr Flags sMenu = 1ull << 4; // start as a menu script std::string mScriptPath; // VFS path to the script. std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'. diff --git a/components/lua/configuration.cpp b/components/lua/configuration.cpp index 85c0cb6724..c6c296f8d5 100644 --- a/components/lua/configuration.cpp +++ b/components/lua/configuration.cpp @@ -18,6 +18,7 @@ namespace LuaUtil { "GLOBAL", ESM::LuaScriptCfg::sGlobal }, { "CUSTOM", ESM::LuaScriptCfg::sCustom }, { "PLAYER", ESM::LuaScriptCfg::sPlayer }, + { "MENU", ESM::LuaScriptCfg::sMenu }, }; const std::map> typeTagsByName{ diff --git a/components/lua/configuration.hpp b/components/lua/configuration.hpp index 3a2df8e43d..eb2a4cd9a5 100644 --- a/components/lua/configuration.hpp +++ b/components/lua/configuration.hpp @@ -22,6 +22,7 @@ namespace LuaUtil std::optional findId(std::string_view path) const; bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; } + ScriptIdsWithInitializationData getMenuConf() const { return getConfByFlag(ESM::LuaScriptCfg::sMenu); } ScriptIdsWithInitializationData getGlobalConf() const { return getConfByFlag(ESM::LuaScriptCfg::sGlobal); } ScriptIdsWithInitializationData getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); } ScriptIdsWithInitializationData getLocalConf( diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index b96da916be..5594b31c6b 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -49,6 +49,14 @@ namespace LuaUtil return !valid; }), mCallbacks.end()); + mPermanentCallbacks.erase(std::remove_if(mPermanentCallbacks.begin(), mPermanentCallbacks.end(), + [&](const Callback& callback) { + bool valid = callback.isValid(); + if (valid) + callback.tryCall(mSectionName, changedKey); + return !valid; + }), + mPermanentCallbacks.end()); mStorage->mRunningCallbacks.erase(this); } @@ -112,7 +120,8 @@ namespace LuaUtil }; sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); }; sview["subscribe"] = [](const SectionView& section, const sol::table& callback) { - std::vector& callbacks = section.mSection->mCallbacks; + std::vector& callbacks + = section.mForMenuScripts ? section.mSection->mPermanentCallbacks : section.mSection->mCallbacks; if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) { callbacks.erase( @@ -166,6 +175,16 @@ namespace LuaUtil return LuaUtil::makeReadOnly(res); } + sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* playerStorage) + { + sol::table res(lua, sol::create); + res["playerSection"] = [playerStorage](std::string_view section) { + return playerStorage->getMutableSection(section, /*forMenuScripts=*/true); + }; + res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); }; + return LuaUtil::makeReadOnly(res); + } + void LuaStorage::clearTemporaryAndRemoveCallbacks() { auto it = mData.begin(); @@ -174,6 +193,7 @@ namespace LuaUtil it->second->mCallbacks.clear(); if (!it->second->mPermanent) { + it->second->mPermanentCallbacks.clear(); it->second->mValues.clear(); it = mData.erase(it); } @@ -231,10 +251,10 @@ namespace LuaUtil return newIt->second; } - sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly) + sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts) { const std::shared_ptr

& section = getSection(sectionName); - return sol::make_object(mLua, SectionView{ section, readOnly }); + return sol::make_object(mLua, SectionView{ section, readOnly, forMenuScripts }); } sol::table LuaStorage::getAllSections(bool readOnly) diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 9998af9430..a785755f10 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -17,6 +17,7 @@ namespace LuaUtil static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); + static sol::table initMenuPackage(lua_State* lua, LuaStorage* playerStorage); explicit LuaStorage(lua_State* lua) : mLua(lua) @@ -27,8 +28,11 @@ namespace LuaUtil void load(const std::filesystem::path& path); void save(const std::filesystem::path& path) const; - sol::object getSection(std::string_view sectionName, bool readOnly); - sol::object getMutableSection(std::string_view sectionName) { return getSection(sectionName, false); } + sol::object getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts = false); + sol::object getMutableSection(std::string_view sectionName, bool forMenuScripts = false) + { + return getSection(sectionName, false, forMenuScripts); + } sol::object getReadOnlySection(std::string_view sectionName) { return getSection(sectionName, true); } sol::table getAllSections(bool readOnly = false); @@ -87,6 +91,7 @@ namespace LuaUtil std::string mSectionName; std::map> mValues; std::vector mCallbacks; + std::vector mPermanentCallbacks; bool mPermanent = true; static Value sEmpty; }; @@ -94,6 +99,7 @@ namespace LuaUtil { std::shared_ptr
mSection; bool mReadOnly; + bool mForMenuScripts = false; }; const std::shared_ptr
& getSection(std::string_view sectionName); diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..14728be732 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -72,9 +72,10 @@ set(BUILTIN_DATA_FILES scripts/omw/camera/settings.lua scripts/omw/camera/move360.lua scripts/omw/camera/first_person_auto_switch.lua - scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua + scripts/omw/console/player.lua + scripts/omw/console/menu.lua scripts/omw/mechanics/playercontroller.lua scripts/omw/playercontrols.lua scripts/omw/settings/player.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index ec08c5299d..c717a13f02 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -19,6 +19,7 @@ NPC,CREATURE: scripts/omw/ai.lua PLAYER: scripts/omw/ui.lua # Lua console +MENU: scripts/omw/console/menu.lua PLAYER: scripts/omw/console/player.lua GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua diff --git a/files/data/scripts/omw/console/menu.lua b/files/data/scripts/omw/console/menu.lua new file mode 100644 index 0000000000..1aa3e7b166 --- /dev/null +++ b/files/data/scripts/omw/console/menu.lua @@ -0,0 +1,114 @@ +local menu = require('openmw.menu') +local ui = require('openmw.ui') +local util = require('openmw.util') + +local menuModeName = 'Lua[Menu]' + +local function printHelp() + local msg = [[ +This is the built-in Lua interpreter. +help() - print this message +exit() - exit Lua mode +view(_G) - print content of the table `_G` (current environment) + standard libraries (math, string, etc.) are loaded by default but not visible in `_G` +view(menu, 2) - print table `menu` (i.e. `openmw.menu`) and its subtables (2 - traversal depth)]] + ui.printToConsole(msg, ui.CONSOLE_COLOR.Info) +end + +local function printToConsole(...) + local strs = {} + for i = 1, select('#', ...) do + strs[i] = tostring(select(i, ...)) + end + return ui.printToConsole(table.concat(strs, '\t'), ui.CONSOLE_COLOR.Info) +end + +local function printRes(...) + if select('#', ...) >= 0 then + printToConsole(...) + end +end + +local function exitLuaMenuMode() + ui.setConsoleMode('') + ui.printToConsole('Lua mode OFF', ui.CONSOLE_COLOR.Success) +end + +local function enterLuaMenuMode() + ui.printToConsole('Lua mode ON, use exit() to return, help() for more info', ui.CONSOLE_COLOR.Success) + ui.printToConsole('Context: Menu', ui.CONSOLE_COLOR.Success) + ui.setConsoleMode(menuModeName) +end + +local env = { + I = require('openmw.interfaces'), + menu = require('openmw.menu'), + util = require('openmw.util'), + core = require('openmw.core'), + storage = require('openmw.storage'), + vfs = require('openmw.vfs'), + ambient = require('openmw.ambient'), + async = require('openmw.async'), + ui = require('openmw.ui'), + aux_util = require('openmw_aux.util'), + view = require('openmw_aux.util').deepToString, + print = printToConsole, + exit = exitLuaMenuMode, + help = printHelp, +} +env._G = env +setmetatable(env, {__index = _G, __metatable = false}) +_G = nil + +local function executeLuaCode(code) + local fn + local ok, err = pcall(function() fn = util.loadCode('return ' .. code, env) end) + if ok then + ok, err = pcall(function() printRes(fn()) end) + else + ok, err = pcall(function() util.loadCode(code, env)() end) + end + if not ok then + ui.printToConsole(err, ui.CONSOLE_COLOR.Error) + end +end + +local usageInfo = [[ +Usage: 'lua menu' or 'luam' - enter menu context +Other contexts are available only when the game is started: + 'lua player' or 'luap' - enter player context + 'lua global' or 'luag' - enter global context + 'lua selected' or 'luas' - enter local context on the selected object]] + +local function onConsoleCommand(mode, cmd) + if mode == '' then + cmd, arg = cmd:lower():match('(%w+) *(%w*)') + if (cmd == 'lua' and arg == 'menu') or cmd == 'luam' then + enterLuaMenuMode() + elseif menu.getState() == menu.STATE.NoGame and (cmd == 'lua' or cmd == 'luap' or cmd == 'luas' or cmd == 'luag') then + ui.printToConsole(usageInfo, ui.CONSOLE_COLOR.Info) + end + elseif mode == menuModeName then + if cmd == 'exit()' then + exitLuaMenuMode() + else + executeLuaCode(cmd) + end + end +end + +local function onStateChanged() + local mode = ui.getConsoleMode() + if menu.getState() ~= menu.STATE.Ended and mode ~= menuModeName then + -- When a new game started or loaded reset console mode (except of `luam`) because + -- other modes become invalid after restarting Lua scripts. + ui.setConsoleMode('') + end +end + +return { + engineHandlers = { + onConsoleCommand = onConsoleCommand, + onStateChanged = onStateChanged, + }, +} diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index c614d2d962..6d0ee790a9 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -77,6 +77,7 @@ local env = { nearby = require('openmw.nearby'), self = require('openmw.self'), input = require('openmw.input'), + postprocessing = require('openmw.postprocessing'), ui = require('openmw.ui'), camera = require('openmw.camera'), aux_util = require('openmw_aux.util'), @@ -114,9 +115,12 @@ local function onConsoleCommand(mode, cmd, selectedObject) cmd = 'luag' elseif arg == 'selected' then cmd = 'luas' + elseif arg == 'menu' then + -- handled in menu.lua else local msg = [[ -Usage: 'lua player' or 'luap' - enter player context +Usage: 'lua menu' or 'luam' - enter menu context + 'lua player' or 'luap' - enter player context 'lua global' or 'luag' - enter global context 'lua selected' or 'luas' - enter local context on the selected object]] ui.printToConsole(msg, ui.CONSOLE_COLOR.Info) @@ -158,4 +162,3 @@ return { OMWConsoleHelp = printHelp, } } - From 889ddc10d6b51827239532efa083590b5e5bae09 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 10:22:58 +0100 Subject: [PATCH 0383/2167] Enable `openmw.input` in menu scripts --- apps/openmw/mwlua/inputbindings.cpp | 10 +++++++++- apps/openmw/mwlua/luabindings.cpp | 10 ++++------ files/data/scripts/omw/console/menu.lua | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 02babf0399..41b2b5c94f 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -24,6 +24,12 @@ namespace MWLua sol::table initInputPackage(const Context& context) { + { + sol::state_view& lua = context.mLua->sol(); + if (lua["openmw_input"] != sol::nil) + return lua["openmw_input"]; + } + sol::usertype keyEvent = context.mLua->sol().new_usertype("KeyEvent"); keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) { if (e.sym > 0 && e.sym <= 255) @@ -291,7 +297,9 @@ namespace MWLua { "Tab", SDL_SCANCODE_TAB }, })); - return LuaUtil::makeReadOnly(api); + sol::state_view& lua = context.mLua->sol(); + lua["openmw_input"] = LuaUtil::makeReadOnly(api); + return lua["openmw_input"]; } } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 931cd43296..a7269d6e52 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -76,13 +76,11 @@ namespace MWLua std::map initMenuPackages(const Context& context) { return { - { "openmw.core", initCorePackageForMenuScripts(context) }, // - { "openmw.ambient", initAmbientPackage(context) }, // - { "openmw.ui", initUserInterfacePackage(context) }, // + { "openmw.core", initCorePackageForMenuScripts(context) }, + { "openmw.ambient", initAmbientPackage(context) }, + { "openmw.ui", initUserInterfacePackage(context) }, { "openmw.menu", initMenuPackage(context) }, - // TODO: Maybe add: - // { "openmw.input", initInputPackage(context) }, - // { "openmw.postprocessing", initPostprocessingPackage(context) }, + { "openmw.input", initInputPackage(context) }, }; } } diff --git a/files/data/scripts/omw/console/menu.lua b/files/data/scripts/omw/console/menu.lua index 1aa3e7b166..9d6dbaf1d7 100644 --- a/files/data/scripts/omw/console/menu.lua +++ b/files/data/scripts/omw/console/menu.lua @@ -50,6 +50,7 @@ local env = { ambient = require('openmw.ambient'), async = require('openmw.async'), ui = require('openmw.ui'), + input = require('openmw.input'), aux_util = require('openmw_aux.util'), view = require('openmw_aux.util').deepToString, print = printToConsole, From 1490f6f082562f4943e4f1fbaabb877249def658 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 10:24:33 +0100 Subject: [PATCH 0384/2167] Fix: lower content file names in `menu.getSaves` --- apps/openmw/mwlua/menuscripts.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp index 300f5ad489..16e57961b1 100644 --- a/apps/openmw/mwlua/menuscripts.cpp +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -1,5 +1,7 @@ #include "menuscripts.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwstate/character.hpp" @@ -85,7 +87,7 @@ namespace MWLua slotInfo["playerLevel"] = slot.mProfile.mPlayerLevel; sol::table contentFiles(lua, sol::create); for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i) - contentFiles[i + 1] = slot.mProfile.mContentFiles[i]; + contentFiles[i + 1] = Misc::StringUtils::lowerCase(slot.mProfile.mContentFiles[i]); slotInfo["contentFiles"] = contentFiles; saves[slot.mPath.filename().string()] = slotInfo; } From dafe858cb4e115f8acaa83147284793d275bf1e0 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 10:28:52 +0100 Subject: [PATCH 0385/2167] Add types.Player.sendMenuEvent --- apps/openmw/mwlua/luaevents.cpp | 9 ++++++++ apps/openmw/mwlua/luaevents.hpp | 8 +++++++- apps/openmw/mwlua/luamanagerimp.cpp | 1 + apps/openmw/mwlua/luamanagerimp.hpp | 2 +- apps/openmw/mwlua/types/player.cpp | 32 ++++++++++++++++------------- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwlua/luaevents.cpp b/apps/openmw/mwlua/luaevents.cpp index 02ea3415d2..4ffb4fc1cc 100644 --- a/apps/openmw/mwlua/luaevents.cpp +++ b/apps/openmw/mwlua/luaevents.cpp @@ -13,6 +13,7 @@ #include "globalscripts.hpp" #include "localscripts.hpp" +#include "menuscripts.hpp" namespace MWLua { @@ -23,6 +24,7 @@ namespace MWLua mLocalEventBatch.clear(); mNewGlobalEventBatch.clear(); mNewLocalEventBatch.clear(); + mMenuEvents.clear(); } void LuaEvents::finalizeEventBatch() @@ -51,6 +53,13 @@ namespace MWLua mLocalEventBatch.clear(); } + void LuaEvents::callMenuEventHandlers() + { + for (const Global& e : mMenuEvents) + mMenuScripts.receiveEvent(e.mEventName, e.mEventData); + mMenuEvents.clear(); + } + template static void saveEvent(ESM::ESMWriter& esm, ESM::RefNum dest, const Event& event) { diff --git a/apps/openmw/mwlua/luaevents.hpp b/apps/openmw/mwlua/luaevents.hpp index 5eeae46538..3890b45b6d 100644 --- a/apps/openmw/mwlua/luaevents.hpp +++ b/apps/openmw/mwlua/luaevents.hpp @@ -23,12 +23,14 @@ namespace MWLua { class GlobalScripts; + class MenuScripts; class LuaEvents { public: - explicit LuaEvents(GlobalScripts& globalScripts) + explicit LuaEvents(GlobalScripts& globalScripts, MenuScripts& menuScripts) : mGlobalScripts(globalScripts) + , mMenuScripts(menuScripts) { } @@ -45,11 +47,13 @@ namespace MWLua }; void addGlobalEvent(Global event) { mNewGlobalEventBatch.push_back(std::move(event)); } + void addMenuEvent(Global event) { mMenuEvents.push_back(std::move(event)); } void addLocalEvent(Local event) { mNewLocalEventBatch.push_back(std::move(event)); } void clear(); void finalizeEventBatch(); void callEventHandlers(); + void callMenuEventHandlers(); void load(lua_State* lua, ESM::ESMReader& esm, const std::map& contentFileMapping, const LuaUtil::UserdataSerializer* serializer); @@ -57,10 +61,12 @@ namespace MWLua private: GlobalScripts& mGlobalScripts; + MenuScripts& mMenuScripts; std::vector mNewGlobalEventBatch; std::vector mNewLocalEventBatch; std::vector mGlobalEventBatch; std::vector mLocalEventBatch; + std::vector mMenuEvents; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 77ddfcc4a7..52b5dace3b 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -229,6 +229,7 @@ namespace MWLua playerScripts->processInputEvent(event); } mInputEvents.clear(); + mLuaEvents.callMenuEventHandlers(); mMenuScripts.update(0); if (playerScripts) playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index b16e81082b..53031516a8 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -172,7 +172,7 @@ namespace MWLua MWWorld::Ptr mPlayer; - LuaEvents mLuaEvents{ mGlobalScripts }; + LuaEvents mLuaEvents{ mGlobalScripts, mMenuScripts }; EngineEvents mEngineEvents{ mGlobalScripts }; std::vector mInputEvents; diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index cef0753817..3acfec12b7 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -35,14 +35,18 @@ namespace sol namespace MWLua { + static void verifyPlayer(const Object& player) + { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player!"); + } void addPlayerQuestBindings(sol::table& player, const Context& context) { MWBase::Journal* const journal = MWBase::Environment::get().getJournal(); player["quests"] = [](const Object& player) { - if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) - throw std::runtime_error("The argument must be a player!"); + verifyPlayer(player); bool allowChanges = dynamic_cast(&player) != nullptr || dynamic_cast(&player) != nullptr; return Quests{ .mMutable = allowChanges }; @@ -134,28 +138,28 @@ namespace MWLua MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); player["getControlSwitch"] = [input](const Object& player, std::string_view key) { - if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) - throw std::runtime_error("The argument must be a player."); + verifyPlayer(player); return input->getControlSwitch(key); }; + player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { + verifyPlayer(player); + if (dynamic_cast(&player) && !dynamic_cast(&player)) + throw std::runtime_error("Only player and global scripts can toggle control switches."); + input->toggleControlSwitch(key, v); + }; player["isTeleportingEnabled"] = [](const Object& player) -> bool { - if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) - throw std::runtime_error("The argument must be a player."); + verifyPlayer(player); return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); }; player["setTeleportingEnabled"] = [](const Object& player, bool state) { - if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) - throw std::runtime_error("The argument must be a player."); + verifyPlayer(player); if (dynamic_cast(&player) && !dynamic_cast(&player)) throw std::runtime_error("Only player and global scripts can toggle teleportation."); MWBase::Environment::get().getWorld()->enableTeleporting(state); }; - player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { - if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) - throw std::runtime_error("The argument must be a player."); - if (dynamic_cast(&player) && !dynamic_cast(&player)) - throw std::runtime_error("Only player and global scripts can toggle control switches."); - input->toggleControlSwitch(key, v); + player["sendMenuEvent"] = [context](const Object& player, std::string eventName, const sol::object& eventData) { + verifyPlayer(player); + context.mLuaEvents->addMenuEvent({ std::move(eventName), LuaUtil::serialize(eventData) }); }; } From f5325e11e343274b40d12fa0dad05f590e44ffb7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 11:01:17 +0100 Subject: [PATCH 0386/2167] Rename mPermanentCallbacks -> mMenuScriptsCallbacks in LuaUtil::Storage --- components/lua/storage.cpp | 22 ++++++++++++---------- components/lua/storage.hpp | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index 5594b31c6b..b2f972e853 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -49,14 +49,14 @@ namespace LuaUtil return !valid; }), mCallbacks.end()); - mPermanentCallbacks.erase(std::remove_if(mPermanentCallbacks.begin(), mPermanentCallbacks.end(), - [&](const Callback& callback) { - bool valid = callback.isValid(); - if (valid) - callback.tryCall(mSectionName, changedKey); - return !valid; - }), - mPermanentCallbacks.end()); + mMenuScriptsCallbacks.erase(std::remove_if(mMenuScriptsCallbacks.begin(), mMenuScriptsCallbacks.end(), + [&](const Callback& callback) { + bool valid = callback.isValid(); + if (valid) + callback.tryCall(mSectionName, changedKey); + return !valid; + }), + mMenuScriptsCallbacks.end()); mStorage->mRunningCallbacks.erase(this); } @@ -121,7 +121,7 @@ namespace LuaUtil sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); }; sview["subscribe"] = [](const SectionView& section, const sol::table& callback) { std::vector& callbacks - = section.mForMenuScripts ? section.mSection->mPermanentCallbacks : section.mSection->mCallbacks; + = section.mForMenuScripts ? section.mSection->mMenuScriptsCallbacks : section.mSection->mCallbacks; if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) { callbacks.erase( @@ -191,9 +191,11 @@ namespace LuaUtil while (it != mData.end()) { it->second->mCallbacks.clear(); + // Note that we don't clear menu callbacks for permanent sections + // because starting/loading a game doesn't reset menu scripts. if (!it->second->mPermanent) { - it->second->mPermanentCallbacks.clear(); + it->second->mMenuScriptsCallbacks.clear(); it->second->mValues.clear(); it = mData.erase(it); } diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index a785755f10..3376e7e50c 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -91,7 +91,8 @@ namespace LuaUtil std::string mSectionName; std::map> mValues; std::vector mCallbacks; - std::vector mPermanentCallbacks; + std::vector mMenuScriptsCallbacks; // menu callbacks are in a separate vector because we don't + // remove them in clear() bool mPermanent = true; static Value sEmpty; }; From ebfcb661eea3133ee9b645bad7c60e71eb12eb5b Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 31 Oct 2023 11:05:54 +0100 Subject: [PATCH 0387/2167] Support reload for settings values Convert local static variables into unique_ptr static members of StaticValues. Add clear member function to reset them. Use it when settings have to be reloaded. --- components/settings/settings.cpp | 2 ++ components/settings/values.cpp | 27 ++++++++++++++++----------- components/settings/values.hpp | 8 ++++++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7b31bf6aad..8560130e66 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -128,6 +128,8 @@ namespace Settings void Manager::clear() { + sInitialized.clear(); + StaticValues::clear(); mDefaultSettings.clear(); mUserSettings.clear(); mChangedSettings.clear(); diff --git a/components/settings/values.cpp b/components/settings/values.cpp index 66460beb9a..a6bbec3df7 100644 --- a/components/settings/values.cpp +++ b/components/settings/values.cpp @@ -4,24 +4,29 @@ namespace Settings { - Index* StaticValues::sIndex = nullptr; - Values* StaticValues::sValues = nullptr; + std::unique_ptr StaticValues::sIndex; + std::unique_ptr StaticValues::sDefaultValues; + std::unique_ptr StaticValues::sValues; void StaticValues::initDefaults() { - if (sValues != nullptr) - throw std::logic_error("Default settings already initialized"); - static Index index; - static Values values(index); - sIndex = &index; - sValues = &values; + if (sDefaultValues != nullptr) + throw std::logic_error("Default settings are already initialized"); + sIndex = std::make_unique(); + sDefaultValues = std::make_unique(*sIndex); } void StaticValues::init() { - if (sValues == nullptr) + if (sDefaultValues == nullptr) throw std::logic_error("Default settings are not initialized"); - static Values values(std::move(*sValues)); - sValues = &values; + sValues = std::make_unique(std::move(*sDefaultValues)); + } + + void StaticValues::clear() + { + sValues = nullptr; + sDefaultValues = nullptr; + sIndex = nullptr; } } diff --git a/components/settings/values.hpp b/components/settings/values.hpp index 323be2e866..af28dc16f8 100644 --- a/components/settings/values.hpp +++ b/components/settings/values.hpp @@ -29,6 +29,7 @@ #include "settingvalue.hpp" #include +#include #include namespace Settings @@ -71,9 +72,12 @@ namespace Settings static void init(); + static void clear(); + private: - static Index* sIndex; - static Values* sValues; + static std::unique_ptr sIndex; + static std::unique_ptr sDefaultValues; + static std::unique_ptr sValues; friend Values& values(); From 54441624a12b62395f1fa68482074371dede94db Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 31 Oct 2023 19:59:06 +0100 Subject: [PATCH 0388/2167] Changelog entry for 4508 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da12eac3..1ff2a50047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 Bug #4382: Sound output device does not change when it should + Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached From 733a6e01adc2d89df9bfa5391b2469329ec56199 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Tue, 31 Oct 2023 21:27:42 +0000 Subject: [PATCH 0389/2167] Sort repair and recharge menu list alphabetically (bug #7642) --- CHANGELOG.md | 1 + apps/openmw/mwgui/itemchargeview.cpp | 5 ++++ apps/openmw/mwgui/merchantrepair.cpp | 39 ++++++++++++++++++---------- apps/openmw/mwgui/repair.cpp | 2 +- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da12eac3..9d5ce61418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 92fff6f873..ba74eadc7a 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -128,6 +128,11 @@ namespace MWGui mLines.swap(lines); + std::stable_sort(mLines.begin(), mLines.end(), + [](const MWGui::ItemChargeView::Line& a, const MWGui::ItemChargeView::Line& b) { + return Misc::StringUtils::ciLess(a.mText->getCaption().asUTF8(), b.mText->getCaption().asUTF8()); + }); + layoutWidgets(); } diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index c5393fbfb7..3be0bb1c06 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -47,6 +47,8 @@ namespace MWGui MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; + std::vector> items; + for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter) { if (iter->getClass().hasItemHealth(*iter)) @@ -76,22 +78,31 @@ namespace MWGui name += " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getESMStore()->get().find("sgp")->mValue.getString(); - MyGUI::Button* button = mList->createWidget(price <= playerGold - ? "SandTextButton" - : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - 0, currentY, 0, lineHeight, MyGUI::Align::Default); - - currentY += lineHeight; - - button->setUserString("Price", MyGUI::utility::toString(price)); - button->setUserData(MWWorld::Ptr(*iter)); - button->setCaptionWithReplacing(name); - button->setSize(mList->getWidth(), lineHeight); - button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); - button->setUserString("ToolTipType", "ItemPtr"); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + items.emplace_back(name, price, *iter); } } + + std::stable_sort(items.begin(), items.end(), + [](const auto& a, const auto& b) { return Misc::StringUtils::ciLess(std::get<0>(a), std::get<0>(b)); }); + + for (const auto& [name, price, ptr] : items) + { + MyGUI::Button* button = mList->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + 0, currentY, 0, lineHeight, MyGUI::Align::Default); + + currentY += lineHeight; + + button->setUserString("Price", MyGUI::utility::toString(price)); + button->setUserData(MWWorld::Ptr(ptr)); + button->setCaptionWithReplacing(name); + button->setSize(mList->getWidth(), lineHeight); + button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); + button->setUserString("ToolTipType", "ItemPtr"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the // scrollbar is hidden mList->setVisibleVScroll(false); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index aabd3d46ab..63b51d7d2c 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -49,7 +49,7 @@ namespace MWGui = new SortFilterItemModel(std::make_unique(MWMechanics::getPlayer())); model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); mRepairBox->setModel(model); - + mRepairBox->update(); // Reset scrollbars mRepairBox->resetScrollbars(); } From 7e9690e53160b226e1b238cf50a3a81d15e848ea Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Mon, 30 Oct 2023 10:57:13 -0700 Subject: [PATCH 0390/2167] [renderer] render scene exclusively to fbo --- apps/openmw/mwrender/npcanimation.cpp | 55 ++-- apps/openmw/mwrender/pingpongcanvas.cpp | 67 ++--- apps/openmw/mwrender/pingpongcanvas.hpp | 94 +++---- apps/openmw/mwrender/pingpongcull.cpp | 30 +- apps/openmw/mwrender/postprocessor.cpp | 310 ++++++++------------- apps/openmw/mwrender/postprocessor.hpp | 62 ++--- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/screenshotmanager.cpp | 21 +- 8 files changed, 234 insertions(+), 407 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index cb7ef3626f..669a6fae45 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -328,46 +328,37 @@ namespace MWRender { osg::State* state = renderInfo.getState(); - PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + PostProcessor* postProcessor = static_cast(renderInfo.getCurrentCamera()->getUserData()); state->applyAttribute(mDepth); unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; - if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)) + postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); + if (mPassNormals) { - postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); - if (mPassNormals) - { - state->get()->glColorMaski(1, true, true, true, true); - state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); - } - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - // color accumulation pass - bin->drawImplementation(renderInfo, previous); - - auto primaryFBO = postProcessor->getPrimaryFbo(frameId); - - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); - else - primaryFBO->apply(*state); - - // depth accumulation pass - osg::ref_ptr restore = bin->getStateSet(); - bin->setStateSet(mStateSet); - bin->drawImplementation(renderInfo, previous); - bin->setStateSet(restore); - - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - primaryFBO->apply(*state); + state->get()->glColorMaski(1, true, true, true, true); + state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); } + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + // color accumulation pass + bin->drawImplementation(renderInfo, previous); + + auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); else - { - // fallback to standard depth clear when we are not rendering our main scene via an intermediate FBO - glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - bin->drawImplementation(renderInfo, previous); - } + primaryFBO->apply(*state); + + // depth accumulation pass + osg::ref_ptr restore = bin->getStateSet(); + bin->setStateSet(mStateSet); + bin->drawImplementation(renderInfo, previous); + bin->setStateSet(restore); + + if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) + primaryFBO->apply(*state); state->checkGLErrors("after DepthClearCallback::drawImplementation"); } diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 6a56f7e5f7..5ac68acf5f 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -43,19 +43,16 @@ namespace MWRender mMultiviewResolveStateSet->addUniform(new osg::Uniform("lastShader", 0)); } - void PingPongCanvas::setCurrentFrameData(size_t frameId, fx::DispatchArray&& data) + void PingPongCanvas::setPasses(fx::DispatchArray&& passes) { - mBufferData[frameId].data = std::move(data); + mPasses = std::move(passes); } - void PingPongCanvas::setMask(size_t frameId, bool underwater, bool exterior) + void PingPongCanvas::setMask(bool underwater, bool exterior) { - mBufferData[frameId].mask = 0; - - mBufferData[frameId].mask - |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; - mBufferData[frameId].mask - |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; + mMask = 0; + mMask |= underwater ? fx::Technique::Flag_Disable_Underwater : fx::Technique::Flag_Disable_Abovewater; + mMask |= exterior ? fx::Technique::Flag_Disable_Exteriors : fx::Technique::Flag_Disable_Interiors; } void PingPongCanvas::drawGeometry(osg::RenderInfo& renderInfo) const @@ -77,19 +74,15 @@ namespace MWRender size_t frameId = state.getFrameStamp()->getFrameNumber() % 2; - auto& bufferData = mBufferData[frameId]; - - const auto& data = bufferData.data; - std::vector filtered; - filtered.reserve(data.size()); + filtered.reserve(mPasses.size()); - for (size_t i = 0; i < data.size(); ++i) + for (size_t i = 0; i < mPasses.size(); ++i) { - const auto& node = data[i]; + const auto& node = mPasses[i]; - if (bufferData.mask & node.mFlags) + if (mMask & node.mFlags) continue; filtered.push_back(i); @@ -97,7 +90,7 @@ namespace MWRender auto* resolveViewport = state.getCurrentViewport(); - if (filtered.empty() || !bufferData.postprocessing) + if (filtered.empty() || !mPostprocessing) { state.pushStateSet(mFallbackStateSet); state.apply(); @@ -108,7 +101,7 @@ namespace MWRender state.apply(); } - state.applyTextureAttribute(0, bufferData.sceneTex); + state.applyTextureAttribute(0, mTextureScene); resolveViewport->apply(state); drawGeometry(renderInfo); @@ -124,13 +117,12 @@ namespace MWRender const unsigned int handle = mFbos[0] ? mFbos[0]->getHandle(state.getContextID()) : 0; - if (handle == 0 || bufferData.dirty) + if (handle == 0 || mDirty) { for (auto& fbo : mFbos) { fbo = new osg::FrameBufferObject; - attachCloneOfTemplate( - fbo, osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); + attachCloneOfTemplate(fbo, osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, mTextureScene); fbo->apply(state); glClearColor(0.5, 0.5, 0.5, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -140,7 +132,7 @@ namespace MWRender { mMultiviewResolveFramebuffer = new osg::FrameBufferObject(); attachCloneOfTemplate(mMultiviewResolveFramebuffer, - osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, bufferData.sceneTexLDR); + osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, mTextureScene); mMultiviewResolveFramebuffer->apply(state); glClearColor(0.5, 0.5, 0.5, 1); glClear(GL_COLOR_BUFFER_BIT); @@ -150,15 +142,15 @@ namespace MWRender .getTexture()); } - mLuminanceCalculator.dirty(bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); if (Stereo::getStereo()) - mRenderViewport = new osg::Viewport( - 0, 0, bufferData.sceneTex->getTextureWidth(), bufferData.sceneTex->getTextureHeight()); + mRenderViewport + = new osg::Viewport(0, 0, mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); else mRenderViewport = nullptr; - bufferData.dirty = false; + mDirty = false; } constexpr std::array, 3> buffers @@ -166,7 +158,7 @@ namespace MWRender { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } }; - (bufferData.hdr) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); + (mAvgLum) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly // supported, so that's what we use for now. @@ -181,8 +173,7 @@ namespace MWRender const unsigned int cid = state.getContextID(); - const osg::ref_ptr& destinationFbo - = bufferData.destination ? bufferData.destination : nullptr; + const osg::ref_ptr& destinationFbo = mDestinationFBO ? mDestinationFBO : nullptr; unsigned int destinationHandle = destinationFbo ? destinationFbo->getHandle(cid) : 0; auto bindDestinationFbo = [&]() { @@ -206,17 +197,16 @@ namespace MWRender for (const size_t& index : filtered) { - const auto& node = data[index]; + const auto& node = mPasses[index]; - node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, bufferData.depthTex); + node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth); - if (bufferData.hdr) + if (mAvgLum) node.mRootStateSet->setTextureAttribute( PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); - if (bufferData.normalsTex) - node.mRootStateSet->setTextureAttribute( - PostProcessor::TextureUnits::Unit_Normals, bufferData.normalsTex); + if (mTextureNormals) + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); state.pushStateSet(node.mRootStateSet); state.apply(); @@ -231,7 +221,7 @@ namespace MWRender // VR-TODO: This won't actually work for tex2darrays if (lastShader == 0) - pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, bufferData.sceneTex); + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, mTextureScene); else pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastShader, (osg::Texture*)mFbos[lastShader - GL_COLOR_ATTACHMENT0_EXT] @@ -239,7 +229,7 @@ namespace MWRender .getTexture()); if (lastDraw == 0) - pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, bufferData.sceneTex); + pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, mTextureScene); else pass.mStateSet->setTextureAttribute(PostProcessor::Unit_LastPass, (osg::Texture*)mFbos[lastDraw - GL_COLOR_ATTACHMENT0_EXT] @@ -260,7 +250,6 @@ namespace MWRender } lastApplied = pass.mRenderTarget->getHandle(state.getContextID()); - ; } else if (pass.mResolve && index == filtered.back()) { diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index a5557a6d6e..d8758303d7 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -24,76 +24,52 @@ namespace MWRender public: PingPongCanvas(Shader::ShaderManager& shaderManager); - void drawImplementation(osg::RenderInfo& renderInfo) const override; - - void dirty(size_t frameId) { mBufferData[frameId].dirty = true; } - - const fx::DispatchArray& getCurrentFrameData(size_t frame) { return mBufferData[frame % 2].data; } - - // Sets current frame pass data and stores copy of dispatch array to apply to next frame data - void setCurrentFrameData(size_t frameId, fx::DispatchArray&& data); - - void setMask(size_t frameId, bool underwater, bool exterior); - - void setSceneTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].sceneTex = tex; } - - void setLDRSceneTexture(size_t frameId, osg::ref_ptr tex) - { - mBufferData[frameId].sceneTexLDR = tex; - } - - void setDepthTexture(size_t frameId, osg::ref_ptr tex) { mBufferData[frameId].depthTex = tex; } - - void setNormalsTexture(size_t frameId, osg::ref_ptr tex) - { - mBufferData[frameId].normalsTex = tex; - } - - void setHDR(size_t frameId, bool hdr) { mBufferData[frameId].hdr = hdr; } - - void setPostProcessing(size_t frameId, bool postprocessing) - { - mBufferData[frameId].postprocessing = postprocessing; - } - - const osg::ref_ptr& getSceneTexture(size_t frameId) const - { - return mBufferData[frameId].sceneTex; - } - void drawGeometry(osg::RenderInfo& renderInfo) const; - private: - void copyNewFrameData(size_t frameId) const; + void drawImplementation(osg::RenderInfo& renderInfo) const override; - mutable LuminanceCalculator mLuminanceCalculator; + void dirty() { mDirty = true; } + + const fx::DispatchArray& getPasses() { return mPasses; } + + void setPasses(fx::DispatchArray&& passes); + + void setMask(bool underwater, bool exterior); + + void setTextureScene(osg::ref_ptr tex) { mTextureScene = tex; } + + void setTextureDepth(osg::ref_ptr tex) { mTextureDepth = tex; } + + void setTextureNormals(osg::ref_ptr tex) { mTextureNormals = tex; } + + void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; } + + void setPostProcessing(bool enabled) { mPostprocessing = enabled; } + + const osg::ref_ptr& getSceneTexture(size_t frameId) const { return mTextureScene; } + + private: + bool mAvgLum = false; + bool mPostprocessing = false; + + fx::DispatchArray mPasses; + fx::FlagsType mMask; osg::ref_ptr mFallbackProgram; osg::ref_ptr mMultiviewResolveProgram; osg::ref_ptr mFallbackStateSet; osg::ref_ptr mMultiviewResolveStateSet; - mutable osg::ref_ptr mMultiviewResolveFramebuffer; - struct BufferData - { - bool dirty = false; - bool hdr = false; - bool postprocessing = true; + osg::ref_ptr mTextureScene; + osg::ref_ptr mTextureDepth; + osg::ref_ptr mTextureNormals; - fx::DispatchArray data; - fx::FlagsType mask; - - osg::ref_ptr destination; - - osg::ref_ptr sceneTex; - osg::ref_ptr depthTex; - osg::ref_ptr sceneTexLDR; - osg::ref_ptr normalsTex; - }; - - mutable std::array mBufferData; - mutable std::array, 3> mFbos; + mutable bool mDirty = false; mutable osg::ref_ptr mRenderViewport; + mutable osg::ref_ptr mMultiviewResolveFramebuffer; + mutable osg::ref_ptr mDestinationFBO; + mutable std::array, 3> mFbos; + mutable LuminanceCalculator mLuminanceCalculator; }; } diff --git a/apps/openmw/mwrender/pingpongcull.cpp b/apps/openmw/mwrender/pingpongcull.cpp index 8dfff5a60c..497c6c734a 100644 --- a/apps/openmw/mwrender/pingpongcull.cpp +++ b/apps/openmw/mwrender/pingpongcull.cpp @@ -21,7 +21,7 @@ namespace MWRender if (Stereo::getStereo()) { mViewportStateset = new osg::StateSet(); - mViewport = new osg::Viewport(0, 0, pp->renderWidth(), pp->renderHeight()); + mViewport = new osg::Viewport; mViewportStateset->setAttribute(mViewport); } } @@ -37,41 +37,31 @@ namespace MWRender size_t frame = cv->getTraversalNumber(); size_t frameId = frame % 2; - MWRender::PostProcessor* postProcessor - = dynamic_cast(cv->getCurrentCamera()->getUserData()); - if (!postProcessor) - throw std::runtime_error("PingPongCull: failed to get a PostProcessor!"); - if (Stereo::getStereo()) { auto& sm = Stereo::Manager::instance(); auto view = sm.getEye(cv); int index = view == Stereo::Eye::Right ? 1 : 0; auto projectionMatrix = sm.computeEyeProjection(index, true); - postProcessor->getStateUpdater()->setProjectionMatrix(projectionMatrix); + mPostProcessor->getStateUpdater()->setProjectionMatrix(projectionMatrix); } - postProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); - postProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix[0]); + mPostProcessor->getStateUpdater()->setViewMatrix(cv->getCurrentCamera()->getViewMatrix()); + mPostProcessor->getStateUpdater()->setPrevViewMatrix(mLastViewMatrix[0]); mLastViewMatrix[0] = cv->getCurrentCamera()->getViewMatrix(); - postProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); - postProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); + mPostProcessor->getStateUpdater()->setEyePos(cv->getEyePoint()); + mPostProcessor->getStateUpdater()->setEyeVec(cv->getLookVectorLocal()); - if (!postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) + if (!mPostProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) { - renderStage->setMultisampleResolveFramebufferObject(nullptr); - renderStage->setFrameBufferObject(nullptr); - } - else if (!postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)) - { - renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + renderStage->setFrameBufferObject(mPostProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); } else { renderStage->setMultisampleResolveFramebufferObject( - postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); - renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); + mPostProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); + renderStage->setFrameBufferObject(mPostProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); // The MultiView patch has a bug where it does not update resolve layers if the resolve framebuffer is // changed. So we do blit manually in this case diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index a6945de299..c3106802db 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -110,30 +110,45 @@ namespace MWRender PostProcessor::PostProcessor( RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs) : osg::Group() - , mEnableLiveReload(false) , mRootNode(rootNode) - , mSamples(Settings::video().mAntialiasing) - , mDirty(false) - , mDirtyFrameId(0) + , mHUDCamera(new osg::Camera) , mRendering(rendering) , mViewer(viewer) , mVFS(vfs) - , mTriggerShaderReload(false) - , mReload(false) - , mEnabled(false) , mUsePostProcessing(Settings::postProcessing().mEnabled) - , mDisableDepthPasses(false) - , mLastFrameNumber(0) - , mLastSimulationTime(0.f) - , mExteriorFlag(false) - , mUnderwater(false) - , mHDR(false) - , mNormals(false) - , mPrevNormals(false) - , mNormalsSupported(false) - , mPassLights(false) - , mPrevPassLights(false) + , mSamples(Settings::video().mAntialiasing) + , mPingPongCull(new PingPongCull(this)) + , mCanvases({ new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()), + new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()) }) { + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); + mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); + mHUDCamera->setClearMask(0); + mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + mHUDCamera->setAllowEventFocus(false); + mHUDCamera->setViewport(0, 0, mWidth, mHeight); + mHUDCamera->setNodeMask(Mask_RenderToTexture); + mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mHUDCamera->addChild(mCanvases[0]); + mHUDCamera->addChild(mCanvases[1]); + mHUDCamera->setCullCallback(new HUDCullCallback); + mViewer->getCamera()->addCullCallback(mPingPongCull); + + if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) + { + mTransparentDepthPostPass + = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), + Settings::postProcessing().mTransparentPostpass); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); + } + + createObjectsForFrame(0); + createObjectsForFrame(1); + + populateTechniqueFiles(); + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -169,11 +184,18 @@ namespace MWRender mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); - if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !Settings::shaders().mSoftParticles - && !mUsePostProcessing) - return; + addChild(mHUDCamera); + addChild(mRootNode); - enable(mUsePostProcessing); + mViewer->setSceneData(this); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); + mViewer->getCamera()->setUserData(this); + + setCullCallback(mStateUpdater); + + if (mUsePostProcessing) + enable(); } PostProcessor::~PostProcessor() @@ -202,7 +224,6 @@ namespace MWRender size_t frameId = frame() % 2; - createTexturesAndCamera(frameId); createObjectsForFrame(frameId); mRendering.updateProjectionMatrix(); @@ -210,8 +231,6 @@ namespace MWRender dirtyTechniques(); - mPingPongCanvas->dirty(frameId); - mDirty = true; mDirtyFrameId = !frameId; } @@ -230,77 +249,20 @@ namespace MWRender } } - void PostProcessor::enable(bool usePostProcessing) + void PostProcessor::enable() { mReload = true; - mEnabled = true; - const bool postPass = Settings::postProcessing().mTransparentPostpass; - mUsePostProcessing = usePostProcessing; - - mDisableDepthPasses = !Settings::shaders().mSoftParticles && !postPass; - -#ifdef ANDROID - mDisableDepthPasses = true; -#endif - - if (!mDisableDepthPasses) - { - mTransparentDepthPostPass = new TransparentDepthBinCallback( - mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), postPass); - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); - } - - if (mUsePostProcessing && mTechniqueFileMap.empty()) - { - populateTechniqueFiles(); - } - - createTexturesAndCamera(frame() % 2); - - removeChild(mHUDCamera); - removeChild(mRootNode); - - addChild(mHUDCamera); - addChild(mRootNode); - - mViewer->setSceneData(this); - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); - mViewer->getCamera()->setUserData(this); - - setCullCallback(mStateUpdater); - mHUDCamera->setCullCallback(new HUDCullCallback); + mUsePostProcessing = true; } void PostProcessor::disable() { - if (!Settings::shaders().mSoftParticles) - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(nullptr); - - if (!SceneUtil::AutoDepth::isReversed() && !Settings::shaders().mSoftParticles) - { - removeChild(mHUDCamera); - setCullCallback(nullptr); - - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); - mViewer->getCamera()->getGraphicsContext()->setResizedCallback(nullptr); - mViewer->getCamera()->setUserData(nullptr); - - mEnabled = false; - } - mUsePostProcessing = false; mRendering.getSkyManager()->setSunglare(true); } void PostProcessor::traverse(osg::NodeVisitor& nv) { - if (!mEnabled) - { - osg::Group::traverse(nv); - return; - } - size_t frameId = nv.getTraversalNumber() % 2; if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) @@ -313,26 +275,23 @@ namespace MWRender void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) { - const auto& fbo = getFbo(FBO_Intercept, frameId); - if (fbo) + if (const auto& fbo = getFbo(FBO_Intercept, frameId)) { osgUtil::RenderStage* rs = cv->getRenderStage(); if (rs && rs->getMultisampleResolveFramebufferObject()) rs->setMultisampleResolveFramebufferObject(fbo); } - mPingPongCanvas->setPostProcessing(frameId, mUsePostProcessing); - mPingPongCanvas->setNormalsTexture(frameId, mNormals ? getTexture(Tex_Normal, frameId) : nullptr); - mPingPongCanvas->setMask(frameId, mUnderwater, mExteriorFlag); - mPingPongCanvas->setHDR(frameId, getHDR()); + mCanvases[frameId]->setPostProcessing(mUsePostProcessing); + mCanvases[frameId]->setTextureNormals(mNormals ? getTexture(Tex_Normal, frameId) : nullptr); + mCanvases[frameId]->setMask(mUnderwater, mExteriorFlag); + mCanvases[frameId]->setCalculateAvgLum(mHDR); - mPingPongCanvas->setSceneTexture(frameId, getTexture(Tex_Scene, frameId)); - if (mDisableDepthPasses) - mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_Depth, frameId)); + mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId)); + if (mTransparentDepthPostPass) + mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); else - mPingPongCanvas->setDepthTexture(frameId, getTexture(Tex_OpaqueDepth, frameId)); - - mPingPongCanvas->setLDRSceneTexture(frameId, getTexture(Tex_Scene_LDR, frameId)); + mCanvases[frameId]->setTextureDepth(getTexture(Tex_Depth, frameId)); if (mTransparentDepthPostPass) { @@ -355,7 +314,7 @@ namespace MWRender mStateUpdater->setDeltaSimulationTime(static_cast(stamp->getSimulationTime() - mLastSimulationTime)); mLastSimulationTime = stamp->getSimulationTime(); - for (const auto& dispatchNode : mPingPongCanvas->getCurrentFrameData(frame)) + for (const auto& dispatchNode : mCanvases[frameId]->getPasses()) { for (auto& uniform : dispatchNode.mHandle->getUniformMap()) { @@ -421,13 +380,15 @@ namespace MWRender reloadIfRequired(); + mCanvases[frameId]->setNodeMask(~0u); + mCanvases[!frameId]->setNodeMask(0); + if (mDirty && mDirtyFrameId == frameId) { - createTexturesAndCamera(frameId); createObjectsForFrame(frameId); - mDirty = false; - mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); + mDirty = false; + mCanvases[frameId]->setPasses(fx::DispatchArray(mTemplateData)); } if ((mNormalsSupported && mNormals != mPrevNormals) || (mPassLights != mPrevPassLights)) @@ -448,7 +409,6 @@ namespace MWRender mViewer->startThreading(); - createTexturesAndCamera(frameId); createObjectsForFrame(frameId); mDirty = true; @@ -458,20 +418,56 @@ namespace MWRender void PostProcessor::createObjectsForFrame(size_t frameId) { - auto& fbos = mFbos[frameId]; auto& textures = mTextures[frameId]; - auto width = renderWidth(); - auto height = renderHeight(); - for (auto& tex : textures) + int width = renderWidth(); + int height = renderHeight(); + + for (osg::ref_ptr& texture : textures) { - if (!tex) - continue; - - Stereo::setMultiviewCompatibleTextureSize(tex, width, height); - tex->dirtyTextureObject(); + if (!texture) + { + if (Stereo::getMultiview()) + texture = new osg::Texture2DArray; + else + texture = new osg::Texture2D; + } + Stereo::setMultiviewCompatibleTextureSize(texture, width, height); + texture->setSourceFormat(GL_RGBA); + texture->setSourceType(GL_UNSIGNED_BYTE); + texture->setInternalFormat(GL_RGBA); + texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setResizeNonPowerOfTwoHint(false); + Stereo::setMultiviewCompatibleTextureSize(texture, width, height); + texture->dirtyTextureObject(); } + textures[Tex_Normal]->setSourceFormat(GL_RGB); + textures[Tex_Normal]->setInternalFormat(GL_RGB); + + auto setupDepth = [](osg::Texture* tex) { + tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); + tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); + }; + + setupDepth(textures[Tex_Depth]); + + if (!mTransparentDepthPostPass) + { + textures[Tex_OpaqueDepth] = nullptr; + } + else + { + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); + } + + auto& fbos = mFbos[frameId]; + fbos[FBO_Primary] = new osg::FrameBufferObject; fbos[FBO_Primary]->setAttachment( osg::Camera::COLOR_BUFFER0, Stereo::createMultiviewCompatibleAttachment(textures[Tex_Scene])); @@ -534,13 +530,12 @@ namespace MWRender osg::FrameBufferAttachment(new osg::RenderBuffer(textures[Tex_OpaqueDepth]->getTextureWidth(), textures[Tex_OpaqueDepth]->getTextureHeight(), textures[Tex_Scene]->getInternalFormat()))); #endif + + mCanvases[frameId]->dirty(); } void PostProcessor::dirtyTechniques() { - if (!isEnabled()) - return; - size_t frameId = frame() % 2; mDirty = true; @@ -667,7 +662,7 @@ namespace MWRender mTemplateData.emplace_back(std::move(node)); } - mPingPongCanvas->setCurrentFrameData(frameId, fx::DispatchArray(mTemplateData)); + mCanvases[frameId]->setPasses(fx::DispatchArray(mTemplateData)); if (auto hud = MWBase::Environment::get().getWindowManager()->getPostProcessorHud()) hud->updateTechniques(); @@ -678,12 +673,6 @@ namespace MWRender PostProcessor::Status PostProcessor::enableTechnique( std::shared_ptr technique, std::optional location) { - if (!isEnabled()) - { - Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << technique->getName() << "'"; - return Status_Error; - } - if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0)) return Status_Error; @@ -721,86 +710,8 @@ namespace MWRender return technique->isValid(); } - void PostProcessor::createTexturesAndCamera(size_t frameId) - { - auto& textures = mTextures[frameId]; - - auto width = renderWidth(); - auto height = renderHeight(); - - for (auto& texture : textures) - { - if (!texture) - { - if (Stereo::getMultiview()) - texture = new osg::Texture2DArray; - else - texture = new osg::Texture2D; - } - Stereo::setMultiviewCompatibleTextureSize(texture, width, height); - texture->setSourceFormat(GL_RGBA); - texture->setSourceType(GL_UNSIGNED_BYTE); - texture->setInternalFormat(GL_RGBA); - texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - texture->setResizeNonPowerOfTwoHint(false); - } - - textures[Tex_Normal]->setSourceFormat(GL_RGB); - textures[Tex_Normal]->setInternalFormat(GL_RGB); - - auto setupDepth = [](osg::Texture* tex) { - tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); - tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); - tex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); - }; - - setupDepth(textures[Tex_Depth]); - - if (mDisableDepthPasses) - { - textures[Tex_OpaqueDepth] = nullptr; - } - else - { - setupDepth(textures[Tex_OpaqueDepth]); - textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); - } - - if (mHUDCamera) - return; - - mHUDCamera = new osg::Camera; - mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); - mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); - mHUDCamera->setClearMask(0); - mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); - mHUDCamera->setAllowEventFocus(false); - mHUDCamera->setViewport(0, 0, mWidth, mHeight); - - mViewer->getCamera()->removeCullCallback(mPingPongCull); - mPingPongCull = new PingPongCull(this); - mViewer->getCamera()->addCullCallback(mPingPongCull); - - mPingPongCanvas = new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()); - mHUDCamera->addChild(mPingPongCanvas); - mHUDCamera->setNodeMask(Mask_RenderToTexture); - - mHUDCamera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - mHUDCamera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); - } - std::shared_ptr PostProcessor::loadTechnique(const std::string& name, bool loadNextFrame) { - if (!isEnabled()) - { - Log(Debug::Warning) << "PostProcessing disabled, cannot load technique '" << name << "'"; - return nullptr; - } - for (const auto& technique : mTemplates) if (Misc::StringUtils::ciEqual(technique->getName(), name)) return technique; @@ -831,9 +742,6 @@ namespace MWRender void PostProcessor::loadChain() { - if (!isEnabled()) - return; - mTechniques.clear(); for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 4473ade836..153ec8166b 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -115,14 +115,14 @@ namespace MWRender return mFbos[frameId][FBO_Multisample] ? mFbos[frameId][FBO_Multisample] : mFbos[frameId][FBO_Primary]; } + osg::ref_ptr getHUDCamera() { return mHUDCamera; } + osg::ref_ptr getStateUpdater() { return mStateUpdater; } const TechniqueList& getTechniques() { return mTechniques; } const TechniqueList& getTemplates() const { return mTemplates; } - osg::ref_ptr getCanvas() { return mPingPongCanvas; } - const auto& getTechniqueMap() const { return mTechniqueFileMap; } void resize(); @@ -173,13 +173,11 @@ namespace MWRender std::shared_ptr loadTechnique(const std::string& name, bool loadNextFrame = false); - bool isEnabled() const { return mUsePostProcessing && mEnabled; } - - bool getHDR() const { return mHDR; } + bool isEnabled() const { return mUsePostProcessing; } void disable(); - void enable(bool usePostProcessing = true); + void enable(); void setRenderTargetSize(int width, int height) { @@ -194,7 +192,7 @@ namespace MWRender void triggerShaderReload(); - bool mEnableLiveReload; + bool mEnableLiveReload = false; void loadChain(); void saveChain(); @@ -206,10 +204,6 @@ namespace MWRender void createObjectsForFrame(size_t frameId); - void createTexturesAndCamera(size_t frameId); - - void reloadMainPass(fx::Technique& technique); - void dirtyTechniques(); void update(size_t frameId); @@ -232,43 +226,39 @@ namespace MWRender std::unordered_map mTechniqueFileMap; - int mSamples; - - bool mDirty; - size_t mDirtyFrameId; - RenderingManager& mRendering; osgViewer::Viewer* mViewer; const VFS::Manager* mVFS; - bool mTriggerShaderReload; - bool mReload; - bool mEnabled; - bool mUsePostProcessing; - bool mDisableDepthPasses; + size_t mDirtyFrameId = 0; + size_t mLastFrameNumber = 0; + float mLastSimulationTime = 0.f; - size_t mLastFrameNumber; - float mLastSimulationTime; + bool mDirty = false; + bool mReload = true; + bool mTriggerShaderReload = false; + bool mUsePostProcessing = false; + + bool mUBO = false; + bool mHDR = false; + bool mNormals = false; + bool mUnderwater = false; + bool mPassLights = false; + bool mPrevNormals = false; + bool mExteriorFlag = false; + bool mNormalsSupported = false; + bool mPrevPassLights = false; - bool mExteriorFlag; - bool mUnderwater; - bool mHDR; - bool mNormals; - bool mPrevNormals; - bool mNormalsSupported; - bool mPassLights; - bool mPrevPassLights; - bool mUBO; int mGLSLVersion; + int mWidth; + int mHeight; + int mSamples; osg::ref_ptr mStateUpdater; osg::ref_ptr mPingPongCull; - osg::ref_ptr mPingPongCanvas; + std::array, 2> mCanvases; osg::ref_ptr mTransparentDepthPostPass; - int mWidth; - int mHeight; - fx::DispatchArray mTemplateData; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 090e6be312..3b6716ce2c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1256,7 +1256,7 @@ namespace MWRender mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); } - else if (!mPostProcessor->isEnabled()) + else { mSharedUniformStateUpdater->setScreenRes(width, height); } diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index b4bdda0a28..a23d242a15 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -21,6 +21,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "postprocessor.hpp" #include "util.hpp" @@ -102,24 +103,6 @@ namespace MWRender int width = screenW - leftPadding * 2; int height = screenH - topPadding * 2; - // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure - // that the readbuffer is set correctly with rendeirng to FBO. glReadPixel() cannot read from multisampled - // targets - PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); - osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); - - if (ext) - { - size_t frameId = renderInfo.getState()->getFrameStamp()->getFrameNumber() % 2; - osg::FrameBufferObject* fbo = nullptr; - - if (postProcessor && postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)) - fbo = postProcessor->getFbo(PostProcessor::FBO_Primary, frameId); - - if (fbo) - fbo->apply(*renderInfo.getState(), osg::FrameBufferObject::READ_FRAMEBUFFER); - } - mImage->readPixels(leftPadding, topPadding, width, height, GL_RGB, GL_UNSIGNED_BYTE); mImage->scaleImage(mWidth, mHeight, 1); } @@ -145,7 +128,7 @@ namespace MWRender void ScreenshotManager::screenshot(osg::Image* image, int w, int h) { - osg::Camera* camera = mViewer->getCamera(); + osg::Camera* camera = MWBase::Environment::get().getWorld()->getPostProcessor()->getHUDCamera(); osg::ref_ptr tempDrw = new osg::Drawable; tempDrw->setDrawCallback(new ReadImageFromFramebufferCallback(image, w, h)); tempDrw->setCullingActive(false); From 6c01ce267294abfd90ed74bcd8973ed4e459e0f9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 11:47:42 +0100 Subject: [PATCH 0391/2167] Use correct template flags for FONV and FO4 NPCs --- apps/openmw/mwclass/esm4npc.cpp | 30 +++++++++++++++++----- components/esm4/loadnpc.cpp | 4 ++- components/esm4/loadnpc.hpp | 45 +++++++++++++++++++-------------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 78cbd89b50..13e7866537 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -34,11 +34,26 @@ namespace MWClass static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) { - // In case of FO3 the function may return nullptr that will lead to "ESM4 NPC traits not found" - // exception and the NPC will not be added to the scene. But in any way it shouldn't cause a crash. for (const auto* rec : recs) - if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag)) + { + if (rec->mIsTES4) return rec; + else if (rec->mIsFONV) + { + // TODO: FO3 should use this branch as well. But it is not clear how to distinguish FO3 from + // TES5. Currently FO3 uses wrong template flags that can lead to "ESM4 NPC traits not found" + // exception the NPC will not be added to the scene. But in any way it shouldn't cause a crash. + if (!(rec->mBaseConfig.fo3.templateFlags & flag)) + return rec; + } + else if (rec->mIsFO4) + { + if (!(rec->mBaseConfig.fo4.templateFlags & flag)) + return rec; + } + else if (!(rec->mBaseConfig.tes5.templateFlags & flag)) + return rec; + } return nullptr; } @@ -75,8 +90,8 @@ namespace MWClass const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); auto npcRecs = withBaseTemplates(ptr.get()->mBase); - data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits); - data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); + data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); if (!data->mTraits) throw std::runtime_error("ESM4 NPC traits not found"); @@ -88,10 +103,13 @@ namespace MWClass data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; else if (data->mTraits->mIsFONV) data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are same as TES5 else data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; - if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseInventory)) + if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { for (const ESM4::InventoryItem& item : inv->mInventory) { diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 251af13630..885263d67b 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -116,9 +116,11 @@ void ESM4::Npc::load(ESM4::Reader& reader) { switch (subHdr.dataSize) { + case 20: // FO4 + mIsFO4 = true; + [[fallthrough]]; case 16: // TES4 case 24: // FO3/FNV, TES5 - case 20: // FO4 reader.get(&mBaseConfig, subHdr.dataSize); break; default: diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp index 6f4b3c7e24..04e56b7bd9 100644 --- a/components/esm4/loadnpc.hpp +++ b/components/esm4/loadnpc.hpp @@ -78,6 +78,7 @@ namespace ESM4 FO3_NoRotateHead = 0x40000000 }; + // In FO4 flags seem to be the same. enum ACBS_TES5 { TES5_Female = 0x00000001, @@ -101,27 +102,32 @@ namespace ESM4 TES5_Invulnerable = 0x80000000 }; + // All FO3+ games. enum Template_Flags { - TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, - // voice type, death item; Sounds tab; Animation tab; Character Gen tabs - TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, - // speed, bleedout, class - TES5_UseFactions = 0x0004, // both factions and assigned crime faction - TES5_UseSpellList = 0x0008, // both spells and perks - TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and - // gift filter - TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; - // rest of tab controlled by Def Pack List - TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, - // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter - TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item - // -- but not death item - TES5_UseScript = 0x0200, - TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) - TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, - // events, and data) - TES5_UseKeywords = 0x1000 + Template_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, + // voice type, death item; Sounds tab; Animation tab; Character Gen tabs + Template_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class + Template_UseFactions = 0x0004, // both factions and assigned crime faction + Template_UseSpellList = 0x0008, // both spells and perks + Template_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and + // gift filter + Template_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; + // rest of tab controlled by Def Pack List + Template_UseModel = 0x0040, // FO3, FONV; probably not used in TES5+ + Template_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter + Template_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item, + // but not death item + Template_UseScript = 0x0200, + + // The following flags were added in TES5+: + + Template_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) + Template_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, + // events, and data) + Template_UseKeywords = 0x1000 }; #pragma pack(push, 1) @@ -172,6 +178,7 @@ namespace ESM4 bool mIsTES4; bool mIsFONV; + bool mIsFO4 = false; std::string mEditorId; std::string mFullName; From 4c6e081da37b0a266668a3d2f89766a04a0201f7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 1 Nov 2023 10:30:19 +0100 Subject: [PATCH 0392/2167] Skip zero links to ArmorAddons --- apps/openmw/mwrender/esm4npcanimation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 1f06e68bc2..3ea8f829ce 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -124,6 +124,8 @@ namespace MWRender auto findArmorAddons = [&](const ESM4::Armor* armor) { for (ESM::FormId armaId : armor->mAddOns) { + if (armaId.isZeroOrUnset()) + continue; const ESM4::ArmorAddon* arma = store->get().search(armaId); if (!arma) { From d3ccc246c54c806801e981404fed66a9c497e982 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 1 Nov 2023 12:03:49 +0000 Subject: [PATCH 0393/2167] Apply 1 suggestion(s) to 1 file(s) --- apps/openmw/mwclass/esm4npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 13e7866537..638144eb66 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -105,7 +105,7 @@ namespace MWClass data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; else if (data->mTraits->mIsFO4) data->mIsFemale - = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are same as TES5 + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 else data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; From 97324136557c0426b8f5d32245227e49df11472e Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 1 Nov 2023 17:45:22 +0000 Subject: [PATCH 0394/2167] Fix two errors in postprocessing docs --- docs/source/reference/postprocessing/omwfx.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index a4028c5040..36a6f0883a 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -318,7 +318,7 @@ Exactly one ``technique`` block is required for every shader file. In this we de +------------------+--------------------+---------------------------------------------------+ | author | string | Shader authors that shows in HUD | +------------------+--------------------+---------------------------------------------------+ -| glsl_Version | integer | GLSL version | +| glsl_version | integer | GLSL version | +------------------+--------------------+---------------------------------------------------+ | glsl_profile | string | GLSL profile, like ``compatibility`` | +------------------+--------------------+---------------------------------------------------+ @@ -625,7 +625,6 @@ together passes, setting up metadata, and setting up various flags. passes = desaturate; version = "1.0"; author = "Fargoth"; - passes = desaturate; } From 482d174ef288613a8380ff9f354208e5be4d18dc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 1 Nov 2023 13:21:14 -0500 Subject: [PATCH 0395/2167] Read only, add skill spec to docs --- apps/openmw/mwlua/luabindings.cpp | 2 +- files/lua_api/openmw/core.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index b55bf3ca27..1731b4dd70 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -163,7 +163,7 @@ namespace MWLua character["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); character["classes"] = initCoreClassBindings(context); - api["character"] = character; + api["character"] = LuaUtil::makeReadOnly(character);; api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index c1d9be3169..a86cd6590e 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -872,6 +872,7 @@ -- @field #string name Human-readable name -- @field #string description Human-readable description -- @field #string icon VFS path to the icon +-- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @type MagicSchoolData From 67e74936ff3ff0ac1b56df32975206dcdd0d8762 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 1 Nov 2023 13:23:32 -0500 Subject: [PATCH 0396/2167] Make classes Read Only --- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/luabindings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 237aaae291..d0f5767226 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -82,6 +82,6 @@ namespace MWLua [](const ESM::Class& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); - return classes; + return LuaUtil::makeReadOnly(classes); } } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 1731b4dd70..782cc07c46 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -163,7 +163,7 @@ namespace MWLua character["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); character["classes"] = initCoreClassBindings(context); - api["character"] = LuaUtil::makeReadOnly(character);; + api["character"] = LuaUtil::makeReadOnly(character); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); From dbd7d341207be95d3371fd3650cb196d851d5b42 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 1 Nov 2023 13:27:37 -0500 Subject: [PATCH 0397/2167] Add class Record --- files/lua_api/openmw/core.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index a86cd6590e..98046ac7c0 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -25,6 +25,12 @@ -- A read-only list of all @{#ClassRecord}s in the world database. -- @field [parent=#Classes] #list<#ClassRecord> records +--- +-- Returns a read-only @{#ClassRecord} +-- @function [parent=#Classes] record +-- @param #string recordId +-- @return #ClassRecord + --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit From 93b723a0662b55004d3f2c7738a78a833f719d16 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 19:47:08 +0100 Subject: [PATCH 0398/2167] Apply legs yaw to accumulated movement. --- apps/openmw/mwrender/animation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0741e24a69..bac9dbb56c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1235,9 +1235,11 @@ namespace MWRender mRootController->setEnabled(enable); if (enable) { - mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)) - * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); + osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)); + mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); yawOffset = mLegsYawRadians; + // When yawing the root, also update the accumulated movement. + movement = legYaw * movement; } } if (mSpineController) From f41de6b02da923f24656b6510d4041c39f144688 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 19:48:16 +0100 Subject: [PATCH 0399/2167] Use accumulated movement whenever possible. Apply diagonal movement by rotating accumulated movement and sliding based on that, rather than ignoring accumulated movement. --- apps/openmw/mwmechanics/character.cpp | 98 +++++++++++++++---------- components/settings/categories/game.hpp | 1 + files/settings-default.cfg | 5 ++ 3 files changed, 67 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 068d91ab42..1fca6c6b71 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2387,48 +2387,72 @@ namespace MWMechanics } osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (duration > 0.0f) - moved /= duration; - else - moved = osg::Vec3f(0.f, 0.f, 0.f); - moved.x() *= scale; - moved.y() *= scale; - - // Ensure we're moving in generally the right direction... - if (speed > 0.f && moved != osg::Vec3f()) + if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { - float l = moved.length(); - if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 - || std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 - || std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) - { - moved = movement; - // For some creatures getSpeed doesn't work, so we adjust speed to the animation. - // TODO: Fix Creature::getSpeed. - float newLength = moved.length(); - if (newLength > 0 && !cls.isNpc()) - moved *= (l / newLength); - } - } + if (duration > 0.0f) + moved /= duration; + else + moved = osg::Vec3f(0.f, 0.f, 0.f); - if (mFloatToSurface && cls.isActor()) - { - if (cls.getCreatureStats(mPtr).isDead() - || (!godmode - && cls.getCreatureStats(mPtr) - .getMagicEffects() - .getOrDefault(ESM::MagicEffect::Paralyze) - .getModifier() - > 0)) - { - moved.z() = 1.0; - } - } + moved.x() *= scale; + moved.y() *= scale; - // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) + if (speed > 0.f && moved != osg::Vec3f()) + { + // Ensure we're moving in generally the right direction + // This is necessary when the "turn to movement direction" feature is off, as animations + // will not be rotated to match diagonal movement. In this case we have to slide the + // character diagonally. + + // First decide the general direction expected from the current animation + float animMovementAngle = 0; + if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) + { + if (cls.getMovementSettings(mPtr).mIsStrafing) + animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; + else + animMovementAngle = movement.y() >= 0 ? 0 : -osg::PIf; + } + else + { + animMovementAngle = mAnimation->getLegsYawRadians(); + if (movement.y() < 0) + animMovementAngle -= osg::PIf; + } + + const float epsilon = 0.001f; + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + if (std::fabsf(diff) > epsilon) + { + moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; + } + + if (isPlayer && Settings::game().mPlayerMovementIgnoresAnimation) + { + moved = movement; + } + } + + if (mFloatToSurface) + { + if (cls.getCreatureStats(mPtr).isDead() + || (!godmode + && cls.getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Paralyze) + .getModifier() + > 0)) + { + moved.z() = 1.0; + } + } + + // Update movement + && !isScriptedAnimPlaying() world->queueMovement(mPtr, moved); + } mSkipAnim = false; diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index ded367a54c..375d8ef819 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -74,6 +74,7 @@ namespace Settings "unarmed creature attacks damage armor" }; SettingValue mActorCollisionShapeType{ mIndex, "Game", "actor collision shape type" }; + SettingValue mPlayerMovementIgnoresAnimation{ mIndex, "Game", "player movement ignores animation" }; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index bbc6b4d1c8..da1c97519a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -365,6 +365,11 @@ unarmed creature attacks damage armor = false # 2 = Cylinder actor collision shape type = 0 +# When false the player character will base movement on animations. This will sway the camera +# while moving in third person like in vanilla, and reproduce movement bugs caused by glitchy +# vanilla animations. +player movement ignores animation = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). From c59fb6c91c9fb5bee9b42fd87d29de95965af605 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 20:29:47 +0100 Subject: [PATCH 0400/2167] Changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8516ae25..97ea9246d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses + Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X From a0f8bbc621d8181ec4acca3d8e28c558cffcba41 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 20:34:39 +0100 Subject: [PATCH 0401/2167] Bad merge --- apps/openmw/mwmechanics/character.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1fca6c6b71..05c0ad2264 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2450,7 +2450,6 @@ namespace MWMechanics } // Update movement - && !isScriptedAnimPlaying() world->queueMovement(mPtr, moved); } From 452f7a470e095f2c3ba5bdd3d09dadd2aecff628 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 23:19:13 +0100 Subject: [PATCH 0402/2167] fabsf -> abs --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 05c0ad2264..4eb223e250 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2424,7 +2424,7 @@ namespace MWMechanics const float epsilon = 0.001f; float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - if (std::fabsf(diff) > epsilon) + if (std::abs(diff) > epsilon) { moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; } From 633fd892700f8b7c146633b5670b8e14d5686b27 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 1 Nov 2023 23:34:54 +0100 Subject: [PATCH 0403/2167] Cleanup settings categories includes --- components/settings/categories/camera.hpp | 4 ++-- components/settings/categories/cells.hpp | 4 ++-- components/settings/categories/fog.hpp | 4 ++-- components/settings/categories/game.hpp | 6 +++--- components/settings/categories/general.hpp | 4 ++-- components/settings/categories/groundcover.hpp | 4 ++-- components/settings/categories/gui.hpp | 4 ++-- components/settings/categories/hud.hpp | 2 +- components/settings/categories/input.hpp | 4 ++-- components/settings/categories/lua.hpp | 4 ++-- components/settings/categories/map.hpp | 6 +++--- components/settings/categories/models.hpp | 2 +- components/settings/categories/navigator.hpp | 4 ++-- components/settings/categories/physics.hpp | 4 ++-- components/settings/categories/postprocessing.hpp | 4 ++-- components/settings/categories/saves.hpp | 4 ++-- components/settings/categories/shaders.hpp | 6 +++--- components/settings/categories/shadows.hpp | 4 ++-- components/settings/categories/sound.hpp | 6 +++--- components/settings/categories/stereo.hpp | 2 +- components/settings/categories/stereoview.hpp | 4 ++-- components/settings/categories/terrain.hpp | 4 ++-- components/settings/categories/video.hpp | 3 +-- components/settings/categories/water.hpp | 4 ++-- components/settings/categories/windows.hpp | 3 +-- 25 files changed, 49 insertions(+), 51 deletions(-) diff --git a/components/settings/categories/camera.hpp b/components/settings/categories/camera.hpp index 4712f88b45..5b3a9b742b 100644 --- a/components/settings/categories/camera.hpp +++ b/components/settings/categories/camera.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/cells.hpp b/components/settings/categories/cells.hpp index 723004d674..86fe944acf 100644 --- a/components/settings/categories/cells.hpp +++ b/components/settings/categories/cells.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/fog.hpp b/components/settings/categories/fog.hpp index 5acf3d20c6..3bc75587f1 100644 --- a/components/settings/categories/fog.hpp +++ b/components/settings/categories/fog.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index ded367a54c..5b400acb50 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H -#include "components/detournavigator/collisionshapetype.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/general.hpp b/components/settings/categories/general.hpp index 7bbb651ee8..a79ef83ea0 100644 --- a/components/settings/categories/general.hpp +++ b/components/settings/categories/general.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/groundcover.hpp b/components/settings/categories/groundcover.hpp index 48f97037ae..78615a4e76 100644 --- a/components/settings/categories/groundcover.hpp +++ b/components/settings/categories/groundcover.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index 1d3a56af39..af438d5ddc 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/hud.hpp b/components/settings/categories/hud.hpp index e97a81501d..fe05e39eda 100644 --- a/components/settings/categories/hud.hpp +++ b/components/settings/categories/hud.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/input.hpp b/components/settings/categories/input.hpp index 1e59b45334..0a450f1dcd 100644 --- a/components/settings/categories/input.hpp +++ b/components/settings/categories/input.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/lua.hpp b/components/settings/categories/lua.hpp index da11a605eb..88d74b2d1f 100644 --- a/components/settings/categories/lua.hpp +++ b/components/settings/categories/lua.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/map.hpp b/components/settings/categories/map.hpp index bdccd6f205..8a80d5aa63 100644 --- a/components/settings/categories/map.hpp +++ b/components/settings/categories/map.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H -#include "components/misc/constants.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/models.hpp b/components/settings/categories/models.hpp index fec8da7775..0d26eeba5f 100644 --- a/components/settings/categories/models.hpp +++ b/components/settings/categories/models.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index b820b2c950..d6d7adcd56 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" #include +#include +#include #include #include diff --git a/components/settings/categories/physics.hpp b/components/settings/categories/physics.hpp index 41005a06cf..4720708db2 100644 --- a/components/settings/categories/physics.hpp +++ b/components/settings/categories/physics.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/postprocessing.hpp b/components/settings/categories/postprocessing.hpp index 04810b847c..7f5ddfba3e 100644 --- a/components/settings/categories/postprocessing.hpp +++ b/components/settings/categories/postprocessing.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/saves.hpp b/components/settings/categories/saves.hpp index e565d98564..3a64785412 100644 --- a/components/settings/categories/saves.hpp +++ b/components/settings/categories/saves.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/shaders.hpp b/components/settings/categories/shaders.hpp index 7efb891822..dce2531c1e 100644 --- a/components/settings/categories/shaders.hpp +++ b/components/settings/categories/shaders.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H -#include "components/sceneutil/lightingmethod.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/shadows.hpp b/components/settings/categories/shadows.hpp index d716272bce..0da6f649c4 100644 --- a/components/settings/categories/shadows.hpp +++ b/components/settings/categories/shadows.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 43313e622d..995bce2a58 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H -#include "components/settings/hrtfmode.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include diff --git a/components/settings/categories/stereo.hpp b/components/settings/categories/stereo.hpp index 98fae8693f..aa903c5b53 100644 --- a/components/settings/categories/stereo.hpp +++ b/components/settings/categories/stereo.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index bcd0f57abc..7f08d9bc35 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/terrain.hpp b/components/settings/categories/terrain.hpp index c2eef9dc20..f26cc264b8 100644 --- a/components/settings/categories/terrain.hpp +++ b/components/settings/categories/terrain.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index 0e0f7a75bb..cb12ea079c 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,13 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H +#include #include #include #include #include -#include - #include #include #include diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index b88d38b023..2e04114244 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/windows.hpp b/components/settings/categories/windows.hpp index 1fc249cc97..2f22e751e6 100644 --- a/components/settings/categories/windows.hpp +++ b/components/settings/categories/windows.hpp @@ -1,8 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include #include #include From 7cea0344b28125c5afbff74c234d4c942597f552 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 21 Oct 2023 12:29:26 +0400 Subject: [PATCH 0404/2167] Add Clangd cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f25adf58e6..39033bd725 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ Doxygen .idea cmake-build-* files/windows/*.aps +.cache/clangd ## qt-creator CMakeLists.txt.user* .vs From 960d9032171e94c75fc4bb9ab88b9f9d04a304a4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 1 Nov 2023 11:49:13 +0400 Subject: [PATCH 0405/2167] Add comment --- components/widgets/fontwrapper.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/widgets/fontwrapper.hpp b/components/widgets/fontwrapper.hpp index f6a57923a9..813d323f99 100644 --- a/components/widgets/fontwrapper.hpp +++ b/components/widgets/fontwrapper.hpp @@ -5,10 +5,11 @@ #include "components/settings/values.hpp" -#include +#include namespace Gui { + /// Wrapper to tell UI element to use font size from settings.cfg template class FontWrapper : public T { From f4efbcc1c4c35a752113956d83f0aeeb122d3fa1 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 1 Nov 2023 23:53:32 +0100 Subject: [PATCH 0406/2167] Use settings values for Shadows settings --- apps/launcher/graphicspage.cpp | 83 +++++++++-------------- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 18 ++--- apps/openmw/mwrender/sky.cpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- components/sceneutil/shadow.cpp | 74 +++++++++----------- components/sceneutil/shadow.hpp | 18 +++-- 8 files changed, 92 insertions(+), 114 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 18fb57805f..952e0c9349 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -158,32 +158,32 @@ bool Launcher::GraphicsPage::loadSettings() lightingMethodComboBox->setCurrentIndex(lightingMethod); // Shadows - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) actorShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) playerShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) objectShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText( - QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); + shadowComputeSceneBoundsComboBox->setCurrentIndex( + shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str())))); - int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); + const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; if (shadowDistLimit > 0) { shadowDistanceCheckBox->setCheckState(Qt::Checked); shadowDistanceSpinBox->setValue(shadowDistLimit); } - float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows"); + const float shadowFadeStart = Settings::shadows().mShadowFadeStart; if (shadowFadeStart != 0) fadeStartSpinBox->setValue(shadowFadeStart); - int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int shadowRes = Settings::shadows().mShadowMapResolution; int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); @@ -240,55 +240,36 @@ void Launcher::GraphicsPage::saveSettings() Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); // Shadows - int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist) - Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist); - float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart) - Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart); + const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; + Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); + const float cFadeStart = fadeStartSpinBox->value(); + if (cShadowDist > 0) + Settings::shadows().mShadowFadeStart.set(cFadeStart); - bool cActorShadows = actorShadowsCheckBox->checkState(); - bool cObjectShadows = objectShadowsCheckBox->checkState(); - bool cTerrainShadows = terrainShadowsCheckBox->checkState(); - bool cPlayerShadows = playerShadowsCheckBox->checkState(); + const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", true); - if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows) - Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows); - if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows) - Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows); - if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows) - Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows); - if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows) - Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows); + Settings::shadows().mEnableShadows.set(true); + Settings::shadows().mActorShadows.set(cActorShadows); + Settings::shadows().mPlayerShadows.set(cPlayerShadows); + Settings::shadows().mObjectShadows.set(cObjectShadows); + Settings::shadows().mTerrainShadows.set(cTerrainShadows); } else { - if (Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", false); - if (Settings::Manager::getBool("actor shadows", "Shadows")) - Settings::Manager::setBool("actor shadows", "Shadows", false); - if (Settings::Manager::getBool("player shadows", "Shadows")) - Settings::Manager::setBool("player shadows", "Shadows", false); - if (Settings::Manager::getBool("object shadows", "Shadows")) - Settings::Manager::setBool("object shadows", "Shadows", false); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) - Settings::Manager::setBool("terrain shadows", "Shadows", false); + Settings::shadows().mEnableShadows.set(false); + Settings::shadows().mActorShadows.set(false); + Settings::shadows().mPlayerShadows.set(false); + Settings::shadows().mObjectShadows.set(false); + Settings::shadows().mTerrainShadows.set(false); } - bool cIndoorShadows = indoorShadowsCheckBox->checkState(); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows) - Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows); - - int cShadowRes = shadowResolutionComboBox->currentText().toInt(); - if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows")) - Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes); - - auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); - if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows")) - Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds); + Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); + Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); + Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString()); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 86371dc0bf..88ceeabd23 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 3d28bf477d..892a8b5428 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b6716ce2c..c722a51c89 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -331,8 +331,8 @@ namespace MWRender // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::Manager::getBool("enable shadows", "Shadows") - || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); + || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ + || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped @@ -367,22 +367,22 @@ namespace MWRender sceneRoot->setName("Scene Root"); int shadowCastingTraversalMask = Mask_Scene; - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) shadowCastingTraversalMask |= Mask_Actor; - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) shadowCastingTraversalMask |= Mask_Player; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) shadowCastingTraversalMask |= (Mask_Object | Mask_Static); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) shadowCastingTraversalMask |= Mask_Terrain; mShadowManager = std::make_unique(sceneRoot, mRootNode, shadowCastingTraversalMask, - indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, + indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, Settings::shadows(), mResourceSystem->getSceneManager()->getShaderManager()); - Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(Settings::shadows()); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); @@ -770,7 +770,7 @@ namespace MWRender if (enabled) mShadowManager->enableOutdoorMode(); else - mShadowManager->enableIndoorMode(); + mShadowManager->enableIndoorMode(Settings::shadows()); mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 51018e93f9..a38030738a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } private: @@ -271,7 +271,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 091ab99821..85df70adfc 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -265,7 +265,8 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet( + Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -341,7 +342,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index f2748d70f1..04f3b65edd 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "mwshadowtechnique.hpp" @@ -13,9 +13,10 @@ namespace SceneUtil { using namespace osgShadow; - void ShadowManager::setupShadowSettings(Shader::ShaderManager& shaderManager) + void ShadowManager::setupShadowSettings( + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { - mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + mEnableShadows = settings.mEnableShadows; if (!mEnableShadows) { @@ -28,64 +29,58 @@ namespace SceneUtil mShadowSettings->setLightNum(0); mShadowSettings->setReceivesShadowTraversalMask(~0u); - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); mShadowSettings->setBaseShadowTextureUnit(shaderManager.reserveGlobalTextureUnits( Shader::ShaderManager::Slot::ShadowMaps, numberOfShadowMapsPerLight)); - const float maximumShadowMapDistance = Settings::Manager::getFloat("maximum shadow map distance", "Shadows"); + const float maximumShadowMapDistance = settings.mMaximumShadowMapDistance; if (maximumShadowMapDistance > 0) { - const float shadowFadeStart - = std::clamp(Settings::Manager::getFloat("shadow fade start", "Shadows"), 0.f, 1.f); + const float shadowFadeStart = settings.mShadowFadeStart; mShadowSettings->setMaximumShadowMapDistance(maximumShadowMapDistance); mShadowTechnique->setShadowFadeStart(maximumShadowMapDistance * shadowFadeStart); } - mShadowSettings->setMinimumShadowMapNearFarRatio( - Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + mShadowSettings->setMinimumShadowMapNearFarRatio(settings.mMinimumLispsmNearFarRatio); - const std::string& computeSceneBounds = Settings::Manager::getString("compute scene bounds", "Shadows"); + const std::string& computeSceneBounds = settings.mComputeSceneBounds; if (Misc::StringUtils::ciEqual(computeSceneBounds, "primitives")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); else if (Misc::StringUtils::ciEqual(computeSceneBounds, "bounds")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int mapres = settings.mShadowMapResolution; mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); - mShadowTechnique->setSplitPointUniformLogarithmicRatio( - Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); - mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + mShadowTechnique->setSplitPointUniformLogarithmicRatio(settings.mSplitPointUniformLogarithmicRatio); + mShadowTechnique->setSplitPointDeltaBias(settings.mSplitPointBias); - mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), - Settings::Manager::getFloat("polygon offset units", "Shadows")); + mShadowTechnique->setPolygonOffset(settings.mPolygonOffsetFactor, settings.mPolygonOffsetUnits); - if (Settings::Manager::getBool("use front face culling", "Shadows")) + if (settings.mUseFrontFaceCulling) mShadowTechnique->enableFrontFaceCulling(); else mShadowTechnique->disableFrontFaceCulling(); - if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + if (settings.mAllowShadowMapOverlap) mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); else mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); - if (Settings::Manager::getBool("enable debug hud", "Shadows")) + if (settings.mEnableDebugHud) mShadowTechnique->enableDebugHUD(); else mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) + if (!settings.mEnableShadows) return; - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; @@ -99,18 +94,18 @@ namespace SceneUtil fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) { - stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); } } ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager) + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) : mShadowedScene(new osgShadow::ShadowedScene) , mShadowTechnique(new MWShadowTechnique) , mOutdoorShadowCastingMask(outdoorShadowCastingMask) @@ -126,7 +121,7 @@ namespace SceneUtil mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); - setupShadowSettings(shaderManager); + setupShadowSettings(settings, shaderManager); mShadowTechnique->setupCastingShader(shaderManager); mShadowTechnique->setWorldMask(worldMask); @@ -140,7 +135,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) { if (!mEnableShadows) return getShadowsDisabledDefines(); @@ -155,24 +150,19 @@ namespace SceneUtil definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr( 0, definesWithShadows["shadow_texture_unit_list"].length() - 1); - definesWithShadows["shadowMapsOverlap"] - = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + definesWithShadows["shadowMapsOverlap"] = settings.mAllowShadowMapOverlap ? "1" : "0"; - definesWithShadows["useShadowDebugOverlay"] - = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + definesWithShadows["useShadowDebugOverlay"] = settings.mEnableDebugOverlay ? "1" : "0"; // switch this to reading settings if it's ever exposed to the user definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; - definesWithShadows["disableNormalOffsetShadows"] - = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + definesWithShadows["disableNormalOffsetShadows"] = settings.mNormalOffsetDistance == 0.0 ? "1" : "0"; - definesWithShadows["shadowNormalOffset"] - = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + definesWithShadows["shadowNormalOffset"] = std::to_string(settings.mNormalOffsetDistance); - definesWithShadows["limitShadowMapDistance"] - = Settings::Manager::getFloat("maximum shadow map distance", "Shadows") > 0 ? "1" : "0"; + definesWithShadows["limitShadowMapDistance"] = settings.mMaximumShadowMapDistance > 0 ? "1" : "0"; return definesWithShadows; } @@ -200,9 +190,9 @@ namespace SceneUtil return definesWithoutShadows; } - void ShadowManager::enableIndoorMode() + void ShadowManager::enableIndoorMode(const Settings::ShadowsCategory& settings) { - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (settings.mEnableIndoorShadows) mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); else mShadowTechnique->disableShadows(true); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index 76fd2b7fdd..fd82e828b6 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -8,32 +8,38 @@ namespace osg class StateSet; class Group; } + namespace osgShadow { class ShadowSettings; class ShadowedScene; } +namespace Settings +{ + struct ShadowsCategory; +} + namespace SceneUtil { class MWShadowTechnique; class ShadowManager { public: - static void disableShadowsForStateSet(osg::ref_ptr stateSet); + static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager); + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); ~ShadowManager(); - void setupShadowSettings(Shader::ShaderManager& shaderManager); + void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(); + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); - void enableIndoorMode(); + void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); From abae5f031d0c78d53ba2ceef646cbee4ee6adcb9 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 2 Nov 2023 16:05:52 +0000 Subject: [PATCH 0407/2167] Fix docs type --- files/data/scripts/omw/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 48412f6a0f..1e76b8e141 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -236,7 +236,7 @@ return { --- -- Returns if the player HUD is visible or not -- @function [parent=#UI] isHudVisible - -- @return #bool + -- @return #boolean isHudVisible = function() return ui._isHudVisible() end, -- TODO From a88f0ecc958c52fd018429cc986fb30755cabfd4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 17:43:09 +0100 Subject: [PATCH 0408/2167] Expose governing attributes to Lua --- apps/openmw/mwlua/stats.cpp | 3 +++ files/lua_api/openmw/core.lua | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 5c1e536dd6..127d8e79d9 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -454,6 +454,9 @@ namespace MWLua return nullptr; return &*rec.mSchool; }); + skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { + return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index abdb099ed7..44ab4473fa 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -862,6 +862,7 @@ -- @field #string description Human-readable description -- @field #string icon VFS path to the icon -- @field #MagicSchoolData school Optional magic school +-- @field #string attribute The id of the skill's governing attribute -- @type MagicSchoolData -- @field #string name Human-readable name From e54eba70431db9231510f99e7bd62570f5ed1ab8 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 2 Nov 2023 12:08:07 -0500 Subject: [PATCH 0409/2167] Formatting fixes --- apps/openmw/mwlua/classbindings.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index d0f5767226..67831b859f 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,19 +1,15 @@ -#include "classbindings.hpp" -#include "stats.hpp" -#include "types/types.hpp" #include #include +#include "classbindings.hpp" +#include "luamanagerimp.hpp" +#include "stats.hpp" +#include "types/types.hpp" + #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "luamanagerimp.hpp" - -namespace -{ -} - namespace sol { template <> @@ -45,37 +41,31 @@ namespace MWLua classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) { ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); res[i + 1] = skillId.serializeText(); } - return res; }); classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto attribute = rec.mData.mAttribute; - for (size_t i = 0; i < attribute.size(); ++i) { ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]); res[i + 1] = attributeId.serializeText(); } - return res; }); classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) { ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]); res[i + 1] = skillId.serializeText(); } - return res; }); classT["specialization"] = sol::readonly_property( From 685c02bd73a5fb3dad6a47d16efd8f3c3bc5da85 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 2 Nov 2023 12:10:52 -0500 Subject: [PATCH 0410/2167] Re-add missing line --- apps/openmw/mwlua/stats.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index 35031ff95f..d265948113 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -6,6 +6,7 @@ namespace MWLua { struct Context; + std::string_view getSpecialization(const int32_t val); void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); From 3c0c699e42008514434ba7fa4069fdeb41e3fa64 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 2 Nov 2023 12:15:48 -0500 Subject: [PATCH 0411/2167] Do not use const --- apps/openmw/mwlua/stats.cpp | 2 +- apps/openmw/mwlua/stats.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index c4440c954c..ad8de5df10 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -60,7 +60,7 @@ namespace namespace MWLua { - std::string_view getSpecialization(const int32_t val) + std::string_view getSpecialization(int32_t val) { if (val == ESM::Class::Stealth) return "stealth"; diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index d265948113..adff27654d 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -7,7 +7,7 @@ namespace MWLua { struct Context; - std::string_view getSpecialization(const int32_t val); + std::string_view getSpecialization(int32_t val); void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); sol::table initCoreStatsBindings(const Context& context); From 25fe42de10a7c77bb48827f2f20356ff7cce8bda Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 2 Nov 2023 12:17:55 -0500 Subject: [PATCH 0412/2167] Spacing --- apps/openmw/mwlua/classbindings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 67831b859f..fd17013326 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -30,6 +30,7 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table classes(context.mLua->sol(), sol::create); addRecordFunctionBinding(classes, context); + // class record auto classT = lua.new_usertype("ESM3_Class"); classT[sol::meta_function::to_string] @@ -38,6 +39,7 @@ namespace MWLua classT["name"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mName; }); classT["description"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); + classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto skills = rec.mData.mSkills; @@ -68,6 +70,7 @@ namespace MWLua } return res; }); + classT["specialization"] = sol::readonly_property( [](const ESM::Class& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); classT["isPlayable"] From 859b6c823fb27655227bce1c26b27b15a4e0ae12 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 2 Nov 2023 12:21:59 -0500 Subject: [PATCH 0413/2167] Fix includes --- apps/openmw/mwlua/classbindings.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index fd17013326..1e75bb674d 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,15 +1,15 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + #include "classbindings.hpp" #include "luamanagerimp.hpp" #include "stats.hpp" #include "types/types.hpp" -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - namespace sol { template <> From 2d90176fe94747d10ce3d9436478c2a31ea7d3f3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 19:29:26 +0100 Subject: [PATCH 0414/2167] Add types.Actor.isDead --- apps/openmw/mwlua/types/actor.cpp | 5 +++++ files/lua_api/openmw/types.lua | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 2afe3386ce..370e9e7f69 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -395,6 +395,11 @@ namespace MWLua return dist <= actorsProcessingRange; }; + actor["isDead"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDead(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..7b7afe5ba8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -15,6 +15,12 @@ -- @param openmw.core#GameObject actor -- @return #number +--- +-- Check if the given actor is dead. +-- @function [parent=#Actor] isDead +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds From 9c93d907dcf09bd84a56b9af56c94810643b81ca Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Thu, 2 Nov 2023 19:30:51 +0100 Subject: [PATCH 0415/2167] Settings page entry for the "player movement ignores animation" setting. --- apps/launcher/settingspage.cpp | 2 ++ files/ui/settingspage.ui | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 5869cc3a73..b8539671b5 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings() } loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox); loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); + loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); distantLandCheckBox->setCheckState( Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); @@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing); saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection); saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement); + saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation); const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked; if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging)) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index c61b4f4229..45fbc11a12 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -14,7 +14,7 @@ - 0 + 1 @@ -293,8 +293,8 @@ 0 0 - 680 - 882 + 671 + 774 @@ -377,6 +377,16 @@ + + + + <html><head/><body><p>In third person, the camera will bob along with the movement animations of the player. Enabling this option disables this by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + Player movement ignores animation + + + From 1e06d74f821a7f3a7cbe4c3e85bbe8a04fd34621 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Oct 2023 15:14:00 +0200 Subject: [PATCH 0416/2167] Fix #7621 --- apps/openmw/mwrender/renderingmanager.cpp | 5 +++-- components/terrain/quadtreeworld.cpp | 5 +++-- components/terrain/quadtreeworld.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b6716ce2c..67b166e1f5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1319,6 +1319,7 @@ namespace MWRender const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; const bool distantTerrain = Settings::terrain().mDistantTerrain; + const double expiryDelay = Settings::cells().mCacheExpiryDelay; if (distantTerrain || groundcover) { const int compMapResolution = Settings::terrain().mCompositeMapResolution; @@ -1329,7 +1330,7 @@ namespace MWRender const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, - lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); + lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace, expiryDelay); if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging @@ -1351,7 +1352,7 @@ namespace MWRender } else newChunkMgr.mTerrain = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, - mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug); + mTerrainStorage.get(), Mask_Terrain, worldspace, expiryDelay, Mask_PreCompile, Mask_Debug); newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate); float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 52edd96b9f..bdf9f56790 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -280,8 +280,9 @@ namespace Terrain QuadTreeWorld::QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace) - : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, preCompileMask, borderMask) + bool debugChunks, ESM::RefId worldspace, double expiryDelay) + : TerrainGrid( + parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, expiryDelay, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 2524dc046f..fa800d2655 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -33,7 +33,7 @@ namespace Terrain QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace); + bool debugChunks, ESM::RefId worldspace, double expiryDelay); ~QuadTreeWorld(); From fdba857464d1bc0232da1b5587b1a32bc4c8076e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 3 Nov 2023 01:21:45 +0100 Subject: [PATCH 0417/2167] Document the "player movement ignores animation" setting. --- docs/source/reference/modding/settings/game.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index c88ae4e28f..215e060b1c 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -517,3 +517,13 @@ will not be useful with another. * 0: Axis-aligned bounding box * 1: Rotating box * 2: Cylinder + +player movement ignores animation +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +In third person, the camera will sway along with the movement animations of the player. +Enabling this option disables this swaying by having the player character move independently of its animation. From 876f6ea2da2adeee30b58ef4914ecf5fcb051593 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 28 Oct 2023 01:27:29 +0300 Subject: [PATCH 0418/2167] Validate enchantment records (bug #7654) Clean up spell validation Fix a flaw in spell effect tooltip code --- CHANGELOG.md | 1 + apps/openmw/mwgui/widgets.cpp | 4 +- apps/openmw/mwworld/esmstore.cpp | 122 ++++++++++++++++--------------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09cde80181..f1301bd5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player + Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index debacfbed9..31e2689485 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -355,12 +355,10 @@ namespace MWGui::Widgets const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* magicEffect = store.get().search(mEffectParams.mEffectID); + const ESM::MagicEffect* magicEffect = store.get().find(mEffectParams.mEffectID); const ESM::Attribute* attribute = store.get().search(mEffectParams.mAttribute); const ESM::Skill* skill = store.get().search(mEffectParams.mSkill); - assert(magicEffect); - auto windowManager = MWBase::Environment::get().getWindowManager(); std::string_view pt = windowManager->getGameSettingString("spoint", {}); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 12fbc0d4df..0b661b4442 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -138,6 +138,59 @@ namespace return npcsToReplace; } + template + std::vector getSpellsToReplace( + const MWWorld::Store& spells, const MWWorld::Store& magicEffects) + { + std::vector spellsToReplace; + + for (RecordType spell : spells) + { + if (spell.mEffects.mList.empty()) + continue; + + bool changed = false; + auto iter = spell.mEffects.mList.begin(); + while (iter != spell.mEffects.mList.end()) + { + const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + if (!mgef) + { + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping invalid effect (index " << iter->mEffectID << ")"; + iter = spell.mEffects.mList.erase(iter); + changed = true; + continue; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + { + iter->mAttribute = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected attribute argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + { + iter->mSkill = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected skill argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + ++iter; + } + + if (changed) + spellsToReplace.emplace_back(spell); + } + + return spellsToReplace; + } + // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no // longer exists however. So instead of removing the item altogether, we're only removing the script. template @@ -538,71 +591,24 @@ namespace MWWorld removeMissingScripts(getWritable(), getWritable().mStatic); - // Validate spell effects for invalid arguments - std::vector spellsToReplace; + // Validate spell effects and enchantments for invalid arguments auto& spells = getWritable(); - for (ESM::Spell spell : spells) - { - if (spell.mEffects.mList.empty()) - continue; - - bool changed = false; - auto iter = spell.mEffects.mList.begin(); - while (iter != spell.mEffects.mList.end()) - { - const ESM::MagicEffect* mgef = getWritable().search(iter->mEffectID); - if (!mgef) - { - Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " - << iter->mEffectID << ") present. Dropping the effect."; - iter = spell.mEffects.mList.erase(iter); - changed = true; - continue; - } - - if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) - { - if (iter->mAttribute != -1) - { - iter->mAttribute = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has an attribute argument present. Dropping the argument."; - changed = true; - } - } - else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - if (iter->mSkill != -1) - { - iter->mSkill = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has a skill argument present. Dropping the argument."; - changed = true; - } - } - else if (iter->mSkill != -1 || iter->mAttribute != -1) - { - iter->mSkill = -1; - iter->mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" - << spell.mId << "' has argument(s) present. Dropping the argument(s)."; - changed = true; - } - - ++iter; - } - - if (changed) - spellsToReplace.emplace_back(spell); - } + auto& enchantments = getWritable(); + auto& magicEffects = getWritable(); + std::vector spellsToReplace = getSpellsToReplace(spells, magicEffects); for (const ESM::Spell& spell : spellsToReplace) { spells.eraseStatic(spell.mId); spells.insertStatic(spell); } + + std::vector enchantmentsToReplace = getSpellsToReplace(enchantments, magicEffects); + for (const ESM::Enchantment& enchantment : enchantmentsToReplace) + { + enchantments.eraseStatic(enchantment.mId); + enchantments.insertStatic(enchantment); + } } void ESMStore::movePlayerRecord() From af08205f190adfe1ce05163ec0a98116a6a01bb8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 3 Nov 2023 15:03:09 +0300 Subject: [PATCH 0419/2167] Support BSShader/BSLightingShader depth flags --- .../nifosg/testnifloader.cpp | 10 ++++++++ components/nif/property.hpp | 8 +++++++ components/nifosg/nifloader.cpp | 23 ++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index a82fba15ca..5c37cb375f 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -156,6 +156,16 @@ osg::Group { StateSet TRUE { osg::StateSet { UniqueID 9 + ModeList 1 { + GL_DEPTH_TEST ON + } + AttributeList 1 { + osg::Depth { + UniqueID 10 + WriteMask FALSE + } + Value OFF + } } } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index da908f2eab..eaaa972c39 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -109,6 +109,12 @@ namespace Nif { BSSFlag1_Specular = 0x00000001, BSSFlag1_Decal = 0x04000000, + BSSFlag1_DepthTest = 0x80000000, + }; + + enum BSShaderFlags2 + { + BSSFlag2_DepthWrite = 0x00000001, }; struct BSSPParallaxParams @@ -140,6 +146,8 @@ namespace Nif // Shader-specific flag methods must be handled on per-record basis bool specular() const { return mShaderFlags1 & BSSFlag1_Specular; } bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } + bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } + bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } }; struct BSShaderLightingProperty : BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9945a8c40e..5b98f54599 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1964,6 +1964,15 @@ namespace NifOsg return texEnv; } + void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite, + osg::Depth::Function depthFunction = osg::Depth::LESS) + { + stateset->setMode(GL_DEPTH_TEST, depthTest ? osg::StateAttribute::ON : osg::StateAttribute::OFF); + osg::ref_ptr depth = new osg::Depth(depthFunction, 0.0, 1.0, depthWrite); + depth = shareAttribute(depth); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) @@ -2319,16 +2328,10 @@ namespace NifOsg { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - stateset->setMode( - GL_DEPTH_TEST, zprop->depthTest() ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth; - depth->setWriteMask(zprop->depthWrite()); // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it // uses a fixed depth function of GL_ALWAYS. - if (hasStencilProperty) - depth->setFunction(osg::Depth::ALWAYS); - depth = shareAttribute(depth); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + osg::Depth::Function depthFunction = hasStencilProperty ? osg::Depth::ALWAYS : osg::Depth::LESS; + handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite(), depthFunction); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when @@ -2367,6 +2370,7 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2405,6 +2409,7 @@ namespace NifOsg } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSLightingShaderProperty: @@ -2422,6 +2427,7 @@ namespace NifOsg stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSEffectShaderProperty: @@ -2477,6 +2483,7 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } // unused by mw From e51d1967f4297e71b8762505dd190c806f0e8994 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 3 Nov 2023 17:24:35 +0100 Subject: [PATCH 0420/2167] Base cell size on worldspace --- components/terrain/cellborder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 06767531d3..b78691cd8d 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -6,6 +6,7 @@ #include "world.hpp" +#include #include #include #include @@ -25,7 +26,7 @@ namespace Terrain osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, ESM::RefId worldspace, float offset, osg::Vec4f color) { - const int cellSize = ESM::Land::REAL_SIZE; + const int cellSize = ESM::getCellSize(worldspace); const int borderSegments = 40; osg::Vec3 cellCorner = osg::Vec3(x * cellSize, y * cellSize, 0); From dd87d01f060cf3ff6d2f466d1910453cb03722e2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 3 Nov 2023 16:31:23 +0000 Subject: [PATCH 0421/2167] Fix minor doc error, throw error when attempting to assign a value to a non-existing global variable in lua --- apps/openmw/mwlua/mwscriptbindings.cpp | 2 +- files/lua_api/openmw/types.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 92957efb71..af88249d3e 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -140,7 +140,7 @@ namespace MWLua = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) - return; + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); if (varType == 's' || varType == 'l') { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..9282fbffcc 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -930,7 +930,6 @@ -- Whether teleportation for this player is enabled. -- @function [parent=#Player] isTeleportingEnabled -- @param openmw.core#GameObject player --- @param #boolean player -- @return #boolean --- From 9a5fa9b8d6f6c59e57b733e7d81245e56801d6a9 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 3 Nov 2023 12:59:17 -0700 Subject: [PATCH 0422/2167] fix persistent buffers and glsl_version --- apps/openmw/mwrender/pingpongcanvas.cpp | 25 ++++ apps/openmw/mwrender/pingpongcanvas.hpp | 3 + apps/openmw/mwrender/postprocessor.cpp | 55 +++----- apps/openmw/mwrender/postprocessor.hpp | 2 +- components/fx/pass.cpp | 2 +- components/fx/technique.cpp | 1 + components/fx/technique.hpp | 4 + components/fx/types.hpp | 10 ++ .../source/reference/postprocessing/omwfx.rst | 128 +++++++++++++++--- 9 files changed, 172 insertions(+), 58 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 5ac68acf5f..6782bdddfd 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -238,10 +238,35 @@ namespace MWRender if (pass.mRenderTarget) { + if (mDirtyAttachments) + { + const auto [w, h] + = pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + pass.mRenderTexture->setTextureSize(w, h); + if (pass.mMipMap) + pass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + pass.mRenderTexture->dirtyTextureObject(); + + // Custom render targets must be shared between frame ids, so it's impossible to double buffer + // without expensive copies. That means the only thread-safe place to resize is in the draw + // thread. + osg::Texture2D* texture = const_cast(dynamic_cast( + pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) + .getTexture())); + + texture->setTextureSize(w, h); + texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); + texture->dirtyTextureObject(); + + mDirtyAttachments = false; + } + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); if (pass.mRenderTexture->getNumMipmapLevels() > 0) { + state.setActiveTextureUnit(0); state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index d8758303d7..557813b816 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -30,6 +30,8 @@ namespace MWRender void dirty() { mDirty = true; } + void resizeRenderTargets() { mDirtyAttachments = true; } + const fx::DispatchArray& getPasses() { return mPasses; } void setPasses(fx::DispatchArray&& passes); @@ -65,6 +67,7 @@ namespace MWRender osg::ref_ptr mTextureNormals; mutable bool mDirty = false; + mutable bool mDirtyAttachments = false; mutable osg::ref_ptr mRenderViewport; mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index c3106802db..66ade9495c 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -211,25 +211,14 @@ namespace MWRender if (Stereo::getStereo()) Stereo::Manager::instance().screenResolutionChanged(); - auto width = renderWidth(); - auto height = renderHeight(); - for (auto& technique : mTechniques) - { - for (auto& [name, rt] : technique->getRenderTargetsMap()) - { - const auto [w, h] = rt.mSize.get(width, height); - rt.mTarget->setTextureSize(w, h); - } - } - size_t frameId = frame() % 2; createObjectsForFrame(frameId); mRendering.updateProjectionMatrix(); - mRendering.setScreenRes(width, height); + mRendering.setScreenRes(renderWidth(), renderHeight()); - dirtyTechniques(); + dirtyTechniques(true); mDirty = true; mDirtyFrameId = !frameId; @@ -534,7 +523,7 @@ namespace MWRender mCanvases[frameId]->dirty(); } - void PostProcessor::dirtyTechniques() + void PostProcessor::dirtyTechniques(bool dirtyAttachments) { size_t frameId = frame() % 2; @@ -613,8 +602,6 @@ namespace MWRender uniform->mName.c_str(), *type, uniform->getNumElements())); } - std::unordered_map renderTargetCache; - for (const auto& pass : technique->getPasses()) { int subTexUnit = texUnit; @@ -626,32 +613,27 @@ namespace MWRender if (!pass->getTarget().empty()) { - const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; - - const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight()); - - subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); - renderTargetCache[rt.mTarget] = subPass.mRenderTexture; - subPass.mRenderTexture->setTextureSize(w, h); - subPass.mRenderTexture->setName(std::string(pass->getTarget())); - - if (rt.mMipMap) - subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; + subPass.mSize = renderTarget.mSize; + subPass.mRenderTexture = renderTarget.mTarget; + subPass.mMipMap = renderTarget.mMipMap; + subPass.mStateSet->setAttributeAndModes(new osg::Viewport( + 0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight())); subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); + + const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); } - for (const auto& whitelist : pass->getRenderTargets()) + for (const auto& name : pass->getRenderTargets()) { - auto it = technique->getRenderTargetsMap().find(whitelist); - if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget]) - { - subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]); - subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++)); - } + subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget); + subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); + + subTexUnit++; } node.mPasses.emplace_back(std::move(subPass)); @@ -668,6 +650,9 @@ namespace MWRender hud->updateTechniques(); mRendering.getSkyManager()->setSunglare(sunglare); + + if (dirtyAttachments) + mCanvases[frameId]->resizeRenderTargets(); } PostProcessor::Status PostProcessor::enableTechnique( @@ -774,7 +759,7 @@ namespace MWRender for (auto& technique : mTemplates) technique->compile(); - dirtyTechniques(); + dirtyTechniques(true); } void PostProcessor::disableDynamicShaders() diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 153ec8166b..e9f19bf6b5 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -204,7 +204,7 @@ namespace MWRender void createObjectsForFrame(size_t frameId); - void dirtyTechniques(); + void dirtyTechniques(bool dirtyAttachments = false); void update(size_t frameId); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 7a7329d755..4b91939e01 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -339,7 +339,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mCompiled) return; - mLegacyGLSL = technique.getGLSLVersion() != 330; + mLegacyGLSL = technique.getGLSLVersion() < 330; if (mType == Type::Pixel) { diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 0b5d784ad9..3abcd8c0ba 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -279,6 +279,7 @@ namespace fx rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + rt.mTarget->setName(std::string(mBlockName)); while (!isNext() && !isNext()) { diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 844e4b552a..ed356e0a37 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -54,10 +54,14 @@ namespace fx osg::ref_ptr mRenderTarget; osg::ref_ptr mRenderTexture; bool mResolve = false; + Types::SizeProxy mSize; + bool mMipMap; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) , mResolve(other.mResolve) + , mSize(other.mSize) + , mMipMap(other.mMipMap) { if (other.mRenderTarget) mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 09f191c61c..0683126dec 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -29,6 +29,16 @@ namespace fx std::optional mWidth; std::optional mHeight; + SizeProxy() = default; + + SizeProxy(const SizeProxy& other) + : mWidthRatio(other.mWidthRatio) + , mHeightRatio(other.mHeightRatio) + , mWidth(other.mWidth) + , mHeight(other.mHeight) + { + } + std::tuple get(int width, int height) const { int scaledWidth = width; diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 36a6f0883a..2fb21a5162 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -529,48 +529,134 @@ is not wanted and you want a custom render target. | mipmaps | boolean | Whether mipmaps should be generated every frame | +------------------+---------------------+-----------------------------------------------------------------------------+ -To use the render target a pass must be assigned to it, along with any optional clear or blend modes. +To use the render target a pass must be assigned to it, along with any optional blend modes. +As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. -In the code snippet below a rendertarget is used to draw the red channel of a scene at half resolution, then a quarter. As a restriction, -only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. +Blending modes can be useful at times. Below is a simple shader which, when activated, will slowly turn the screen pure red. +Notice how we only ever write the value `.01` to the `RT_Red` buffer. Since we're using appropriate blending modes the +color buffer will accumulate. .. code-block:: none - render_target RT_Downsample { - width_ratio = 0.5; - height_ratio = 0.5; - internal_format = r16f; + render_target RT_Red { + width = 4; + height = 4; + source_format = rgb; + internal_format = rgb16f; source_type = float; - source_format = red; } - render_target RT_Downsample4 { - width_ratio = 0.25; - height_ratio = 0.25; - } - - fragment downsample2x(target=RT_Downsample) { - + fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; + omw_FragColor.rgb = vec3(0.01,0,0); } } - fragment downsample4x(target=RT_Downsample4, rt1=RT_Downsample) { - + fragment view(rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor = omw_Texture2D(RT_Downsample, omw_TexCoord); + omw_FragColor = omw_Texture2D(RT_Red, omw_TexCoord); } } -Now, when the `downsample2x` pass runs it will write to the target buffer instead of the default -one assigned by the engine. + technique { + author = "OpenMW"; + passes = red, view; + } + + +These custom render targets are persistent and ownership is given to the shader which defines them. +This gives potential to implement temporal effects by storing previous frame data in these buffers. +Below is an example which calculates a naive average scene luminance and transitions between values smoothly. + +.. code-block:: none + + render_target RT_Lum { + width = 256; + height = 256; + mipmaps = true; + source_format = rgb; + internal_format = rgb16f; + source_type = float; + min_filter = linear_mipmap_linear; + mag_filter = linear; + } + + render_target RT_LumAvg { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + render_target RT_LumAvgLastFrame { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + fragment calculateLum(target=RT_Lum) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 orgi = pow(omw_GetLastShader(omw_TexCoord), vec4(2.2)).rgb; + omw_FragColor.rgb = orgi; + } + } + + fragment fetchLumAvg(target=RT_LumAvg, rt1=RT_Lum, rt2=RT_LumAvgLastFrame) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLumaCurrFrame = textureLod(RT_Lum, vec2(0.5, 0.5), 6).rgb; + vec3 avgLumaLastFrame = omw_Texture2D(RT_LumAvgLastFrame, vec2(0.5, 0.5)).rgb; + + const float speed = 0.9; + + vec3 avgLuma = avgLumaLastFrame + (avgLumaCurrFrame - avgLumaLastFrame) * (1.0 - exp(-omw.deltaSimulationTime * speed)); + + omw_FragColor.rgb = avgLuma; + } + } + + fragment adaptation(rt1=RT_LumAvg) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + + if (omw_TexCoord.y < 0.2) + omw_FragColor = vec4(avgLuma, 1.0); + else + omw_FragColor = omw_GetLastShader(omw_TexCoord); + } + } + + fragment store(target=RT_LumAvgLastFrame, rt1=RT_LumAvg) { + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + omw_FragColor.rgb = avgLuma; + } + } + + technique { + author = "OpenMW"; + passes = calculateLum, fetchLumAvg, store, adaptation; + glsl_version = 330; + } + Simple Example ############## From 7a0d1a08689b681bb725ed1045682758a89e5eeb Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 3 Nov 2023 21:23:37 +0100 Subject: [PATCH 0423/2167] Print uint8_t as unsigned --- apps/esmtool/record.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e293055919..96c418c0c4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1204,7 +1204,8 @@ namespace EsmTool std::array weathers = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; for (size_t i = 0; i < weathers.size(); ++i) - std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl; + std::cout << " " << weathers[i] << ": " << static_cast(mData.mData.mProbabilities[i]) + << std::endl; std::cout << " Map Color: " << mData.mMapColor << std::endl; if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; From 8d0d9a49c60b3ed9adea454e4c778073bdd6d039 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 3 Nov 2023 20:36:14 +0000 Subject: [PATCH 0424/2167] Remember sneaking when game is saved and loaded(#7664) --- files/data/scripts/omw/playercontrols.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 0bd7cf6bea..7b405180e8 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -199,10 +199,21 @@ local function onInputAction(action) end end +local function onSave() + return {sneaking = self.controls.sneak} +end + +local function onLoad(data) + if not data then return end + self.controls.sneak = data.sneaking or false +end + return { engineHandlers = { onFrame = onFrame, onInputAction = onInputAction, + onSave = onSave, + onLoad = onLoad, }, interfaceName = 'Controls', --- From 2c1db92d04bfee28278578bffafdeec022e8132d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 3 Nov 2023 23:25:14 +0300 Subject: [PATCH 0425/2167] Don't use Bounding Box node bounds as the original collision shape Bounding Box node bounds are not used for non-actor collision in Morrowind and the generated box isn't actually used for actor collision in OpenMW Preserving btBoxShape cloning code because it might get used in the future --- .../nifloader/testbulletnifloader.cpp | 26 +++---------------- components/nifbullet/bulletnifloader.cpp | 23 ++-------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 72959f8591..58e1073307 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -411,7 +411,7 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -427,15 +427,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_compound_shape_with_box_inside) + TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -453,15 +449,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_but_should_use_bounding_box) + TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_should_use_bounding_box) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -483,10 +475,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -519,10 +507,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -555,10 +539,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6); - std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 95744a8cfe..66c7eea12d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -171,23 +170,8 @@ namespace NifBullet } for (const Nif::NiAVObject* node : roots) - { - // Try to find a valid bounding box first. If one's found for any root node, use that. if (findBoundingBox(*node)) - { - const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); - const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); - auto compound = std::make_unique(); - auto boxShape = std::make_unique(extents); - btTransform transform = btTransform::getIdentity(); - transform.setOrigin(center); - compound->addChildShape(transform, boxShape.get()); - std::ignore = boxShape.release(); - - mShape->mCollisionShape.reset(compound.release()); - return mShape; - } - } + break; HandleNodeArgs args; @@ -196,8 +180,6 @@ namespace NifBullet // TODO: investigate whether this should and could be optimized. args.mAnimated = pathFileNameStartsWithX(mShape->mFileName); - // If there's no bounding box, we'll have to generate a Bullet collision shape - // from the collision data present in every root node. for (const Nif::NiAVObject* node : roots) handleRoot(nif, *node, args); @@ -210,8 +192,7 @@ namespace NifBullet return mShape; } - // Find a boundingBox in the node hierarchy. - // Return: use bounding box for collision? + // Find a bounding box in the node hierarchy to use for actor collision bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) From c311caef9ab7cfe14da7bb9a02fd5498bfa6418a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 3 Nov 2023 17:19:53 -0500 Subject: [PATCH 0426/2167] Redo specialization field --- apps/openmw/mwlua/classbindings.cpp | 5 +++-- apps/openmw/mwlua/stats.cpp | 15 +++------------ components/esm3/loadclas.cpp | 1 + components/esm3/loadclas.hpp | 2 +- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 1e75bb674d..e506916337 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -71,8 +71,9 @@ namespace MWLua return res; }); - classT["specialization"] = sol::readonly_property( - [](const ESM::Class& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); + classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { + return ESM::Class::indexToLuaId[rec.mData.mSpecialization]; + }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); return LuaUtil::makeReadOnly(classes); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ad8de5df10..0311ccaab8 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -60,16 +60,6 @@ namespace namespace MWLua { - std::string_view getSpecialization(int32_t val) - { - if (val == ESM::Class::Stealth) - return "stealth"; - else if (val == ESM::Class::Magic) - return "magic"; - else - return "combat"; - } - static void addStatUpdateAction(MWLua::LuaManager* manager, const SelfObject& obj) { if (!obj.mStatsCache.empty()) @@ -456,8 +446,9 @@ namespace MWLua skillT["name"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mName; }); skillT["description"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); - skillT["specialization"] = sol::readonly_property( - [](const ESM::Skill& rec) -> std::string_view { return getSpecialization(rec.mData.mSpecialization); }); + skillT["specialization"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { + return ESM::Class::indexToLuaId[rec.mData.mSpecialization]; + }); skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index 0c58f1d45a..2dd605c7c6 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -10,6 +10,7 @@ namespace ESM { const std::string_view Class::sGmstSpecializationIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; + const std::string_view Class::indexToLuaId[3] = { "combat", "magic", "stealth" }; int32_t& Class::CLDTstruct::getSkill(int index, bool major) { diff --git a/components/esm3/loadclas.hpp b/components/esm3/loadclas.hpp index 47b8b73b7e..f4bf60bc11 100644 --- a/components/esm3/loadclas.hpp +++ b/components/esm3/loadclas.hpp @@ -30,8 +30,8 @@ namespace ESM Magic = 1, Stealth = 2 }; - static const std::string_view sGmstSpecializationIds[3]; + static const std::string_view indexToLuaId[3]; struct CLDTstruct { From 515a90e9e017db09e3482e80e2e52d37dc2e0dfb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 4 Nov 2023 02:12:15 +0300 Subject: [PATCH 0427/2167] Cast displayed health to int in saved game dialog (#7656) --- apps/openmw/mwgui/savegamedialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 63e4fbc5cc..4f78c27f05 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -413,8 +413,8 @@ namespace MWGui text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; if (mCurrentSlot->mProfile.mMaximumHealth > 0) - text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/" - << mCurrentSlot->mProfile.mMaximumHealth << "\n"; + text << "#{sHealth} " << static_cast(mCurrentSlot->mProfile.mCurrentHealth) << "/" + << static_cast(mCurrentSlot->mProfile.mMaximumHealth) << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n"; From 4886d31d89025f297285e9ce761f3a32ed610087 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 13:37:23 +0100 Subject: [PATCH 0428/2167] Language, bob -> sway --- files/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index 45fbc11a12..1f5f206f67 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -380,7 +380,7 @@ - <html><head/><body><p>In third person, the camera will bob along with the movement animations of the player. Enabling this option disables this by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> Player movement ignores animation From 68fe1361f1a4926e178f7f49917e06381fd153f6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:00:13 +0100 Subject: [PATCH 0429/2167] Attempt at clarifying the code --- apps/openmw/mwmechanics/character.cpp | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4eb223e250..08cb8f6ccd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2386,26 +2386,26 @@ namespace MWMechanics } } - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { if (duration > 0.0f) - moved /= duration; + movementFromAnimation /= duration; else - moved = osg::Vec3f(0.f, 0.f, 0.f); + movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - moved.x() *= scale; - moved.y() *= scale; + movementFromAnimation.x() *= scale; + movementFromAnimation.y() *= scale; - if (speed > 0.f && moved != osg::Vec3f()) + if (speed > 0.f && movementFromAnimation != osg::Vec3f()) { - // Ensure we're moving in generally the right direction - // This is necessary when the "turn to movement direction" feature is off, as animations - // will not be rotated to match diagonal movement. In this case we have to slide the - // character diagonally. - - // First decide the general direction expected from the current animation + // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, + // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, + // we have to rotate the movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and + // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. float animMovementAngle = 0; if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) { @@ -2426,12 +2426,12 @@ namespace MWMechanics float diff = targetMovementAngle - animMovementAngle; if (std::abs(diff) > epsilon) { - moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } - if (isPlayer && Settings::game().mPlayerMovementIgnoresAnimation) + if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { - moved = movement; + movement = movementFromAnimation; } } @@ -2445,12 +2445,12 @@ namespace MWMechanics .getModifier() > 0)) { - moved.z() = 1.0; + movement.z() = 1.0; } } // Update movement - world->queueMovement(mPtr, moved); + world->queueMovement(mPtr, movement); } mSkipAnim = false; From 1edc82062de593aefc8ea702e05a84e01075a946 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:13:02 +0100 Subject: [PATCH 0430/2167] Account for strafing when draw state is not nothing, and "turn to movement direction" is true --- apps/openmw/mwmechanics/character.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08cb8f6ccd..73bec3f2ae 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2407,13 +2407,8 @@ namespace MWMechanics // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. float animMovementAngle = 0; - if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) - { - if (cls.getMovementSettings(mPtr).mIsStrafing) - animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; - else - animMovementAngle = movement.y() >= 0 ? 0 : -osg::PIf; - } + if (cls.getMovementSettings(mPtr).mIsStrafing) + animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; else { animMovementAngle = mAnimation->getLegsYawRadians(); From 475bb1af659dad998fbe81d32665259f84fd0a09 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:34:41 +0100 Subject: [PATCH 0431/2167] Move calculating the animation direction into its own function to help simplify update(). Eliminate a pointless epsilon. --- apps/openmw/mwmechanics/character.cpp | 60 +++++++++++++++++---------- apps/openmw/mwmechanics/character.hpp | 2 + 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 73bec3f2ae..6c898ee23e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2398,36 +2398,19 @@ namespace MWMechanics movementFromAnimation.x() *= scale; movementFromAnimation.y() *= scale; - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + if (speed > 0.f && movementFromAnimation != osg::Vec3f() + && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, // we have to rotate the movement taken from the animation to the intended direction. // // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and - // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. - float animMovementAngle = 0; - if (cls.getMovementSettings(mPtr).mIsStrafing) - animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; - else - { - animMovementAngle = mAnimation->getLegsYawRadians(); - if (movement.y() < 0) - animMovementAngle -= osg::PIf; - } - - const float epsilon = 0.001f; + // therefore we have to determine the direction based on the currently playing cycle instead. + float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - if (std::abs(diff) > epsilon) - { - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; - } - - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - { - movement = movementFromAnimation; - } + movement = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } if (mFloatToSurface) @@ -2927,6 +2910,39 @@ namespace MWMechanics MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); } + float CharacterController::getAnimationMovementDirection() + { + switch (mMovementState) + { + case CharState_RunLeft: + case CharState_SneakLeft: + case CharState_SwimWalkLeft: + case CharState_SwimRunLeft: + case CharState_WalkLeft: + return osg::PI_2f; + case CharState_RunRight: + case CharState_SneakRight: + case CharState_SwimWalkRight: + case CharState_SwimRunRight: + case CharState_WalkRight: + return -osg::PI_2f; + case CharState_RunForward: + case CharState_SneakForward: + case CharState_SwimRunForward: + case CharState_SwimWalkForward: + case CharState_WalkForward: + return mAnimation->getLegsYawRadians(); + case CharState_RunBack: + case CharState_SneakBack: + case CharState_SwimWalkBack: + case CharState_SwimRunBack: + case CharState_WalkBack: + return mAnimation->getLegsYawRadians() - osg::PIf; + + } + return 0.0f; + } + void CharacterController::updateHeadTracking(float duration) { const osg::Node* head = mAnimation->getNode("Bip01 Head"); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index c3d45fe0fb..d92c0ffe5a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -319,6 +319,8 @@ namespace MWMechanics void playSwishSound() const; + float getAnimationMovementDirection(); + MWWorld::MovementDirectionFlags getSupportedMovementDirections() const; }; } From c7c3a52e6a7e6afb20efa57bb81623d24bd62425 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:41:08 +0100 Subject: [PATCH 0432/2167] Clang --- apps/openmw/mwmechanics/character.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6c898ee23e..c245a17155 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2386,7 +2386,8 @@ namespace MWMechanics } } - osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + osg::Vec3f movementFromAnimation + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { @@ -2401,12 +2402,14 @@ namespace MWMechanics if (speed > 0.f && movementFromAnimation != osg::Vec3f() && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, - // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, - // we have to rotate the movement taken from the animation to the intended direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and - // therefore we have to determine the direction based on the currently playing cycle instead. + // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from + // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive + // diagonal movement, we have to rotate the movement taken from the animation to the intended + // direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no individual + // frame will, and therefore we have to determine the direction based on the currently playing cycle + // instead. float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; @@ -2938,7 +2941,6 @@ namespace MWMechanics case CharState_SwimRunBack: case CharState_WalkBack: return mAnimation->getLegsYawRadians() - osg::PIf; - } return 0.0f; } From 820fc068d19e91758695aa136322961d84bd0ebb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 2 Nov 2023 23:24:58 +0300 Subject: [PATCH 0433/2167] Support point specular lighting (#6188) Fix passing light specular colors with shader lighting methods (with help from wazabear) --- CHANGELOG.md | 1 + components/sceneutil/lightcontroller.cpp | 9 +++- components/sceneutil/lightmanager.cpp | 2 + components/sceneutil/lightutil.cpp | 2 +- files/shaders/compatibility/bs/default.frag | 2 +- files/shaders/compatibility/objects.frag | 2 +- files/shaders/compatibility/terrain.frag | 2 +- files/shaders/lib/light/lighting.glsl | 48 +++++++++++++++++---- 8 files changed, 54 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09cde80181..2eea3946b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts + Feature #6188: Specular lighting from point light sources Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 98af5d21c8..caff6826f5 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -36,9 +36,11 @@ namespace SceneUtil // if (time == mLastTime) // return; + osg::Light* light = node->getLight(nv->getTraversalNumber()); + if (mType == LT_Normal) { - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + light->setDiffuse(mDiffuseColor); traverse(node, nv); return; } @@ -63,7 +65,10 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); + osg::Vec4f result = mDiffuseColor * mBrightness * node->getActorFade(); + + light->setDiffuse(result); + light->setSpecular(result); traverse(node, nv); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 6bca92fdb4..8f7304416b 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -536,6 +536,7 @@ namespace SceneUtil configurePosition(lightMat, light->getPosition() * mViewMatrix); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); + configureSpecular(lightMat, light->getSpecular()); configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius()); @@ -1214,6 +1215,7 @@ namespace SceneUtil auto& buf = getUBOManager()->getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); + buf->setSpecular(index, light->getSpecular()); buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 9c0ebdf5c2..f69461fa3c 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -124,7 +124,7 @@ namespace SceneUtil } light->setDiffuse(diffuse); light->setAmbient(ambient); - light->setSpecular(osg::Vec4f(0, 0, 0, 0)); + light->setSpecular(diffuse); // ESM format doesn't provide specular lightSource->setLight(light); diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 8c429947b0..f1be8da80c 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -95,7 +95,7 @@ void main() #endif if (matSpec != vec3(0.0)) - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4caf6c97e2..b86678af87 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -242,7 +242,7 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); matSpec *= specStrength; if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, viewVec, shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index a2fbddf3f7..744a56d18b 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -108,7 +108,7 @@ void main() if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 8c1262ba4d..8351fce8a0 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -90,15 +90,47 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } -vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) +float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) { - vec3 lightDir = normalize(lcalcPosition(0)); - float NdotL = dot(viewNormal, lightDir); - if (NdotL <= 0.0) - return vec3(0.0); - vec3 halfVec = normalize(lightDir - viewDirection); - float NdotH = dot(viewNormal, halfVec); - return pow(max(NdotH, 0.0), max(1e-4, shininess)) * lcalcSpecular(0).xyz * matSpec; + if (dot(viewNormal, lightDir) > 0.0) + { + vec3 halfVec = normalize(lightDir - viewDir); + float NdotH = max(dot(viewNormal, halfVec), 0.0); + return pow(NdotH, shininess); + } + + return 0.0; +} + +vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, float shadowing) +{ + shininess = max(shininess, 1e-4); + vec3 viewDir = normalize(viewPos); + vec3 specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lcalcPosition(0))); + specularLight *= shadowing; + + for (int i = @startLight; i < @endLight; ++i) + { +#if @lightingMethodUBO + int lightIndex = PointLightIndex[i]; +#else + int lightIndex = i; +#endif + + vec3 lightPos = lcalcPosition(lightIndex) - viewPos; + float lightDistance = length(lightPos); + +#if !@lightingMethodFFP + if (lightDistance > lcalcRadius(lightIndex) * 2.0) + continue; +#endif + + float illumination = lcalcIllumination(lightIndex, lightDistance); + float intensity = calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lightPos)); + specularLight += lcalcSpecular(lightIndex).xyz * intensity * illumination; + } + + return specularLight; } #endif From 9ebec27dafa2811d47373f7d17638c86cdd3b0fd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 16:18:36 +0100 Subject: [PATCH 0434/2167] use const. --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c245a17155..b9df3d1082 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2913,7 +2913,7 @@ namespace MWMechanics MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); } - float CharacterController::getAnimationMovementDirection() + float CharacterController::getAnimationMovementDirection() const { switch (mMovementState) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d92c0ffe5a..63491ec776 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -319,7 +319,7 @@ namespace MWMechanics void playSwishSound() const; - float getAnimationMovementDirection(); + float getAnimationMovementDirection() const; MWWorld::MovementDirectionFlags getSupportedMovementDirections() const; }; From e86a4ebafebb93dc7883b974a5e915916ed92a8b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 21:01:06 +0100 Subject: [PATCH 0435/2167] Fixes based on comments by capo --- apps/openmw/mwmechanics/character.cpp | 3 ++- docs/source/reference/modding/settings/game.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9df3d1082..da2bf4b488 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2941,8 +2941,9 @@ namespace MWMechanics case CharState_SwimRunBack: case CharState_WalkBack: return mAnimation->getLegsYawRadians() - osg::PIf; + default: + return 0.0f; } - return 0.0f; } void CharacterController::updateHeadTracking(float duration) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 215e060b1c..31cc2703f2 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -519,7 +519,7 @@ will not be useful with another. * 2: Cylinder player movement ignores animation --------------------------- +--------------------------------- :Type: boolean :Range: True/False @@ -527,3 +527,5 @@ player movement ignores animation In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. + +This setting can be controlled in the Settings tab of the launcher, under Visuals. From 88749b03d4836ffab05c43b8d2c5a2b86e8a8c9c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 5 Nov 2023 05:07:04 +0300 Subject: [PATCH 0436/2167] Use display name instead of editor ID for World::getCellName Doesn't affect Morrowind cells, but allows TES4+ cells to have legible names --- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5be1e52530..20e4122766 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -660,8 +660,8 @@ namespace MWWorld std::string_view World::getCellName(const MWWorld::Cell& cell) const { - if (!cell.isExterior() || !cell.getNameId().empty()) - return cell.getNameId(); + if (!cell.isExterior() || !cell.getDisplayName().empty()) + return cell.getDisplayName(); return ESM::visit(ESM::VisitOverload{ [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, From acf6178ea5089af4a7d89627a2f57374939e05c8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 5 Nov 2023 16:46:11 +0100 Subject: [PATCH 0437/2167] `movement = movementFromAnimation;` also when speed is 0. --- apps/openmw/mwmechanics/character.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index da2bf4b488..dd7b97b6a5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2399,8 +2399,7 @@ namespace MWMechanics movementFromAnimation.x() *= scale; movementFromAnimation.y() *= scale; - if (speed > 0.f && movementFromAnimation != osg::Vec3f() - && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) + if (speed > 0.f && movementFromAnimation != osg::Vec3f()) { // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive @@ -2413,9 +2412,12 @@ namespace MWMechanics float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - movement = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } + if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) + movement = movementFromAnimation; + if (mFloatToSurface) { if (cls.getCreatureStats(mPtr).isDead() From 0f530880148a97fbd02864c8e1a708b7cc3dec59 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sun, 5 Nov 2023 10:22:09 -0800 Subject: [PATCH 0438/2167] re-sync docs with postprocessing API --- components/fx/pass.cpp | 5 ++++ .../source/reference/postprocessing/omwfx.rst | 27 +++++++++++++------ files/data/l10n/OMWShaders/en.yaml | 1 + files/data/l10n/OMWShaders/ru.yaml | 1 + files/data/shaders/debug.omwfx | 13 +++++++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 4b91939e01..daaaf22968 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -190,6 +190,11 @@ mat4 omw_InvProjectionMatrix() #endif } + vec3 omw_GetNormalsWorldSpace(vec2 uv) + { + return (vec4(omw_GetNormals(uv), 0.0) * omw.viewMatrix).rgb; + } + vec3 omw_GetWorldPosFromUV(vec2 uv) { float depth = omw_GetDepth(uv); diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 2fb21a5162..8ff075bc6d 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -189,19 +189,30 @@ The following functions can be accessed in any fragment or vertex shader. +--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec4 omw_GetLastPass(vec2 uv)`` | Returns RGBA color output of the last pass | +--------------------------------------------------+-------------------------------------------------------------------------------+ -| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized worldspace normals [-1, 1] | -| | | -| | The values in sampler are in [0, 1] but are transformed to [-1, 1] | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ +| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized view-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetNormalsWorldSpace(vec2 uv)`` | Returns normalized world-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec3 omw_GetWorldPosFromUV(vec2 uv)`` | Returns world position for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_GetLinearDepth(vec2 uv)`` | Returns the depth in game units for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_EstimateFogCoverageFromUV(vec2 uv)`` | Returns a fog coverage in the range from 0.0 (no fog) and 1.0 (full fog) | | | | | | Calculates an estimated fog coverage for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ - ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightCount()`` | Returns the number of point lights available to sample from in the scene. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightWorldPos(int index)`` | Returns the world space position of a point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightDiffuse(int index)`` | Returns the diffuse color of the point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightAttenuation(int index)`` | Returns the attenuation values of the point light. | +| | | +| | The XYZ channels hold the constant, linear, and quadratic components. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``float omw_GetPointLightRadius(int index)`` | Returns the radius of the point light, in game units. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ Special Attributes ################## diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 8221be933f..6588591f00 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Depth colour factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." DisplayDepthName: "Visualize depth buffer" DisplayNormalsName: "Visualize pass normals" +NormalsInWorldSpace: "Show normals in world space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." diff --git a/files/data/l10n/OMWShaders/ru.yaml b/files/data/l10n/OMWShaders/ru.yaml index b886f72b54..7a2bcfe80d 100644 --- a/files/data/l10n/OMWShaders/ru.yaml +++ b/files/data/l10n/OMWShaders/ru.yaml @@ -33,6 +33,7 @@ DisplayDepthName: "Визуализация буфера глубины" DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение." DisplayDepthFactorName: "Соотношение цвета" DisplayNormalsName: "Визуализация нормалей" +NormalsInWorldSpace: "Показывать нормали мирового пространства" ContrastLevelDescription: "Контрастность изображения" ContrastLevelName: "Контрастность" GammaLevelDescription: "Яркость изображения" diff --git a/files/data/shaders/debug.omwfx b/files/data/shaders/debug.omwfx index 360dfa26cd..a0c8754ec4 100644 --- a/files/data/shaders/debug.omwfx +++ b/files/data/shaders/debug.omwfx @@ -19,6 +19,11 @@ uniform_bool uDisplayNormals { display_name = "#{OMWShaders:DisplayNormalsName}"; } +uniform_bool uNormalsInWorldSpace { + default = false; + display_name = "#{OMWShaders:NormalsInWorldSpace}"; +} + fragment main { omw_In vec2 omw_TexCoord; @@ -30,8 +35,12 @@ fragment main { if (uDisplayDepth) omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0); #if OMW_NORMALS - if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) - omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) { + if (uNormalsInWorldSpace) + omw_FragColor.rgb = omw_GetNormalsWorldSpace(omw_TexCoord) * 0.5 + 0.5; + else + omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + } #endif } } From a60726ce35440c16a2a079569d3e82462ecf7f8b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 5 Nov 2023 21:32:18 +0100 Subject: [PATCH 0439/2167] Fix #7674 --- apps/openmw/mwlua/luamanagerimp.cpp | 6 ++++++ apps/openmw/mwlua/luamanagerimp.hpp | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 63a2838250..48b0c15381 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -344,6 +344,12 @@ namespace MWLua playerScripts->uiModeChanged(argId, false); } + void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) + { + MWBase::Environment::get().getWorldModel()->registerPtr(object); + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a725761dbd..404820cc6b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,10 +77,7 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } - void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override - { - mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); - } + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); From 116ef1c62bd305a914791a4e5f100e30e5d5c74a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 6 Nov 2023 00:21:10 +0300 Subject: [PATCH 0440/2167] Depth flag handling fixes (bug #7380) Properly disable depth test while allowing depth writes to happen Remove NiStencilProperty interaction Don't set up depth flags for BSShaderPPLightingProperty --- CHANGELOG.md | 1 + .../nifosg/testnifloader.cpp | 66 +++++++++++++++++-- components/nifosg/nifloader.cpp | 21 +++--- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 645bc59e9e..180e468e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives + Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies Bug #7428: AutoCalc flag is not used to calculate enchantment costs diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 5c37cb375f..f05d651301 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -108,7 +108,64 @@ osg::Group { )"); } - std::string formatOsgNodeForShaderProperty(std::string_view shaderPrefix) + std::string formatOsgNodeForBSShaderProperty(std::string_view shaderPrefix) + { + std::ostringstream oss; + oss << R"( +osg::Group { + UniqueID 1 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 2 + UDC_UserObjects 1 { + osg::StringValueObject { + UniqueID 3 + Name "fileHash" + } + } + } + } + Children 1 { + osg::Group { + UniqueID 4 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 5 + UDC_UserObjects 3 { + osg::UIntValueObject { + UniqueID 6 + Name "recIndex" + Value 4294967295 + } + osg::StringValueObject { + UniqueID 7 + Name "shaderPrefix" + Value ")" + << shaderPrefix << R"(" + } + osg::BoolValueObject { + UniqueID 8 + Name "shaderRequired" + Value TRUE + } + } + } + } + StateSet TRUE { + osg::StateSet { + UniqueID 9 + } + } + } + } +} +)"; + return oss.str(); + } + + std::string formatOsgNodeForBSLightingShaderProperty(std::string_view shaderPrefix) { std::ostringstream oss; oss << R"( @@ -162,7 +219,6 @@ osg::Group { AttributeList 1 { osg::Depth { UniqueID 10 - WriteMask FALSE } Value OFF } @@ -204,7 +260,7 @@ osg::Group { Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P(Params, NifOsgLoaderBSShaderPrefixTest, ValuesIn(NifOsgLoaderBSShaderPrefixTest::sParams)); @@ -228,11 +284,13 @@ osg::Group { property.mTextureSet = nullptr; property.mController = nullptr; property.mType = GetParam().mShaderType; + property.mShaderFlags1 |= Nif::BSShaderFlags1::BSSFlag1_DepthTest; + property.mShaderFlags2 |= Nif::BSShaderFlags2::BSSFlag2_DepthWrite; node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P( diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5b98f54599..b7ef547bd6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1964,11 +1964,17 @@ namespace NifOsg return texEnv; } - void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite, - osg::Depth::Function depthFunction = osg::Depth::LESS) + void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite) { - stateset->setMode(GL_DEPTH_TEST, depthTest ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth(depthFunction, 0.0, 1.0, depthWrite); + if (!depthWrite && !depthTest) + { + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + return; + } + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(depthWrite); + if (!depthTest) + depth->setFunction(osg::Depth::ALWAYS); depth = shareAttribute(depth); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } @@ -2328,10 +2334,8 @@ namespace NifOsg { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it - // uses a fixed depth function of GL_ALWAYS. - osg::Depth::Function depthFunction = hasStencilProperty ? osg::Depth::ALWAYS : osg::Depth::LESS; - handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite(), depthFunction); + // The test function from this property seems to be ignored. + handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite()); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when @@ -2370,7 +2374,6 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); - handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSShaderNoLightingProperty: From d0294019cee00726bc793c054bf294b6b83846a4 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Nov 2023 02:05:25 +0100 Subject: [PATCH 0441/2167] Remove doc for removed setting --- docs/source/reference/modding/settings/navigator.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 0de061d8f0..a86b174251 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -246,17 +246,6 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. -nav mesh version ----------------- - -:Type: integer -:Range: > 0 -:Default: 1 - -Version of navigation mesh generation algorithm. -Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. -Changing the value will invalidate navmesh disk cache. - Expert settings *************** From 392218dde326bb6bda182e02e7282d705bd0461f Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Nov 2023 01:41:32 +0100 Subject: [PATCH 0442/2167] Reword navigation mesh related docs and tooltips To explain the effect of the setting in a more user friendly language. --- .../reference/modding/settings/navigator.rst | 77 +++++++++---------- files/ui/settingspage.ui | 4 +- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index a86b174251..55b9e19b19 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -13,16 +13,15 @@ enable :Range: True/False :Default: True -Enable navigator. -When enabled background threads are started to build nav mesh for world geometry. -Pathfinding system uses nav mesh to build paths. -When disabled only pathgrid is used to build paths. -Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. +Enable navigator to make all settings in this category take effect. +When enabled, a navigation mesh (navmesh) is built in the background for world geometry to be used for pathfinding. +When disabled only the path grid is used to build paths. +Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. -Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. -Moving across external world, entering/exiting location produce nav mesh update. -NPC and creatures may not be able to find path before nav mesh is built around them. -Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and casting a firebolt. +Multi-core CPU systems may have different latency for navigation mesh update depending on other settings and system performance. +Moving across external world, entering/exiting location produce navigation mesh update. +NPC and creatures may not be able to find path before navigation mesh is built around them. +Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt. max tiles number ---------------- @@ -31,14 +30,14 @@ max tiles number :Range: >= 0 :Default: 512 -Number of tiles at nav mesh. +Number of tiles at navigation mesh. Nav mesh covers circle area around player. -This option allows to set an explicit limit for nav mesh size, how many tiles should fit into circle. -If actor is inside this area it able to find path over nav mesh. +This option allows to set an explicit limit for navigation mesh size, how many tiles should fit into circle. +If actor is inside this area it able to find path over navigation mesh. Increasing this value may decrease performance. .. note:: - Don't expect infinite nav mesh size increasing. + Don't expect infinite navigation mesh size increasing. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. @@ -49,9 +48,9 @@ wait until min distance to player :Range: >= 0 :Default: 5 -Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated. +Distance in navigation mesh tiles around the player to keep loading screen until navigation mesh is generated. Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors -nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation +nearby the player. Increasing this value will keep loading screen longer but will slightly increase navigation mesh generation speed on systems bound by CPU. Zero means no waiting. enable nav mesh disk cache @@ -61,8 +60,8 @@ enable nav mesh disk cache :Range: True/False :Default: True -If true navmesh cache stored on disk will be used in addition to memory cache. -If navmesh tile is not present in memory cache, it will be looked up in the disk cache. +If true navigation mesh cache stored on disk will be used in addition to memory cache. +If navigation mesh tile is not present in memory cache, it will be looked up in the disk cache. If it's not found there it will be generated. write to navmeshdb @@ -72,7 +71,7 @@ write to navmeshdb :Range: True/False :Default: True -If true generated navmesh tiles will be stored into disk cache while game is running. +If true generated navigation mesh tiles will be stored into disk cache while game is running. max navmeshdb file size ----------------------- @@ -95,8 +94,8 @@ async nav mesh updater threads :Range: >= 1 :Default: 1 -Number of background threads to update nav mesh. -Increasing this value may decrease performance, but also may decrease or increase nav mesh update latency depending on number of CPU cores. +Number of background threads to update navigation mesh. +Increasing this value may decrease performance, but also may decrease or increase navigation mesh update latency depending on number of CPU cores. On systems with not less than 4 CPU cores latency dependens approximately like 1/log(n) from number of threads. Don't expect twice better latency by doubling this value. @@ -107,12 +106,12 @@ max nav mesh tiles cache size :Range: >= 0 :Default: 268435456 -Maximum total cached size of all nav mesh tiles in bytes. -Setting greater than zero value will reduce nav mesh update latency for previously visited locations. +Maximum total cached size of all navigation mesh tiles in bytes. +Setting greater than zero value will reduce navigation mesh update latency for previously visited locations. Increasing this value may increase total memory consumption, but potentially will reduce latency for recently visited locations. Limit this value by total available physical memory minus base game memory consumption and other applications. Game will not eat all memory at once. -Memory will be consumed in approximately linear dependency from number of nav mesh updates. +Memory will be consumed in approximately linear dependency from number of navigation mesh updates. But only for new locations or already dropped from cache. min update interval ms @@ -122,17 +121,17 @@ min update interval ms :Range: >= 0 :Default: 250 -Minimum time duration required to pass before next navmesh update for the same tile in milliseconds. +Minimum time duration required to pass before next navigation mesh update for the same tile in milliseconds. Only tiles affected where objects are transformed. Next update for tile with added or removed object will not be delayed. -Visible ingame effect is navmesh update around opening or closing door. +Visible ingame effect is navigation mesh update around opening or closing door. Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance. Decreasing this value may increase CPU usage by background threads. Developer's settings ******************** -This section is for developers or anyone who wants to investigate how nav mesh system works in OpenMW. +This section is for developers or anyone who wants to learn how navigation mesh system works in OpenMW. enable write recast mesh to file -------------------------------- @@ -141,8 +140,8 @@ enable write recast mesh to file :Range: True/False :Default: False -Write recast mesh to file in .obj format for each use to update nav mesh. -Option is used to find out what world geometry is used to build nav mesh. +Write recast mesh to file in .obj format for each use to update navigation mesh. +Option is used to find out what world geometry is used to build navigation mesh. Potentially decreases performance. enable write nav mesh to file @@ -152,7 +151,7 @@ enable write nav mesh to file :Range: True/False :Default: False -Write nav mesh to file to be able to open by RecastDemo application. +Write navigation mesh to file to be able to open by RecastDemo application. Usually it is more useful to have both enable write recast mesh to file and this options enabled. RecastDemo supports .obj files. Potentially decreases performance. @@ -175,9 +174,9 @@ enable nav mesh file name revision :Range: True/False :Default: False -Write each nav mesh file with revision in name. +Write each navigation mesh file with revision in name. Otherwise will rewrite same file. -If it is unclear when nav mesh is changed use this option to dump multiple files for each state. +If it is unclear when navigation mesh is changed use this option to dump multiple files for each state. recast mesh path prefix ----------------------- @@ -195,7 +194,7 @@ nav mesh path prefix :Range: file system path :Default: "" -Write nav mesh file at path with this prefix. +Write navigation mesh file at path with this prefix. enable nav mesh render ---------------------- @@ -206,7 +205,7 @@ enable nav mesh render Render navigation mesh. Allows to do in-game debug. -Every nav mesh is visible and every update is noticeable. +Every navigation mesh is visible and every update is noticeable. Potentially decreases performance. nav mesh render mode @@ -258,12 +257,12 @@ recast scale factor :Range: > 0.0 :Default: 0.029411764705882353 -Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. +Scale of navigation mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size "recast scale factor" / "cell size". Default value calculates by this equation: sStepSizeUp * "recast scale factor" / "cell size" = 5 (max climb height should be equal to 4 voxels). -Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures. -Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency. +Changing this value will change generated navigation mesh. Some locations may become unavailable for NPC and creatures. +Pay attention to slopes and roofs when change it. Increasing this value will reduce navigation mesh update latency. max polygon path size --------------------- @@ -386,13 +385,13 @@ max polygons per tile :Range: 2^n, 0 < n < 22 :Default: 4096 -Maximum number of polygons per nav mesh tile. Maximum number of nav mesh tiles depends on +Maximum number of polygons per navigation mesh tile. Maximum number of navigation mesh tiles depends on this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))). See `recastnavigation `_ for more details. .. Warning:: - Lower value may lead to ignored world geometry on nav mesh. - Greater value will reduce number of nav mesh tiles. + Lower value may lead to ignored world geometry on navigation mesh. + Greater value will reduce number of navigation mesh tiles. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index c61b4f4229..339ae22910 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -116,10 +116,10 @@ - <html><head/><body><p>Enable navigator. When enabled background threads are started to build nav mesh for world geometry. Pathfinding system uses nav mesh to build paths. When disabled only pathgrid is used to build paths. Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn’t know where to go when you stand behind that stone and casting a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Build nav mesh for world geometry + Use navigation mesh for pathfinding From 75c5ce5f3161f2229fda6849b020b93ac359e245 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 6 Nov 2023 21:38:40 +0000 Subject: [PATCH 0443/2167] Fix MWScript variables documetnation type --- files/lua_api/openmw/world.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 13fa75e0ad..5baa624c5d 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -22,6 +22,10 @@ -- Functions related to MWScript. -- @type MWScriptFunctions +--- +-- @type MWScriptVariables +-- @map <#string, #number> + --- -- Returns local mwscript on ``object``. Returns `nil` if the script doesn't exist or is not started. -- @function [parent=#MWScriptFunctions] getLocalScript @@ -33,7 +37,7 @@ -- Returns mutable global variables. In multiplayer, these may be specific to the provided player. -- @function [parent=#MWScriptFunctions] getGlobalVariables -- @param openmw.core#GameObject player (optional) Will be used in multiplayer mode to get the globals if there is a separate instance for each player. Currently has no effect. --- @return #list<#number> +-- @return #MWScriptVariables --- -- Returns global mwscript with given recordId. Returns `nil` if the script doesn't exist or is not started. From afbfed78ad9be0818bb0c827d51146d49939f6d2 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 6 Nov 2023 21:57:49 +0000 Subject: [PATCH 0444/2167] Add ItemUsage to interfaces package type definition --- files/lua_api/openmw/interfaces.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 5048fc2f3e..d4a290aa47 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -20,6 +20,9 @@ --- -- @field [parent=#interfaces] scripts.omw.ui#scripts.omw.ui UI +--- +-- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage + --- -- @function [parent=#interfaces] __index -- @param #interfaces self From 0f3dba28a720c08ff14f0ca79f5b80d36fb7c89f Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Tue, 7 Nov 2023 02:59:37 +0000 Subject: [PATCH 0445/2167] Consider 75% Chameleon magical invisibility as well --- apps/openmw/mwmechanics/actorutil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index c414ff3032..2d2980075e 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -39,6 +39,6 @@ namespace MWMechanics { const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) - || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() > 75); + || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() >= 75); } } From e1cd5250af8e3a6003a233ed4b9be130c1b099e4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 7 Oct 2023 00:53:07 +0300 Subject: [PATCH 0446/2167] Use sun visibility for sunlight scattering (bug #7309) --- CHANGELOG.md | 1 + files/shaders/compatibility/water.frag | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26957e6c8d..c1d3ec0f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes + Bug #7309: Sunlight scattering is visible in inappropriate situations Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 987dab10a6..09b7090958 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -173,7 +173,7 @@ void main(void) vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); - // alpha component is sun visibility; we want to start fading specularity when visibility is low + // alpha component is sun visibility; we want to start fading lighting effects when visibility is low sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // artificial specularity to make rain ripples more noticeable @@ -202,7 +202,7 @@ void main(void) float sunHeight = lVec.z; vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); - float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; From 2d4e1b88b225d598cae29369d3962a7f1f6c64fc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 7 Nov 2023 15:52:49 +0400 Subject: [PATCH 0447/2167] Init missing field --- components/nif/controller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 33c04a6d35..947fae1ab2 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -171,7 +171,7 @@ namespace Nif }; NiPosDataPtr mData; - TargetColor mTargetColor; + TargetColor mTargetColor = TargetColor::Ambient; void read(NIFStream* nif) override; void post(Reader& nif) override; From 47c7997a23e39471675a43dcf20df7a752851502 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 7 Nov 2023 15:57:25 +0400 Subject: [PATCH 0448/2167] Init an another field --- components/esm4/loadrace.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp index 9cd4d00891..125fefbd9f 100644 --- a/components/esm4/loadrace.hpp +++ b/components/esm4/loadrace.hpp @@ -110,7 +110,7 @@ namespace ESM4 ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details - bool mIsTES5; + bool mIsTES5 = false; std::string mEditorId; std::string mFullName; From 76f872aaa272536320f4fcb5ad90b65ba9859d7e Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 7 Nov 2023 11:40:43 -0600 Subject: [PATCH 0449/2167] use std:;array --- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/stats.cpp | 2 +- components/esm3/loadclas.cpp | 2 +- components/esm3/loadclas.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index e506916337..d62e5bca4d 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -72,7 +72,7 @@ namespace MWLua }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { - return ESM::Class::indexToLuaId[rec.mData.mSpecialization]; + return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization + 10); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 0311ccaab8..e2ea04e95f 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -447,7 +447,7 @@ namespace MWLua skillT["description"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); skillT["specialization"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { - return ESM::Class::indexToLuaId[rec.mData.mSpecialization]; + return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization); }); skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index 2dd605c7c6..dcfeb958c1 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -10,7 +10,7 @@ namespace ESM { const std::string_view Class::sGmstSpecializationIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; - const std::string_view Class::indexToLuaId[3] = { "combat", "magic", "stealth" }; + const std::array Class::indexToLuaId = { "combat", "magic", "stealth" }; int32_t& Class::CLDTstruct::getSkill(int index, bool major) { diff --git a/components/esm3/loadclas.hpp b/components/esm3/loadclas.hpp index f4bf60bc11..e1c9b851ee 100644 --- a/components/esm3/loadclas.hpp +++ b/components/esm3/loadclas.hpp @@ -31,7 +31,7 @@ namespace ESM Stealth = 2 }; static const std::string_view sGmstSpecializationIds[3]; - static const std::string_view indexToLuaId[3]; + static const std::array indexToLuaId; struct CLDTstruct { From 81da58478dedb2d7abdbcbaada49ebc6da66191c Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 7 Nov 2023 11:41:14 -0600 Subject: [PATCH 0450/2167] Remove test use --- apps/openmw/mwlua/classbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index d62e5bca4d..24a8e7e2eb 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -72,7 +72,7 @@ namespace MWLua }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { - return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization + 10); + return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); From ae4eafdfd49ee03fb1d347fab83ba19da3f48c4d Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 7 Nov 2023 11:50:09 -0600 Subject: [PATCH 0451/2167] Move classes to types.NPC --- apps/openmw/mwlua/luabindings.cpp | 6 +----- apps/openmw/mwlua/types/npc.cpp | 4 ++++ files/lua_api/openmw/core.lua | 32 +------------------------------ files/lua_api/openmw/types.lua | 29 ++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 782cc07c46..db3057f982 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -37,7 +37,6 @@ #include "camerabindings.hpp" #include "cellbindings.hpp" -#include "classbindings.hpp" #include "debugbindings.hpp" #include "factionbindings.hpp" #include "inputbindings.hpp" @@ -158,12 +157,9 @@ namespace MWLua api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); - sol::table character(lua->sol(), sol::create); initCoreFactionBindings(context); - character["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); + api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get(); - character["classes"] = initCoreClassBindings(context); - api["character"] = LuaUtil::makeReadOnly(character); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 25997b6468..823cf59613 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -12,6 +12,7 @@ #include #include +#include "../classbindings.hpp" #include "../localscripts.hpp" #include "../stats.hpp" @@ -81,6 +82,9 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); + sol::table character(lua, sol::create); + character["classes"] = initCoreClassBindings(context); + npc["character"] = LuaUtil::makeReadOnly(character); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 98046ac7c0..441136805b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -12,24 +12,7 @@ --- -- A read-only list of all @{#FactionRecord}s in the world database. --- @field [parent=#Character] #list<#FactionRecord> factions - ---- @{#Character}: Class and Character Data --- @field [parent=#core] #Character character - - ---- @{#Classes}: Class Data --- @field [parent=#Character] #Classes classes - ---- --- A read-only list of all @{#ClassRecord}s in the world database. --- @field [parent=#Classes] #list<#ClassRecord> records - ---- --- Returns a read-only @{#ClassRecord} --- @function [parent=#Classes] record --- @param #string recordId --- @return #ClassRecord +-- @field [parent=#core] #list<#FactionRecord> factions --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. @@ -889,19 +872,6 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound ---- --- Class data record --- @type ClassRecord --- @field #string id Class id --- @field #string name Class name --- @field #list<#string> attributes A read-only list containing the specialized attributes of the class. --- @field #list<#string> majorSkills A read-only list containing the major skills of the class. --- @field #list<#string> minorSkills A read-only list containing the minor skills of the class. --- @field #string description Class description --- @field #boolean isPlayable True if the player can play as this class --- @field #string specialization Class specialization. Either combat, magic, or stealth. - - --- -- Faction data record -- @type FactionRecord diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..b300351df3 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -872,6 +872,35 @@ -- @param openmw.core#GameObject actor -- @return #number +--- @{#Character}: Class and Character Data +-- @field [parent=#NPC] #Character character + + +--- @{#Classes}: Class Data +-- @field [parent=#Character] #Classes classes + +--- +-- A read-only list of all @{#ClassRecord}s in the world database. +-- @field [parent=#Classes] #list<#ClassRecord> records + +--- +-- Returns a read-only @{#ClassRecord} +-- @function [parent=#Classes] record +-- @param #string recordId +-- @return #ClassRecord + +--- +-- Class data record +-- @type ClassRecord +-- @field #string id Class id +-- @field #string name Class name +-- @field #list<#string> attributes A read-only list containing the specialized attributes of the class. +-- @field #list<#string> majorSkills A read-only list containing the major skills of the class. +-- @field #list<#string> minorSkills A read-only list containing the minor skills of the class. +-- @field #string description Class description +-- @field #boolean isPlayable True if the player can play as this class +-- @field #string specialization Class specialization. Either combat, magic, or stealth. + --- -- Whether the NPC or player is in the werewolf form at the moment. -- @function [parent=#NPC] isWerewolf From cb705ff02a34ca46a27dd2009a8d6f460f654bbb Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 7 Nov 2023 11:52:53 -0600 Subject: [PATCH 0452/2167] Revert redundant changes --- apps/openmw/mwlua/luabindings.cpp | 1 - apps/openmw/mwlua/stats.hpp | 1 - components/esm3/loadclas.hpp | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index db3057f982..a50459502b 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/apps/openmw/mwlua/stats.hpp b/apps/openmw/mwlua/stats.hpp index adff27654d..4ce2f6b5eb 100644 --- a/apps/openmw/mwlua/stats.hpp +++ b/apps/openmw/mwlua/stats.hpp @@ -7,7 +7,6 @@ namespace MWLua { struct Context; - std::string_view getSpecialization(int32_t val); void addActorStatsBindings(sol::table& actor, const Context& context); void addNpcStatsBindings(sol::table& npc, const Context& context); sol::table initCoreStatsBindings(const Context& context); diff --git a/components/esm3/loadclas.hpp b/components/esm3/loadclas.hpp index e1c9b851ee..00bdbb9845 100644 --- a/components/esm3/loadclas.hpp +++ b/components/esm3/loadclas.hpp @@ -30,6 +30,7 @@ namespace ESM Magic = 1, Stealth = 2 }; + static const std::string_view sGmstSpecializationIds[3]; static const std::array indexToLuaId; From ec81bd7f1bd5e211e009130477efb7acd52427cd Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 7 Nov 2023 12:01:54 -0600 Subject: [PATCH 0453/2167] rename specilizationIndexToLuaId --- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/stats.cpp | 2 +- components/esm3/loadclas.cpp | 2 +- components/esm3/loadclas.hpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 24a8e7e2eb..8b277b41fe 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -72,7 +72,7 @@ namespace MWLua }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { - return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization); + return ESM::Class::specilizationIndexToLuaId.at(rec.mData.mSpecialization); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index e2ea04e95f..ac94e29f99 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -447,7 +447,7 @@ namespace MWLua skillT["description"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); skillT["specialization"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { - return ESM::Class::indexToLuaId.at(rec.mData.mSpecialization); + return ESM::Class::specilizationIndexToLuaId.at(rec.mData.mSpecialization); }); skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index dcfeb958c1..01bf6f54f6 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -10,7 +10,7 @@ namespace ESM { const std::string_view Class::sGmstSpecializationIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; - const std::array Class::indexToLuaId = { "combat", "magic", "stealth" }; + const std::array Class::specilizationIndexToLuaId = { "combat", "magic", "stealth" }; int32_t& Class::CLDTstruct::getSkill(int index, bool major) { diff --git a/components/esm3/loadclas.hpp b/components/esm3/loadclas.hpp index 00bdbb9845..2070f2127a 100644 --- a/components/esm3/loadclas.hpp +++ b/components/esm3/loadclas.hpp @@ -30,9 +30,9 @@ namespace ESM Magic = 1, Stealth = 2 }; - + static const std::string_view sGmstSpecializationIds[3]; - static const std::array indexToLuaId; + static const std::array specilizationIndexToLuaId; struct CLDTstruct { From fdf9184cae5dd92a5eb8e33e096a10630c3ce656 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 8 Nov 2023 12:27:12 +0300 Subject: [PATCH 0454/2167] Improve or fix FO76-related definitions --- components/nif/effect.cpp | 4 ++- components/nif/effect.hpp | 3 ++- components/nif/particle.cpp | 6 ++--- components/nif/property.cpp | 52 ++++++++++++++++++------------------- components/nif/property.hpp | 8 +++--- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index ae4c7947cb..47d1863352 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -45,7 +45,9 @@ namespace Nif { NiPointLight::read(nif); - nif->read(mCutoff); + nif->read(mOuterSpotAngle); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) + nif->read(mInnerSpotAngle); nif->read(mExponent); } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 906a7fdedf..2dd18d5304 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -58,7 +58,8 @@ namespace Nif struct NiSpotLight : public NiPointLight { - float mCutoff; + float mOuterSpotAngle; + float mInnerSpotAngle{ 0.f }; float mExponent; void read(NIFStream* nif) override; }; diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 0581c5a1d1..d81d423fb6 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -231,7 +231,7 @@ namespace Nif info.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) nif->skip(12); // Unknown if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2) && nif->get() && hasData) @@ -420,8 +420,8 @@ namespace Nif { nif->read(mRotationSpeedVariation); - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->skip(5); // Unknown + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + nif->skip(17); // Unknown nif->read(mRotationAngle); nif->read(mRotationAngleVariation); diff --git a/components/nif/property.cpp b/components/nif/property.cpp index bcc70540c8..2a5f91385d 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -148,12 +148,6 @@ namespace Nif } else { - uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; - nif->read(numShaderFlags1); - if (nif->getBethVersion() >= 152) - nif->read(numShaderFlags2); - nif->readVector(mShaderFlags1Hashes, numShaderFlags1); - nif->readVector(mShaderFlags2Hashes, numShaderFlags2); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76 && recType == RC_BSLightingShaderProperty) { nif->read(mType); @@ -181,6 +175,13 @@ namespace Nif break; } } + + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); } nif->read(mUVOffset); @@ -324,7 +325,7 @@ namespace Nif { nif->read(mSubsurfaceRolloff); nif->read(mRimlightPower); - if (mRimlightPower == std::numeric_limits::max()) + if (nif->getBethVersion() == 130 && mRimlightPower == std::numeric_limits::max()) nif->read(mBacklightPower); } @@ -335,27 +336,27 @@ namespace Nif mWetness.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + { mLuminance.read(nif); - - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) - { - nif->read(mDoTranslucency); - if (mDoTranslucency) - mTranslucency.read(nif); - if (nif->get() != 0) + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) { - mTextureArrays.resize(nif->get()); - for (std::vector& textureArray : mTextureArrays) - nif->getSizedStrings(textureArray, nif->get()); + nif->read(mDoTranslucency); + if (mDoTranslucency) + mTranslucency.read(nif); + if (nif->get() != 0) + { + mTextureArrays.resize(nif->get()); + for (std::vector& textureArray : mTextureArrays) + nif->getSizedStrings(textureArray, nif->get()); + } + } + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + { + nif->skip(4); // Unknown + nif->skip(4); // Unknown + nif->skip(2); // Unknown } - } - - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) - { - nif->skip(4); // Unknown - nif->skip(4); // Unknown - nif->skip(2); // Unknown } switch (static_cast(mType)) @@ -439,7 +440,6 @@ namespace Nif if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) { - nif->read(mRefractionPower); mReflectanceTexture = nif->getSizedString(); mLightingTexture = nif->getSizedString(); nif->read(mEmittanceColor); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index eaaa972c39..2506633867 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -295,15 +295,15 @@ namespace Nif { BSShaderTextureSetPtr mTextureSet; osg::Vec3f mEmissive; - float mEmissiveMult; + float mEmissiveMult{ 1.f }; std::string mRootMaterial; - uint32_t mClamp; - float mAlpha; + uint32_t mClamp{ 3 }; + float mAlpha{ 1.f }; float mRefractionStrength; float mGlossiness{ 80.f }; float mSmoothness{ 1.f }; osg::Vec3f mSpecular; - float mSpecStrength; + float mSpecStrength{ 1.f }; std::array mLightingEffects; float mSubsurfaceRolloff; float mRimlightPower; From 7f92c1821eba9350a19d9371c1dd75ec8ddb2bd3 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 8 Nov 2023 12:33:26 +0300 Subject: [PATCH 0455/2167] Read BSCollisionQueryProxyExtraData --- components/nif/extra.cpp | 5 +++++ components/nif/extra.hpp | 7 +++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 1 + 4 files changed, 15 insertions(+) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 2d222f5a54..4ebd0bf517 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -136,6 +136,11 @@ namespace Nif nif->readVector(mData, nif->get()); } + void BSCollisionQueryProxyExtraData::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + void BSConnectPoint::Point::read(NIFStream* nif) { mParent = nif->getSizedString(); diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 1efa4ae7bb..2b46c81e26 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -173,6 +173,13 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSCollisionQueryProxyExtraData : BSExtraData + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + struct BSConnectPoint { struct Point diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 81a223e095..37e40938d3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -248,6 +248,8 @@ namespace Nif { "BSBehaviorGraphExtraData", &construct }, { "BSBoneLODExtraData", &construct }, { "BSClothExtraData", &construct }, + { "BSCollisionQueryProxyExtraData", + &construct }, { "BSConnectPoint::Children", &construct }, { "BSConnectPoint::Parents", &construct }, { "BSDecalPlacementVectorExtraData", diff --git a/components/nif/record.hpp b/components/nif/record.hpp index d2a30b1317..699522d24c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -77,6 +77,7 @@ namespace Nif RC_BSBound, RC_BSBoneLODExtraData, RC_BSClothExtraData, + RC_BSCollisionQueryProxyExtraData, RC_BSConnectPointChildren, RC_BSConnectPointParents, RC_BSDecalPlacementVectorExtraData, From a7a48aaa910638ea98432638f5d00d302954e750 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 9 Nov 2023 00:27:07 +0800 Subject: [PATCH 0456/2167] make successful lockspell play unlock sound --- apps/openmw/mwmechanics/spelleffects.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 2e28aaa1f3..db9ec3e588 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -932,6 +932,9 @@ namespace MWMechanics if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude { + MWBase::Environment::get().getSoundManager()->playSound3D( + target, ESM::RefId::stringRefId("Open Lock"), 1.f, 1.f); + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().lock(magnitude); From d54c8b82d9fcb8ff6ef1946e170f9d781c696e05 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 9 Nov 2023 00:29:57 +0800 Subject: [PATCH 0457/2167] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26957e6c8d..9f1046179b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7675: Successful lock spell doesn't produce a sound Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION From cdaa44f24cdef69ac5aa13047a9aa60fefc4e944 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 8 Nov 2023 22:50:50 +0000 Subject: [PATCH 0458/2167] [Postprocessing] Fix dirty flag and share luminance calculator between frames --- apps/openmw/mwrender/pingpongcanvas.cpp | 22 ++++++++++++---------- apps/openmw/mwrender/pingpongcanvas.hpp | 5 +++-- apps/openmw/mwrender/postprocessor.cpp | 14 ++++++++------ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 6782bdddfd..b96b40ff3f 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -10,9 +10,11 @@ namespace MWRender { - PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + PingPongCanvas::PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator) : mFallbackStateSet(new osg::StateSet) , mMultiviewResolveStateSet(new osg::StateSet) + , mLuminanceCalculator(luminanceCalculator) { setUseDisplayList(false); setUseVertexBufferObjects(true); @@ -26,8 +28,7 @@ namespace MWRender addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); - mLuminanceCalculator = LuminanceCalculator(shaderManager); - mLuminanceCalculator.disable(); + mLuminanceCalculator->disable(); Shader::ShaderManager::DefineMap defines; Stereo::shaderStereoDefines(defines); @@ -142,7 +143,7 @@ namespace MWRender .getTexture()); } - mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + mLuminanceCalculator->dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); if (Stereo::getStereo()) mRenderViewport @@ -158,11 +159,11 @@ namespace MWRender { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } }; - (mAvgLum) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); + (mAvgLum) ? mLuminanceCalculator->enable() : mLuminanceCalculator->disable(); // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly // supported, so that's what we use for now. - mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId); + mLuminanceCalculator->draw(*this, renderInfo, state, ext, frameId); auto buffer = buffers[0]; @@ -202,8 +203,8 @@ namespace MWRender node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth); if (mAvgLum) - node.mRootStateSet->setTextureAttribute( - PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, + mLuminanceCalculator->getLuminanceTexture(frameId)); if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); @@ -258,8 +259,6 @@ namespace MWRender texture->setTextureSize(w, h); texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); texture->dirtyTextureObject(); - - mDirtyAttachments = false; } pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); @@ -336,5 +335,8 @@ namespace MWRender { bindDestinationFbo(); } + + if (mDirtyAttachments) + mDirtyAttachments = false; } } diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index 557813b816..0d53fd049a 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -22,7 +22,8 @@ namespace MWRender class PingPongCanvas : public osg::Geometry { public: - PingPongCanvas(Shader::ShaderManager& shaderManager); + PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator); void drawGeometry(osg::RenderInfo& renderInfo) const; @@ -72,7 +73,7 @@ namespace MWRender mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; mutable std::array, 3> mFbos; - mutable LuminanceCalculator mLuminanceCalculator; + mutable std::shared_ptr mLuminanceCalculator; }; } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 66ade9495c..2527f52df1 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -118,9 +118,14 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) - , mCanvases({ new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()), - new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()) }) { + auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); + + std::shared_ptr luminanceCalculator = std::make_shared(shaderManager); + + for (auto& canvas : mCanvases) + canvas = new PingPongCanvas(shaderManager, luminanceCalculator); + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); @@ -139,8 +144,7 @@ namespace MWRender if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) { mTransparentDepthPostPass - = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), - Settings::postProcessing().mTransparentPostpass); + = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } @@ -617,8 +621,6 @@ namespace MWRender subPass.mSize = renderTarget.mSize; subPass.mRenderTexture = renderTarget.mTarget; subPass.mMipMap = renderTarget.mMipMap; - subPass.mStateSet->setAttributeAndModes(new osg::Viewport( - 0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight())); subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, From aefab1aac50e8d68318d672f3a0e0373fc5abaa9 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 18:36:39 +0100 Subject: [PATCH 0459/2167] List installed packages --- CI/install_debian_deps.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index bd767bb173..1606b18b07 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -125,3 +125,4 @@ add-apt-repository -y ppa:openmw/openmw add-apt-repository -y ppa:openmw/openmw-daily add-apt-repository -y ppa:openmw/staging apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null +apt list --installed From e8362c7feddab46ad56c8c47f529a8195c6fe3e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 18:37:07 +0100 Subject: [PATCH 0460/2167] Install libyaml-cpp0.8 for integration tests --- CI/install_debian_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 1606b18b07..8d7c0a493f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=( libswresample3 libswscale5 libtinyxml2.6.2v5 - libyaml-cpp0.7 + libyaml-cpp0.8 python3-pip xvfb " From dec120f38c80a6e8dc609339ea5ab8ab478c9dee Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 8 Nov 2023 17:25:41 -0800 Subject: [PATCH 0461/2167] consistent average scene luminance --- CHANGELOG.md | 1 + apps/openmw/mwrender/luminancecalculator.cpp | 37 ++++++++++++-------- apps/openmw/mwrender/luminancecalculator.hpp | 1 + 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5228662d08..1afca19c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking Bug #7675: Successful lock spell doesn't produce a sound + Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index 5b7fe272aa..ae29b7fdcc 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -20,11 +20,6 @@ namespace MWRender mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); - } - - void LuminanceCalculator::compile() - { - int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); for (auto& buffer : mBuffers) { @@ -38,7 +33,6 @@ namespace MWRender osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); - buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); buffer.luminanceTex = new osg::Texture2D; buffer.luminanceTex->setInternalFormat(GL_R16F); @@ -62,14 +56,6 @@ namespace MWRender buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.luminanceProxyTex)); - buffer.resolveSceneLumFbo = new osg::FrameBufferObject; - buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); - - buffer.sceneLumFbo = new osg::FrameBufferObject; - buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); - buffer.sceneLumSS = new osg::StateSet; buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram); buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0)); @@ -84,6 +70,26 @@ namespace MWRender mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex); mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex); + } + + void LuminanceCalculator::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); + buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); + buffer.mipmappedSceneLuminanceTex->dirtyTextureObject(); + + buffer.resolveSceneLumFbo = new osg::FrameBufferObject; + buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); + + buffer.sceneLumFbo = new osg::FrameBufferObject; + buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); + } mCompiled = true; } @@ -114,13 +120,14 @@ namespace MWRender buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); - if (dirty) + if (mIsBlank) { // Use current frame data for previous frame to warm up calculations and prevent popin mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + mIsBlank = false; } buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); diff --git a/apps/openmw/mwrender/luminancecalculator.hpp b/apps/openmw/mwrender/luminancecalculator.hpp index 71ea2f7971..8b51081e2f 100644 --- a/apps/openmw/mwrender/luminancecalculator.hpp +++ b/apps/openmw/mwrender/luminancecalculator.hpp @@ -58,6 +58,7 @@ namespace MWRender bool mCompiled = false; bool mEnabled = false; + bool mIsBlank = true; int mWidth = 1; int mHeight = 1; From 85fcfbafda1226d3eb70e7e8dcd499dcb2f1101c Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 10 Nov 2023 21:22:11 -0800 Subject: [PATCH 0462/2167] apply same logic to render targets, remove UB --- apps/openmw/mwrender/pingpongcanvas.cpp | 44 +++++++++++++++---- apps/openmw/mwrender/pingpongcanvas.hpp | 7 ++- apps/openmw/mwrender/postprocessor.cpp | 24 ++++++++-- components/fx/pass.cpp | 4 -- components/fx/pass.hpp | 1 - components/fx/technique.cpp | 12 +---- components/fx/types.hpp | 11 +++++ .../source/reference/postprocessing/omwfx.rst | 3 ++ 8 files changed, 76 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index b96b40ff3f..4fecdf87f9 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -196,6 +196,39 @@ namespace MWRender } }; + // When textures are created (or resized) we need to either dirty them and/or clear them. + // Otherwise, there will be undefined behavior when reading from a texture that has yet to be written to in a + // later pass. + for (const auto& attachment : mDirtyAttachments) + { + const auto [w, h] + = attachment.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + attachment.mTarget->setTextureSize(w, h); + if (attachment.mMipMap) + attachment.mTarget->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + attachment.mTarget->dirtyTextureObject(); + + osg::ref_ptr fbo = new osg::FrameBufferObject; + + fbo->setAttachment( + osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(attachment.mTarget)); + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + glViewport(0, 0, attachment.mTarget->getTextureWidth(), attachment.mTarget->getTextureHeight()); + state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT); + glClearColor(attachment.mClearColor.r(), attachment.mClearColor.g(), attachment.mClearColor.b(), + attachment.mClearColor.a()); + glClear(GL_COLOR_BUFFER_BIT); + + if (attachment.mTarget->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, attachment.mTarget); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + } + for (const size_t& index : filtered) { const auto& node = mPasses[index]; @@ -239,16 +272,11 @@ namespace MWRender if (pass.mRenderTarget) { - if (mDirtyAttachments) + if (mDirtyAttachments.size() > 0) { const auto [w, h] = pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); - pass.mRenderTexture->setTextureSize(w, h); - if (pass.mMipMap) - pass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); - pass.mRenderTexture->dirtyTextureObject(); - // Custom render targets must be shared between frame ids, so it's impossible to double buffer // without expensive copies. That means the only thread-safe place to resize is in the draw // thread. @@ -265,7 +293,6 @@ namespace MWRender if (pass.mRenderTexture->getNumMipmapLevels() > 0) { - state.setActiveTextureUnit(0); state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) @@ -336,7 +363,6 @@ namespace MWRender bindDestinationFbo(); } - if (mDirtyAttachments) - mDirtyAttachments = false; + mDirtyAttachments.clear(); } } diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index 0d53fd049a..a03e3591ae 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -31,7 +31,10 @@ namespace MWRender void dirty() { mDirty = true; } - void resizeRenderTargets() { mDirtyAttachments = true; } + void setDirtyAttachments(const std::vector& attachments) + { + mDirtyAttachments = attachments; + } const fx::DispatchArray& getPasses() { return mPasses; } @@ -68,7 +71,7 @@ namespace MWRender osg::ref_ptr mTextureNormals; mutable bool mDirty = false; - mutable bool mDirtyAttachments = false; + mutable std::vector mDirtyAttachments; mutable osg::ref_ptr mRenderViewport; mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 2527f52df1..2c77981244 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -541,6 +541,8 @@ namespace MWRender mNormals = false; mPassLights = false; + std::vector attachmentsToDirty; + for (const auto& technique : mTechniques) { if (!technique->isValid()) @@ -617,7 +619,7 @@ namespace MWRender if (!pass->getTarget().empty()) { - const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; + auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; subPass.mSize = renderTarget.mSize; subPass.mRenderTexture = renderTarget.mTarget; subPass.mMipMap = renderTarget.mMipMap; @@ -628,13 +630,27 @@ namespace MWRender const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) + { + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); + } } for (const auto& name : pass->getRenderTargets()) { - subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget); + auto& renderTarget = technique->getRenderTargetsMap()[name]; + subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) + { + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); + } subTexUnit++; } @@ -654,7 +670,7 @@ namespace MWRender mRendering.getSkyManager()->setSunglare(sunglare); if (dirtyAttachments) - mCanvases[frameId]->resizeRenderTargets(); + mCanvases[frameId]->setDirtyAttachments(attachmentsToDirty); } PostProcessor::Status PostProcessor::enableTechnique( @@ -668,7 +684,7 @@ namespace MWRender int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); - dirtyTechniques(); + dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); return Status_Toggled; } diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index daaaf22968..d20813f1ce 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -326,9 +325,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mBlendEq) stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value())); - - if (mClearColor) - stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); } void Pass::dirty() diff --git a/components/fx/pass.hpp b/components/fx/pass.hpp index 509127a163..e176afc699 100644 --- a/components/fx/pass.hpp +++ b/components/fx/pass.hpp @@ -72,7 +72,6 @@ namespace fx std::array mRenderTargets; std::string mTarget; - std::optional mClearColor; std::optional mBlendSource; std::optional mBlendDest; diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 3abcd8c0ba..defb581cfc 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -313,6 +313,8 @@ namespace fx rt.mTarget->setSourceFormat(parseSourceFormat()); else if (key == "mipmaps") rt.mMipMap = parseBool(); + else if (key == "clear_color") + rt.mClearColor = parseVec(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); @@ -798,9 +800,6 @@ namespace fx if (!pass) pass = std::make_shared(); - bool clear = true; - osg::Vec4f clearColor = { 1, 1, 1, 1 }; - while (!isNext()) { expect("invalid key in block header"); @@ -844,10 +843,6 @@ namespace fx if (blendEq != osg::BlendEquation::FUNC_ADD) pass->mBlendEq = blendEq; } - else if (key == "clear") - clear = parseBool(); - else if (key == "clear_color") - clearColor = parseVec(); else error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); @@ -865,9 +860,6 @@ namespace fx return; } - if (clear) - pass->mClearColor = clearColor; - error("malformed block header"); } diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 0683126dec..0f33d29e1a 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -63,6 +63,17 @@ namespace fx osg::ref_ptr mTarget = new osg::Texture2D; SizeProxy mSize; bool mMipMap = false; + osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); + + RenderTarget() = default; + + RenderTarget(const RenderTarget& other) + : mTarget(other.mTarget) + , mSize(other.mSize) + , mMipMap(other.mMipMap) + , mClearColor(other.mClearColor) + { + } }; template diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 8ff075bc6d..6d191e0a7b 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -539,6 +539,8 @@ is not wanted and you want a custom render target. +------------------+---------------------+-----------------------------------------------------------------------------+ | mipmaps | boolean | Whether mipmaps should be generated every frame | +------------------+---------------------+-----------------------------------------------------------------------------+ +| clear_color | vec4 | The color the texture will be cleared to when it's first created | ++------------------+---------------------+-----------------------------------------------------------------------------+ To use the render target a pass must be assigned to it, along with any optional blend modes. As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. @@ -555,6 +557,7 @@ color buffer will accumulate. source_format = rgb; internal_format = rgb16f; source_type = float; + clear_color = vec4(1,0,0,1); } fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { From 96178a260536a8d9173763650ac8eca5f38af7cc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 11 Nov 2023 16:30:16 +0000 Subject: [PATCH 0463/2167] Allow Shoulder Buttons on Controller to act as Tab and Shift Tab in menus --- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index a8fb52c95e..85c7d8ba88 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -183,6 +183,10 @@ namespace MWGui return switchFocus(D_Down, false); case MyGUI::KeyCode::Tab: return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Period: + return switchFocus(D_Prev, true); + case MyGUI::KeyCode::Slash: + return switchFocus(D_Next, true); case MyGUI::KeyCode::Return: case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::Space: From 435e9731df781c357629a6b7534bd3f984110723 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 18:09:00 +0100 Subject: [PATCH 0464/2167] Render invalid 'select' setting renderer values instead of silent failure --- files/data/scripts/omw/settings/renderers.lua | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/files/data/scripts/omw/settings/renderers.lua b/files/data/scripts/omw/settings/renderers.lua index 82005916de..fd9ab0b743 100644 --- a/files/data/scripts/omw/settings/renderers.lua +++ b/files/data/scripts/omw/settings/renderers.lua @@ -151,17 +151,26 @@ return function(registerRenderer) if not argument.l10n then error('"select" renderer requires a "l10n" argument') end + if not pcall(function() + local _ = ipairs(argument.items) + assert(#argument.items > 0) + end) + then + error('"select" renderer requires an "items" array as an argument') + end local l10n = core.l10n(argument.l10n) local index = nil - local itemCount = 0 + local itemCount = #argument.items for i, item in ipairs(argument.items) do - itemCount = itemCount + 1 if item == value then index = i end end - if not index then return {} end - local label = l10n(value) + local label = l10n(tostring(value)) + local labelColor = nil + if index == nil then + labelColor = util.color.rgb(1, 0, 0) + end local body = { type = ui.TYPE.Flex, props = { @@ -177,6 +186,10 @@ return function(registerRenderer) }, events = { mouseClick = async:callback(function() + if not index then + set(argument.items[#argument.items]) + return + end index = (index - 2) % itemCount + 1 set(argument.items[index]) end), @@ -187,6 +200,7 @@ return function(registerRenderer) template = I.MWUI.templates.textNormal, props = { text = label, + textColor = labelColor, }, external = { grow = 1, @@ -201,6 +215,10 @@ return function(registerRenderer) }, events = { mouseClick = async:callback(function() + if not index then + set(argument.items[1]) + return + end index = (index) % itemCount + 1 set(argument.items[index]) end), @@ -246,7 +264,9 @@ return function(registerRenderer) focusLoss = async:callback(function() if not lastInput then return end if not pcall(function() set(util.color.hex(lastInput)) end) - then set(value) end + then + set(value) + end end), }, } From a2de47080478bf8d433c6f3a20c6c2e624738b19 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sun, 12 Nov 2023 12:22:35 +0800 Subject: [PATCH 0465/2167] minor partial equipping fix --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 16f135df17..932723c3fd 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -566,8 +566,8 @@ namespace MWGui MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); invStore.unequipItemQuantity(ptr, count); updateItemView(); - mEquippedStackableCount.reset(); } + mEquippedStackableCount.reset(); } if (isVisible()) From 155b07f341ab347c7c2f82d8e976ff8cbc5210f4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Nov 2023 12:09:54 +0400 Subject: [PATCH 0466/2167] Do not update WindowManager by world data when there is no game --- apps/openmw/engine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c8c1ada5a9..92483bd8c3 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -318,9 +318,14 @@ bool OMW::Engine::frame(float frametime) mViewer->eventTraversal(); mViewer->updateTraversal(); + // update GUI by world data { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mWorld->updateWindowManager(); + + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) + { + mWorld->updateWindowManager(); + } } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now From 01316f15b8b3b3c6e46af3ec571e6b4c2d08b552 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 16:20:57 +0100 Subject: [PATCH 0467/2167] Avoid redundant conversion to string --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1d41bab34f..a9ae3bb55a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1115,7 +1115,7 @@ namespace MWGui else { std::vector split; - Misc::StringUtils::split(std::string{ tag }, split, ":"); + Misc::StringUtils::split(tag, split, ":"); l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager(); From 8f27178a0bc2f0a91b1583198b168d705ab9d9d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 13:26:39 +0100 Subject: [PATCH 0468/2167] Use settings values for navigator in the launcher --- apps/launcher/datafilespage.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b6192d3c02..4d3f0cc64f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include "utils/profilescombobox.hpp" @@ -123,7 +123,7 @@ namespace Launcher int getMaxNavMeshDbFileSizeMiB() { - return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024); + return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); } std::optional findFirstPath(const QStringList& directories, const QString& fileName) @@ -359,9 +359,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) void Launcher::DataFilesPage::saveSettings(const QString& profile) { - if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) - Settings::Manager::setUInt64( - "max navmeshdb file size", "Navigator", static_cast(std::max(0, value)) * 1024 * 1024); + Settings::navigator().mMaxNavmeshdbFileSize.set( + static_cast(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024); QString profileName = profile; From 1fa5d2ca9885c013aafe6320f166ec58f84cb09e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 13:26:39 +0100 Subject: [PATCH 0469/2167] Use settings values for GUI tags --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- components/settings/categories/gui.hpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a9ae3bb55a..d98b7472bb 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1100,7 +1100,7 @@ namespace MWGui std::string_view settingSection = tag.substr(0, comma_pos); std::string_view settingTag = tag.substr(comma_pos + 1, tag.length()); - _result = Settings::Manager::getString(settingTag, settingSection); + _result = Settings::get(settingSection, settingTag).get().print(); } else if (tag.starts_with(tokenToFind)) { diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index af438d5ddc..4a5e50fd8a 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -28,10 +28,8 @@ namespace Settings SettingValue mSubtitles{ mIndex, "GUI", "subtitles" }; SettingValue mHitFader{ mIndex, "GUI", "hit fader" }; SettingValue mWerewolfOverlay{ mIndex, "GUI", "werewolf overlay" }; - SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned", - makeClampSanitizerFloat(0, 1) }; - SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned", - makeClampSanitizerFloat(0, 1) }; + SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned" }; + SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned" }; SettingValue mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; SettingValue mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; From d76ae20c29ee65419d79446a9411c754de99ec86 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 12 Nov 2023 23:59:52 +0000 Subject: [PATCH 0470/2167] Feat(textedit): Set max text length for lua textEdit boxes to int_max by default --- components/lua_ui/textedit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index a8c19fa8fd..e12bd20c35 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -8,6 +8,7 @@ namespace LuaUi { mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->setMaxTextLength(std::numeric_limits::max()); registerEvents(mEditBox); WidgetExtension::initialize(); } From c7d5ea9fbf221c425400903a2ffd56160f0e843e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 14 Nov 2023 00:44:23 +0300 Subject: [PATCH 0471/2167] Improve BulletNifLoader handling of extra data Only handle extra data for the root node(s) Properly handle MRK flag editor marker filtering Fix BSXFlags test --- .../nifloader/testbulletnifloader.cpp | 139 +++++++++--------- components/nifbullet/bulletnifloader.cpp | 62 ++++---- components/nifbullet/bulletnifloader.hpp | 1 + 3 files changed, 99 insertions(+), 103 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 58e1073307..7dce21bac6 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -957,12 +957,12 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -985,13 +985,13 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1012,8 +1012,62 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F( + TestBulletNifLoader, for_root_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mData = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + for_root_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_should_ignore_extra_data) { mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; @@ -1034,35 +1088,6 @@ namespace Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; - - EXPECT_EQ(*result, expected); - } - - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) - { - mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.mData = "NC___"; - mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; EXPECT_EQ(*result, expected); } @@ -1101,33 +1126,13 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) - { - mNiStringExtraData.mData = "MRK"; - mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - Resource::BulletShape expected; - - EXPECT_EQ(*result, expected); - } - TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" - mNiIntegerExtraData.recType = Nif::RC_BSXFlags; - mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mName = "EditorMarker"; + mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" + mNiIntegerExtraData.recType = Nif::RC_BSXFlags; + mNiNode.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1142,18 +1147,14 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + TEST_F(TestBulletNifLoader, mrk_editor_marker_flag_disables_collision_for_markers) { + mNiTriShape.mParents.push_back(&mNiNode); + mNiTriShape.mName = "Tri EditorMarker"; mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode2); - mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) }; - mNiNode.recType = Nif::RC_NiNode; + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1161,13 +1162,7 @@ namespace const auto result = mLoader.load(file); - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 66c7eea12d..ec46afec41 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -263,6 +263,32 @@ namespace NifBullet args.mAutogenerated = colNode == nullptr; + // Check for extra data + for (const auto& e : node.getExtraList()) + { + if (e->recType == Nif::RC_NiStringExtraData) + { + // String markers may contain important information + // affecting the entire subtree of this node + auto sd = static_cast(e.getPtr()); + + // Editor marker flag + if (sd->mData == "MRK") + args.mHasTriMarkers = true; + else if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) + { + // NC prefix is case-insensitive but the second C in NCC flag needs be uppercase. + + // Collide only with camera. + if (sd->mData.length() > 2 && sd->mData[2] == 'C') + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; + // No collision. + else + mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; + } + } + } + // FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision if (colNode && colNode->mChildren.empty()) { @@ -323,35 +349,6 @@ namespace NifBullet if (node.recType == Nif::RC_AvoidNode) args.mAvoid = true; - // Check for extra data - for (const auto& e : node.getExtraList()) - { - if (e->recType == Nif::RC_NiStringExtraData) - { - // String markers may contain important information - // affecting the entire subtree of this node - auto sd = static_cast(e.getPtr()); - - if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) - { - // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be - // uppercase - if (sd->mData.length() > 2 && sd->mData[2] == 'C') - // Collide only with camera. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - else - // No collision. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; - } - // Don't autogenerate collision if MRK is set. - // FIXME: verify if this covers the entire subtree - else if (sd->mData == "MRK" && args.mAutogenerated) - { - return; - } - } - } - if ((args.mAutogenerated || args.mIsCollisionNode) && isTypeNiGeometry(node.recType)) handleNiTriShape(static_cast(node), parent, args); @@ -373,11 +370,14 @@ namespace NifBullet void BulletNifLoader::handleNiTriShape( const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, HandleNodeArgs args) { - // mHasMarkers is specifically BSXFlags editor marker flag. - // If this changes, the check must be corrected. + // This flag comes from BSXFlags if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "EditorMarker")) return; + // This flag comes from Morrowind + if (args.mHasTriMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "Tri EditorMarker")) + return; + if (niGeometry.mData.empty() || niGeometry.mData->mVertices.empty()) return; diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 521bbe91dd..c87c1242de 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -53,6 +53,7 @@ namespace NifBullet struct HandleNodeArgs { bool mHasMarkers{ false }; + bool mHasTriMarkers{ false }; bool mAnimated{ false }; bool mIsCollisionNode{ false }; bool mAutogenerated{ false }; From f62b43d2d6d62e9e044b5c7f80587cb69c2e6525 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Tue, 14 Nov 2023 05:27:30 +0000 Subject: [PATCH 0472/2167] Update file sv.yaml --- files/data/l10n/OMWEngine/sv.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 1ee8bdc707..4ee69f70c7 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -140,11 +140,11 @@ TextureFilteringBilinear: "Bilinjär" TextureFilteringDisabled: "Ingen" TextureFilteringOther: "Annan" TextureFilteringTrilinear: "Trilinjär" -ToggleHUD: "Växla till HUD" -TogglePostProcessorHUD: "Växla till Postprocess-HUD" +ToggleHUD: "Visa/dölj HUD" +TogglePostProcessorHUD: "Visa/dölj Postprocess-HUD" TransparencyFull: "Full" TransparencyNone: "Ingen" -Video: "Video" +Video: "Bild" VSync: "VSynk" ViewDistance: "Siktavstånd" Water: "Vatten" From 818cd8343feb719dee9c59cf03ab2cde5d336b06 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Tue, 14 Nov 2023 10:52:28 +0000 Subject: [PATCH 0473/2167] Update file sv.yaml --- files/data/l10n/OMWShaders/sv.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/files/data/l10n/OMWShaders/sv.yaml b/files/data/l10n/OMWShaders/sv.yaml index c1f1a5a333..2dff2946a8 100644 --- a/files/data/l10n/OMWShaders/sv.yaml +++ b/files/data/l10n/OMWShaders/sv.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Färgfaktor" DisplayDepthFactorDescription: "Avgör korrelation mellan djupvärdet på pixeln och dess producerade färg. Högre värden ger ljusare bild." DisplayDepthName: "Visualisera djupbufferten." DisplayNormalsName: "Visualisera normalvektorer" # på engelska står det "pass normals", finns bättre översättning? +NormalsInWorldSpace: "Visa normalvektorer i världens koordination" # "Show normals in world space", beskrivs såhär: "makes the shader visualize normals in the world's coordinate frame (blue = up, green = north, red = east) as opposed to the screen's coordinate frame (blue = depth, green = up, red = right), which is the default." ContrastLevelDescription: "Kontrastnivå" ContrastLevelName: "Kontrast" GammaLevelDescription: "Gammanivå" From 624d6ce7d0f915ba8cef1356574c5a5c21e3aa2d Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Tue, 14 Nov 2023 21:11:27 +0000 Subject: [PATCH 0474/2167] Update file sv.yaml --- files/data/l10n/OMWShaders/sv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/l10n/OMWShaders/sv.yaml b/files/data/l10n/OMWShaders/sv.yaml index 2dff2946a8..f7876b9ee3 100644 --- a/files/data/l10n/OMWShaders/sv.yaml +++ b/files/data/l10n/OMWShaders/sv.yaml @@ -34,7 +34,7 @@ DisplayDepthFactorName: "Färgfaktor" DisplayDepthFactorDescription: "Avgör korrelation mellan djupvärdet på pixeln och dess producerade färg. Högre värden ger ljusare bild." DisplayDepthName: "Visualisera djupbufferten." DisplayNormalsName: "Visualisera normalvektorer" # på engelska står det "pass normals", finns bättre översättning? -NormalsInWorldSpace: "Visa normalvektorer i världens koordination" # "Show normals in world space", beskrivs såhär: "makes the shader visualize normals in the world's coordinate frame (blue = up, green = north, red = east) as opposed to the screen's coordinate frame (blue = depth, green = up, red = right), which is the default." +NormalsInWorldSpace: "Visa normalvektorer enligt världens koordinatsystem" ContrastLevelDescription: "Kontrastnivå" ContrastLevelName: "Kontrast" GammaLevelDescription: "Gammanivå" From 1e0d549d1d733ba147f28e5703a07f1c95d20e79 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 16 Nov 2023 15:18:28 +0300 Subject: [PATCH 0475/2167] NifLoader: Only handle editor marker extra data for the root node --- components/nifosg/nifloader.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index b7ef547bd6..17c608f2d4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -679,10 +679,11 @@ namespace NifOsg // String markers may contain important information // affecting the entire subtree of this obj - if (sd->mData == "MRK" && !Loader::getShowMarkers()) + if (sd->mData == "MRK") { // Marker objects. These meshes are only visible in the editor. - args.mHasMarkers = true; + if (!Loader::getShowMarkers() && args.mRootNode == node) + args.mHasMarkers = true; } else if (sd->mData == "BONE") { @@ -696,8 +697,12 @@ namespace NifOsg } else if (e->recType == Nif::RC_BSXFlags) { + if (args.mRootNode != node) + continue; + auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->mData & 32) // Editor marker flag + // Marker objects. + if (!Loader::getShowMarkers() && (bsxFlags->mData & 32)) args.mHasMarkers = true; } } From a2b47f44d2062ccc307b92b39fa980eb733710e1 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 16 Nov 2023 07:36:40 -0600 Subject: [PATCH 0476/2167] Fix misspelling --- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/stats.cpp | 2 +- components/esm3/loadclas.cpp | 2 +- components/esm3/loadclas.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 8b277b41fe..f8290a263d 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -72,7 +72,7 @@ namespace MWLua }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { - return ESM::Class::specilizationIndexToLuaId.at(rec.mData.mSpecialization); + return ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization); }); classT["isPlayable"] = sol::readonly_property([](const ESM::Class& rec) -> bool { return rec.mData.mIsPlayable; }); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ac94e29f99..7e8cbb1b5e 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -447,7 +447,7 @@ namespace MWLua skillT["description"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { return rec.mDescription; }); skillT["specialization"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string_view { - return ESM::Class::specilizationIndexToLuaId.at(rec.mData.mSpecialization); + return ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization); }); skillT["icon"] = sol::readonly_property([vfs](const ESM::Skill& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index 01bf6f54f6..ec4ff680fa 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -10,7 +10,7 @@ namespace ESM { const std::string_view Class::sGmstSpecializationIds[3] = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; - const std::array Class::specilizationIndexToLuaId = { "combat", "magic", "stealth" }; + const std::array Class::specializationIndexToLuaId = { "combat", "magic", "stealth" }; int32_t& Class::CLDTstruct::getSkill(int index, bool major) { diff --git a/components/esm3/loadclas.hpp b/components/esm3/loadclas.hpp index 2070f2127a..a804a8da8e 100644 --- a/components/esm3/loadclas.hpp +++ b/components/esm3/loadclas.hpp @@ -32,7 +32,7 @@ namespace ESM }; static const std::string_view sGmstSpecializationIds[3]; - static const std::array specilizationIndexToLuaId; + static const std::array specializationIndexToLuaId; struct CLDTstruct { From 16f178b80ed8b6b66c1e6f76fdd783fb98b58a98 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 16 Nov 2023 07:43:45 -0600 Subject: [PATCH 0477/2167] Remove character --- apps/openmw/mwlua/types/npc.cpp | 5 ++--- files/lua_api/openmw/types.lua | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 823cf59613..55bb59afc0 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -82,9 +82,8 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - sol::table character(lua, sol::create); - character["classes"] = initCoreClassBindings(context); - npc["character"] = LuaUtil::makeReadOnly(character); + npc["classes"] = initCoreClassBindings(context); + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 73bed6799d..dbf884f737 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -878,12 +878,8 @@ -- @param openmw.core#GameObject actor -- @return #number ---- @{#Character}: Class and Character Data --- @field [parent=#NPC] #Character character - - --- @{#Classes}: Class Data --- @field [parent=#Character] #Classes classes +-- @field [parent=#NPC] #Classes classes --- -- A read-only list of all @{#ClassRecord}s in the world database. From aeb46f5fba0b0e6d912c75202c1c2f7518b7afa4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Nov 2023 11:37:03 +0400 Subject: [PATCH 0478/2167] Fix Coverity warnings --- components/nif/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 8b459d015a..e653959bbc 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -413,7 +413,7 @@ namespace Nif if (!hasPresenceFlags || nif->get()) nif->readVector(mVertexMap, numVertices); if (!hasPresenceFlags || nif->get()) - nif->readVector(mWeights, numVertices * bonesPerVertex); + nif->readVector(mWeights, static_cast(numVertices) * bonesPerVertex); std::vector stripLengths; nif->readVector(stripLengths, numStrips); if (!hasPresenceFlags || nif->get()) @@ -428,7 +428,7 @@ namespace Nif nif->readVector(mTriangles, numTriangles * 3); } if (nif->get() != 0) - nif->readVector(mBoneIndices, numVertices * bonesPerVertex); + nif->readVector(mBoneIndices, static_cast(numVertices) * bonesPerVertex); if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { nif->read(mLODLevel); From ba71eefbaeaf91935f3b75e108f76525dcff8de1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Nov 2023 18:53:33 +0400 Subject: [PATCH 0479/2167] Fix GCC warnings --- apps/opencs/model/tools/mergestages.cpp | 4 ++-- apps/openmw/mwrender/ripples.cpp | 5 +++-- apps/openmw/mwworld/worldimp.cpp | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/tools/mergestages.cpp b/apps/opencs/model/tools/mergestages.cpp index 5a7fa6c1b9..c2ab74bcca 100644 --- a/apps/opencs/model/tools/mergestages.cpp +++ b/apps/opencs/model/tools/mergestages.cpp @@ -189,9 +189,9 @@ void CSMTools::FixLandsAndLandTexturesMergeStage::perform(int stage, CSMDoc::Mes CSMWorld::IdTable& ltexTable = dynamic_cast( *mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); - const std::string& id = mState.mTarget->getData().getLand().getId(stage).getRefIdString(); + const auto& id = mState.mTarget->getData().getLand().getId(stage); - CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id); + CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id.getRefIdString()); cmd.redo(); // Get rid of base data diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 191ff0e714..130e005729 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -128,10 +128,11 @@ namespace MWRender { size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; - const ESM::Position& player = MWMechanics::getPlayer().getRefData().getPosition(); + const auto& player = MWMechanics::getPlayer(); + const ESM::Position& playerPos = player.getRefData().getPosition(); mCurrentPlayerPos = osg::Vec2f( - std::floor(player.pos[0] / mWorldScaleFactor), std::floor(player.pos[1] / mWorldScaleFactor)); + std::floor(playerPos.pos[0] / mWorldScaleFactor), std::floor(playerPos.pos[1] / mWorldScaleFactor)); osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; mLastPlayerPos = mCurrentPlayerPos; mState[frameId].mPaused = mPaused; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 20e4122766..d945aa5848 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1737,13 +1737,14 @@ namespace MWWorld void World::updateSoundListener() { osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); - const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition(); + const auto& player = getPlayerPtr(); + const ESM::Position& refpos = player.getRefData().getPosition(); osg::Vec3f listenerPos; if (isFirstPerson()) listenerPos = cameraPosition; else - listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z()); + listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(player).z()); osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); @@ -1751,7 +1752,7 @@ namespace MWWorld osg::Vec3f forward = listenerOrient * osg::Vec3f(0, 1, 0); osg::Vec3f up = listenerOrient * osg::Vec3f(0, 0, 1); - bool underwater = isUnderwater(getPlayerPtr().getCell(), cameraPosition); + bool underwater = isUnderwater(player.getCell(), cameraPosition); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } From cbfcd21d98bfd7ba396cc58a614a5c8a9f594cdc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 16 Nov 2023 15:23:09 +0000 Subject: [PATCH 0480/2167] Make actor.providedServices read only --- apps/openmw/mwlua/types/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 409559475f..4a16b65cbf 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -45,7 +45,7 @@ namespace MWLua providedServices[name] = (services & flag) != 0; } providedServices["Travel"] = !rec.getTransport().empty(); - return providedServices; + return LuaUtil::makeReadOnly(providedServices); }); } } From de8c93d04926bb0885ce7e90461e5ac96462614b Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Thu, 16 Nov 2023 15:24:16 +0000 Subject: [PATCH 0481/2167] [Postprocessing] Add an API version, mirroring Lua --- CMakeLists.txt | 1 + components/CMakeLists.txt | 1 + components/fx/pass.cpp | 3 + components/version/version.cpp.in | 5 ++ components/version/version.hpp | 1 + docs/source/conf.py | 5 ++ .../source/reference/postprocessing/omwfx.rst | 82 ++++++++++--------- 7 files changed, 59 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cad6290274..e0687ec198 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_LUA_API_REVISION 50) +set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b2fc46f358..85f6c26449 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -26,6 +26,7 @@ if (GIT_CHECKOUT) -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} -DOPENMW_LUA_API_REVISION=${OPENMW_LUA_API_REVISION} + -DOPENMW_POSTPROCESSING_API_REVISION=${OPENMW_POSTPROCESSING_API_REVISION} -DOPENMW_VERSION=${OPENMW_VERSION} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 7a7329d755..6b692272db 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "stateupdater.hpp" #include "technique.hpp" @@ -68,6 +69,7 @@ namespace fx @uboStruct +#define OMW_API_VERSION @apiVersion #define OMW_REVERSE_Z @reverseZ #define OMW_RADIAL_FOG @radialFog #define OMW_EXPONENTIAL_FOG @exponentialFog @@ -255,6 +257,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) const std::vector> defines = { { "@pointLightCount", std::to_string(SceneUtil::PPLightBuffer::sMaxPPLightsArraySize) }, + { "@apiVersion", std::to_string(Version::getPostprocessingApiRevision()) }, { "@version", std::to_string(technique.getGLSLVersion()) }, { "@multiview", Stereo::getMultiview() ? "1" : "0" }, { "@builtinSampler", Stereo::getMultiview() ? "sampler2DArray" : "sampler2D" }, diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in index d95e47cbd6..312520acbb 100644 --- a/components/version/version.cpp.in +++ b/components/version/version.cpp.in @@ -25,6 +25,11 @@ namespace Version return @OPENMW_LUA_API_REVISION@; } + int getPostprocessingApiRevision() + { + return @OPENMW_POSTPROCESSING_API_REVISION@; + } + std::string getOpenmwVersionDescription() { std::string str = "OpenMW version "; diff --git a/components/version/version.hpp b/components/version/version.hpp index 64b8cd05e0..c05cf8a594 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -11,6 +11,7 @@ namespace Version std::string_view getCommitHash(); std::string_view getTagHash(); int getLuaApiRevision(); + int getPostprocessingApiRevision(); // Prepares string that contains version and commit hash. std::string getOpenmwVersionDescription(); diff --git a/docs/source/conf.py b/docs/source/conf.py index 096dec6ae0..902e84c393 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,6 +67,7 @@ copyright = u'2023, OpenMW Team' release = version = "UNRELEASED" luaApiRevision = "UNKNOWN" +ppApiRevision = "UNDEFINED" try: cmake_raw = open(project_root+'/CMakeLists.txt', 'r').read() @@ -80,6 +81,9 @@ try: luaApiRevisionMatch = re.search('set\(OPENMW_LUA_API_REVISION (\d+)\)', cmake_raw) if luaApiRevisionMatch: luaApiRevision = luaApiRevisionMatch.group(1) + ppApiRevisionMatch = re.search('set\(OPENMW_POSTPROCESSING_API_REVISION (\d+)\)', cmake_raw) + if ppApiRevisionMatch: + ppApiRevision = ppApiRevisionMatch.group(1) except Exception as ex: print("WARNING: Version will be set to '{0}' because: '{1}'.".format(release, str(ex))) @@ -87,6 +91,7 @@ except Exception as ex: rst_prolog = f""" .. |luaApiRevision| replace:: {luaApiRevision} +.. |ppApiRevision| replace:: {ppApiRevision} """ # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 36a6f0883a..2909f8cc70 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -126,45 +126,49 @@ Builtin Uniforms Builtin Macros ############## -+-----------------------+----------------+----------------------------------------------------------------------+ -| Macro | Definition | Description | -+=======================+================+======================================================================+ -|``OMW_REVERSE_Z`` | ``0`` or ``1`` | Whether a reversed depth buffer is in use. | -| | | | -| | | ``0`` Depth sampler will be in range [1, 0] | -| | | | -| | | ``1`` Depth sampler will be in range [0, 1] | -+-----------------------+----------------+----------------------------------------------------------------------+ -|``OMW_RADIAL_FOG`` | ``0`` or ``1`` | Whether radial fog is in use. | -| | | | -| | | ``0`` Fog is linear | -| | | | -| | | ``1`` Fog is radial | -+-----------------------+----------------+----------------------------------------------------------------------+ -|``OMW_EXPONENTIAL_FOG``| ``0`` or ``1`` | Whether exponential fog is in use. | -| | | | -| | | ``0`` Fog is linear | -| | | | -| | | ``1`` Fog is exponential | -+-----------------------+----------------+----------------------------------------------------------------------+ -| ``OMW_HDR`` | ``0`` or ``1`` | Whether average scene luminance is computed every frame. | -| | | | -| | | ``0`` Average scene luminance is not computed | -| | | | -| | | ``1`` Average scene luminance is computed | -+-----------------------+----------------+----------------------------------------------------------------------+ -| ``OMW_NORMALS`` | ``0`` or ``1`` | Whether normals are available as a sampler in the technique. | -| | | | -| | | ``0`` Normals are not available | -| | | | -| | | ``1`` Normals are available. | -+-----------------------+----------------+----------------------------------------------------------------------+ -| ``OMW_MULTIVIEW`` | ``0`` or ``1`` | Whether multiview rendering is in use. | -| | | | -| | | ``0`` Multiview not in use | -| | | | -| | | ``1`` Multiview in use. | -+-----------------------+----------------+----------------------------------------------------------------------+ ++-----------------------+-----------------+----------------------------------------------------------------------+ +| Macro | Definition | Description | ++=======================+=================+======================================================================+ +|``OMW_REVERSE_Z`` | ``0`` or ``1`` | Whether a reversed depth buffer is in use. | +| | | | +| | | ``0`` Depth sampler will be in range [1, 0] | +| | | | +| | | ``1`` Depth sampler will be in range [0, 1] | ++-----------------------+-----------------+----------------------------------------------------------------------+ +|``OMW_RADIAL_FOG`` | ``0`` or ``1`` | Whether radial fog is in use. | +| | | | +| | | ``0`` Fog is linear | +| | | | +| | | ``1`` Fog is radial | ++-----------------------+-----------------+----------------------------------------------------------------------+ +|``OMW_EXPONENTIAL_FOG``| ``0`` or ``1`` | Whether exponential fog is in use. | +| | | | +| | | ``0`` Fog is linear | +| | | | +| | | ``1`` Fog is exponential | ++-----------------------+-----------------+----------------------------------------------------------------------+ +| ``OMW_HDR`` | ``0`` or ``1`` | Whether average scene luminance is computed every frame. | +| | | | +| | | ``0`` Average scene luminance is not computed | +| | | | +| | | ``1`` Average scene luminance is computed | ++-----------------------+-----------------+----------------------------------------------------------------------+ +| ``OMW_NORMALS`` | ``0`` or ``1`` | Whether normals are available as a sampler in the technique. | +| | | | +| | | ``0`` Normals are not available | +| | | | +| | | ``1`` Normals are available. | ++-----------------------+-----------------+----------------------------------------------------------------------+ +| ``OMW_MULTIVIEW`` | ``0`` or ``1`` | Whether multiview rendering is in use. | +| | | | +| | | ``0`` Multiview not in use | +| | | | +| | | ``1`` Multiview in use. | ++-----------------------+-----------------+----------------------------------------------------------------------+ +| ``OMW_API_VERSION`` | |ppApiRevision| | The revision of OpenMW postprocessing API. | +| | | It is an integer that is incremented every time the API is changed. | +| | | This was added in 0.49, so it will be undefined in 0.48. | ++-----------------------+-----------------+----------------------------------------------------------------------+ Builtin Functions From 2ce99b0c7f57cb3e70fbcc4104478b97c726e84b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 16 Nov 2023 11:08:57 -0600 Subject: [PATCH 0482/2167] Increment API_REVISION --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cad6290274..5174e6532f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 50) +set(OPENMW_LUA_API_REVISION 51) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") From bc63737488a11d496c71f61682aa4a601535d9aa Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 16 Nov 2023 11:20:08 -0600 Subject: [PATCH 0483/2167] Update Formatting --- apps/openmw/mwlua/types/npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 55bb59afc0..68d13342c4 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -83,7 +83,7 @@ namespace MWLua addActorServicesBindings(record, context); npc["classes"] = initCoreClassBindings(context); - + // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); From 335dbffe6eb928da9b134f917e1b1cf5f988f9d2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:09:37 +0100 Subject: [PATCH 0484/2167] Avoid std::string to QString conversion for label --- apps/opencs/model/prefs/boolsetting.cpp | 4 ++-- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 4 ++-- apps/opencs/model/prefs/coloursetting.hpp | 3 +-- apps/opencs/model/prefs/doublesetting.cpp | 4 ++-- apps/opencs/model/prefs/doublesetting.hpp | 3 +-- apps/opencs/model/prefs/enumsetting.cpp | 4 ++-- apps/opencs/model/prefs/enumsetting.hpp | 4 ++-- apps/opencs/model/prefs/intsetting.cpp | 4 ++-- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 4 ++-- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 7 +------ apps/opencs/model/prefs/setting.hpp | 6 +++--- apps/opencs/model/prefs/shortcutsetting.cpp | 4 ++-- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 22 +++++++++------------ apps/opencs/model/prefs/state.hpp | 19 +++++++++--------- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.hpp | 4 ++-- apps/opencs/view/prefs/keybindingpage.cpp | 4 ++-- 21 files changed, 49 insertions(+), 61 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c668bc0af4..c2718cd7aa 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,7 +11,7 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) @@ -26,7 +26,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { - mWidget = new QCheckBox(QString::fromUtf8(getLabel().c_str()), parent); + mWidget = new QCheckBox(getLabel(), parent); mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index e75ea1a346..60d51a9888 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs QCheckBox* mWidget; public: - BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_); + BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 86f3a5d772..b3f1b70513 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,7 +14,7 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_) : Setting(parent, mutex, key, label) , mDefault(std::move(default_)) , mWidget(nullptr) @@ -29,7 +29,7 @@ CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string& std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new CSVWidget::ColorEditor(mDefault, parent); diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index 0c22d9cc5d..e36ceb48b5 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -29,8 +29,7 @@ namespace CSMPrefs CSVWidget::ColorEditor* mWidget; public: - ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_); + ColourSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 7e3aadb0c3..d88678a8e9 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_) : Setting(parent, mutex, key, label) , mPrecision(2) , mMin(0) @@ -58,7 +58,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string& std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QDoubleSpinBox(parent); mWidget->setDecimals(mPrecision); diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index c951d2a88c..76135c4cdb 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -21,8 +21,7 @@ namespace CSMPrefs QDoubleSpinBox* mWidget; public: - DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_); + DoubleSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index a3ac9bce2b..6d925d1b31 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -45,7 +45,7 @@ CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const } CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, const EnumValue& default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) @@ -78,7 +78,7 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QComboBox(parent); diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 57bd2115ce..82ce209922 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -44,8 +44,8 @@ namespace CSMPrefs QComboBox* mWidget; public: - EnumSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, - const EnumValue& default_); + EnumSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_); EnumSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 90cc77c788..e8fd4acce6 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_) : Setting(parent, mutex, key, label) , mMin(0) , mMax(std::numeric_limits::max()) @@ -51,7 +51,7 @@ CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& toolti std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QSpinBox(parent); mWidget->setRange(mMin, mMax); diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 8a655178a4..48f24e5b78 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -23,7 +23,7 @@ namespace CSMPrefs QSpinBox* mWidget; public: - IntSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_); + IntSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 8752a4d51e..d20a427217 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -19,7 +19,7 @@ class QWidget; namespace CSMPrefs { - ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) @@ -33,7 +33,7 @@ namespace CSMPrefs QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index ae984243ac..fb186329a9 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -20,7 +20,7 @@ namespace CSMPrefs Q_OBJECT public: - ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); std::pair makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index efe360d1e8..d507236cc5 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -14,7 +14,7 @@ QMutex* CSMPrefs::Setting::getMutex() return mMutex; } -CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) +CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) @@ -40,11 +40,6 @@ const std::string& CSMPrefs::Setting::getKey() const return mKey; } -const std::string& CSMPrefs::Setting::getLabel() const -{ - return mLabel; -} - int CSMPrefs::Setting::toInt() const { QMutexLocker lock(mMutex); diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f63271b3f2..01138c6a14 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -21,13 +21,13 @@ namespace CSMPrefs Category* mParent; QMutex* mMutex; std::string mKey; - std::string mLabel; + QString mLabel; protected: QMutex* getMutex(); public: - Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); ~Setting() override = default; @@ -46,7 +46,7 @@ namespace CSMPrefs const std::string& getKey() const; - const std::string& getLabel() const; + const QString& getLabel() const { return mLabel; } int toInt() const; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index d8c71d7008..23c8d9c7a9 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -18,7 +18,7 @@ namespace CSMPrefs { - ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) @@ -37,7 +37,7 @@ namespace CSMPrefs QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index bef140dada..7dd2aecd61 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs Q_OBJECT public: - ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); std::pair makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 97f29bc8be..f95ceb896f 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -463,7 +463,7 @@ void CSMPrefs::State::declareCategory(const std::string& key) } } -CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const std::string& label, int default_) +CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const QString& label, int default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -479,8 +479,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const return *setting; } -CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble( - const std::string& key, const std::string& label, double default_) +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, const QString& label, double default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -499,7 +498,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble( return *setting; } -CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const std::string& label, bool default_) +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const QString& label, bool default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -516,8 +515,7 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons return *setting; } -CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum( - const std::string& key, const std::string& label, EnumValue default_) +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, const QString& label, EnumValue default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -534,8 +532,7 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum( return *setting; } -CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( - const std::string& key, const std::string& label, QColor default_) +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, const QString& label, QColor default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -554,7 +551,7 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( } CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( - const std::string& key, const std::string& label, const QKeySequence& default_) + const std::string& key, const QString& label, const QKeySequence& default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -576,7 +573,7 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const std::string& label, std::string default_) + const std::string& key, const QString& label, std::string default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -593,8 +590,7 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( return *setting; } -CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier( - const std::string& key, const std::string& label, int default_) +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const QString& label, int default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -625,7 +621,7 @@ void CSMPrefs::State::declareSeparator() mCurrentCategory->second.addSetting(setting); } -void CSMPrefs::State::declareSubcategory(const std::string& label) +void CSMPrefs::State::declareSubcategory(const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 354f4552e3..c74f317c94 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -60,25 +60,24 @@ namespace CSMPrefs void declareCategory(const std::string& key); - IntSetting& declareInt(const std::string& key, const std::string& label, int default_); - DoubleSetting& declareDouble(const std::string& key, const std::string& label, double default_); + IntSetting& declareInt(const std::string& key, const QString& label, int default_); + DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); - BoolSetting& declareBool(const std::string& key, const std::string& label, bool default_); + BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); - EnumSetting& declareEnum(const std::string& key, const std::string& label, EnumValue default_); + EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); - ColourSetting& declareColour(const std::string& key, const std::string& label, QColor default_); + ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); - ShortcutSetting& declareShortcut( - const std::string& key, const std::string& label, const QKeySequence& default_); + ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const std::string& label, std::string default_); + StringSetting& declareString(const std::string& key, const QString& label, std::string default_); - ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); + ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); void declareSeparator(); - void declareSubcategory(const std::string& label); + void declareSubcategory(const QString& label); void setDefault(const std::string& key, const std::string& default_); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 2a8fdd587a..bb7a9b7fe3 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,7 +12,7 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, std::string_view default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 4b5499ef86..81adc55361 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -23,8 +23,8 @@ namespace CSMPrefs QLineEdit* mWidget; public: - StringSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, - std::string_view default_); + StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_); StringSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index d3cc1ff889..7b6337a6ab 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -82,7 +82,7 @@ namespace CSVPrefs } else { - if (setting->getLabel().empty()) + if (setting->getLabel().isEmpty()) { // Insert empty space assert(mPageLayout); @@ -99,7 +99,7 @@ namespace CSVPrefs mStackedLayout->addWidget(pageWidget); - mPageSelector->addItem(QString::fromUtf8(setting->getLabel().c_str())); + mPageSelector->addItem(setting->getLabel()); } } } From fb6e429daddc77516ef125a725dd0b9485b26f0f Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:17:06 +0100 Subject: [PATCH 0485/2167] Remove "separators" from cs settings Those are just empty widgets which do not make significant visible difference in the UI. --- apps/opencs/model/prefs/state.cpp | 17 --------------- apps/opencs/model/prefs/state.hpp | 2 -- apps/opencs/view/prefs/keybindingpage.cpp | 25 +++++++---------------- apps/opencs/view/prefs/page.cpp | 4 ---- 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index f95ceb896f..15f8cacef1 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -40,7 +40,6 @@ void CSMPrefs::State::declare() .setTooltip( "If a newly open top level window is showing status bars or not. " " Note that this does not affect existing windows."); - declareSeparator(); declareBool("reuse", "Reuse Subviews", true) .setTooltip( "When a new subview is requested and a matching subview already " @@ -58,7 +57,6 @@ void CSMPrefs::State::declare() declareInt("minimum-width", "Minimum subview width", 325) .setTooltip("Minimum width of subviews.") .setRange(50, 10000); - declareSeparator(); EnumValue scrollbarOnly("Scrollbar Only", "Simple addition of scrollbars, the view window " "does not grow automatically."); @@ -98,7 +96,6 @@ void CSMPrefs::State::declare() declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues); declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues); declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues); - declareSeparator(); EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection"); declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect) .addValue(jumpAndSelect) @@ -161,7 +158,6 @@ void CSMPrefs::State::declare() declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000); declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); - declareSeparator(); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); declareColour("colour-name", "Highlight Colour: Names", QColor("grey")); @@ -180,14 +176,12 @@ void CSMPrefs::State::declare() declareDouble("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); - declareSeparator(); declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); declareBool("p-navi-free-invert", "Invert Free Camera Mouse Input", false); declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); - declareSeparator(); declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); declareBool("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); @@ -195,7 +189,6 @@ void CSMPrefs::State::declare() declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) .setRange(0.001, 1000.0); declareBool("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); - declareSeparator(); declareBool("context-select", "Context Sensitive Selection", false); declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); @@ -611,16 +604,6 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k return *setting; } -void CSMPrefs::State::declareSeparator() -{ - if (mCurrentCategory == mCategories.end()) - throw std::logic_error("no category for setting"); - - CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", ""); - - mCurrentCategory->second.addSetting(setting); -} - void CSMPrefs::State::declareSubcategory(const QString& label) { if (mCurrentCategory == mCategories.end()) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index c74f317c94..86d2c19da6 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -75,8 +75,6 @@ namespace CSMPrefs ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); - void declareSeparator(); - void declareSubcategory(const QString& label); void setDefault(const std::string& key, const std::string& default_); diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index 7b6337a6ab..60170be283 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -80,27 +80,16 @@ namespace CSVPrefs int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.second, next, 0, 1, 2); } - else + else if (!setting->getLabel().isEmpty()) { - if (setting->getLabel().isEmpty()) - { - // Insert empty space - assert(mPageLayout); + // Create new page + QWidget* pageWidget = new QWidget(); + mPageLayout = new QGridLayout(pageWidget); + mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); - int next = mPageLayout->rowCount(); - mPageLayout->addWidget(new QWidget(), next, 0); - } - else - { - // Create new page - QWidget* pageWidget = new QWidget(); - mPageLayout = new QGridLayout(pageWidget); - mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + mStackedLayout->addWidget(pageWidget); - mStackedLayout->addWidget(pageWidget); - - mPageSelector->addItem(setting->getLabel()); - } + mPageSelector->addItem(setting->getLabel()); } } diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp index 4f04a39f00..fc70adf482 100644 --- a/apps/opencs/view/prefs/page.cpp +++ b/apps/opencs/view/prefs/page.cpp @@ -37,8 +37,4 @@ void CSVPrefs::Page::addSetting(CSMPrefs::Setting* setting) { mGrid->addWidget(widgets.second, next, 0, 1, 2); } - else - { - mGrid->addWidget(new QWidget(this), next, 0); - } } From e07d8f30662512b5f376bfdaaee2040be83ce112 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:28:10 +0100 Subject: [PATCH 0486/2167] Add separate setting type for subcategory --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/prefs/boolsetting.cpp | 4 ++-- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 4 ++-- apps/opencs/model/prefs/coloursetting.hpp | 2 +- apps/opencs/model/prefs/doublesetting.cpp | 4 ++-- apps/opencs/model/prefs/doublesetting.hpp | 2 +- apps/opencs/model/prefs/enumsetting.cpp | 4 ++-- apps/opencs/model/prefs/enumsetting.hpp | 2 +- apps/opencs/model/prefs/intsetting.cpp | 4 ++-- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 4 ++-- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 7 ------ apps/opencs/model/prefs/setting.hpp | 17 +++++++++----- apps/opencs/model/prefs/shortcutsetting.cpp | 4 ++-- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 5 ++-- apps/opencs/model/prefs/stringsetting.cpp | 4 ++-- apps/opencs/model/prefs/stringsetting.hpp | 2 +- apps/opencs/model/prefs/subcategory.cpp | 21 +++++++++++++++++ apps/opencs/model/prefs/subcategory.hpp | 26 +++++++++++++++++++++ apps/opencs/view/prefs/keybindingpage.cpp | 19 ++++++++------- apps/opencs/view/prefs/page.cpp | 13 ++++++----- 24 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 apps/opencs/model/prefs/subcategory.cpp create mode 100644 apps/opencs/model/prefs/subcategory.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 20bd62d145..b040980529 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -116,7 +116,7 @@ opencs_units (view/prefs opencs_units (model/prefs state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut - shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting + shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting subcategory ) opencs_units (model/prefs diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c2718cd7aa..c1eb626969 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -24,7 +24,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool return *this; } -std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { mWidget = new QCheckBox(getLabel(), parent); mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); @@ -37,7 +37,7 @@ std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged); - return std::make_pair(static_cast(nullptr), mWidget); + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::BoolSetting::updateWidget() diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index 60d51a9888..9d53f98e9e 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -26,7 +26,7 @@ namespace CSMPrefs BoolSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index b3f1b70513..e2ece04722 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -27,7 +27,7 @@ CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -42,7 +42,7 @@ std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* pare connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::ColourSetting::updateWidget() diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index e36ceb48b5..5a1a7a2df2 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -34,7 +34,7 @@ namespace CSMPrefs ColourSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index d88678a8e9..153298ce57 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -56,7 +56,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -74,7 +74,7 @@ std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* pare connect(mWidget, qOverload(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::DoubleSetting::updateWidget() diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 76135c4cdb..33342d2f5b 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -35,7 +35,7 @@ namespace CSMPrefs DoubleSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 6d925d1b31..d55e4005a4 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -76,7 +76,7 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, return *this; } -std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -105,7 +105,7 @@ std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent connect(mWidget, qOverload(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::EnumSetting::updateWidget() diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 82ce209922..f430988aa6 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -56,7 +56,7 @@ namespace CSMPrefs EnumSetting& addValue(const std::string& value, const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index e8fd4acce6..7d0b40a45a 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -49,7 +49,7 @@ CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& toolti return *this; } -std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -66,7 +66,7 @@ std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QSpinBox::valueChanged), this, &IntSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::IntSetting::updateWidget() diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 48f24e5b78..8fb3bdb1f6 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -35,7 +35,7 @@ namespace CSMPrefs IntSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index d20a427217..173092dc2b 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -26,7 +26,7 @@ namespace CSMPrefs { } - std::pair ModifierSetting::makeWidgets(QWidget* parent) + SettingWidgets ModifierSetting::makeWidgets(QWidget* parent) { int modifier = 0; State::get().getShortcutManager().getModifier(getKey(), modifier); @@ -46,7 +46,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled); - return std::make_pair(label, widget); + return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; } void ModifierSetting::updateWidget() diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index fb186329a9..9e308875fd 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs public: ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index d507236cc5..666b4ba1b1 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -23,13 +23,6 @@ CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& k { } -std::pair CSMPrefs::Setting::makeWidgets(QWidget* parent) -{ - return std::pair(0, 0); -} - -void CSMPrefs::Setting::updateWidget() {} - const CSMPrefs::Category* CSMPrefs::Setting::getParent() const { return mParent; diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index 01138c6a14..882add20ca 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -9,11 +9,20 @@ class QWidget; class QColor; class QMutex; +class QGridLayout; +class QLabel; namespace CSMPrefs { class Category; + struct SettingWidgets + { + QLabel* mLabel; + QWidget* mInput; + QGridLayout* mLayout; + }; + class Setting : public QObject { Q_OBJECT @@ -31,16 +40,12 @@ namespace CSMPrefs ~Setting() override = default; - /// Return label, input widget. - /// - /// \note first can be a 0-pointer, which means that the label is part of the input - /// widget. - virtual std::pair makeWidgets(QWidget* parent); + virtual SettingWidgets makeWidgets(QWidget* parent) = 0; /// Updates the widget returned by makeWidgets() to the current setting. /// /// \note If make_widgets() has not been called yet then nothing happens. - virtual void updateWidget(); + virtual void updateWidget() = 0; const Category* getParent() const; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 23c8d9c7a9..aae290b3f4 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -30,7 +30,7 @@ namespace CSMPrefs } } - std::pair ShortcutSetting::makeWidgets(QWidget* parent) + SettingWidgets ShortcutSetting::makeWidgets(QWidget* parent) { QKeySequence sequence; State::get().getShortcutManager().getSequence(getKey(), sequence); @@ -50,7 +50,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled); - return std::make_pair(label, widget); + return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; } void ShortcutSetting::updateWidget() diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 7dd2aecd61..74cad995d4 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -24,7 +24,7 @@ namespace CSMPrefs public: ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 15f8cacef1..21965810e5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -609,9 +610,7 @@ void CSMPrefs::State::declareSubcategory(const QString& label) if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", label); - - mCurrentCategory->second.addSetting(setting); + mCurrentCategory->second.addSetting(new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label)); } void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index bb7a9b7fe3..0cba2d047d 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -25,7 +25,7 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::StringSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent); @@ -37,7 +37,7 @@ std::pair CSMPrefs::StringSetting::makeWidgets(QWidget* pare connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return std::make_pair(static_cast(nullptr), mWidget); + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::StringSetting::updateWidget() diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 81adc55361..5c03a6ea12 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -29,7 +29,7 @@ namespace CSMPrefs StringSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp new file mode 100644 index 0000000000..cca558407c --- /dev/null +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -0,0 +1,21 @@ +#include "subcategory.hpp" + +#include + +namespace CSMPrefs +{ + class Category; + + Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label) + : Setting(parent, mutex, "", label) + { + } + + SettingWidgets Subcategory::makeWidgets(QWidget* /*parent*/) + { + QGridLayout* const layout = new QGridLayout(); + layout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr, .mLayout = layout }; + } +} diff --git a/apps/opencs/model/prefs/subcategory.hpp b/apps/opencs/model/prefs/subcategory.hpp new file mode 100644 index 0000000000..4f62c1743c --- /dev/null +++ b/apps/opencs/model/prefs/subcategory.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H + +#include "setting.hpp" + +#include +#include + +namespace CSMPrefs +{ + class Category; + + class Subcategory final : public Setting + { + Q_OBJECT + + public: + explicit Subcategory(Category* parent, QMutex* mutex, const QString& label); + + SettingWidgets makeWidgets(QWidget* parent) override; + + void updateWidget() override {} + }; +} + +#endif diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index 60170be283..b88627aca1 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -61,31 +62,31 @@ namespace CSVPrefs void KeyBindingPage::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets(this); + const CSMPrefs::SettingWidgets widgets = setting->makeWidgets(this); - if (widgets.first) + if (widgets.mLabel != nullptr && widgets.mInput != nullptr) { // Label, Option widgets assert(mPageLayout); int next = mPageLayout->rowCount(); - mPageLayout->addWidget(widgets.first, next, 0); - mPageLayout->addWidget(widgets.second, next, 1); + mPageLayout->addWidget(widgets.mLabel, next, 0); + mPageLayout->addWidget(widgets.mInput, next, 1); } - else if (widgets.second) + else if (widgets.mInput != nullptr) { // Wide single widget assert(mPageLayout); int next = mPageLayout->rowCount(); - mPageLayout->addWidget(widgets.second, next, 0, 1, 2); + mPageLayout->addWidget(widgets.mInput, next, 0, 1, 2); } - else if (!setting->getLabel().isEmpty()) + else if (widgets.mLayout != nullptr) { // Create new page QWidget* pageWidget = new QWidget(); - mPageLayout = new QGridLayout(pageWidget); - mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + mPageLayout = widgets.mLayout; + mPageLayout->setParent(pageWidget); mStackedLayout->addWidget(pageWidget); diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp index fc70adf482..cc74122782 100644 --- a/apps/opencs/view/prefs/page.cpp +++ b/apps/opencs/view/prefs/page.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "../../model/prefs/category.hpp" #include "../../model/prefs/setting.hpp" @@ -24,17 +25,17 @@ CSVPrefs::Page::Page(CSMPrefs::Category& category, QWidget* parent) void CSVPrefs::Page::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets(this); + const CSMPrefs::SettingWidgets widgets = setting->makeWidgets(this); int next = mGrid->rowCount(); - if (widgets.first) + if (widgets.mLabel != nullptr && widgets.mInput != nullptr) { - mGrid->addWidget(widgets.first, next, 0); - mGrid->addWidget(widgets.second, next, 1); + mGrid->addWidget(widgets.mLabel, next, 0); + mGrid->addWidget(widgets.mInput, next, 1); } - else if (widgets.second) + else if (widgets.mInput != nullptr) { - mGrid->addWidget(widgets.second, next, 0, 1, 2); + mGrid->addWidget(widgets.mInput, next, 0, 1, 2); } } From 755fef62f73cc9d2aa7570b44fa0f40bf193f812 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 00:42:58 +0100 Subject: [PATCH 0487/2167] Mark State copy constructor and assignment operators as delete --- apps/opencs/model/prefs/state.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 86d2c19da6..6119fbda1a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -51,11 +51,6 @@ namespace CSMPrefs Iterator mCurrentCategory; QMutex mMutex; - // not implemented - State(const State&); - State& operator=(const State&); - - private: void declare(); void declareCategory(const std::string& key); @@ -82,8 +77,12 @@ namespace CSMPrefs public: State(const Files::ConfigurationManager& configurationManager); + State(const State&) = delete; + ~State(); + State& operator=(const State&) = delete; + void save(); Iterator begin(); From fb0b95a2dd3891b98a7d880d378690fb601a4d1b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 11 Nov 2023 00:53:27 +0100 Subject: [PATCH 0488/2167] Define editor settings as typed struct members --- apps/opencs/model/prefs/state.cpp | 6 + apps/opencs/model/prefs/state.hpp | 8 + apps/opencs/model/prefs/values.hpp | 532 +++++++++++++++++++++++++++ components/settings/settingvalue.hpp | 9 + 4 files changed, 555 insertions(+) create mode 100644 apps/opencs/model/prefs/values.hpp diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 21965810e5..d74044f6d5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -25,6 +25,7 @@ #include "modifiersetting.hpp" #include "shortcutsetting.hpp" #include "stringsetting.hpp" +#include "values.hpp" CSMPrefs::State* CSMPrefs::State::sThis = nullptr; @@ -628,13 +629,18 @@ CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager) , mDefaultConfigFile("defaults-cs.bin") , mConfigurationManager(configurationManager) , mCurrentCategory(mCategories.end()) + , mIndex(std::make_unique()) { if (sThis) throw std::logic_error("An instance of CSMPRefs::State already exists"); sThis = this; + Values values(*mIndex); + declare(); + + mValues = std::make_unique(std::move(values)); } CSMPrefs::State::~State() diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 6119fbda1a..e398f06e4a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -17,6 +17,11 @@ class QColor; +namespace Settings +{ + class Index; +} + namespace CSMPrefs { class IntSetting; @@ -27,6 +32,7 @@ namespace CSMPrefs class ModifierSetting; class Setting; class StringSetting; + struct Values; /// \brief User settings state /// @@ -50,6 +56,8 @@ namespace CSMPrefs Collection mCategories; Iterator mCurrentCategory; QMutex mMutex; + std::unique_ptr mIndex; + std::unique_ptr mValues; void declare(); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp new file mode 100644 index 0000000000..247c025e80 --- /dev/null +++ b/apps/opencs/model/prefs/values.hpp @@ -0,0 +1,532 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace CSMPrefs +{ + struct EnumValueView + { + std::string_view mValue; + std::string_view mTooltip; + }; + + class EnumSanitizer final : public Settings::Sanitizer + { + public: + explicit EnumSanitizer(std::span values) + : mValues(values) + { + } + + std::string apply(const std::string& value) const override + { + const auto hasValue = [&](const EnumValueView& v) { return v.mValue == value; }; + if (std::find_if(mValues.begin(), mValues.end(), hasValue) == mValues.end()) + { + std::ostringstream message; + message << "Invalid enum value: " << value; + throw std::runtime_error(message.str()); + } + return value; + } + + private: + std::span mValues; + }; + + inline std::unique_ptr> makeEnumSanitizerString( + std::span values) + { + return std::make_unique(values); + } + + struct WindowsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Windows"; + + static constexpr std::array sMainwindowScrollbarValues{ + EnumValueView{ + "Scrollbar Only", "Simple addition of scrollbars, the view window does not grow automatically." }, + EnumValueView{ "Grow Only", "The view window grows as subviews are added. No scrollbars." }, + EnumValueView{ + "Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further." }, + }; + + Settings::SettingValue mDefaultWidth{ mIndex, sName, "default-width", 800 }; + Settings::SettingValue mDefaultHeight{ mIndex, sName, "default-height", 600 }; + Settings::SettingValue mShowStatusbar{ mIndex, sName, "show-statusbar", true }; + Settings::SettingValue mReuse{ mIndex, sName, "reuse", true }; + Settings::SettingValue mMaxSubviews{ mIndex, sName, "max-subviews", 256 }; + Settings::SettingValue mHideSubview{ mIndex, sName, "hide-subview", false }; + Settings::SettingValue mMinimumWidth{ mIndex, sName, "minimum-width", 325 }; + Settings::SettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", + std::string(sMainwindowScrollbarValues[0].mValue), makeEnumSanitizerString(sMainwindowScrollbarValues) }; + Settings::SettingValue mGrowLimit{ mIndex, sName, "grow-limit", false }; + }; + + struct RecordsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Records"; + + static constexpr std::array sRecordValues{ + EnumValueView{ "Icon and Text", "" }, + EnumValueView{ "Icon Only", "" }, + EnumValueView{ "Text Only", "" }, + }; + + Settings::SettingValue mStatusFormat{ mIndex, sName, "status-format", + std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + Settings::SettingValue mTypeFormat{ mIndex, sName, "type-format", + std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + }; + + struct IdTablesCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "ID Tables"; + + static constexpr std::array sDoubleClickValues{ + EnumValueView{ "Edit in Place", "Edit the clicked cell" }, + EnumValueView{ "Edit Record", "Open a dialogue subview for the clicked record" }, + EnumValueView{ "View", "Open a scene subview for the clicked record (not available everywhere)" }, + EnumValueView{ "Revert", "" }, + EnumValueView{ "Delete", "" }, + EnumValueView{ "Edit Record and Close", "" }, + EnumValueView{ + "View and Close", "Open a scene subview for the clicked record and close the table subview" }, + }; + + static constexpr std::array sJumpAndSelectValues{ + EnumValueView{ "Jump and Select", "Scroll new record into view and make it the selection" }, + EnumValueView{ "Jump Only", "Scroll new record into view" }, + EnumValueView{ "No Jump", "No special action" }, + }; + + Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sDoubleClickValues[0].mValue), + makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", + std::string(sDoubleClickValues[1].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", + std::string(sDoubleClickValues[2].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", + std::string(sDoubleClickValues[5].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", + std::string(sJumpAndSelectValues[0].mValue), makeEnumSanitizerString(sJumpAndSelectValues) }; + Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; + Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; + }; + + struct IdDialoguesCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "ID Dialogues"; + + Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; + }; + + struct ReportsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Reports"; + + static constexpr std::array sReportValues{ + EnumValueView{ "None", "" }, + EnumValueView{ "Edit", "Open a table or dialogue suitable for addressing the listed report" }, + EnumValueView{ "Remove", "Remove the report from the report table" }, + EnumValueView{ "Edit And Remove", + "Open a table or dialogue suitable for addressing the listed report, then remove the report from the " + "report table" }, + }; + + Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sReportValues[1].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", std::string(sReportValues[2].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", std::string(sReportValues[3].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", std::string(sReportValues[0].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mIgnoreBaseRecords{ mIndex, sName, "ignore-base-records", false }; + }; + + struct SearchAndReplaceCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Search & Replace"; + + Settings::SettingValue mCharBefore{ mIndex, sName, "char-before", 10 }; + Settings::SettingValue mCharAfter{ mIndex, sName, "char-after", 10 }; + Settings::SettingValue mAutoDelete{ mIndex, sName, "auto-delete", true }; + }; + + struct ScriptsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Scripts"; + + static constexpr std::array sWarningValues{ + EnumValueView{ "Ignore", "Do not report warning" }, + EnumValueView{ "Normal", "Report warnings as warning" }, + EnumValueView{ "Strict", "Promote warning to an error" }, + }; + + Settings::SettingValue mShowLinenum{ mIndex, sName, "show-linenum", true }; + Settings::SettingValue mWrapLines{ mIndex, sName, "wrap-lines", false }; + Settings::SettingValue mMonoFont{ mIndex, sName, "mono-font", true }; + Settings::SettingValue mTabWidth{ mIndex, sName, "tab-width", 4 }; + Settings::SettingValue mWarnings{ mIndex, sName, "warnings", std::string(sWarningValues[1].mValue), + makeEnumSanitizerString(sWarningValues) }; + Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; + Settings::SettingValue mCompileDelay{ mIndex, sName, "compile-delay", 100 }; + Settings::SettingValue mErrorHeight{ mIndex, sName, "error-height", 100 }; + Settings::SettingValue mHighlightOccurrences{ mIndex, sName, "highlight-occurrences", true }; + Settings::SettingValue mColourHighlight{ mIndex, sName, "colour-highlight", "lightcyan" }; + Settings::SettingValue mColourInt{ mIndex, sName, "colour-int", "darkmagenta" }; + Settings::SettingValue mColourFloat{ mIndex, sName, "colour-float", "magenta" }; + Settings::SettingValue mColourName{ mIndex, sName, "colour-name", "grey" }; + Settings::SettingValue mColourKeyword{ mIndex, sName, "colour-keyword", "red" }; + Settings::SettingValue mColourSpecial{ mIndex, sName, "colour-special", "darkorange" }; + Settings::SettingValue mColourComment{ mIndex, sName, "colour-comment", "green" }; + Settings::SettingValue mColourId{ mIndex, sName, "colour-id", "blue" }; + }; + + struct GeneralInputCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "General Input"; + + Settings::SettingValue mCycle{ mIndex, sName, "cycle", false }; + }; + + struct SceneInputCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "3D Scene Input"; + + Settings::SettingValue mNaviWheelFactor{ mIndex, sName, "navi-wheel-factor", 8 }; + Settings::SettingValue mSNaviSensitivity{ mIndex, sName, "s-navi-sensitivity", 50 }; + Settings::SettingValue mPNaviFreeSensitivity{ mIndex, sName, "p-navi-free-sensitivity", 1 / 650.0 }; + Settings::SettingValue mPNaviFreeInvert{ mIndex, sName, "p-navi-free-invert", false }; + Settings::SettingValue mNaviFreeLinSpeed{ mIndex, sName, "navi-free-lin-speed", 1000 }; + Settings::SettingValue mNaviFreeRotSpeed{ mIndex, sName, "navi-free-rot-speed", 3.14 / 2 }; + Settings::SettingValue mNaviFreeSpeedMult{ mIndex, sName, "navi-free-speed-mult", 8 }; + Settings::SettingValue mPNaviOrbitSensitivity{ mIndex, sName, "p-navi-orbit-sensitivity", 1 / 650.0 }; + Settings::SettingValue mPNaviOrbitInvert{ mIndex, sName, "p-navi-orbit-invert", false }; + Settings::SettingValue mNaviOrbitRotSpeed{ mIndex, sName, "navi-orbit-rot-speed", 3.14 / 4 }; + Settings::SettingValue mNaviOrbitSpeedMult{ mIndex, sName, "navi-orbit-speed-mult", 4 }; + Settings::SettingValue mNaviOrbitConstRoll{ mIndex, sName, "navi-orbit-const-roll", true }; + Settings::SettingValue mContextSelect{ mIndex, sName, "context-select", false }; + Settings::SettingValue mDragFactor{ mIndex, sName, "drag-factor", 1 }; + Settings::SettingValue mDragWheelFactor{ mIndex, sName, "drag-wheel-factor", 1 }; + Settings::SettingValue mDragShiftFactor{ mIndex, sName, "drag-shift-factor", 4 }; + Settings::SettingValue mRotateFactor{ mIndex, sName, "rotate-factor", 0.007 }; + }; + + struct RenderingCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Rendering"; + + Settings::SettingValue mFramerateLimit{ mIndex, sName, "framerate-limit", 60 }; + Settings::SettingValue mCameraFov{ mIndex, sName, "camera-fov", 90 }; + Settings::SettingValue mCameraOrtho{ mIndex, sName, "camera-ortho", false }; + Settings::SettingValue mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 }; + Settings::SettingValue mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 }; + Settings::SettingValue mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true }; + Settings::SettingValue mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour", + "#6e7880" }; + Settings::SettingValue mSceneDayGradientColour{ mIndex, sName, "scene-day-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneBrightBackgroundColour{ mIndex, sName, + "scene-bright-background-colour", "#4f575c" }; + Settings::SettingValue mSceneBrightGradientColour{ mIndex, sName, "scene-bright-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneNightBackgroundColour{ mIndex, sName, "scene-night-background-colour", + "#404d4f" }; + Settings::SettingValue mSceneNightGradientColour{ mIndex, sName, "scene-night-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneDayNightSwitchNodes{ mIndex, sName, "scene-day-night-switch-nodes", true }; + }; + + struct TooltipsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Tooltips"; + + Settings::SettingValue mScene{ mIndex, sName, "scene", true }; + Settings::SettingValue mSceneHideBasic{ mIndex, sName, "scene-hide-basic", false }; + Settings::SettingValue mSceneDelay{ mIndex, sName, "scene-delay", 500 }; + }; + + struct SceneEditingCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "3D Scene Editing"; + + static constexpr std::array sInsertOutsideCellValues{ + EnumValueView{ "Create cell and insert", "" }, + EnumValueView{ "Discard", "" }, + EnumValueView{ "Insert anyway", "" }, + }; + + static constexpr std::array sInsertOutsideVisibleCellValues{ + EnumValueView{ "Show cell and insert", "" }, + EnumValueView{ "Discard", "" }, + EnumValueView{ "Insert anyway", "" }, + }; + + static constexpr std::array sLandEditOutsideCellValues{ + EnumValueView{ "Create cell and land, then edit", "" }, + EnumValueView{ "Discard", "" }, + }; + + static constexpr std::array sLandEditOutsideVisibleCellValues{ + EnumValueView{ "Show cell and edit", "" }, + EnumValueView{ "Discard", "" }, + }; + + static constexpr std::array sPrimarySelectAction{ + EnumValueView{ "Select only", "" }, + EnumValueView{ "Add to selection", "" }, + EnumValueView{ "Remove from selection", "" }, + EnumValueView{ "Invert selection", "" }, + }; + + static constexpr std::array sSecondarySelectAction{ + EnumValueView{ "Select only", "" }, + EnumValueView{ "Add to selection", "" }, + EnumValueView{ "Remove from selection", "" }, + EnumValueView{ "Invert selection", "" }, + }; + + Settings::SettingValue mGridsnapMovement{ mIndex, sName, "gridsnap-movement", 16 }; + Settings::SettingValue mGridsnapRotation{ mIndex, sName, "gridsnap-rotation", 15 }; + Settings::SettingValue mGridsnapScale{ mIndex, sName, "gridsnap-scale", 0.25 }; + Settings::SettingValue mDistance{ mIndex, sName, "distance", 50 }; + Settings::SettingValue mOutsideDrop{ mIndex, sName, "outside-drop", + std::string(sInsertOutsideCellValues[0].mValue), makeEnumSanitizerString(sInsertOutsideCellValues) }; + Settings::SettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", + std::string(sInsertOutsideVisibleCellValues[0].mValue), + makeEnumSanitizerString(sInsertOutsideVisibleCellValues) }; + Settings::SettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", + std::string(sLandEditOutsideCellValues[0].mValue), makeEnumSanitizerString(sLandEditOutsideCellValues) }; + Settings::SettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", + std::string(sLandEditOutsideVisibleCellValues[0].mValue), + makeEnumSanitizerString(sLandEditOutsideVisibleCellValues) }; + Settings::SettingValue mTexturebrushMaximumsize{ mIndex, sName, "texturebrush-maximumsize", 50 }; + Settings::SettingValue mShapebrushMaximumsize{ mIndex, sName, "shapebrush-maximumsize", 100 }; + Settings::SettingValue mLandeditPostSmoothpainting{ mIndex, sName, "landedit-post-smoothpainting", + false }; + Settings::SettingValue mLandeditPostSmoothstrength{ mIndex, sName, "landedit-post-smoothstrength", + 0.25 }; + Settings::SettingValue mOpenListView{ mIndex, sName, "open-list-view", false }; + Settings::SettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", + std::string(sPrimarySelectAction[0].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + Settings::SettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", + std::string(sPrimarySelectAction[1].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + }; + + struct KeyBindingsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Key Bindings"; + + Settings::SettingValue mDocumentFileNewgame{ mIndex, sName, "document-file-newgame", "Ctrl+N" }; + Settings::SettingValue mDocumentFileNewaddon{ mIndex, sName, "document-file-newaddon", "" }; + Settings::SettingValue mDocumentFileOpen{ mIndex, sName, "document-file-open", "Ctrl+O" }; + Settings::SettingValue mDocumentFileSave{ mIndex, sName, "document-file-save", "Ctrl+S" }; + Settings::SettingValue mDocumentHelpHelp{ mIndex, sName, "document-help-help", "F1" }; + Settings::SettingValue mDocumentHelpTutorial{ mIndex, sName, "document-help-tutorial", "" }; + Settings::SettingValue mDocumentFileVerify{ mIndex, sName, "document-file-verify", "" }; + Settings::SettingValue mDocumentFileMerge{ mIndex, sName, "document-file-merge", "" }; + Settings::SettingValue mDocumentFileErrorlog{ mIndex, sName, "document-file-errorlog", "" }; + Settings::SettingValue mDocumentFileMetadata{ mIndex, sName, "document-file-metadata", "" }; + Settings::SettingValue mDocumentFileClose{ mIndex, sName, "document-file-close", "Ctrl+W" }; + Settings::SettingValue mDocumentFileExit{ mIndex, sName, "document-file-exit", "Ctrl+Q" }; + Settings::SettingValue mDocumentEditUndo{ mIndex, sName, "document-edit-undo", "Ctrl+Z" }; + Settings::SettingValue mDocumentEditRedo{ mIndex, sName, "document-edit-redo", "Ctrl+Shift+Z" }; + Settings::SettingValue mDocumentEditPreferences{ mIndex, sName, "document-edit-preferences", "" }; + Settings::SettingValue mDocumentEditSearch{ mIndex, sName, "document-edit-search", "Ctrl+F" }; + Settings::SettingValue mDocumentViewNewview{ mIndex, sName, "document-view-newview", "" }; + Settings::SettingValue mDocumentViewStatusbar{ mIndex, sName, "document-view-statusbar", "" }; + Settings::SettingValue mDocumentViewFilters{ mIndex, sName, "document-view-filters", "" }; + Settings::SettingValue mDocumentWorldRegions{ mIndex, sName, "document-world-regions", "" }; + Settings::SettingValue mDocumentWorldCells{ mIndex, sName, "document-world-cells", "" }; + Settings::SettingValue mDocumentWorldReferencables{ mIndex, sName, "document-world-referencables", + "" }; + Settings::SettingValue mDocumentWorldReferences{ mIndex, sName, "document-world-references", "" }; + Settings::SettingValue mDocumentWorldLands{ mIndex, sName, "document-world-lands", "" }; + Settings::SettingValue mDocumentWorldLandtextures{ mIndex, sName, "document-world-landtextures", + "" }; + Settings::SettingValue mDocumentWorldPathgrid{ mIndex, sName, "document-world-pathgrid", "" }; + Settings::SettingValue mDocumentWorldRegionmap{ mIndex, sName, "document-world-regionmap", "" }; + Settings::SettingValue mDocumentMechanicsGlobals{ mIndex, sName, "document-mechanics-globals", + "" }; + Settings::SettingValue mDocumentMechanicsGamesettings{ mIndex, sName, + "document-mechanics-gamesettings", "" }; + Settings::SettingValue mDocumentMechanicsScripts{ mIndex, sName, "document-mechanics-scripts", + "" }; + Settings::SettingValue mDocumentMechanicsSpells{ mIndex, sName, "document-mechanics-spells", "" }; + Settings::SettingValue mDocumentMechanicsEnchantments{ mIndex, sName, + "document-mechanics-enchantments", "" }; + Settings::SettingValue mDocumentMechanicsMagiceffects{ mIndex, sName, + "document-mechanics-magiceffects", "" }; + Settings::SettingValue mDocumentMechanicsStartscripts{ mIndex, sName, + "document-mechanics-startscripts", "" }; + Settings::SettingValue mDocumentCharacterSkills{ mIndex, sName, "document-character-skills", "" }; + Settings::SettingValue mDocumentCharacterClasses{ mIndex, sName, "document-character-classes", + "" }; + Settings::SettingValue mDocumentCharacterFactions{ mIndex, sName, "document-character-factions", + "" }; + Settings::SettingValue mDocumentCharacterRaces{ mIndex, sName, "document-character-races", "" }; + Settings::SettingValue mDocumentCharacterBirthsigns{ mIndex, sName, + "document-character-birthsigns", "" }; + Settings::SettingValue mDocumentCharacterTopics{ mIndex, sName, "document-character-topics", "" }; + Settings::SettingValue mDocumentCharacterJournals{ mIndex, sName, "document-character-journals", + "" }; + Settings::SettingValue mDocumentCharacterTopicinfos{ mIndex, sName, + "document-character-topicinfos", "" }; + Settings::SettingValue mDocumentCharacterJournalinfos{ mIndex, sName, + "document-character-journalinfos", "" }; + Settings::SettingValue mDocumentCharacterBodyparts{ mIndex, sName, "document-character-bodyparts", + "" }; + Settings::SettingValue mDocumentAssetsReload{ mIndex, sName, "document-assets-reload", "F5" }; + Settings::SettingValue mDocumentAssetsSounds{ mIndex, sName, "document-assets-sounds", "" }; + Settings::SettingValue mDocumentAssetsSoundgens{ mIndex, sName, "document-assets-soundgens", "" }; + Settings::SettingValue mDocumentAssetsMeshes{ mIndex, sName, "document-assets-meshes", "" }; + Settings::SettingValue mDocumentAssetsIcons{ mIndex, sName, "document-assets-icons", "" }; + Settings::SettingValue mDocumentAssetsMusic{ mIndex, sName, "document-assets-music", "" }; + Settings::SettingValue mDocumentAssetsSoundres{ mIndex, sName, "document-assets-soundres", "" }; + Settings::SettingValue mDocumentAssetsTextures{ mIndex, sName, "document-assets-textures", "" }; + Settings::SettingValue mDocumentAssetsVideos{ mIndex, sName, "document-assets-videos", "" }; + Settings::SettingValue mDocumentDebugRun{ mIndex, sName, "document-debug-run", "" }; + Settings::SettingValue mDocumentDebugShutdown{ mIndex, sName, "document-debug-shutdown", "" }; + Settings::SettingValue mDocumentDebugProfiles{ mIndex, sName, "document-debug-profiles", "" }; + Settings::SettingValue mDocumentDebugRunlog{ mIndex, sName, "document-debug-runlog", "" }; + Settings::SettingValue mTableEdit{ mIndex, sName, "table-edit", "" }; + Settings::SettingValue mTableAdd{ mIndex, sName, "table-add", "Shift+A" }; + Settings::SettingValue mTableClone{ mIndex, sName, "table-clone", "Shift+D" }; + Settings::SettingValue mTouchRecord{ mIndex, sName, "touch-record", "" }; + Settings::SettingValue mTableRevert{ mIndex, sName, "table-revert", "" }; + Settings::SettingValue mTableRemove{ mIndex, sName, "table-remove", "Delete" }; + Settings::SettingValue mTableMoveup{ mIndex, sName, "table-moveup", "" }; + Settings::SettingValue mTableMovedown{ mIndex, sName, "table-movedown", "" }; + Settings::SettingValue mTableView{ mIndex, sName, "table-view", "Shift+C" }; + Settings::SettingValue mTablePreview{ mIndex, sName, "table-preview", "Shift+V" }; + Settings::SettingValue mTableExtendeddelete{ mIndex, sName, "table-extendeddelete", "" }; + Settings::SettingValue mTableExtendedrevert{ mIndex, sName, "table-extendedrevert", "" }; + Settings::SettingValue mReporttableShow{ mIndex, sName, "reporttable-show", "" }; + Settings::SettingValue mReporttableRemove{ mIndex, sName, "reporttable-remove", "Delete" }; + Settings::SettingValue mReporttableReplace{ mIndex, sName, "reporttable-replace", "" }; + Settings::SettingValue mReporttableRefresh{ mIndex, sName, "reporttable-refresh", "" }; + Settings::SettingValue mSceneNaviPrimary{ mIndex, sName, "scene-navi-primary", "LMB" }; + Settings::SettingValue mSceneNaviSecondary{ mIndex, sName, "scene-navi-secondary", "Ctrl+LMB" }; + Settings::SettingValue mSceneOpenPrimary{ mIndex, sName, "scene-open-primary", "Shift+LMB" }; + Settings::SettingValue mSceneEditPrimary{ mIndex, sName, "scene-edit-primary", "RMB" }; + Settings::SettingValue mSceneEditSecondary{ mIndex, sName, "scene-edit-secondary", "Ctrl+RMB" }; + Settings::SettingValue mSceneSelectPrimary{ mIndex, sName, "scene-select-primary", "MMB" }; + Settings::SettingValue mSceneSelectSecondary{ mIndex, sName, "scene-select-secondary", + "Ctrl+MMB" }; + Settings::SettingValue mSceneSelectTertiary{ mIndex, sName, "scene-select-tertiary", "Shift+MMB" }; + Settings::SettingValue mSceneSpeedModifier{ mIndex, sName, "scene-speed-modifier", "Shift" }; + Settings::SettingValue mSceneDelete{ mIndex, sName, "scene-delete", "Delete" }; + Settings::SettingValue mSceneInstanceDropTerrain{ mIndex, sName, "scene-instance-drop-terrain", + "G" }; + Settings::SettingValue mSceneInstanceDropCollision{ mIndex, sName, "scene-instance-drop-collision", + "H" }; + Settings::SettingValue mSceneInstanceDropTerrainSeparately{ mIndex, sName, + "scene-instance-drop-terrain-separately", "" }; + Settings::SettingValue mSceneInstanceDropCollisionSeparately{ mIndex, sName, + "scene-instance-drop-collision-separately", "" }; + Settings::SettingValue mSceneLoadCamCell{ mIndex, sName, "scene-load-cam-cell", "Keypad+5" }; + Settings::SettingValue mSceneLoadCamEastcell{ mIndex, sName, "scene-load-cam-eastcell", + "Keypad+6" }; + Settings::SettingValue mSceneLoadCamNorthcell{ mIndex, sName, "scene-load-cam-northcell", + "Keypad+8" }; + Settings::SettingValue mSceneLoadCamWestcell{ mIndex, sName, "scene-load-cam-westcell", + "Keypad+4" }; + Settings::SettingValue mSceneLoadCamSouthcell{ mIndex, sName, "scene-load-cam-southcell", + "Keypad+2" }; + Settings::SettingValue mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" }; + Settings::SettingValue mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" }; + Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; + Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; + Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; + Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; + Settings::SettingValue mFreeRight{ mIndex, sName, "free-right", "D" }; + Settings::SettingValue mFreeRollLeft{ mIndex, sName, "free-roll-left", "Q" }; + Settings::SettingValue mFreeRollRight{ mIndex, sName, "free-roll-right", "E" }; + Settings::SettingValue mFreeSpeedMode{ mIndex, sName, "free-speed-mode", "F" }; + Settings::SettingValue mOrbitUp{ mIndex, sName, "orbit-up", "W" }; + Settings::SettingValue mOrbitDown{ mIndex, sName, "orbit-down", "S" }; + Settings::SettingValue mOrbitLeft{ mIndex, sName, "orbit-left", "A" }; + Settings::SettingValue mOrbitRight{ mIndex, sName, "orbit-right", "D" }; + Settings::SettingValue mOrbitRollLeft{ mIndex, sName, "orbit-roll-left", "Q" }; + Settings::SettingValue mOrbitRollRight{ mIndex, sName, "orbit-roll-right", "E" }; + Settings::SettingValue mOrbitSpeedMode{ mIndex, sName, "orbit-speed-mode", "F" }; + Settings::SettingValue mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" }; + Settings::SettingValue mScriptEditorComment{ mIndex, sName, "script-editor-comment", "" }; + Settings::SettingValue mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment", "" }; + }; + + struct ModelsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Models"; + + Settings::SettingValue mBaseanim{ mIndex, sName, "baseanim", "meshes/base_anim.nif" }; + Settings::SettingValue mBaseanimkna{ mIndex, sName, "baseanimkna", "meshes/base_animkna.nif" }; + Settings::SettingValue mBaseanimfemale{ mIndex, sName, "baseanimfemale", + "meshes/base_anim_female.nif" }; + Settings::SettingValue mWolfskin{ mIndex, sName, "wolfskin", "meshes/wolf/skin.nif" }; + }; + + struct Values : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + WindowsCategory mWindows{ mIndex }; + RecordsCategory mRecords{ mIndex }; + IdTablesCategory mIdTables{ mIndex }; + IdDialoguesCategory mIdDialogues{ mIndex }; + ReportsCategory mReports{ mIndex }; + SearchAndReplaceCategory mSearchAndReplace{ mIndex }; + ScriptsCategory mScripts{ mIndex }; + GeneralInputCategory mGeneralInput{ mIndex }; + SceneInputCategory mSceneInput{ mIndex }; + RenderingCategory mRendering{ mIndex }; + TooltipsCategory mTooltips{ mIndex }; + SceneEditingCategory mSceneEditing{ mIndex }; + KeyBindingsCategory mKeyBindings{ mIndex }; + ModelsCategory mModels{ mIndex }; + }; +} + +#endif diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 2f239a4ebd..392e5b646f 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -335,6 +335,15 @@ namespace Settings { } + explicit SettingValue(Index& index, std::string_view category, std::string_view name, T&& defaultValue, + std::unique_ptr>&& sanitizer = nullptr) + : BaseSettingValue(getSettingValueType(), category, name, index) + , mSanitizer(std::move(sanitizer)) + , mDefaultValue(defaultValue) + , mValue(defaultValue) + { + } + SettingValue(SettingValue&& other) : BaseSettingValue(std::move(other)) , mSanitizer(std::move(other.mSanitizer)) From b17afc4641ce3185f682eb7f939ee5ddf1e808ab Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 16 Nov 2023 00:33:59 +0100 Subject: [PATCH 0489/2167] Use concepts for some argument types --- .../detournavigator/navmeshtilescache.cpp | 40 +++++++------------ components/detournavigator/findsmoothpath.hpp | 40 +++++++++---------- components/detournavigator/navigatorutils.hpp | 11 ++--- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 3be1c8762a..746739c856 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace @@ -24,29 +25,25 @@ namespace PreparedNavMeshData mValue; }; - template - osg::Vec2i generateVec2i(int max, Random& random) + osg::Vec2i generateVec2i(int max, auto& random) { std::uniform_int_distribution distribution(0, max); return osg::Vec2i(distribution(random), distribution(random)); } - template - osg::Vec3f generateAgentHalfExtents(float min, float max, Random& random) + osg::Vec3f generateAgentHalfExtents(float min, float max, auto& random) { std::uniform_int_distribution distribution(min, max); return osg::Vec3f(distribution(random), distribution(random), distribution(random)); } - template - void generateVertices(OutputIterator out, std::size_t number, Random& random) + void generateVertices(std::output_iterator auto out, std::size_t number, auto& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, 3 * (number - number % 3), [&] { return distribution(random); }); } - template - void generateIndices(OutputIterator out, int max, std::size_t number, Random& random) + void generateIndices(std::output_iterator auto out, int max, std::size_t number, auto& random) { std::uniform_int_distribution distribution(0, max); std::generate_n(out, number - number % 3, [&] { return distribution(random); }); @@ -70,21 +67,18 @@ namespace return AreaType_null; } - template - AreaType generateAreaType(Random& random) + AreaType generateAreaType(auto& random) { std::uniform_int_distribution distribution(0, 4); return toAreaType(distribution(random)); } - template - void generateAreaTypes(OutputIterator out, std::size_t triangles, Random& random) + void generateAreaTypes(std::output_iterator auto out, std::size_t triangles, auto& random) { std::generate_n(out, triangles, [&] { return generateAreaType(random); }); } - template - void generateWater(OutputIterator out, std::size_t count, Random& random) + void generateWater(std::output_iterator auto out, std::size_t count, auto& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::generate_n(out, count, [&] { @@ -92,8 +86,7 @@ namespace }); } - template - Mesh generateMesh(std::size_t triangles, Random& random) + Mesh generateMesh(std::size_t triangles, auto& random) { std::uniform_real_distribution distribution(0.0, 1.0); std::vector vertices; @@ -109,8 +102,7 @@ namespace return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); } - template - Heightfield generateHeightfield(Random& random) + Heightfield generateHeightfield(auto& random) { std::uniform_real_distribution distribution(0.0, 1.0); Heightfield result; @@ -127,8 +119,7 @@ namespace return result; } - template - FlatHeightfield generateFlatHeightfield(Random& random) + FlatHeightfield generateFlatHeightfield(auto& random) { std::uniform_real_distribution distribution(0.0, 1.0); FlatHeightfield result; @@ -138,8 +129,7 @@ namespace return result; } - template - Key generateKey(std::size_t triangles, Random& random) + Key generateKey(std::size_t triangles, auto& random) { const CollisionShapeType agentShapeType = CollisionShapeType::Aabb; const osg::Vec3f agentHalfExtents = generateAgentHalfExtents(0.5, 1.5, random); @@ -158,14 +148,12 @@ namespace constexpr std::size_t trianglesPerTile = 239; - template - void generateKeys(OutputIterator out, std::size_t count, Random& random) + void generateKeys(std::output_iterator auto out, std::size_t count, auto& random) { std::generate_n(out, count, [&] { return generateKey(trianglesPerTile, random); }); } - template - void fillCache(OutputIterator out, Random& random, NavMeshTilesCache& cache) + void fillCache(std::output_iterator auto out, auto& random, NavMeshTilesCache& cache) { std::size_t size = cache.getStats().mNavMeshCacheSize; diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index b038226797..8b6889cc07 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -16,54 +16,54 @@ #include #include #include +#include #include #include namespace DetourNavigator { - template - class OutputTransformIterator + template OutputIterator> + class FromNavMeshCoordinatesIterator { public: - explicit OutputTransformIterator(OutputIterator& impl, Function&& function) + using iterator_category = std::output_iterator_tag; + using value_type = osg::Vec3f; + using difference_type = std::ptrdiff_t; + using pointer = osg::Vec3f*; + using reference = osg::Vec3f&; + + explicit FromNavMeshCoordinatesIterator(OutputIterator& impl, const RecastSettings& settings) : mImpl(impl) - , mFunction(std::forward(function)) + , mSettings(settings) { } - OutputTransformIterator& operator*() { return *this; } + FromNavMeshCoordinatesIterator& operator*() { return *this; } - OutputTransformIterator& operator++() + FromNavMeshCoordinatesIterator& operator++() { ++mImpl.get(); return *this; } - OutputTransformIterator operator++(int) + FromNavMeshCoordinatesIterator operator++(int) { const auto copy = *this; ++(*this); return copy; } - OutputTransformIterator& operator=(const osg::Vec3f& value) + FromNavMeshCoordinatesIterator& operator=(const osg::Vec3f& value) { - *mImpl.get() = mFunction(value); + *mImpl.get() = fromNavMeshCoordinates(mSettings, value); return *this; } private: std::reference_wrapper mImpl; - Function mFunction; + std::reference_wrapper mSettings; }; - template - auto withFromNavMeshCoordinates(OutputIterator& impl, const RecastSettings& settings) - { - return OutputTransformIterator( - impl, [&settings](const osg::Vec3f& value) { return fromNavMeshCoordinates(settings, value); }); - } - inline std::optional findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, std::span pathBuffer) @@ -78,10 +78,9 @@ namespace DetourNavigator return static_cast(pathLen); } - template Status makeSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& start, const osg::Vec3f& end, std::span polygonPath, std::size_t polygonPathSize, std::size_t maxSmoothPathSize, - OutputIterator& out) + std::output_iterator auto& out) { assert(polygonPathSize <= polygonPath.size()); @@ -102,10 +101,9 @@ namespace DetourNavigator return Status::Success; } - template Status findSmoothPath(const dtNavMeshQuery& navMeshQuery, const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, const DetourSettings& settings, - float endTolerance, OutputIterator out) + float endTolerance, std::output_iterator auto out) { dtQueryFilter queryFilter; queryFilter.setIncludeFlags(includeFlags); diff --git a/components/detournavigator/navigatorutils.hpp b/components/detournavigator/navigatorutils.hpp index 927066b6bb..ef6ae96313 100644 --- a/components/detournavigator/navigatorutils.hpp +++ b/components/detournavigator/navigatorutils.hpp @@ -9,6 +9,7 @@ #include +#include #include namespace DetourNavigator @@ -21,22 +22,18 @@ namespace DetourNavigator * @param includeFlags setup allowed surfaces for actor to walk. * @param out the beginning of the destination range. * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents - * @return Output iterator to the element in the destination range, one past the last element of found path. + * @return Status. * Equal to out if no path is found. */ - template inline Status findPath(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const AreaCosts& areaCosts, float endTolerance, - OutputIterator out) + std::output_iterator auto out) { - static_assert(std::is_same::iterator_category, - std::output_iterator_tag>::value, - "out is not an OutputIterator"); const auto navMesh = navigator.getNavMesh(agentBounds); if (navMesh == nullptr) return Status::NavMeshNotFound; const Settings& settings = navigator.getSettings(); - auto outTransform = withFromNavMeshCoordinates(out, settings.mRecast); + FromNavMeshCoordinatesIterator outTransform(out, settings.mRecast); const auto locked = navMesh->lock(); return findSmoothPath(locked->getQuery(), toNavMeshCoordinates(settings.mRecast, agentBounds.mHalfExtents), toNavMeshCoordinates(settings.mRecast, start), toNavMeshCoordinates(settings.mRecast, end), includeFlags, From 1440bcaf2adf62909fa72ab4e0f24b5f559af2e4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Nov 2023 16:35:54 +0400 Subject: [PATCH 0490/2167] Fix HUD cleanup from main menu --- apps/openmw/mwgui/hud.cpp | 25 ++++++++++++++++++------- apps/openmw/mwgui/hud.hpp | 1 - 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index d6eb07e8aa..f52228e280 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -650,17 +650,28 @@ namespace MWGui updateEnemyHealthBar(); } - void HUD::resetEnemy() + void HUD::clear() { mEnemyActorId = -1; mEnemyHealthTimer = -1; - } - void HUD::clear() - { - unsetSelectedSpell(); - unsetSelectedWeapon(); - resetEnemy(); + mWeaponSpellTimer = 0.f; + mWeaponName = std::string(); + mSpellName = std::string(); + mWeaponSpellBox->setVisible(false); + + mWeapStatus->setProgressRange(100); + mWeapStatus->setProgressPosition(0); + mSpellStatus->setProgressRange(100); + mSpellStatus->setProgressPosition(0); + + mWeapImage->setItem(MWWorld::Ptr()); + mWeapImage->setIcon(std::string()); + mSpellImage->setItem(MWWorld::Ptr()); + mSpellImage->setIcon(std::string()); + + mWeapBox->clearUserStrings(); + mSpellBox->clearUserStrings(); } void HUD::customMarkerCreated(MyGUI::Widget* marker) diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 1dd9cdb521..8dd98628c4 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -58,7 +58,6 @@ namespace MWGui MyGUI::Widget* getEffectBox() { return mEffectBox; } void setEnemy(const MWWorld::Ptr& enemy); - void resetEnemy(); void clear() override; From fa8f92b5ab2d39e6101a662656871c447962e132 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 17 Nov 2023 10:59:44 +0400 Subject: [PATCH 0491/2167] Do not keep outdated references in the UserData --- apps/openmw/mwgui/hud.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index f52228e280..d3c1762b4f 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -421,6 +421,7 @@ namespace MWGui mSpellBox->setUserString("ToolTipType", "Spell"); mSpellBox->setUserString("Spell", spellId.serialize()); + mSpellBox->setUserData(MyGUI::Any::Null); // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( @@ -491,6 +492,7 @@ namespace MWGui mSpellStatus->setProgressPosition(0); mSpellImage->setItem(MWWorld::Ptr()); mSpellBox->clearUserStrings(); + mSpellBox->setUserData(MyGUI::Any::Null); } void HUD::unsetSelectedWeapon() @@ -520,6 +522,7 @@ namespace MWGui mWeapBox->setUserString("ToolTipLayout", "HandToHandToolTip"); mWeapBox->setUserString("Caption_HandToHandText", itemName); mWeapBox->setUserString("ImageTexture_HandToHandImage", icon); + mWeapBox->setUserData(MyGUI::Any::Null); } void HUD::setCrosshairVisible(bool visible) @@ -671,7 +674,9 @@ namespace MWGui mSpellImage->setIcon(std::string()); mWeapBox->clearUserStrings(); + mWeapBox->setUserData(MyGUI::Any::Null); mSpellBox->clearUserStrings(); + mSpellBox->setUserData(MyGUI::Any::Null); } void HUD::customMarkerCreated(MyGUI::Widget* marker) From 99939022bde4f98ccb63c4416cb5a970c3073065 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 17 Nov 2023 11:18:59 +0400 Subject: [PATCH 0492/2167] Remove redundant code --- apps/openmw/mwgui/hud.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index d3c1762b4f..bd38174183 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -669,9 +669,7 @@ namespace MWGui mSpellStatus->setProgressPosition(0); mWeapImage->setItem(MWWorld::Ptr()); - mWeapImage->setIcon(std::string()); mSpellImage->setItem(MWWorld::Ptr()); - mSpellImage->setIcon(std::string()); mWeapBox->clearUserStrings(); mWeapBox->setUserData(MyGUI::Any::Null); From fbb39802a14bbc1e6c37a3c8eb8d71498a1b04d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 17 Nov 2023 13:14:42 +0100 Subject: [PATCH 0493/2167] Define tables for navmesh related lua bindings as separate types --- files/lua_api/openmw/nearby.lua | 97 +++++++++++++++++---------------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 2ee44a6df6..2e9abb06f6 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -108,22 +108,22 @@ --- -- @type NAVIGATOR_FLAGS --- @field [parent=#NAVIGATOR_FLAGS] #number Walk allow agent to walk on the ground area; --- @field [parent=#NAVIGATOR_FLAGS] #number Swim allow agent to swim on the water surface; --- @field [parent=#NAVIGATOR_FLAGS] #number OpenDoor allow agent to open doors on the way; --- @field [parent=#NAVIGATOR_FLAGS] #number UsePathgrid allow agent to use predefined pathgrid imported from ESM files. +-- @field [parent=#NAVIGATOR_FLAGS] #number Walk Allow agent to walk on the ground area. +-- @field [parent=#NAVIGATOR_FLAGS] #number Swim Allow agent to swim on the water surface. +-- @field [parent=#NAVIGATOR_FLAGS] #number OpenDoor Allow agent to open doors on the way. +-- @field [parent=#NAVIGATOR_FLAGS] #number UsePathgrid Allow agent to use predefined pathgrid imported from ESM files. --- -- @type COLLISION_SHAPE_TYPE -- @field [parent=#COLLISION_SHAPE_TYPE] #number Aabb Axis-Aligned Bounding Box is used for NPC and symmetric --- Creatures; +-- Creatures. -- @field [parent=#COLLISION_SHAPE_TYPE] #number RotatingBox is used for Creatures with big difference in width and --- height; +-- height. -- @field [parent=#COLLISION_SHAPE_TYPE] #number Cylinder is used for NPC and symmetric Creatures. --- -- @type FIND_PATH_STATUS --- @field [parent=#FIND_PATH_STATUS] #number Success Path is found; +-- @field [parent=#FIND_PATH_STATUS] #number Success Path is found. -- @field [parent=#FIND_PATH_STATUS] #number PartialPath Last path point is not a destination but a nearest position -- among found; -- @field [parent=#FIND_PATH_STATUS] #number NavMeshNotFound Provided `agentBounds` don't have corresponding navigation @@ -135,16 +135,52 @@ -- @field [parent=#FIND_PATH_STATUS] #number EndPolygonNotFound `destination` position is too far from available -- navigation mesh. The status may appear when navigation mesh is not fully generated or position is outside of covered -- area; --- @field [parent=#FIND_PATH_STATUS] #number TargetPolygonNotFound adjusted `destination` position is too far from available --- navigation mesh. The status may appear when navigation mesh is not fully generated or position is outside of covered --- area; +-- @field [parent=#FIND_PATH_STATUS] #number TargetPolygonNotFound adjusted `destination` position is too far from +-- available navigation mesh. The status may appear when navigation mesh is not fully generated or position is outside +-- of covered area; -- @field [parent=#FIND_PATH_STATUS] #number MoveAlongSurfaceFailed Found path couldn't be smoothed due to imperfect -- algorithm implementation or bad navigation mesh data; -- @field [parent=#FIND_PATH_STATUS] #number FindPathOverPolygonsFailed Path over navigation mesh from `source` to -- `destination` does not exist or navigation mesh is not fully generated to provide the path; -- @field [parent=#FIND_PATH_STATUS] #number InitNavMeshQueryFailed Couldn't initialize required data due to bad input -- or bad navigation mesh data. --- @field [parent=#FIND_PATH_STATUS] #number FindStraightPathFailed Couldn't map path over polygons into world coordinates. +-- @field [parent=#FIND_PATH_STATUS] #number FindStraightPathFailed Couldn't map path over polygons into world +-- coordinates. + +--- +-- A table of parameters identifying navmesh +-- @type AgentBounds +-- @field [parent=#AgentBounds] #COLLISION_SHAPE_TYPE shapeType. +-- @field [parent=#AgentBounds] openmw.util#Vector3 halfExtents. + +--- +-- A table of parameters to specify relative path cost per each area type +-- @type AreaCosts +-- @field [parent=#AreaCosts] #number ground Value >= 0, used in combination with @{#NAVIGATOR_FLAGS.Walk} (default: 1). +-- @field [parent=#AreaCosts] #number water Value >= 0, used in combination with @{#NAVIGATOR_FLAGS.Swim} (default: 1). +-- @field [parent=#AreaCosts] #number door Value >= 0, used in combination with @{#NAVIGATOR_FLAGS.OpenDoor} +-- (default: 2). +-- @field [parent=#AreaCosts] #number pathgrid Value >= 0, used in combination with @{#NAVIGATOR_FLAGS.UsePathgrid} +-- (default: 1). + +--- +-- A table of parameters for @{#nearby.findPath} +-- @type FindPathOptions +-- @field [parent=#FindPathOptions] #AgentBounds agentBounds identifies which navmesh to use. +-- @field [parent=#FindPathOptions] #number includeFlags allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} +-- values (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + @{#NAVIGATOR_FLAGS.OpenDoor} +-- + @{#NAVIGATOR_FLAGS.UsePathgrid}). +-- @field [parent=#FindPathOptions] #AreaCosts areaCosts a table defining relative cost for each type of area. +-- @field [parent=#FindPathOptions] #number destinationTolerance a floating point number representing maximum allowed +-- distance between destination and a nearest point on the navigation mesh in addition to agent size (default: 1). + +--- +-- A table of parameters for @{#nearby.findRandomPointAroundCircle} and @{#nearby.castNavigationRay} +-- @type NavMeshOptions +-- @field [parent=#NavMeshOptions] #AgentBounds agentBounds Identifies which navmesh to use. +-- @field [parent=#NavMeshOptions] #number includeFlags Allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} +-- values (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + @{#NAVIGATOR_FLAGS.OpenDoor} +-- + @{#NAVIGATOR_FLAGS.UsePathgrid}). --- -- Find path over navigation mesh from source to destination with given options. Result is unstable since navigation @@ -152,24 +188,7 @@ -- @function [parent=#nearby] findPath -- @param openmw.util#Vector3 source Initial path position. -- @param openmw.util#Vector3 destination Final path position. --- @param #table options An optional table with additional optional arguments. Can contain: --- --- * `agentBounds` - a table identifying which navmesh to use, can contain: --- --- * `shapeType` - one of @{#COLLISION_SHAPE_TYPE} values; --- * `halfExtents` - @{openmw.util#Vector3} defining agent bounds size; --- * `includeFlags` - allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} values --- (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + --- @{#NAVIGATOR_FLAGS.OpenDoor} + @{#NAVIGATOR_FLAGS.UsePathgrid}); --- * `areaCosts` - a table defining relative cost for each type of area, can contain: --- --- * `ground` - a floating point number >= 0, used in combination with @{#NAVIGATOR_FLAGS.Walk} (default: 1); --- * `water` - a floating point number >= 0, used in combination with @{#NAVIGATOR_FLAGS.Swim} (default: 1); --- * `door` - a floating point number >= 0, used in combination with @{#NAVIGATOR_FLAGS.OpenDoor} (default: 2); --- * `pathgrid` - a floating point number >= 0, used in combination with @{#NAVIGATOR_FLAGS.UsePathgrid} --- (default: 1); --- * `destinationTolerance` - a floating point number representing maximum allowed distance between destination and a --- nearest point on the navigation mesh in addition to agent size (default: 1); +-- @param #FindPathOptions options An optional table with additional optional arguments. -- @return #FIND_PATH_STATUS -- @return #list -- @usage local status, path = nearby.findPath(source, destination) @@ -189,15 +208,7 @@ -- @function [parent=#nearby] findRandomPointAroundCircle -- @param openmw.util#Vector3 position Center of the search circle. -- @param #number maxRadius Approximate maximum search distance. --- @param #table options An optional table with additional optional arguments. Can contain: --- --- * `agentBounds` - a table identifying which navmesh to use, can contain: --- --- * `shapeType` - one of @{#COLLISION_SHAPE_TYPE} values; --- * `halfExtents` - @{openmw.util#Vector3} defining agent bounds size; --- * `includeFlags` - allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} values --- (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + --- @{#NAVIGATOR_FLAGS.OpenDoor} + @{#NAVIGATOR_FLAGS.UsePathgrid}); +-- @param #NavMeshOptions options An optional table with additional optional arguments. -- @return openmw.util#Vector3, #nil -- @usage local position = nearby.findRandomPointAroundCircle(position, maxRadius) -- @usage local position = nearby.findRandomPointAroundCircle(position, maxRadius, { @@ -213,15 +224,7 @@ -- @function [parent=#nearby] castNavigationRay -- @param openmw.util#Vector3 from Initial ray position. -- @param openmw.util#Vector3 to Target ray position. --- @param #table options An optional table with additional optional arguments. Can contain: --- --- * `agentBounds` - a table identifying which navmesh to use, can contain: --- --- * `shapeType` - one of @{#COLLISION_SHAPE_TYPE} values; --- * `halfExtents` - @{openmw.util#Vector3} defining agent bounds size; --- * `includeFlags` - allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} values --- (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + --- @{#NAVIGATOR_FLAGS.OpenDoor} + @{#NAVIGATOR_FLAGS.UsePathgrid}); +-- @param #NavMeshOptions options An optional table with additional optional arguments. -- @return openmw.util#Vector3, #nil -- @usage local position = nearby.castNavigationRay(from, to) -- @usage local position = nearby.castNavigationRay(from, to, { From 6d0dceae348e93915c58ea6b01222b33ac3fca14 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 9 Nov 2023 22:58:32 +0800 Subject: [PATCH 0494/2167] Allow choosing different apparatus in alchemy window --- apps/openmw/mwgui/alchemywindow.cpp | 101 ++++++++++++++++++++++ apps/openmw/mwgui/alchemywindow.hpp | 8 ++ apps/openmw/mwgui/itemselection.hpp | 2 + apps/openmw/mwgui/sortfilteritemmodel.cpp | 16 ++++ apps/openmw/mwgui/sortfilteritemmodel.hpp | 3 + apps/openmw/mwmechanics/alchemy.cpp | 18 ++++ apps/openmw/mwmechanics/alchemy.hpp | 6 ++ 7 files changed, 154 insertions(+) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 1eb41a2d86..6f13151c6a 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -77,6 +78,11 @@ namespace MWGui mIngredients[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mApparatus[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); @@ -293,6 +299,80 @@ namespace MWGui update(); } + void AlchemyWindow::onItemSelected(MWWorld::Ptr item) + { + mItemSelectionDialog->setVisible(false); + + int32_t index = item.get()->mBase->mData.mType; + const auto& widget = mApparatus[index]; + + widget->setItem(item); + + if (item.isEmpty()) + { + widget->clearUserStrings(); + return; + } + + mAlchemy->addApparatus(item); + + widget->setUserString("ToolTipType", "ItemPtr"); + widget->setUserData(MWWorld::Ptr(item)); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); + update(); + } + + void AlchemyWindow::onItemCancel() + { + mItemSelectionDialog->setVisible(false); + } + + void AlchemyWindow::onApparatusSelected(MyGUI::Widget* _sender) + { + if (_sender->getUserData()->isEmpty()) // if this apparatus slot is empty + { + std::string title; + + size_t i = 0; + for (; i < mApparatus.size(); ++i) + { + if (mApparatus[i] == _sender) + break; + } + + switch (i) + { + case ESM::Apparatus::AppaType::MortarPestle: + title = "#{sMortar}"; + break; + case ESM::Apparatus::AppaType::Alembic: + title = "#{sAlembic}"; + break; + case ESM::Apparatus::AppaType::Calcinator: + title = "#{sCalcinator}"; + break; + case ESM::Apparatus::AppaType::Retort: + title = "#{sRetort}"; + break; + default: + title = "#{sApparatus}"; + } + + mItemSelectionDialog = std::make_unique(title); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &AlchemyWindow::onItemSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &AlchemyWindow::onItemCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); + mItemSelectionDialog->getSortModel()->setApparatusTypeFilter(i); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyAlchemyTools); + } + else + removeApparatus(_sender); + + update(); + } + void AlchemyWindow::onSelectedItem(int index) { MWWorld::Ptr item = mSortModel->getItem(index).mBase; @@ -395,6 +475,27 @@ namespace MWGui update(); } + void AlchemyWindow::removeApparatus(MyGUI::Widget* apparatus) + { + for (size_t i = 0; i < mApparatus.size(); ++i) + { + const auto& widget = mApparatus[i]; + if (widget == apparatus) + { + mAlchemy->removeApparatus(i); + + if (widget->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); + + widget->clearUserStrings(); + + widget->setItem(MWWorld::Ptr()); + + widget->setUserData(MWWorld::Ptr()); + } + } + } + void AlchemyWindow::addRepeatController(MyGUI::Widget* widget) { MyGUI::ControllerItem* item diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 39ea5ec9b3..a88c36c22d 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -10,6 +10,7 @@ #include #include +#include "itemselection.hpp" #include "windowbase.hpp" #include "../mwmechanics/alchemy.hpp" @@ -44,6 +45,8 @@ namespace MWGui }; FilterType mCurrentFilter; + std::unique_ptr mItemSelectionDialog; + ItemView* mItemView; InventoryItemModel* mModel; SortFilterItemModel* mSortModel; @@ -63,6 +66,7 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); + void onApparatusSelected(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox*); void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); @@ -85,6 +89,10 @@ namespace MWGui void onSelectedItem(int index); void removeIngredient(MyGUI::Widget* ingredient); + void removeApparatus(MyGUI::Widget* ingredient); + + void onItemSelected(MWWorld::Ptr item); + void onItemCancel(); void createPotions(int count); diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 78f865bb55..fe87d7e38a 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -32,6 +32,8 @@ namespace MWGui void setCategory(int category); void setFilter(int filter); + SortFilterItemModel* getSortModel() { return mSortModel; } + private: ItemView* mItemView; SortFilterItemModel* mSortModel; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 4a5e02a881..e5b87abff7 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -174,6 +174,7 @@ namespace MWGui : mCategory(Category_All) , mFilter(0) , mSortByType(true) + , mApparatusTypeFilter(-1) { mSourceModel = std::move(sourceModel); } @@ -311,6 +312,16 @@ namespace MWGui return false; } + if ((mFilter & Filter_OnlyAlchemyTools)) + { + if (base.getType() != ESM::Apparatus::sRecordId) + return false; + + int32_t apparatusType = base.get()->mBase->mData.mType; + if (mApparatusTypeFilter >= 0 && apparatusType != mApparatusTypeFilter) + return false; + } + std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); if (compare.find(mNameFilter) == std::string::npos) return false; @@ -352,6 +363,11 @@ namespace MWGui mEffectFilter = Utf8Stream::lowerCaseUtf8(filter); } + void SortFilterItemModel::setApparatusTypeFilter(const int32_t type) + { + mApparatusTypeFilter = type; + } + void SortFilterItemModel::update() { mSourceModel->update(); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 66a22b3afa..d8490f7db1 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -27,6 +27,7 @@ namespace MWGui void setFilter(int filter); void setNameFilter(const std::string& filter); void setEffectFilter(const std::string& filter); + void setApparatusTypeFilter(const int32_t type); /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } @@ -49,6 +50,7 @@ namespace MWGui static constexpr int Filter_OnlyRepairable = (1 << 5); static constexpr int Filter_OnlyRechargable = (1 << 6); static constexpr int Filter_OnlyRepairTools = (1 << 7); + static constexpr int Filter_OnlyAlchemyTools = (1 << 8); private: std::vector mItems; @@ -59,6 +61,7 @@ namespace MWGui int mFilter; bool mSortByType; + int32_t mApparatusTypeFilter; // filter by apparatus type std::string mNameFilter; // filter by item name std::string mEffectFilter; // filter by magic effect }; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 995d55759b..4fd8ab7738 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -461,6 +461,24 @@ void MWMechanics::Alchemy::removeIngredient(int index) } } +void MWMechanics::Alchemy::addApparatus(const MWWorld::Ptr& apparatus) +{ + int32_t slot = apparatus.get()->mBase->mData.mType; + + mTools[slot] = apparatus; + + updateEffects(); +} + +void MWMechanics::Alchemy::removeApparatus(int index) +{ + if (index >= 0 && index < static_cast(mTools.size())) + { + mTools[index] = MWWorld::Ptr(); + updateEffects(); + } +} + MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const { return mEffects.begin(); diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index ab6225e544..0ae7fd3f2e 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -119,9 +119,15 @@ namespace MWMechanics /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being /// listed already. + void addApparatus(const MWWorld::Ptr& apparatus); + ///< Add apparatus into the appropriate slot. + void removeIngredient(int index); ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). + void removeApparatus(int index); + ///< Remove apparatus from slot. + std::string suggestPotionName(); ///< Suggest a name for the potion, based on the current effects From 60711cedb00e0d49aa2e59196c5e7c3971569f0d Mon Sep 17 00:00:00 2001 From: kuyondo Date: Fri, 17 Nov 2023 23:12:18 +0800 Subject: [PATCH 0495/2167] use std::distance --- apps/openmw/mwgui/alchemywindow.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 6f13151c6a..2ac9c281b6 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -333,14 +333,7 @@ namespace MWGui if (_sender->getUserData()->isEmpty()) // if this apparatus slot is empty { std::string title; - - size_t i = 0; - for (; i < mApparatus.size(); ++i) - { - if (mApparatus[i] == _sender) - break; - } - + size_t i = std::distance(mApparatus.begin(), std::find(mApparatus.begin(), mApparatus.end(), _sender)); switch (i) { case ESM::Apparatus::AppaType::MortarPestle: @@ -468,7 +461,7 @@ namespace MWGui void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) { - for (int i = 0; i < 4; ++i) + for (int i = 0; i < mIngredients.size(); ++i) if (mIngredients[i] == ingredient) mAlchemy->removeIngredient(i); @@ -479,19 +472,18 @@ namespace MWGui { for (size_t i = 0; i < mApparatus.size(); ++i) { - const auto& widget = mApparatus[i]; - if (widget == apparatus) + if (mApparatus[i] == apparatus) { + const auto& widget = mApparatus[i]; mAlchemy->removeApparatus(i); if (widget->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); widget->clearUserStrings(); - widget->setItem(MWWorld::Ptr()); - widget->setUserData(MWWorld::Ptr()); + break; } } } From 033be4e6e66d26d9edbc2e4f90b8de34d6f28751 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Fri, 17 Nov 2023 23:33:58 +0800 Subject: [PATCH 0496/2167] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afca19c6c..a4d66c28ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples From f3ad16620affe24ce5a7d61423b98a376821ab67 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sat, 18 Nov 2023 00:41:44 +0800 Subject: [PATCH 0497/2167] refactor and maintenance alchemywindow and alchemy --- apps/openmw/mwgui/alchemywindow.cpp | 50 +++++++++-------------------- apps/openmw/mwgui/alchemywindow.hpp | 3 -- apps/openmw/mwmechanics/alchemy.cpp | 8 ++--- apps/openmw/mwmechanics/alchemy.hpp | 4 +-- 4 files changed, 22 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 2ac9c281b6..7208fce5f6 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -147,12 +147,12 @@ namespace MWGui } // remove ingredient slots that have been fully used up - for (int i = 0; i < 4; ++i) + for (size_t i = 0; i < mIngredients.size(); ++i) if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredients[i]); + mAlchemy->removeIngredient(i); } updateFilters(); @@ -295,7 +295,8 @@ namespace MWGui void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) { - removeIngredient(_sender); + size_t i = std::distance(mIngredients.begin(), std::find(mIngredients.begin(), mIngredients.end(), _sender)); + mAlchemy->removeIngredient(i); update(); } @@ -330,10 +331,10 @@ namespace MWGui void AlchemyWindow::onApparatusSelected(MyGUI::Widget* _sender) { + size_t i = std::distance(mApparatus.begin(), std::find(mApparatus.begin(), mApparatus.end(), _sender)); if (_sender->getUserData()->isEmpty()) // if this apparatus slot is empty { std::string title; - size_t i = std::distance(mApparatus.begin(), std::find(mApparatus.begin(), mApparatus.end(), _sender)); switch (i) { case ESM::Apparatus::AppaType::MortarPestle: @@ -361,7 +362,17 @@ namespace MWGui mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyAlchemyTools); } else - removeApparatus(_sender); + { + const auto& widget = mApparatus[i]; + mAlchemy->removeApparatus(i); + + if (widget->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); + + widget->clearUserStrings(); + widget->setItem(MWWorld::Ptr()); + widget->setUserData(MWWorld::Ptr()); + } update(); } @@ -459,35 +470,6 @@ namespace MWGui effectsWidget->setCoord(coord); } - void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) - { - for (int i = 0; i < mIngredients.size(); ++i) - if (mIngredients[i] == ingredient) - mAlchemy->removeIngredient(i); - - update(); - } - - void AlchemyWindow::removeApparatus(MyGUI::Widget* apparatus) - { - for (size_t i = 0; i < mApparatus.size(); ++i) - { - if (mApparatus[i] == apparatus) - { - const auto& widget = mApparatus[i]; - mAlchemy->removeApparatus(i); - - if (widget->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); - - widget->clearUserStrings(); - widget->setItem(MWWorld::Ptr()); - widget->setUserData(MWWorld::Ptr()); - break; - } - } - } - void AlchemyWindow::addRepeatController(MyGUI::Widget* widget) { MyGUI::ControllerItem* item diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index a88c36c22d..82e5c3f583 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -88,9 +88,6 @@ namespace MWGui void onSelectedItem(int index); - void removeIngredient(MyGUI::Widget* ingredient); - void removeApparatus(MyGUI::Widget* ingredient); - void onItemSelected(MWWorld::Ptr item); void onItemCancel(); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 4fd8ab7738..b697387f50 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -452,9 +452,9 @@ int MWMechanics::Alchemy::addIngredient(const MWWorld::Ptr& ingredient) return slot; } -void MWMechanics::Alchemy::removeIngredient(int index) +void MWMechanics::Alchemy::removeIngredient(size_t index) { - if (index >= 0 && index < static_cast(mIngredients.size())) + if (index >= 0 && index < mIngredients.size()) { mIngredients[index] = MWWorld::Ptr(); updateEffects(); @@ -470,9 +470,9 @@ void MWMechanics::Alchemy::addApparatus(const MWWorld::Ptr& apparatus) updateEffects(); } -void MWMechanics::Alchemy::removeApparatus(int index) +void MWMechanics::Alchemy::removeApparatus(size_t index) { - if (index >= 0 && index < static_cast(mTools.size())) + if (index >= 0 && index < mTools.size()) { mTools[index] = MWWorld::Ptr(); updateEffects(); diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 0ae7fd3f2e..1b76e400f5 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -122,10 +122,10 @@ namespace MWMechanics void addApparatus(const MWWorld::Ptr& apparatus); ///< Add apparatus into the appropriate slot. - void removeIngredient(int index); + void removeIngredient(size_t index); ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). - void removeApparatus(int index); + void removeApparatus(size_t index); ///< Remove apparatus from slot. std::string suggestPotionName(); From d2d99a4348b1b2d6eb91df42f948c45e8715526e Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sat, 18 Nov 2023 01:04:31 +0800 Subject: [PATCH 0498/2167] give gcc satisfaction --- apps/openmw/mwmechanics/alchemy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index b697387f50..f74c08da2a 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -454,7 +454,7 @@ int MWMechanics::Alchemy::addIngredient(const MWWorld::Ptr& ingredient) void MWMechanics::Alchemy::removeIngredient(size_t index) { - if (index >= 0 && index < mIngredients.size()) + if (index < mIngredients.size()) { mIngredients[index] = MWWorld::Ptr(); updateEffects(); @@ -472,7 +472,7 @@ void MWMechanics::Alchemy::addApparatus(const MWWorld::Ptr& apparatus) void MWMechanics::Alchemy::removeApparatus(size_t index) { - if (index >= 0 && index < mTools.size()) + if (index < mTools.size()) { mTools[index] = MWWorld::Ptr(); updateEffects(); From 3e3a39539c3b5b90b20ca53beb4e9e0747b41ee7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 17 Nov 2023 14:51:19 -0600 Subject: [PATCH 0499/2167] Reorder, remove comment --- apps/openmw/mwlua/classbindings.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index f8290a263d..339b724f19 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -31,7 +31,6 @@ namespace MWLua sol::table classes(context.mLua->sol(), sol::create); addRecordFunctionBinding(classes, context); - // class record auto classT = lua.new_usertype("ESM3_Class"); classT[sol::meta_function::to_string] = [](const ESM::Class& rec) -> std::string { return "ESM3_Class[" + rec.mId.toDebugString() + "]"; }; @@ -40,16 +39,6 @@ namespace MWLua classT["description"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); - classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); - res[i + 1] = skillId.serializeText(); - } - return res; - }); classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto attribute = rec.mData.mAttribute; @@ -60,6 +49,16 @@ namespace MWLua } return res; }); + classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { + sol::table res(lua, sol::create); + auto skills = rec.mData.mSkills; + for (size_t i = 0; i < skills.size(); ++i) + { + ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); + res[i + 1] = skillId.serializeText(); + } + return res; + }); classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { sol::table res(lua, sol::create); auto skills = rec.mData.mSkills; From f88b99201aceec7e6ea15e734795b8e002f5b5b4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Nov 2023 11:41:19 +0400 Subject: [PATCH 0500/2167] Display missing plugins upon savegame loading (feature 7608) --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 72 +++++++++++++++++++------ apps/openmw/mwstate/statemanagerimp.hpp | 2 +- components/esm3/savedgame.cpp | 14 +++++ components/esm3/savedgame.hpp | 2 + files/data/l10n/Interface/de.yaml | 1 + files/data/l10n/Interface/en.yaml | 1 + files/data/l10n/Interface/fr.yaml | 1 + files/data/l10n/Interface/ru.yaml | 1 + files/data/l10n/Interface/sv.yaml | 1 + files/data/l10n/OMWEngine/de.yaml | 14 ++++- files/data/l10n/OMWEngine/en.yaml | 14 ++++- files/data/l10n/OMWEngine/fr.yaml | 14 ++++- files/data/l10n/OMWEngine/ru.yaml | 14 ++++- files/data/l10n/OMWEngine/sv.yaml | 14 ++++- 15 files changed, 143 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afca19c6c..21ddd7df26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music + Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index fb3590a3f0..5ee5b21aa2 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include @@ -440,7 +442,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file { ESM::SavedGame profile; profile.load(reader); - if (!verifyProfile(profile)) + const auto& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); + auto missingFiles = profile.getMissingContentFiles(selectedContentFiles); + if (!missingFiles.empty() && !confirmLoading(missingFiles)) { cleanup(true); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); @@ -668,30 +672,64 @@ void MWState::StateManager::update(float duration) } } -bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const +bool MWState::StateManager::confirmLoading(const std::vector& missingFiles) const { - const std::vector& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); - bool notFound = false; - for (const std::string& contentFile : profile.mContentFiles) + std::ostringstream stream; + for (auto& contentFile : missingFiles) { - if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile) - == selectedContentFiles.end()) + Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; + stream << contentFile << "\n"; + } + + auto fullList = stream.str(); + if (!fullList.empty()) + fullList.pop_back(); + + constexpr size_t missingPluginsDisplayLimit = 12; + + std::vector buttons; + buttons.emplace_back("#{Interface:Yes}"); + buttons.emplace_back("#{Interface:Copy}"); + buttons.emplace_back("#{Interface:No}"); + std::string message = "#{OMWEngine:MissingContentFilesConfirmation}"; + + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + message += l10n->formatMessage("MissingContentFilesList", { "files" }, { static_cast(missingFiles.size()) }); + auto cappedSize = std::min(missingFiles.size(), missingPluginsDisplayLimit); + if (cappedSize == missingFiles.size()) + { + message += fullList; + } + else + { + for (size_t i = 0; i < cappedSize - 1; ++i) { - Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing."; - notFound = true; + message += missingFiles[i]; + message += "\n"; } + + message += "..."; } - if (notFound) + + message + += l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast(missingFiles.size()) }); + + while (true) { - std::vector buttons; - buttons.emplace_back("#{Interface:Yes}"); - buttons.emplace_back("#{Interface:No}"); - MWBase::Environment::get().getWindowManager()->interactiveMessageBox( - "#{OMWEngine:MissingContentFilesConfirmation}", buttons, true); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if (selectedButton == 1 || selectedButton == -1) - return false; + if (selectedButton == 0) + break; + + if (selectedButton == 1) + { + SDL_SetClipboardText(fullList.c_str()); + continue; + } + + return false; } + return true; } diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index df62ca7ebf..c293209f34 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -21,7 +21,7 @@ namespace MWState private: void cleanup(bool force = false); - bool verifyProfile(const ESM::SavedGame& profile) const; + bool confirmLoading(const std::vector& missingFiles) const; void writeScreenshot(std::vector& imageData) const; diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index e84cb27ad8..cec2b5e189 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -61,4 +61,18 @@ namespace ESM esm.writeHNT("MHLT", mMaximumHealth); } + std::vector SavedGame::getMissingContentFiles( + const std::vector& allContentFiles) const + { + std::vector missingFiles; + for (const std::string& contentFile : mContentFiles) + { + if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end()) + { + missingFiles.emplace_back(contentFile); + } + } + + return missingFiles; + } } diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 4632e98927..f174340203 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -40,6 +40,8 @@ namespace ESM void load(ESMReader& esm); void save(ESMWriter& esm) const; + + std::vector getMissingContentFiles(const std::vector& allContentFiles) const; }; } diff --git a/files/data/l10n/Interface/de.yaml b/files/data/l10n/Interface/de.yaml index 1cabad01a9..ac1a95a0ea 100644 --- a/files/data/l10n/Interface/de.yaml +++ b/files/data/l10n/Interface/de.yaml @@ -25,3 +25,4 @@ Yes: "Ja" #OK: "OK" #Off: "Off" #On: "On" +#Copy: "Copy" diff --git a/files/data/l10n/Interface/en.yaml b/files/data/l10n/Interface/en.yaml index df450b5c38..82c1aeba1a 100644 --- a/files/data/l10n/Interface/en.yaml +++ b/files/data/l10n/Interface/en.yaml @@ -22,3 +22,4 @@ None: "None" OK: "OK" Cancel: "Cancel" Close: "Close" +Copy: "Copy" diff --git a/files/data/l10n/Interface/fr.yaml b/files/data/l10n/Interface/fr.yaml index 5aa0260680..bac4346364 100644 --- a/files/data/l10n/Interface/fr.yaml +++ b/files/data/l10n/Interface/fr.yaml @@ -22,3 +22,4 @@ None: "Aucun" OK: "Valider" Cancel: "Annuler" Close: "Fermer" +#Copy: "Copy" diff --git a/files/data/l10n/Interface/ru.yaml b/files/data/l10n/Interface/ru.yaml index 6d81dd7797..44b38a77b8 100644 --- a/files/data/l10n/Interface/ru.yaml +++ b/files/data/l10n/Interface/ru.yaml @@ -1,5 +1,6 @@ Cancel: "Отмена" Close: "Закрыть" +Copy: "Скопировать" DurationDay: "{days} д " DurationHour: "{hours} ч " DurationMinute: "{minutes} мин " diff --git a/files/data/l10n/Interface/sv.yaml b/files/data/l10n/Interface/sv.yaml index 5e9260cf97..aae63a1941 100644 --- a/files/data/l10n/Interface/sv.yaml +++ b/files/data/l10n/Interface/sv.yaml @@ -14,3 +14,4 @@ Off: "Av" On: "På" Reset: "Återställ" Yes: "Ja" +#Copy: "Copy" diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index f2b4ee7e5a..26838bd93c 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -41,10 +41,22 @@ TimePlayed: "Spielzeit" #DeleteGameConfirmation: "Are you sure you want to delete this saved game?" #EmptySaveNameError: "Game can not be saved without a name!" #LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -#MissingContentFilesConfirmation: | +#MissingContentFilesConfirmation: |- # The currently selected content files do not match the ones used by this save game. # Errors may occur during load or game play. # Do you wish to continue? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } #OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 08df886f18..ee2a33ee71 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -34,10 +34,22 @@ DeleteGame: "Delete Game" DeleteGameConfirmation: "Are you sure you want to delete this saved game?" EmptySaveNameError: "Game can not be saved without a name!" LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- The currently selected content files do not match the ones used by this save game. Errors may occur during load or game play. Do you wish to continue? +MissingContentFilesList: |- + {files, plural, + one{\n\nFound missing file: } + few{\n\nFound {files} missing files:\n} + other{\n\nFound {files} missing files:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nPress Copy to place its name to the clipboard.} + few{\n\nPress Copy to place their names to the clipboard.} + other{\n\nPress Copy to place their names to the clipboard.} + } OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" SelectCharacter: "Select Character..." TimePlayed: "Time played" diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 5a6209b44c..689ccc59a5 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -37,10 +37,22 @@ DeleteGame: "Supprimer la partie" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" LoadGameConfirmation: "Voulez-vous charger cette autre partie ? Toute progression non sauvegardée sera perdue." -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie. Voulez-vous continuer ? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index cbc71f91e4..b645b681b1 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -34,10 +34,22 @@ DeleteGame: "Удалить игру" DeleteGameConfirmation: "Вы уверены, что хотите удалить это сохранение?" EmptySaveNameError: "Имя сохранения не может быть пустым!" LoadGameConfirmation: "Вы хотите загрузить сохранение? Текущая игра будет потеряна." -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- Выбранные ESM/ESP файлы не соответствуют тем, которые использовались для этого сохранения. Во время загрузки или в процессе игры могут возникнуть ошибки. Вы хотите продолжить? +MissingContentFilesList: |- + {files, plural, + one{\n\nОтсутствует файл } + few{\n\nОтсутствуют {files} файла:\n} + other{\n\nОтсутствуют {files} файлов:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nНажмите Скопировать, чтобы поместить его название в буфер обмена.} + few{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.} + other{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.} + } OverwriteGameConfirmation: "Вы уверены, что хотите перезаписать это сохранение?" SelectCharacter: "Выберите персонажа..." TimePlayed: "Время в игре" diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 1ee8bdc707..b9d2d44076 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -35,10 +35,22 @@ DeleteGame: "Radera spel" DeleteGameConfirmation: "Är du säker på att du vill radera sparfilen?" EmptySaveNameError: "Spelet kan inte sparas utan ett namn!" LoadGameConfirmation: "Vill du ladda ett sparat spel och förlora det pågående spelet?" -MissingContentFilesConfirmation: | +MissingContentFilesConfirmation: |- De valda innehållsfilerna matchar inte filerna som används av denna sparfil. Fel kan uppstå vid laddning eller under spel. Vill du fortsätta? +#MissingContentFilesList: |- +# {files, plural, +# one{\n\nFound missing file: } +# few{\n\nFound {files} missing files:\n} +# other{\n\nFound {files} missing files:\n} +# } +#MissingContentFilesListCopy: |- +# {files, plural, +# one{\n\nPress Copy to place its name to the clipboard.} +# few{\n\nPress Copy to place their names to the clipboard.} +# other{\n\nPress Copy to place their names to the clipboard.} +# } OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?" SelectCharacter: "Välj spelfigur..." From ff418f16f21c3e2ab8477fbd6428dde53d94eb13 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 18 Nov 2023 16:47:06 +0400 Subject: [PATCH 0501/2167] Do not wait one frame for blocking messageboxes --- apps/openmw/mwgui/messagebox.cpp | 29 +++++++++++++++++++++---- apps/openmw/mwgui/messagebox.hpp | 10 +++++++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 +++- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 49d474c826..9d1d98aa4e 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -46,6 +46,20 @@ namespace MWGui mLastButtonPressed = -1; } + void MessageBoxManager::resetInteractiveMessageBox() + { + if (mInterMessageBoxe) + { + mInterMessageBoxe->setVisible(false); + mInterMessageBoxe.reset(); + } + } + + void MessageBoxManager::setLastButtonPressed(int index) + { + mLastButtonPressed = index; + } + void MessageBoxManager::onFrame(float frameDuration) { for (auto it = mMessageBoxes.begin(); it != mMessageBoxes.end();) @@ -112,7 +126,7 @@ namespace MWGui } bool MessageBoxManager::createInteractiveMessageBox( - std::string_view message, const std::vector& buttons) + std::string_view message, const std::vector& buttons, bool immediate) { if (mInterMessageBoxe != nullptr) { @@ -120,7 +134,7 @@ namespace MWGui mInterMessageBoxe->setVisible(false); } - mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons); + mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons, immediate); mLastButtonPressed = -1; return true; @@ -200,13 +214,14 @@ namespace MWGui mMainWidget->setVisible(value); } - InteractiveMessageBox::InteractiveMessageBox( - MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) + InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, + const std::vector& buttons, bool immediate) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) + , mImmediate(immediate) { int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 10; // padding between the text-widget und the button-widget @@ -393,6 +408,12 @@ namespace MWGui { mButtonPressed = index; mMessageBoxManager.onButtonPressed(mButtonPressed); + if (!mImmediate) + return; + + mMessageBoxManager.setLastButtonPressed(mButtonPressed); + MWBase::Environment::get().getInputManager()->changeInputMode( + MWBase::Environment::get().getWindowManager()->isGuiMode()); return; } index++; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index b10586549f..742d355c60 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -25,7 +25,8 @@ namespace MWGui void onFrame(float frameDuration); void createMessageBox(std::string_view message, bool stat = false); void removeStaticMessageBox(); - bool createInteractiveMessageBox(std::string_view message, const std::vector& buttons); + bool createInteractiveMessageBox( + std::string_view message, const std::vector& buttons, bool immediate = false); bool isInteractiveMessageBox(); int getMessagesCount(); @@ -40,6 +41,10 @@ namespace MWGui /// @param reset Reset the pressed button to -1 after reading it. int readPressedButton(bool reset = true); + void resetInteractiveMessageBox(); + + void setLastButtonPressed(int index); + typedef MyGUI::delegates::MultiDelegate EventHandle_Int; // Note: this delegate unassigns itself after it was fired, i.e. works once. @@ -88,7 +93,7 @@ namespace MWGui { public: InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons); + const std::vector& buttons, bool immediate); void mousePressed(MyGUI::Widget* _widget); int readPressedButton(); @@ -107,6 +112,7 @@ namespace MWGui std::vector mButtons; int mButtonPressed; + bool mImmediate; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1d41bab34f..71c28230a4 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -746,7 +746,7 @@ namespace MWGui void WindowManager::interactiveMessageBox( std::string_view message, const std::vector& buttons, bool block) { - mMessageBoxManager->createInteractiveMessageBox(message, buttons); + mMessageBoxManager->createInteractiveMessageBox(message, buttons, block); updateVisible(); if (block) @@ -779,6 +779,8 @@ namespace MWGui frameRateLimiter.limit(); } + + mMessageBoxManager->resetInteractiveMessageBox(); } } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 5ee5b21aa2..757021805f 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -716,8 +716,9 @@ bool MWState::StateManager::confirmLoading(const std::vector& while (true) { - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true); - int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + auto windowManager = MWBase::Environment::get().getWindowManager(); + windowManager->interactiveMessageBox(message, buttons, true); + int selectedButton = windowManager->readPressedButton(); if (selectedButton == 0) break; From 9bbb89e2682c76b994ffada36dfe24656f2fda8e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 18 Nov 2023 17:57:15 +0400 Subject: [PATCH 0502/2167] Allow to set default focus for interactive messagebox --- apps/openmw/mwbase/windowmanager.hpp | 4 ++-- apps/openmw/mwgui/messagebox.cpp | 11 ++++++++--- apps/openmw/mwgui/messagebox.hpp | 7 ++++--- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 4 ++-- apps/openmw/mwstate/statemanagerimp.cpp | 5 +++-- 6 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index f225ebf24e..0c60fe9778 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -254,8 +254,8 @@ namespace MWBase = 0; virtual void staticMessageBox(std::string_view message) = 0; virtual void removeStaticMessageBox() = 0; - virtual void interactiveMessageBox( - std::string_view message, const std::vector& buttons = {}, bool block = false) + virtual void interactiveMessageBox(std::string_view message, const std::vector& buttons = {}, + bool block = false, int defaultFocus = -1) = 0; /// returns the index of the pressed button or -1 if no button was pressed diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 9d1d98aa4e..b22fb873fa 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -126,7 +126,7 @@ namespace MWGui } bool MessageBoxManager::createInteractiveMessageBox( - std::string_view message, const std::vector& buttons, bool immediate) + std::string_view message, const std::vector& buttons, bool immediate, int defaultFocus) { if (mInterMessageBoxe != nullptr) { @@ -134,7 +134,8 @@ namespace MWGui mInterMessageBoxe->setVisible(false); } - mInterMessageBoxe = std::make_unique(*this, std::string{ message }, buttons, immediate); + mInterMessageBoxe + = std::make_unique(*this, std::string{ message }, buttons, immediate, defaultFocus); mLastButtonPressed = -1; return true; @@ -215,12 +216,13 @@ namespace MWGui } InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons, bool immediate) + const std::vector& buttons, bool immediate, int defaultFocus) : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) + , mDefaultFocus(defaultFocus) , mImmediate(immediate) { int textPadding = 10; // padding between text-widget and main-widget @@ -378,6 +380,9 @@ namespace MWGui MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { std::vector keywords{ "sOk", "sYes" }; + if (mDefaultFocus >= 0 && mDefaultFocus < static_cast(mButtons.size())) + return mButtons[mDefaultFocus]; + for (MyGUI::Button* button : mButtons) { for (const std::string& keyword : keywords) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 742d355c60..bb61bd6bd9 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -25,8 +25,8 @@ namespace MWGui void onFrame(float frameDuration); void createMessageBox(std::string_view message, bool stat = false); void removeStaticMessageBox(); - bool createInteractiveMessageBox( - std::string_view message, const std::vector& buttons, bool immediate = false); + bool createInteractiveMessageBox(std::string_view message, const std::vector& buttons, + bool immediate = false, int defaultFocus = -1); bool isInteractiveMessageBox(); int getMessagesCount(); @@ -93,7 +93,7 @@ namespace MWGui { public: InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, - const std::vector& buttons, bool immediate); + const std::vector& buttons, bool immediate, int defaultFocus); void mousePressed(MyGUI::Widget* _widget); int readPressedButton(); @@ -112,6 +112,7 @@ namespace MWGui std::vector mButtons; int mButtonPressed; + int mDefaultFocus; bool mImmediate; }; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 71c28230a4..c6a69af2c0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -744,9 +744,9 @@ namespace MWGui } void WindowManager::interactiveMessageBox( - std::string_view message, const std::vector& buttons, bool block) + std::string_view message, const std::vector& buttons, bool block, int defaultFocus) { - mMessageBoxManager->createInteractiveMessageBox(message, buttons, block); + mMessageBoxManager->createInteractiveMessageBox(message, buttons, block, defaultFocus); updateVisible(); if (block) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5f6b12b7e5..7ee0554a26 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -268,8 +268,8 @@ namespace MWGui enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override; void staticMessageBox(std::string_view message) override; void removeStaticMessageBox() override; - void interactiveMessageBox( - std::string_view message, const std::vector& buttons = {}, bool block = false) override; + void interactiveMessageBox(std::string_view message, const std::vector& buttons = {}, + bool block = false, int defaultFocus = -1) override; int readPressedButton() override; ///< returns the index of the pressed button or -1 if no button was pressed ///< (->MessageBoxmanager->InteractiveMessageBox) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 757021805f..826c0dbba6 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -714,11 +714,12 @@ bool MWState::StateManager::confirmLoading(const std::vector& message += l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast(missingFiles.size()) }); + int selectedButton = -1; while (true) { auto windowManager = MWBase::Environment::get().getWindowManager(); - windowManager->interactiveMessageBox(message, buttons, true); - int selectedButton = windowManager->readPressedButton(); + windowManager->interactiveMessageBox(message, buttons, true, selectedButton); + selectedButton = windowManager->readPressedButton(); if (selectedButton == 0) break; From 9c526b66398a6b9d3b0f7e5311cf15a0e0d22caf Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Feb 2023 12:50:11 +0100 Subject: [PATCH 0503/2167] Add Navigator test for zero distance path --- .../detournavigator/navigator.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index a93693c08b..93fb41b5e4 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -177,6 +177,31 @@ namespace << mPath; } + TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point) + { + constexpr std::array heightfieldData{ { + 0, 0, 0, 0, 0, // row 0 + 0, -25, -25, -25, -25, // row 1 + 0, -25, -100, -100, -100, // row 2 + 0, -25, -100, -100, -100, // row 3 + 0, -25, -100, -100, -100, // row 4 + } }; + const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + + ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); + auto updateGuard = mNavigator->makeUpdateGuard(); + mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get()); + mNavigator->update(mPlayerPosition, updateGuard.get()); + updateGuard.reset(); + mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener); + + EXPECT_EQ(findPath(*mNavigator, mAgentBounds, mStart, mStart, Flag_walk, mAreaCosts, mEndTolerance, mOut), + Status::Success); + + EXPECT_THAT(mPath, ElementsAre(Vec3fEq(56.66666412353515625, 460, 1.99998295307159423828125))) << mPath; + } + TEST_F(DetourNavigatorNavigatorTest, add_object_should_change_navmesh) { mSettings.mWaitUntilMinDistanceToPlayer = 0; From 40688c0e7c1adb9fe125e9fd9a00c652aaccd596 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Feb 2023 12:51:12 +0100 Subject: [PATCH 0504/2167] Rename findPath to findPolygonPath to avoid name collision with other findPath --- components/detournavigator/findsmoothpath.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/detournavigator/findsmoothpath.hpp b/components/detournavigator/findsmoothpath.hpp index 8b6889cc07..e5efa8815f 100644 --- a/components/detournavigator/findsmoothpath.hpp +++ b/components/detournavigator/findsmoothpath.hpp @@ -64,7 +64,7 @@ namespace DetourNavigator std::reference_wrapper mSettings; }; - inline std::optional findPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, + inline std::optional findPolygonPath(const dtNavMeshQuery& navMeshQuery, const dtPolyRef startRef, const dtPolyRef endRef, const osg::Vec3f& startPos, const osg::Vec3f& endPos, const dtQueryFilter& queryFilter, std::span pathBuffer) { @@ -132,7 +132,7 @@ namespace DetourNavigator std::vector polygonPath(settings.mMaxPolygonPathSize); const auto polygonPathSize - = findPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, polygonPath); + = findPolygonPath(navMeshQuery, startRef, endRef, startNavMeshPos, endNavMeshPos, queryFilter, polygonPath); if (!polygonPathSize.has_value()) return Status::FindPathOverPolygonsFailed; From 1322f7b75be703a65534b12a15d7289e74d6b7f6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Feb 2023 13:49:01 +0100 Subject: [PATCH 0505/2167] Deduplicate height field data definition --- .../detournavigator/navigator.cpp | 143 ++++-------------- 1 file changed, 31 insertions(+), 112 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 93fb41b5e4..29639c6c06 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -70,6 +70,22 @@ namespace } }; + constexpr std::array defaultHeightfieldData{ { + 0, 0, 0, 0, 0, // row 0 + 0, -25, -25, -25, -25, // row 1 + 0, -25, -100, -100, -100, // row 2 + 0, -25, -100, -100, -100, // row 3 + 0, -25, -100, -100, -100, // row 4 + } }; + + constexpr std::array defaultHeightfieldDataScalar{ { + 0, 0, 0, 0, 0, // row 0 + 0, -25, -25, -25, -25, // row 1 + 0, -25, -100, -100, -100, // row 2 + 0, -25, -100, -100, -100, // row 3 + 0, -25, -100, -100, -100, // row 4 + } }; + template std::unique_ptr makeSquareHeightfieldTerrainShape( const std::array& values, btScalar heightScale = 1, int upAxis = 2, @@ -150,14 +166,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) { - constexpr std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); @@ -179,14 +188,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point) { - constexpr std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); @@ -208,14 +210,7 @@ namespace mNavigator.reset(new NavigatorImpl( mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); @@ -260,14 +255,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); @@ -313,14 +301,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_objects_should_use_higher) { - const std::array heightfieldData1{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - CollisionShapeInstance heightfield1(makeSquareHeightfieldTerrainShape(heightfieldData1)); + CollisionShapeInstance heightfield1(makeSquareHeightfieldTerrainShape(defaultHeightfieldDataScalar)); heightfield1.shape().setLocalScaling(btVector3(128, 128, 1)); const std::array heightfieldData2{ { @@ -353,14 +334,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed) { - const std::array heightfieldData1{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(heightfieldData1); + const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); const std::array heightfieldData2{ { @@ -391,14 +365,8 @@ namespace { osg::ref_ptr bulletShape(new Resource::BulletShape); - std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - std::unique_ptr shapePtr = makeSquareHeightfieldTerrainShape(heightfieldData); + std::unique_ptr shapePtr + = makeSquareHeightfieldTerrainShape(defaultHeightfieldDataScalar); shapePtr->setLocalScaling(btVector3(128, 128, 1)); bulletShape->mCollisionShape.reset(shapePtr.release()); @@ -567,14 +535,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_object_remove_and_update_then_find_path_should_return_path) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - CollisionShapeInstance heightfield(makeSquareHeightfieldTerrainShape(heightfieldData)); + CollisionShapeInstance heightfield(makeSquareHeightfieldTerrainShape(defaultHeightfieldDataScalar)); heightfield.shape().setLocalScaling(btVector3(128, 128, 1)); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); @@ -604,14 +565,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); @@ -674,14 +628,7 @@ namespace mNavigator.reset(new NavigatorImpl( mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); @@ -770,14 +717,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); @@ -796,14 +736,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); @@ -862,14 +795,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); @@ -895,14 +821,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) { - const std::array heightfieldData{ { - 0, 0, 0, 0, 0, // row 0 - 0, -25, -25, -25, -25, // row 1 - 0, -25, -100, -100, -100, // row 2 - 0, -25, -100, -100, -100, // row 3 - 0, -25, -100, -100, -100, // row 4 - } }; - const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); From 94b085af9ed01234597a6c94a8a25cd0361def93 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 4 Feb 2023 13:49:31 +0100 Subject: [PATCH 0506/2167] Add Navigator and Lua API function to find nearest position on navmesh --- apps/openmw/mwlua/nearbybindings.cpp | 37 +++++++++++++ .../detournavigator/navigator.cpp | 54 +++++++++++++++++++ components/detournavigator/navigatorutils.cpp | 40 ++++++++++++++ components/detournavigator/navigatorutils.hpp | 23 +++++--- files/lua_api/openmw/nearby.lua | 29 ++++++++++ .../integration_tests/test_lua_api/player.lua | 23 ++++++-- .../integration_tests/test_lua_api/test.lua | 4 ++ .../testing_util/testing_util.lua | 4 ++ 8 files changed, 205 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 86c8ef31e8..7e1845aeac 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -3,11 +3,15 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" +#include "../mwworld/cell.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/scene.hpp" #include "luamanagerimp.hpp" #include "objectlists.hpp" @@ -262,6 +266,39 @@ namespace MWLua *MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, from, to, includeFlags); }; + api["findNearestNavMeshPosition"] = [](const osg::Vec3f& position, const sol::optional& options) { + DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; + std::optional searchAreaHalfExtents; + DetourNavigator::Flags includeFlags = defaultIncludeFlags; + + if (options.has_value()) + { + if (const auto& t = options->get>("agentBounds")) + { + if (const auto& v = t->get>("shapeType")) + agentBounds.mShapeType = *v; + if (const auto& v = t->get>("halfExtents")) + agentBounds.mHalfExtents = *v; + } + if (const auto& v = options->get>("searchAreaHalfExtents")) + searchAreaHalfExtents = *v; + if (const auto& v = options->get>("includeFlags")) + includeFlags = *v; + } + + if (!searchAreaHalfExtents.has_value()) + { + const bool isEsm4 = MWBase::Environment::get().getWorldScene()->getCurrentCell()->getCell()->isEsm4(); + const float halfExtents = isEsm4 + ? (1 + 2 * Constants::ESM4CellGridRadius) * Constants::ESM4CellSizeInUnits + : (1 + 2 * Constants::CellGridRadius) * Constants::CellSizeInUnits; + searchAreaHalfExtents = osg::Vec3f(halfExtents, halfExtents, halfExtents); + } + + return DetourNavigator::findNearestNavMeshPosition(*MWBase::Environment::get().getWorld()->getNavigator(), + agentBounds, position, *searchAreaHalfExtents, includeFlags); + }; + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 29639c6c06..df4d7a1e99 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -944,4 +944,58 @@ namespace INSTANTIATE_TEST_SUITE_P(NotSupportedAgentBounds, DetourNavigatorNavigatorNotSupportedAgentBoundsTest, ValuesIn(notSupportedAgentBounds)); + + TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nav_mesh_position) + { + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + + ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); + auto updateGuard = mNavigator->makeUpdateGuard(); + mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get()); + mNavigator->update(mPlayerPosition, updateGuard.get()); + updateGuard.reset(); + mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener); + + const osg::Vec3f position(250, 250, 0); + const osg::Vec3f searchAreaHalfExtents(1000, 1000, 1000); + EXPECT_THAT(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_walk), + Optional(Vec3fEq(250, 250, -62.5186))); + } + + TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far) + { + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + + ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); + auto updateGuard = mNavigator->makeUpdateGuard(); + mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get()); + mNavigator->update(mPlayerPosition, updateGuard.get()); + updateGuard.reset(); + mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener); + + const osg::Vec3f position(250, 250, 250); + const osg::Vec3f searchAreaHalfExtents(100, 100, 100); + EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_walk), + std::nullopt); + } + + TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_flags_do_not_match) + { + const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); + const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + + ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); + auto updateGuard = mNavigator->makeUpdateGuard(); + mNavigator->addHeightfield(mCellPosition, cellSize, surface, updateGuard.get()); + mNavigator->update(mPlayerPosition, updateGuard.get()); + updateGuard.reset(); + mNavigator->wait(WaitConditionType::requiredTilesPresent, &mListener); + + const osg::Vec3f position(250, 250, 0); + const osg::Vec3f searchAreaHalfExtents(1000, 1000, 1000); + EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim), + std::nullopt); + } } diff --git a/components/detournavigator/navigatorutils.cpp b/components/detournavigator/navigatorutils.cpp index cad201825c..2f9fbbb32e 100644 --- a/components/detournavigator/navigatorutils.cpp +++ b/components/detournavigator/navigatorutils.cpp @@ -1,8 +1,11 @@ #include "navigatorutils.hpp" +#include "debug.hpp" #include "findrandompointaroundcircle.hpp" #include "navigator.hpp" #include "raycast.hpp" +#include + namespace DetourNavigator { std::optional findRandomPointAroundCircle(const Navigator& navigator, const AgentBounds& agentBounds, @@ -37,4 +40,41 @@ namespace DetourNavigator return std::nullopt; return fromNavMeshCoordinates(settings.mRecast, *result); } + + std::optional findNearestNavMeshPosition(const Navigator& navigator, const AgentBounds& agentBounds, + const osg::Vec3f& position, const osg::Vec3f& searchAreaHalfExtents, const Flags includeFlags) + { + const auto navMesh = navigator.getNavMesh(agentBounds); + if (navMesh == nullptr) + return std::nullopt; + + const auto& settings = navigator.getSettings(); + const osg::Vec3f navMeshPosition = toNavMeshCoordinates(settings.mRecast, position); + const auto lockedNavMesh = navMesh->lockConst(); + + dtNavMeshQuery navMeshQuery; + if (const dtStatus status + = navMeshQuery.init(&lockedNavMesh->getImpl(), settings.mDetour.mMaxNavMeshQueryNodes); + dtStatusFailed(status)) + { + Log(Debug::Error) << "Failed to init dtNavMeshQuery for findNearestNavMeshPosition: " + << WriteDtStatus{ status }; + return std::nullopt; + } + + dtQueryFilter queryFilter; + queryFilter.setIncludeFlags(includeFlags); + + osg::Vec3f nearestNavMeshPos; + const osg::Vec3f endPolyHalfExtents = toNavMeshCoordinates(settings.mRecast, searchAreaHalfExtents); + dtPolyRef polyRef; + if (const dtStatus status = navMeshQuery.findNearestPoly( + navMeshPosition.ptr(), endPolyHalfExtents.ptr(), &queryFilter, &polyRef, nearestNavMeshPos.ptr()); + dtStatusFailed(status) || polyRef == 0) + { + return std::nullopt; + } + + return fromNavMeshCoordinates(settings.mRecast, nearestNavMeshPos); + } } diff --git a/components/detournavigator/navigatorutils.hpp b/components/detournavigator/navigatorutils.hpp index ef6ae96313..ca02682ecd 100644 --- a/components/detournavigator/navigatorutils.hpp +++ b/components/detournavigator/navigatorutils.hpp @@ -16,10 +16,10 @@ namespace DetourNavigator { /** * @brief findPath fills output iterator with points of scene surfaces to be used for actor to walk through. - * @param agentBounds allows to find navmesh for given actor. + * @param agentBounds defines which navmesh to use. * @param start path from given point. * @param end path at given point. - * @param includeFlags setup allowed surfaces for actor to walk. + * @param includeFlags setup allowed navmesh areas. * @param out the beginning of the destination range. * @param endTolerance defines maximum allowed distance to end path point in addition to agentHalfExtents * @return Status. @@ -42,10 +42,10 @@ namespace DetourNavigator /** * @brief findRandomPointAroundCircle returns random location on navmesh within the reach of specified location. - * @param agentBounds allows to find navmesh for given actor. + * @param agentBounds defines which navmesh to use. * @param start is a position where the search starts. * @param maxRadius limit maximum distance from start. - * @param includeFlags setup allowed surfaces for actor to walk. + * @param includeFlags setup allowed navmesh areas. * @return not empty optional with position if point is found and empty optional if point is not found. */ std::optional findRandomPointAroundCircle(const Navigator& navigator, const AgentBounds& agentBounds, @@ -53,14 +53,25 @@ namespace DetourNavigator /** * @brief raycast finds farest navmesh point from start on a line from start to end that has path from start. - * @param agentBounds allows to find navmesh for given actor. + * @param agentBounds defines which navmesh to use. * @param start of the line * @param end of the line - * @param includeFlags setup allowed surfaces for actor to walk. + * @param includeFlags setup allowed navmesh areas. * @return not empty optional with position if point is found and empty optional if point is not found. */ std::optional raycast(const Navigator& navigator, const AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags); + + /** + * @brief findNearestNavMeshPosition finds nearest position on navmesh within given area having given flags. + * @param agentBounds defines which navmesh to use. + * @param position is a center of the search area. + * @param searchAreaHalfExtents defines AABB like area around given postion. + * @param includeFlags setup allowed navmesh areas. + * @return not empty optional with position if position is found and empty optional if position is not found. + */ + std::optional findNearestNavMeshPosition(const Navigator& navigator, const AgentBounds& agentBounds, + const osg::Vec3f& position, const osg::Vec3f& searchAreaHalfExtents, const Flags includeFlags); } #endif diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 2e9abb06f6..70b09efd90 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -182,6 +182,17 @@ -- values (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + @{#NAVIGATOR_FLAGS.OpenDoor} -- + @{#NAVIGATOR_FLAGS.UsePathgrid}). +--- +-- A table of parameters for @{#nearby.findNearestNavMeshPosition} +-- @type FindNearestNavMeshPositionOptions +-- @field [parent=#NavMeshOptions] #AgentBounds agentBounds Identifies which navmesh to use. +-- @field [parent=#NavMeshOptions] #number includeFlags Allowed areas for agent to move, a sum of @{#NAVIGATOR_FLAGS} +-- values (default: @{#NAVIGATOR_FLAGS.Walk} + @{#NAVIGATOR_FLAGS.Swim} + @{#NAVIGATOR_FLAGS.OpenDoor} +-- + @{#NAVIGATOR_FLAGS.UsePathgrid}). +-- @field [parent=#NavMeshOptions] openmw.util#Vector3 searchAreaHalfExtents Defines AABB like area half extents around +-- given position (default: (1 + 2 * CellGridRadius) * CellSize * (1, 1, 1) where CellGridRadius and depends on cell +-- type to cover the whole active grid). + --- -- Find path over navigation mesh from source to destination with given options. Result is unstable since navigation -- mesh generation is asynchronous. @@ -234,4 +245,22 @@ -- agentBounds = Actor.getPathfindingAgentBounds(self), -- }) +--- +-- Finds a nearest position on navigation mesh to the given position within given search area. +-- @function [parent=#nearby] findNearestNavMeshPosition +-- @param openmw.util#Vector3 position Search area center. +-- @param #FindNearestNavMeshPositionOptions options An optional table with additional optional arguments. +-- @return openmw.util#Vector3, #nil +-- @usage local navMeshPosition = nearby.findNearestNavMeshPosition(position) +-- @usage local navMeshPosition = nearby.findNearestNavMeshPosition(position, { +-- includeFlags = nearby.NAVIGATOR_FLAGS.Swim, +-- }) +-- @usage local navMeshPosition = nearby.findNearestNavMeshPosition(position, { +-- agentBounds = Actor.getPathfindingAgentBounds(self), +-- }) +-- @usage local navMeshPosition = nearby.findNearestNavMeshPosition(position, { +-- searchAreaHalfExtents = util.vector3(1000, 1000, 1000), +-- includeFlags = nearby.NAVIGATOR_FLAGS.Walk, +-- }) + return nil diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 93c16a8b88..41022828f9 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -82,7 +82,8 @@ testing.registerLocalTest('findPath', } local status, path = nearby.findPath(src, dst, options) testing.expectEqual(status, nearby.FIND_PATH_STATUS.Success, 'Status') - testing.expectLessOrEqual((path[path:size()] - dst):length(), 1, 'Last path point') + testing.expectLessOrEqual((path[path:size()] - dst):length(), 1, + 'Last path point ' .. testing.formatActualExpected(path[path:size()], dst)) end) testing.registerLocalTest('findRandomPointAroundCircle', @@ -94,7 +95,8 @@ testing.registerLocalTest('findRandomPointAroundCircle', includeFlags = nearby.NAVIGATOR_FLAGS.Walk, } local result = nearby.findRandomPointAroundCircle(position, maxRadius, options) - testing.expectGreaterThan((result - position):length(), 1, 'Random point') + testing.expectGreaterThan((result - position):length(), 1, + 'Random point ' .. testing.formatActualExpected(result, position)) end) testing.registerLocalTest('castNavigationRay', @@ -106,7 +108,22 @@ testing.registerLocalTest('castNavigationRay', includeFlags = nearby.NAVIGATOR_FLAGS.Walk + nearby.NAVIGATOR_FLAGS.Swim, } local result = nearby.castNavigationRay(src, dst, options) - testing.expectLessOrEqual((result - dst):length(), 1, 'Navigation hit point') + testing.expectLessOrEqual((result - dst):length(), 1, + 'Navigation hit point ' .. testing.formatActualExpected(result, dst)) + end) + +testing.registerLocalTest('findNearestNavMeshPosition', + function() + local position = util.vector3(4096, 4096, 1000) + local options = { + agentBounds = types.Actor.getPathfindingAgentBounds(self), + includeFlags = nearby.NAVIGATOR_FLAGS.Walk + nearby.NAVIGATOR_FLAGS.Swim, + searchAreaHalfExtents = util.vector3(1000, 1000, 1000), + } + local result = nearby.findNearestNavMeshPosition(position, options) + local expected = util.vector3(4096, 4096, 872.674) + testing.expectLessOrEqual((result - expected):length(), 1, + 'Navigation mesh position ' .. testing.formatActualExpected(result, expected)) end) return { diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index acc43eca2a..2ec9f09b97 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -95,6 +95,10 @@ tests = { initPlayer() testing.runLocalTest(player, 'castNavigationRay') end}, + {'findNearestNavMeshPosition', function() + initPlayer() + testing.runLocalTest(player, 'findNearestNavMeshPosition') + end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, } diff --git a/scripts/data/integration_tests/testing_util/testing_util.lua b/scripts/data/integration_tests/testing_util/testing_util.lua index db67cd3b1a..f73ea83e79 100644 --- a/scripts/data/integration_tests/testing_util/testing_util.lua +++ b/scripts/data/integration_tests/testing_util/testing_util.lua @@ -154,6 +154,10 @@ function M.expectThat(value, matcher, msg) end end +function M.formatActualExpected(actual, expected) + return string.format('actual: %s, expected: %s', actual, expected) +end + local localTests = {} local localTestRunner = nil From 1b820b980aa6f9bbdfdd8dcd96c1e673579650fb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 19 Nov 2023 10:25:20 +0100 Subject: [PATCH 0507/2167] Don't try to escort to nowhere --- apps/openmw/mwmechanics/aiescort.cpp | 19 +++++++++++++------ apps/openmw/mwmechanics/aiescort.hpp | 3 --- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index e1d657a207..380c0ace9f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -30,8 +30,6 @@ namespace MWMechanics , mZ(z) , mDuration(duration) , mRemainingDuration(static_cast(duration)) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; } @@ -45,8 +43,6 @@ namespace MWMechanics , mZ(z) , mDuration(duration) , mRemainingDuration(static_cast(duration)) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) { mTargetActorRefId = actorId; } @@ -59,8 +55,6 @@ namespace MWMechanics , mZ(escort->mData.mZ) , mDuration(escort->mData.mDuration) , mRemainingDuration(escort->mRemainingDuration) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) { mTargetActorRefId = escort->mTargetId; mTargetActorId = escort->mTargetActorId; @@ -96,6 +90,19 @@ namespace MWMechanics if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist) { + // TESCS allows the creation of Escort packages without a specific destination + constexpr float nowhere = std::numeric_limits::max(); + if (mX == nowhere || mY == nowhere) + return true; + if (mZ == nowhere) + { + if (mCellId.empty() + && ESM::positionToExteriorCellLocation(mX, mY) + == actor.getCell()->getCell()->getExteriorCellLocation()) + return false; + return true; + } + const osg::Vec3f dest(mX, mY, mZ); if (pathTo(actor, dest, duration, characterController.getSupportedMovementDirections(), maxHalfExtent)) { diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index e22752446d..709b2bee59 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -59,9 +59,6 @@ namespace MWMechanics float mMaxDist = 450; const float mDuration; // In hours float mRemainingDuration; // In hours - - const int mCellX; - const int mCellY; }; } #endif From 8ca6f1ad4940d98afca21aeb491e12cff7eda801 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 19 Nov 2023 10:53:50 +0100 Subject: [PATCH 0508/2167] Use destination cell --- apps/openmw/mwmechanics/aisequence.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index de626ace95..af35be3763 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -467,7 +467,7 @@ namespace MWMechanics { ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(ESM::RefId::stringRefId(data.mId.toStringView()), - data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + esmPackage.mCellName, data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } else if (esmPackage.mType == ESM::AI_Travel) { @@ -484,7 +484,7 @@ namespace MWMechanics { ESM::AITarget data = esmPackage.mTarget; package = std::make_unique(ESM::RefId::stringRefId(data.mId.toStringView()), - data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); + esmPackage.mCellName, data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); } onPackageAdded(*package); From a96c038f19868b43b079943c11340ac33b7f0945 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sun, 19 Nov 2023 19:10:12 +0800 Subject: [PATCH 0509/2167] prefer previous tools over best tools --- apps/openmw/mwmechanics/alchemy.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index f74c08da2a..5f8ffc1750 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -368,6 +368,8 @@ void MWMechanics::Alchemy::setAlchemist(const MWWorld::Ptr& npc) mTools.resize(4); + std::vector prevTools(mTools); + std::fill(mTools.begin(), mTools.end(), MWWorld::Ptr()); mEffects.clear(); @@ -384,6 +386,12 @@ void MWMechanics::Alchemy::setAlchemist(const MWWorld::Ptr& npc) if (type < 0 || type >= static_cast(mTools.size())) throw std::runtime_error("invalid apparatus type"); + if (prevTools[type] == *iter) + mTools[type] = *iter; // prefer the previous tool if still in the container + + if (!mTools[type].isEmpty() && !prevTools[type].isEmpty() && mTools[type] == prevTools[type]) + continue; + if (!mTools[type].isEmpty()) if (ref->mBase->mData.mQuality <= mTools[type].get()->mBase->mData.mQuality) continue; @@ -415,7 +423,6 @@ MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients( void MWMechanics::Alchemy::clear() { mAlchemist = MWWorld::Ptr(); - mTools.clear(); mIngredients.clear(); mEffects.clear(); setPotionName(""); From 81fa7836fefd6bf20c55c7b52f852e91f98ecb3a Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Nov 2023 00:36:53 +0100 Subject: [PATCH 0510/2167] Remove unused variable --- components/detournavigator/navmeshmanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 5c93aee5e7..9fda0566d9 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -177,7 +177,6 @@ namespace DetourNavigator const std::map& changedTiles) { std::map tilesToPost = changedTiles; - std::map tilesToPost1; { const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); From 9ebbdc3a22fafd5850b41d9a28376d0dbc5eccb5 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Mon, 20 Nov 2023 15:59:11 +0800 Subject: [PATCH 0511/2167] expell->expel --- apps/openmw/mwlua/types/npc.cpp | 2 +- files/lua_api/openmw/types.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 25997b6468..67080304c2 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -265,7 +265,7 @@ namespace MWLua npcStats.setFactionReputation(factionId, existingReputation + value); }; - npc["expell"] = [](Object& actor, std::string_view faction) { + npc["expel"] = [](Object& actor, std::string_view faction) { if (dynamic_cast(&actor) && !dynamic_cast(&actor)) throw std::runtime_error("Local scripts can modify only self"); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 17c2991d87..2a352b58f1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -837,14 +837,14 @@ -- NPC.modifyFactionReputation(player, "mages guild", 5); --- --- Expell NPC from given faction. +-- Expel NPC from given faction. -- Throws an exception if there is no such faction. -- Note: expelled NPC still keeps his rank and reputation in faction, he just get an additonal flag for given faction. --- @function [parent=#NPC] expell +-- @function [parent=#NPC] expel -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID -- @usage local NPC = require('openmw.types').NPC; --- NPC.expell(player, "mages guild"); +-- NPC.expel(player, "mages guild"); --- -- Clear expelling of NPC from given faction. From 5cc1b117eaf5d00303663c3dd2eb8bc2347212eb Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 21 Nov 2023 03:00:36 +0000 Subject: [PATCH 0512/2167] Fix(Log): Don't Put Timestamps Mid-Message --- apps/openmw/mwgui/debugwindow.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index 5d3948e76d..59f695e7f8 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -129,6 +129,7 @@ namespace MWGui static std::mutex sBufferMutex; static int64_t sLogStartIndex; static int64_t sLogEndIndex; + static bool hasPrefix = false; void DebugWindow::startLogRecording() { @@ -170,11 +171,17 @@ namespace MWGui addChar(c); if (c == '#') addChar(c); + if (c == '\n') + hasPrefix = false; } }; for (char c : color) addChar(c); - addShieldedStr(prefix); + if (!hasPrefix) + { + addShieldedStr(prefix); + hasPrefix = true; + } addShieldedStr(msg); if (bufferOverflow) sLogStartIndex = (sLogEndIndex + 1) % bufSize; From c9eaeb47d5d28547ea13c368f955efc070309b20 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 21 Nov 2023 03:04:24 +0000 Subject: [PATCH 0513/2167] Minor API inconsistencies --- apps/openmw/mwlua/objectbindings.cpp | 12 +++++++++--- components/lua/utilpackage.cpp | 3 ++- files/lua_api/openmw/core.lua | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 7db7877245..2f3a020971 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -312,11 +312,17 @@ namespace MWLua }; objectT["ownerFactionId"] = sol::property(getOwnerFactionId, setOwnerFactionId); - auto getOwnerFactionRank = [](const ObjectT& o) -> int { return o.ptr().getCellRef().getFactionRank(); }; - auto setOwnerFactionRank = [](const ObjectT& object, int factionRank) { + auto getOwnerFactionRank = [](const ObjectT& o) -> sol::optional { + int rank = o.ptr().getCellRef().getFactionRank(); + if (rank < 0) + return sol::nullopt; + else + return rank; + }; + auto setOwnerFactionRank = [](const ObjectT& object, sol::optional factionRank) { if (std::is_same_v && !dynamic_cast(&object)) throw std::runtime_error("Local scripts can set an owner faction rank only on self"); - object.ptr().getCellRef().setFactionRank(factionRank); + object.ptr().getCellRef().setFactionRank(factionRank.value_or(-1)); }; objectT["ownerFactionRank"] = sol::property(getOwnerFactionRank, setOwnerFactionRank); diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 85c5dcdbc1..e9b8e886d2 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -133,7 +133,8 @@ namespace LuaUtil // Lua bindings for Box util["box"] = sol::overload([](const Vec3& center, const Vec3& halfSize) { return Box(center, halfSize); }, - [](const TransformM& transform) { return Box(transform.mM); }); + [](const TransformM& transform) { return Box(transform.mM); }, + [](const TransformQ& transform) { return Box(Vec3(), Vec3(1, 1, 1), transform.mQ); }); sol::usertype boxType = lua.new_usertype("Box"); boxType["center"] = sol::readonly_property([](const Box& b) { return b.mCenter; }); boxType["halfSize"] = sol::readonly_property([](const Box& b) { return b.mHalfSize; }); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 44ab4473fa..ccac83bd4b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -161,7 +161,7 @@ -- @field openmw.util#Transform startingRotation The object original rotation -- @field #string ownerRecordId NPC who owns the object (nil if missing). Global and self scripts can set the value. -- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value. --- @field #number ownerFactionRank Rank required to be allowed to pick up the object. Global and self scripts can set the value. +-- @field #number ownerFactionRank Rank required to be allowed to pick up the object (`nil` if any rank is allowed). Global and self scripts can set the value. -- @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 #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). From da9b0c5119c2521a2bd98cb375157ef5dd57473e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 22 Nov 2023 22:02:06 +0100 Subject: [PATCH 0514/2167] Use MyGUI::UString's new string_view support --- apps/openmw/mwclass/activator.cpp | 4 +--- apps/openmw/mwclass/apparatus.cpp | 4 +--- apps/openmw/mwclass/armor.cpp | 4 +--- apps/openmw/mwclass/book.cpp | 4 +--- apps/openmw/mwclass/clothing.cpp | 4 +--- apps/openmw/mwclass/container.cpp | 3 +-- apps/openmw/mwclass/creature.cpp | 3 +-- apps/openmw/mwclass/door.cpp | 3 +-- apps/openmw/mwclass/esm4base.cpp | 4 +--- apps/openmw/mwclass/ingredient.cpp | 4 +--- apps/openmw/mwclass/light.cpp | 4 +--- apps/openmw/mwclass/lockpick.cpp | 4 +--- apps/openmw/mwclass/misc.cpp | 5 ++--- apps/openmw/mwclass/npc.cpp | 3 +-- apps/openmw/mwclass/potion.cpp | 4 +--- apps/openmw/mwclass/probe.cpp | 4 +--- apps/openmw/mwclass/repair.cpp | 4 +--- apps/openmw/mwclass/weapon.cpp | 4 +--- apps/openmw/mwgui/alchemywindow.cpp | 9 ++++----- apps/openmw/mwgui/birth.cpp | 10 +++++----- apps/openmw/mwgui/class.cpp | 11 +++++------ apps/openmw/mwgui/dialogue.cpp | 2 +- apps/openmw/mwgui/enchantingdialog.cpp | 20 +++++++++++--------- apps/openmw/mwgui/itemchargeview.cpp | 5 ++--- apps/openmw/mwgui/layout.cpp | 9 +++------ apps/openmw/mwgui/levelupdialog.cpp | 3 +-- apps/openmw/mwgui/loadingscreen.cpp | 2 +- apps/openmw/mwgui/messagebox.cpp | 10 +++++----- apps/openmw/mwgui/race.cpp | 8 ++++---- apps/openmw/mwgui/review.cpp | 5 ++--- apps/openmw/mwgui/savegamedialog.cpp | 5 ++--- apps/openmw/mwgui/settingswindow.cpp | 10 ++++------ apps/openmw/mwgui/statswindow.cpp | 5 ++--- apps/openmw/mwgui/textinput.cpp | 6 ++---- apps/openmw/mwgui/tooltips.cpp | 3 +-- apps/openmw/mwgui/ustring.hpp | 15 --------------- apps/openmw/mwgui/widgets.cpp | 9 +++------ apps/openmw/mwgui/windowmanagerimp.cpp | 7 +++---- 38 files changed, 80 insertions(+), 143 deletions(-) delete mode 100644 apps/openmw/mwgui/ustring.hpp diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 9e99b4cacb..96f5d5e993 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -27,7 +27,6 @@ #include "../mwrender/vismask.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwmechanics/npcstats.hpp" @@ -102,8 +101,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 2fbe2f9f87..28a53b85d2 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -15,7 +15,6 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "classmodel.hpp" @@ -92,8 +91,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 54561e3b0f..dbe4c6add6 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -24,7 +24,6 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "classmodel.hpp" @@ -217,8 +216,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index b2b65e01b2..51ad0b6252 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -19,7 +19,6 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwmechanics/npcstats.hpp" @@ -111,8 +110,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index cbc5cecb70..fa866b299a 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -16,7 +16,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -154,8 +153,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index db9e2fc08d..8197701b07 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -24,7 +24,6 @@ #include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" @@ -249,7 +248,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); std::string text; int lockLevel = ptr.getCellRef().getLockLevel(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index eebcb99512..ee4c36ad2c 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -45,7 +45,6 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "classmodel.hpp" @@ -582,7 +581,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ecd6cb59aa..ef928ecc89 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -25,7 +25,6 @@ #include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/objects.hpp" @@ -267,7 +266,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); std::string text; diff --git a/apps/openmw/mwclass/esm4base.cpp b/apps/openmw/mwclass/esm4base.cpp index 956fc210ee..fe75aab61e 100644 --- a/apps/openmw/mwclass/esm4base.cpp +++ b/apps/openmw/mwclass/esm4base.cpp @@ -5,7 +5,6 @@ #include #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -35,8 +34,7 @@ namespace MWClass MWGui::ToolTipInfo ESM4Impl::getToolTipInfo(std::string_view name, int count) { MWGui::ToolTipInfo info; - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); return info; } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index e87f74218b..a35973dd87 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -16,7 +16,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -107,8 +106,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 931ed73dfe..54cb75e88c 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -21,7 +21,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -159,8 +158,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 1fc65c8f79..0d5f10082c 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -15,7 +15,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -104,8 +103,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 6c517e3dde..f223e94530 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -19,7 +19,6 @@ #include "../mwworld/worldmodel.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -151,8 +150,8 @@ namespace MWClass countString = " (" + std::to_string(count) + ")"; std::string_view name = getName(ptr); - info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) - + MWGui::ToolTips::getCountString(count) + MWGui::ToolTips::getSoulString(ptr.getCellRef()); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count) + + MWGui::ToolTips::getSoulString(ptr.getCellRef()); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index dab6dc99ae..22c1a81446 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -57,7 +57,6 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" namespace { @@ -1089,7 +1088,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); if (fullHelp && !ref->mBase->mName.empty() && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 9bab0345cb..211a89ed4f 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -13,7 +13,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -95,8 +94,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index e020c89443..a3ababa2ca 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -15,7 +15,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -103,8 +102,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 68fc2f60da..36deffebb3 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -13,7 +13,6 @@ #include "../mwworld/ptr.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -105,8 +104,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 68a66b69d9..c2e1b47370 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -20,7 +20,6 @@ #include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" -#include "../mwgui/ustring.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -150,8 +149,7 @@ namespace MWClass MWGui::ToolTipInfo info; std::string_view name = getName(ptr); - info.caption - = MyGUI::TextIterator::toTagsString(MWGui::toUString(name)) + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 7208fce5f6..ab8a52db7c 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -28,7 +28,6 @@ #include "itemview.hpp" #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" -#include "ustring.hpp" #include "widgets.hpp" namespace MWGui @@ -164,7 +163,7 @@ namespace MWGui auto const& wm = MWBase::Environment::get().getWindowManager(); std::string_view ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); - if (mFilterType->getCaption() == toUString(ingredient)) + if (mFilterType->getCaption() == ingredient) mCurrentFilter = FilterType::ByName; else mCurrentFilter = FilterType::ByEffect; @@ -176,17 +175,17 @@ namespace MWGui void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender) { auto const& wm = MWBase::Environment::get().getWindowManager(); - MyGUI::UString ingredient = toUString(wm->getGameSettingString("sIngredients", "Ingredients")); + std::string_view ingredient = wm->getGameSettingString("sIngredients", "Ingredients"); auto* button = _sender->castType(); if (button->getCaption() == ingredient) { - button->setCaption(toUString(wm->getGameSettingString("sMagicEffects", "Magic Effects"))); + button->setCaption(MyGUI::UString(wm->getGameSettingString("sMagicEffects", "Magic Effects"))); mCurrentFilter = FilterType::ByEffect; } else { - button->setCaption(ingredient); + button->setCaption(MyGUI::UString(ingredient)); mCurrentFilter = FilterType::ByName; } mSortModel->setNameFilter({}); diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 617c373b0b..151ac8bd1e 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -18,7 +18,6 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" -#include "ustring.hpp" #include "widgets.hpp" namespace @@ -56,7 +55,8 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + okButton->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked); updateBirths(); @@ -70,10 +70,10 @@ namespace MWGui if (shown) okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void BirthDialog::onOpen() @@ -235,7 +235,7 @@ namespace MWGui { MyGUI::TextBox* label = mSpellArea->createWidget("SandBrightText", coord, MyGUI::Align::Default, "Label"); - label->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + label->setCaption(MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString( categories[category].label, {}))); mSpellItems.push_back(label); coord.top += lineHeight; diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index f71da8bdf5..39df017c19 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -20,7 +20,6 @@ #include #include "tooltips.hpp" -#include "ustring.hpp" namespace { @@ -129,10 +128,10 @@ namespace MWGui if (shown) okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void PickClassDialog::onOpen() @@ -546,10 +545,10 @@ namespace MWGui if (shown) okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } // widget controls @@ -869,7 +868,7 @@ namespace MWGui getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked); okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sInputMenu1", {}))); // Make sure the edit box has focus MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mTextEdit); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 4ab77c3956..1d3b453214 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -337,7 +337,7 @@ namespace MWGui void DialogueWindow::onTradeComplete() { MyGUI::UString message = MyGUI::LanguageManager::getInstance().replaceTags("#{sBarterDialog5}"); - addResponse({}, message.asUTF8()); + addResponse({}, message); } bool DialogueWindow::exit() diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index e70599697a..6ae6a6e9e5 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -26,7 +26,6 @@ #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" -#include "ustring.hpp" namespace MWGui { @@ -95,7 +94,7 @@ namespace MWGui else { std::string_view name = item.getClass().getName(item); - mName->setCaption(toUString(name)); + mName->setCaption(MyGUI::UString(name)); mItemBox->setItem(item); mItemBox->setUserString("ToolTipType", "ItemPtr"); mItemBox->setUserData(MWWorld::Ptr(item)); @@ -115,23 +114,26 @@ namespace MWGui switch (mEnchanting.getCastStyle()) { case ESM::Enchantment::CastOnce: - mTypeButton->setCaption(toUString( + mTypeButton->setCaption(MyGUI::UString( MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce", "Cast Once"))); setConstantEffect(false); break; case ESM::Enchantment::WhenStrikes: - mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( - "sItemCastWhenStrikes", "When Strikes"))); + mTypeButton->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastWhenStrikes", "When Strikes"))); setConstantEffect(false); break; case ESM::Enchantment::WhenUsed: - mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( - "sItemCastWhenUsed", "When Used"))); + mTypeButton->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastWhenUsed", "When Used"))); setConstantEffect(false); break; case ESM::Enchantment::ConstantEffect: - mTypeButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString( - "sItemCastConstant", "Cast Constant"))); + mTypeButton->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString( + "sItemCastConstant", "Cast Constant"))); setConstantEffect(true); break; } diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index ba74eadc7a..e631b671b0 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -18,7 +18,6 @@ #include "itemmodel.hpp" #include "itemwidget.hpp" -#include "ustring.hpp" namespace MWGui { @@ -130,7 +129,7 @@ namespace MWGui std::stable_sort(mLines.begin(), mLines.end(), [](const MWGui::ItemChargeView::Line& a, const MWGui::ItemChargeView::Line& b) { - return Misc::StringUtils::ciLess(a.mText->getCaption().asUTF8(), b.mText->getCaption().asUTF8()); + return Misc::StringUtils::ciLess(a.mText->getCaption(), b.mText->getCaption()); }); layoutWidgets(); @@ -182,7 +181,7 @@ namespace MWGui void ItemChargeView::updateLine(const ItemChargeView::Line& line) { std::string_view name = line.mItemPtr.getClass().getName(line.mItemPtr); - line.mText->setCaption(toUString(name)); + line.mText->setCaption(MyGUI::UString(name)); line.mCharge->setVisible(false); switch (mDisplayMode) diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index fb0fb5e1c5..5f8bea7bba 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -6,8 +6,6 @@ #include #include -#include "ustring.hpp" - namespace MWGui { void Layout::initialise(std::string_view _layout) @@ -52,16 +50,15 @@ namespace MWGui { MyGUI::Widget* pt; getWidget(pt, name); - static_cast(pt)->setCaption(toUString(caption)); + static_cast(pt)->setCaption(MyGUI::UString(caption)); } void Layout::setTitle(std::string_view title) { MyGUI::Window* window = static_cast(mMainWidget); - MyGUI::UString uTitle = toUString(title); - if (window->getCaption() != uTitle) - window->setCaptionWithReplacing(uTitle); + if (window->getCaption() != title) + window->setCaptionWithReplacing(MyGUI::UString(title)); } MyGUI::Widget* Layout::getWidget(std::string_view _name) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 41b2dadeb9..056c4bdb16 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -22,7 +22,6 @@ #include "../mwmechanics/npcstats.hpp" #include "class.hpp" -#include "ustring.hpp" namespace { @@ -176,7 +175,7 @@ namespace MWGui if (levelupdescription.empty()) levelupdescription = Fallback::Map::getString("Level_Up_Default"); - mLevelDescription->setCaption(toUString(levelupdescription)); + mLevelDescription->setCaption(MyGUI::UString(levelupdescription)); unsigned int availableAttributes = 0; for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index fd7afa2d8a..b70651f597 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -191,7 +191,7 @@ namespace MWGui // we may still want to show the label if the caller requested it if (mImportantLabel) { - MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption().asUTF8()); + MWBase::Environment::get().getWindowManager()->messageBox(mLoadingText->getCaption()); mImportantLabel = false; } } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index b22fb873fa..712a5b7c7b 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -379,17 +379,17 @@ namespace MWGui MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus() { - std::vector keywords{ "sOk", "sYes" }; if (mDefaultFocus >= 0 && mDefaultFocus < static_cast(mButtons.size())) return mButtons[mDefaultFocus]; + auto& languageManager = MyGUI::LanguageManager::getInstance(); + std::vector keywords{ languageManager.replaceTags("#{sOk}"), + languageManager.replaceTags("#{sYes}") }; for (MyGUI::Button* button : mButtons) { - for (const std::string& keyword : keywords) + for (const MyGUI::UString& keyword : keywords) { - if (Misc::StringUtils::ciEqual( - MyGUI::LanguageManager::getInstance().replaceTags("#{" + keyword + "}").asUTF8(), - button->getCaption().asUTF8())) + if (Misc::StringUtils::ciEqual(keyword, button->getCaption())) { return button; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index da5c0c9ca8..cf0eea1f0c 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -19,7 +19,6 @@ #include "../mwworld/esmstore.hpp" #include "tooltips.hpp" -#include "ustring.hpp" namespace { @@ -114,7 +113,8 @@ namespace MWGui MyGUI::Button* okButton; getWidget(okButton, "OKButton"); - okButton->setCaption(toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + okButton->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); updateRaces(); @@ -129,10 +129,10 @@ namespace MWGui if (shown) okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void RaceDialog::onOpen() diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 04c3806c0e..322497c84d 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -19,7 +19,6 @@ #include "../mwworld/esmstore.hpp" #include "tooltips.hpp" -#include "ustring.hpp" namespace { @@ -272,7 +271,7 @@ namespace MWGui MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - groupWidget->setCaption(toUString(label)); + groupWidget->setCaption(MyGUI::UString(label)); mSkillWidgets.push_back(groupWidget); const int lineHeight = Settings::gui().mFontSize + 2; @@ -287,7 +286,7 @@ namespace MWGui MyGUI::TextBox* skillValueWidget; skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Default); - skillNameWidget->setCaption(toUString(text)); + skillNameWidget->setCaption(MyGUI::UString(text)); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillValueWidget = mSkillView->createWidget("SandTextRight", coord2, MyGUI::Align::Default); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 4f78c27f05..50e1b63fe3 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -35,7 +35,6 @@ #include "../mwstate/character.hpp" #include "confirmationdialog.hpp" -#include "ustring.hpp" namespace MWGui { @@ -198,7 +197,7 @@ namespace MWGui } title << " (#{sLevel} " << signature.mPlayerLevel << " " - << MyGUI::TextIterator::toTagsString(toUString(className)) << ")"; + << MyGUI::TextIterator::toTagsString(MyGUI::UString(className)) << ")"; mCharacterSelection->addItem(MyGUI::LanguageManager::getInstance().replaceTags(title.str())); @@ -302,7 +301,7 @@ namespace MWGui if (mSaving) { - MWBase::Environment::get().getStateManager()->saveGame(mSaveNameEdit->getCaption().asUTF8(), mCurrentSlot); + MWBase::Environment::get().getStateManager()->saveGame(mSaveNameEdit->getCaption(), mCurrentSlot); } else { diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 7cefa7bda8..b3f0d75c12 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -38,7 +38,6 @@ #include "../mwbase/world.hpp" #include "confirmationdialog.hpp" -#include "ustring.hpp" namespace { @@ -659,18 +658,17 @@ namespace MWGui void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { - MyGUI::UString on = toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On")); + std::string_view on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); bool newState; if (_sender->castType()->getCaption() == on) { - MyGUI::UString off - = toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "Off")); - _sender->castType()->setCaption(off); + _sender->castType()->setCaption( + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOff", "Off"))); newState = false; } else { - _sender->castType()->setCaption(on); + _sender->castType()->setCaption(MyGUI::UString(on)); newState = true; } diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index cb16ab6d15..6e7d2c2ba2 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -32,7 +32,6 @@ #include "../mwmechanics/npcstats.hpp" #include "tooltips.hpp" -#include "ustring.hpp" namespace MWGui { @@ -417,7 +416,7 @@ namespace MWGui MyGUI::TextBox* groupWidget = mSkillView->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaption(toUString(label)); + groupWidget->setCaption(MyGUI::UString(label)); groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); mSkillWidgets.push_back(groupWidget); @@ -433,7 +432,7 @@ namespace MWGui skillNameWidget = mSkillView->createWidget( "SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - skillNameWidget->setCaption(toUString(text)); + skillNameWidget->setCaption(MyGUI::UString(text)); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillValueWidget = mSkillView->createWidget( diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 18a56e7284..881a671183 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -6,8 +6,6 @@ #include #include -#include "ustring.hpp" - namespace MWGui { @@ -35,10 +33,10 @@ namespace MWGui if (shown) okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sNext", {}))); else okButton->setCaption( - toUString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); + MyGUI::UString(MWBase::Environment::get().getWindowManager()->getGameSettingString("sOK", {}))); } void TextInputDialog::setTextLabel(std::string_view label) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 323579317a..b9da92e097 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -953,8 +953,7 @@ namespace MWGui widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " + MyGUI::TextIterator::toTagsString( - store->get().find(effect->mData.mSchool)->mSchool->mName) - .asUTF8()); + store->get().find(effect->mData.mSchool)->mSchool->mName)); widget->setUserString("ImageTexture_MagicEffectImage", icon); } } diff --git a/apps/openmw/mwgui/ustring.hpp b/apps/openmw/mwgui/ustring.hpp deleted file mode 100644 index 5a6c30312a..0000000000 --- a/apps/openmw/mwgui/ustring.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef MWGUI_USTRING_H -#define MWGUI_USTRING_H - -#include - -namespace MWGui -{ - // FIXME: Remove once we get a version of MyGUI that supports string_view - inline MyGUI::UString toUString(std::string_view string) - { - return { string.data(), string.size() }; - } -} - -#endif \ No newline at end of file diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 31e2689485..4a0eebf7c0 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -20,8 +20,6 @@ #include "../mwworld/esmstore.hpp" -#include "ustring.hpp" - namespace MWGui::Widgets { /* MWSkill */ @@ -135,8 +133,7 @@ namespace MWGui::Widgets } else { - MyGUI::UString name = toUString(attribute->mName); - mAttributeNameWidget->setCaption(name); + mAttributeNameWidget->setCaption(MyGUI::UString(attribute->mName)); } } if (mAttributeValueWidget) @@ -497,13 +494,13 @@ namespace MWGui::Widgets { std::stringstream out; out << mValue << "/" << mMax; - mBarTextWidget->setCaption(out.str().c_str()); + mBarTextWidget->setCaption(out.str()); } } void MWDynamicStat::setTitle(std::string_view text) { if (mTextWidget) - mTextWidget->setCaption(toUString(text)); + mTextWidget->setCaption(MyGUI::UString(text)); } MWDynamicStat::~MWDynamicStat() {} diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 7b3d35e011..3bd779a186 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -117,7 +117,6 @@ #include "tradewindow.hpp" #include "trainingwindow.hpp" #include "travelwindow.hpp" -#include "ustring.hpp" #include "videowidget.hpp" #include "waitdialog.hpp" @@ -788,8 +787,8 @@ namespace MWGui { if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { - MyGUI::UString text = MyGUI::LanguageManager::getInstance().replaceTags(toUString(message)); - mDialogueWindow->addMessageBox(text.asUTF8()); + MyGUI::UString text = MyGUI::LanguageManager::getInstance().replaceTags(MyGUI::UString(message)); + mDialogueWindow->addMessageBox(text); } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { @@ -1089,7 +1088,7 @@ namespace MWGui void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) { - std::string_view tag = _tag.asUTF8(); + std::string_view tag = _tag; std::string_view MyGuiPrefix = "setting="; From 829a9160c3a2569046cc1595322f710b3b3f0bdd Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 23 Nov 2023 00:04:22 +0100 Subject: [PATCH 0515/2167] Enable __cplusplus for MSVC To build OpenSceneGraph with osg::ref_ptr move constructor. This affects only code in the OpenMW itself including the file defining the constructor. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10d7fba386..13dce30702 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,8 @@ if (MSVC) # there should be no relevant downsides to having it on: # https://docs.microsoft.com/en-us/cpp/build/reference/bigobj-increase-number-of-sections-in-dot-obj-file add_compile_options(/bigobj) + + add_compile_options(/Zc:__cplusplus) endif() # Set up common paths From 81b8328bdde98bcffc3d91f915a6b6fbb16df0bd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 23 Nov 2023 19:52:18 +0100 Subject: [PATCH 0516/2167] Include UString --- apps/openmw/mwclass/activator.cpp | 1 + apps/openmw/mwclass/apparatus.cpp | 1 + apps/openmw/mwclass/armor.cpp | 1 + apps/openmw/mwclass/book.cpp | 1 + apps/openmw/mwclass/clothing.cpp | 1 + apps/openmw/mwclass/container.cpp | 1 + apps/openmw/mwclass/creature.cpp | 1 + apps/openmw/mwclass/door.cpp | 1 + apps/openmw/mwclass/esm4base.cpp | 1 + apps/openmw/mwclass/ingredient.cpp | 1 + apps/openmw/mwclass/light.cpp | 1 + apps/openmw/mwclass/lockpick.cpp | 1 + apps/openmw/mwclass/misc.cpp | 1 + apps/openmw/mwclass/npc.cpp | 1 + apps/openmw/mwclass/potion.cpp | 1 + apps/openmw/mwclass/probe.cpp | 1 + apps/openmw/mwclass/repair.cpp | 1 + apps/openmw/mwclass/weapon.cpp | 1 + apps/openmw/mwgui/alchemywindow.cpp | 1 + apps/openmw/mwgui/birth.cpp | 1 + apps/openmw/mwgui/class.cpp | 1 + apps/openmw/mwgui/dialogue.cpp | 1 + apps/openmw/mwgui/enchantingdialog.cpp | 1 + apps/openmw/mwgui/itemchargeview.cpp | 1 + apps/openmw/mwgui/layout.cpp | 1 + apps/openmw/mwgui/levelupdialog.cpp | 1 + apps/openmw/mwgui/loadingscreen.cpp | 1 + apps/openmw/mwgui/messagebox.cpp | 1 + apps/openmw/mwgui/race.cpp | 1 + apps/openmw/mwgui/review.cpp | 1 + apps/openmw/mwgui/savegamedialog.cpp | 1 + apps/openmw/mwgui/settingswindow.cpp | 1 + apps/openmw/mwgui/textinput.cpp | 1 + apps/openmw/mwgui/tooltips.cpp | 1 + apps/openmw/mwgui/widgets.cpp | 1 + 35 files changed, 35 insertions(+) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 96f5d5e993..fc6cfadb55 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,6 +1,7 @@ #include "activator.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 28a53b85d2..10687171a0 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -1,6 +1,7 @@ #include "apparatus.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index dbe4c6add6..4006f21ce7 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -1,6 +1,7 @@ #include "armor.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51ad0b6252..55de7a64ab 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -1,6 +1,7 @@ #include "book.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index fa866b299a..0614c92eb9 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -1,6 +1,7 @@ #include "clothing.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 8197701b07..0efbbc84fd 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -1,6 +1,7 @@ #include "container.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ee4c36ad2c..2628cd3905 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,6 +1,7 @@ #include "creature.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ef928ecc89..695bea5f10 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -1,6 +1,7 @@ #include "door.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/esm4base.cpp b/apps/openmw/mwclass/esm4base.cpp index fe75aab61e..77a5ad94a6 100644 --- a/apps/openmw/mwclass/esm4base.cpp +++ b/apps/openmw/mwclass/esm4base.cpp @@ -1,6 +1,7 @@ #include "esm4base.hpp" #include +#include #include diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index a35973dd87..3e07a24610 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -1,6 +1,7 @@ #include "ingredient.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 54cb75e88c..92ba8e1512 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -1,6 +1,7 @@ #include "light.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 0d5f10082c..6c46f2e66f 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -1,6 +1,7 @@ #include "lockpick.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index f223e94530..ae78773fa1 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,6 +1,7 @@ #include "misc.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 22c1a81446..b9e8bc8dfb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,6 +1,7 @@ #include "npc.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 211a89ed4f..5811ec10db 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -1,6 +1,7 @@ #include "potion.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index a3ababa2ca..7a6c00824d 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -1,6 +1,7 @@ #include "probe.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 36deffebb3..0d38271aab 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -1,6 +1,7 @@ #include "repair.hpp" #include +#include #include #include diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index c2e1b47370..1cc2c86761 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -1,6 +1,7 @@ #include "weapon.hpp" #include +#include #include #include diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index ab8a52db7c..333722a149 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 151ac8bd1e..3dfdd17627 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 39df017c19..d6b4e7f635 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 1d3b453214..79673463ef 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 6ae6a6e9e5..f5bcb1fb5f 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index e631b671b0..02c3cc182c 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include diff --git a/apps/openmw/mwgui/layout.cpp b/apps/openmw/mwgui/layout.cpp index 5f8bea7bba..8d70bc956b 100644 --- a/apps/openmw/mwgui/layout.cpp +++ b/apps/openmw/mwgui/layout.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 056c4bdb16..2160a04b1b 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index b70651f597..1723841b32 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 712a5b7c7b..b27adacd0f 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index cf0eea1f0c..7b445d419f 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 322497c84d..4ea21df00c 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 50e1b63fe3..8330c23f2f 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b3f0d75c12..696596a46f 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 881a671183..5f47b96f03 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace MWGui { diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index b9da92e097..bdcc4e76d7 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 4a0eebf7c0..d824682308 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include From f08ab9af5649327f06c18d6d77b0365085d3cc88 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 24 Nov 2023 14:26:53 +0100 Subject: [PATCH 0517/2167] Use SDL2 generate cmake files to find the package --- CI/before_script.msvc.sh | 7 +- CMakeLists.txt | 4 +- apps/launcher/CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- cmake/FindSDL2.cmake | 129 ------------------- components/CMakeLists.txt | 2 +- extern/oics/CMakeLists.txt | 2 + extern/osg-ffmpeg-videoplayer/CMakeLists.txt | 1 + 8 files changed, 11 insertions(+), 138 deletions(-) delete mode 100644 cmake/FindSDL2.cmake diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 338ca1ee9e..cdac794a7e 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -902,8 +902,7 @@ printf "Qt ${QT_VER}... " fi cd $QT_SDK - add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ - -DCMAKE_PREFIX_PATH="$QT_SDK" + add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then DLLSUFFIX="d" @@ -930,7 +929,7 @@ printf "SDL 2.24.0... " rm -rf SDL2-2.24.0 eval 7z x -y SDL2-devel-2.24.0-VC.zip $STRIP fi - export SDL2DIR="$(real_pwd)/SDL2-2.24.0" + SDL2DIR="$(real_pwd)/SDL2-2.24.0" for config in ${CONFIGURATIONS[@]}; do add_runtime_dlls $config "$(pwd)/SDL2-2.24.0/lib/x${ARCHSUFFIX}/SDL2.dll" done @@ -1025,6 +1024,8 @@ printf "zlib 1.2.11... " echo Done. } +add_cmake_opts -DCMAKE_PREFIX_PATH="\"${QT_SDK};${SDL2DIR}\"" + echo cd $DEPS_INSTALL/.. echo diff --git a/CMakeLists.txt b/CMakeLists.txt index 10d7fba386..d61075c057 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,7 +114,6 @@ include(WholeArchive) configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_BINARY_DIR}/docs/mainpage.hpp") option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) -option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) option(QT_STATIC "Link static build of Qt into the binaries" FALSE) option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON) @@ -481,7 +480,6 @@ set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) include_directories( BEFORE SYSTEM "." - ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${MyGUI_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} @@ -493,7 +491,7 @@ include_directories( ${ICU_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS}) +link_directories(${Boost_LIBRARY_DIRS} ${COLLADA_DOM_LIBRARY_DIRS}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 87cee06e5d..daae65dc66 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -77,7 +77,7 @@ if (WIN32) endif (WIN32) target_link_libraries(openmw-launcher - ${SDL2_LIBRARY_ONLY} + SDL2::SDL2 ${OPENAL_LIBRARY} components_qt ) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 7e1c39f3df..db44b91159 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -150,7 +150,7 @@ target_link_libraries(openmw ${OPENAL_LIBRARY} ${FFmpeg_LIBRARIES} ${MyGUI_LIBRARIES} - ${SDL2_LIBRARY} + SDL2::SDL2 ${RecastNavigation_LIBRARIES} "osg-ffmpeg-videoplayer" "oics" diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake deleted file mode 100644 index 4f2be8c421..0000000000 --- a/cmake/FindSDL2.cmake +++ /dev/null @@ -1,129 +0,0 @@ -# Locate SDL2 library -# This module defines -# SDL2_LIBRARY, the SDL2 library, with no other libraries -# SDL2_LIBRARIES, the SDL library and required components with compiler flags -# SDL2_FOUND, if false, do not try to link to SDL2 -# SDL2_INCLUDE_DIR, where to find SDL.h -# SDL2_VERSION, the version of the found library -# -# This module accepts the following env variables -# SDL2DIR - Can be set to ./configure --prefix=$SDL2DIR used in building SDL2. l.e.galup 9-20-02 -# This module responds to the the flag: -# SDL2_BUILDING_LIBRARY -# If this is defined, then no SDL2_main will be linked in because -# only applications need main(). -# Otherwise, it is assumed you are building an application and this -# module will attempt to locate and set the the proper link flags -# as part of the returned SDL2_LIBRARIES variable. -# -# Don't forget to include SDL2main.h and SDL2main.m your project for the -# OS X framework based version. (Other versions link to -lSDL2main which -# this module will try to find on your behalf.) Also for OS X, this -# module will automatically add the -framework Cocoa on your behalf. -# -# -# Modified by Eric Wing. -# Added code to assist with automated building by using environmental variables -# and providing a more controlled/consistent search behavior. -# Added new modifications to recognize OS X frameworks and -# additional Unix paths (FreeBSD, etc). -# Also corrected the header search path to follow "proper" SDL2 guidelines. -# Added a search for SDL2main which is needed by some platforms. -# Added a search for threads which is needed by some platforms. -# Added needed compile switches for MinGW. -# -# On OSX, this will prefer the Framework version (if found) over others. -# People will have to manually change the cache values of -# SDL2_LIBRARY to override this selection or set the CMake environment -# CMAKE_INCLUDE_PATH to modify the search paths. -# -# Note that the header path has changed from SDL2/SDL.h to just SDL.h -# This needed to change because "proper" SDL2 convention -# is #include "SDL.h", not . This is done for portability -# reasons because not all systems place things in SDL2/ (see FreeBSD). -# -# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake -# module with the minor edit of changing "SDL" to "SDL2" where necessary. This -# was not created for redistribution, and exists temporarily pending official -# SDL2 CMake modules. - -#============================================================================= -# Copyright 2003-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - - -if (CMAKE_SIZEOF_VOID_P EQUAL 8) - set(_sdl_lib_suffix lib/x64) -else() - set(_sdl_lib_suffix lib/x86) -endif() - -libfind_pkg_detect(SDL2 sdl2 - FIND_PATH SDL.h - HINTS $ENV{SDL2DIR} - PATH_SUFFIXES include SDL2 include/SDL2 - FIND_LIBRARY SDL2 - HINTS $ENV{SDL2DIR} - PATH_SUFFIXES lib ${_sdl_lib_suffix} -) -libfind_version_n_header(SDL2 NAMES SDL_version.h DEFINES SDL_MAJOR_VERSION SDL_MINOR_VERSION SDL_PATCHLEVEL) - -IF(NOT SDL2_BUILDING_LIBRARY AND NOT APPLE) - # Non-OS X framework versions expect you to also dynamically link to - # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms - # seem to provide SDL2main for compatibility even though they don't - # necessarily need it. - libfind_pkg_detect(SDL2MAIN sdl2 - FIND_LIBRARY SDL2main - HINTS $ENV{SDL2DIR} - PATH_SUFFIXES lib ${_sdl_lib_suffix} - ) - set(SDL2MAIN_FIND_QUIETLY TRUE) - libfind_process(SDL2MAIN) - list(APPEND SDL2_PROCESS_LIBS SDL2MAIN_LIBRARY) -ENDIF() - - -set(SDL2_TARGET_SPECIFIC) - -if (APPLE) - # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. - list(APPEND SDL2_TARGET_SPECIFIC "-framework Cocoa") -else() - # SDL2 may require threads on your system. - # The Apple build may not need an explicit flag because one of the - # frameworks may already provide it. - # But for non-OSX systems, I will use the CMake Threads package. - libfind_package(SDL2 Threads) - list(APPEND SDL2_TARGET_SPECIFIC ${CMAKE_THREAD_LIBS_INIT}) -endif() - -# MinGW needs an additional library, mwindows -# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows -# (Actually on second look, I think it only needs one of the m* libraries.) -if(MINGW) - list(APPEND SDL2_TARGET_SPECIFIC mingw32) -endif() - -if(WIN32) - list(APPEND SDL2_TARGET_SPECIFIC winmm imm32 version msimg32) -endif() - -set(SDL2_PROCESS_LIBS SDL2_TARGET_SPECIFIC) - -libfind_process(SDL2) - -if (SDL2_STATIC AND UNIX AND NOT APPLE) - execute_process(COMMAND sdl2-config --static-libs OUTPUT_VARIABLE SDL2_STATIC_FLAGS) - string(REGEX REPLACE "(\r?\n)+$" "" SDL2_STATIC_FLAGS "${SDL2_STATIC_FLAGS}") - set(SDL2_LIBRARIES ${SDL2_STATIC_FLAGS}) -endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 85f6c26449..c9c74957ec 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -556,7 +556,7 @@ target_link_libraries(components ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_IOSTREAMS_LIBRARY} - ${SDL2_LIBRARIES} + SDL2::SDL2 ${OPENGL_gl_LIBRARY} ${MyGUI_LIBRARIES} ${LUA_LIBRARIES} diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 1b9fea9217..4bd3bc51ad 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -20,6 +20,8 @@ else() target_link_libraries(oics local_tinyxml) endif() +target_link_libraries(oics SDL2::SDL2) + if (MSVC) target_precompile_headers(oics PUBLIC ) endif() diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 7e2712f19b..10c8d356a0 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -14,6 +14,7 @@ include_directories(${FFmpeg_INCLUDE_DIRS}) add_library(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} STATIC ${OSG_FFMPEG_VIDEOPLAYER_SOURCE_FILES}) target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${FFmpeg_LIBRARIES}) target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} ${OSG_LIBRARIES}) +target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} SDL2::SDL2) link_directories(${CMAKE_CURRENT_BINARY_DIR}) From f037dc814d5447efee884328d83398d56ee59260 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 11:35:58 +0100 Subject: [PATCH 0518/2167] Allow UI Elements in UI Content --- components/lua_ui/content.cpp | 3 +++ components/lua_ui/content.hpp | 2 +- components/lua_ui/content.lua | 22 ++++++++++++---------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index 2e1d4ca0c4..dd169a9291 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -1,4 +1,5 @@ #include "content.hpp" +#include "element.hpp" namespace LuaUi { @@ -14,6 +15,8 @@ namespace LuaUi bool isValidContent(const sol::object& object) { + if (object.is()) + return true; if (object.get_type() != sol::type::table) return false; sol::table table = object; diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index c8bb82ecf3..945f833b48 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -22,7 +22,7 @@ namespace LuaUi : mTable(std::move(table)) { if (!isValidContent(mTable)) - throw std::domain_error("Expected a Content table"); + throw std::domain_error("Invalid UI Content"); } size_t size() const { return mTable.size(); } diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index fbd39d5f68..99fdb86b70 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -1,12 +1,17 @@ local M = {} M.__Content = true + +function validateContentChild(v) + if not (type(v) == 'table' or v.__type and v.__type.name == 'LuaUi::Element') then + error('Content can only contain tables and Elements') + end +end + M.new = function(source) local result = {} result.__nameIndex = {} for i, v in ipairs(source) do - if type(v) ~= 'table' then - error('Content can only contain tables') - end + validateContentChild(v) result[i] = v if type(v.name) == 'string' then result.__nameIndex[v.name] = i @@ -38,9 +43,7 @@ end local methods = { insert = function(self, index, value) validateIndex(self, index) - if type(value) ~= 'table' then - error('Content can only contain tables') - end + validateContentChild(value) for i = #self, index, -1 do rawset(self, i + 1, rawget(self, i)) local name = rawget(self, i + 1) @@ -56,7 +59,7 @@ local methods = { indexOf = function(self, value) if type(value) == 'string' then return self.__nameIndex[value] - elseif type(value) == 'table' then + else for i = 1, #self do if rawget(self, i) == value then return i @@ -113,10 +116,9 @@ M.__newindex = function(self, key, value) local index = getIndexFromKey(self, key) if value == nil then remove(self, index) - elseif type(value) == 'table' then - assign(self, index, value) else - error('Content can only contain tables') + validateContentChild(value) + assign(self, index, value) end end M.__tostring = function(self) From 4a4cef570955e2bdf303829bf4ffe35f06ed9754 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 13:34:56 +0100 Subject: [PATCH 0519/2167] Attach elements to each other, safely destroy --- components/lua_ui/adapter.cpp | 2 +- components/lua_ui/content.hpp | 8 ++-- components/lua_ui/element.cpp | 78 +++++++++++++++++++++++++++++------ components/lua_ui/widget.cpp | 4 +- components/lua_ui/widget.hpp | 5 ++- 5 files changed, 78 insertions(+), 19 deletions(-) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp index 2fd6365977..6db9420398 100644 --- a/components/lua_ui/adapter.cpp +++ b/components/lua_ui/adapter.cpp @@ -18,7 +18,7 @@ namespace LuaUi { mContainer = MyGUI::Gui::getInstancePtr()->createWidget( "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); - mContainer->initialize(luaState, mContainer); + mContainer->initialize(luaState, mContainer, false); mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { setSize(coord.size()); }); mContainer->widget()->attachToWidget(this); } diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 945f833b48..1a0379b817 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -43,17 +43,17 @@ namespace LuaUi } void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); } - sol::table at(size_t index) const + sol::object at(size_t index) const { if (index < size()) - return mTable.get(toLua(index)); + return mTable.get(toLua(index)); else throw std::range_error("Invalid Content index"); } - sol::table at(std::string_view name) const + sol::object at(std::string_view name) const { if (indexOf(name).has_value()) - return mTable.get(name); + return mTable.get(name); else throw std::range_error("Invalid Content key"); } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 4fe9349b9e..b15647b1c4 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -49,6 +49,36 @@ namespace LuaUi MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } + void destroyChild(LuaUi::WidgetExtension* ext) + { + if (!ext->isRoot()) + destroyWidget(ext); + } + + void detachElements(LuaUi::WidgetExtension* ext) + { + for (auto* child : ext->children()) + { + if (child->isRoot()) + child->widget()->detachFromWidget(); + else + detachElements(child); + } + for (auto* child : ext->templateChildren()) + { + if (child->isRoot()) + child->widget()->detachFromWidget(); + else + detachElements(child); + } + } + + void destroyRoot(LuaUi::WidgetExtension* ext) + { + detachElements(ext); + destroyWidget(ext); + } + WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); @@ -60,7 +90,7 @@ namespace LuaUi if (contentObj == sol::nil) { for (WidgetExtension* w : children) - destroyWidget(w); + destroyChild(w); return result; } ContentView content(LuaUtil::cast(contentObj)); @@ -69,22 +99,46 @@ namespace LuaUi for (size_t i = 0; i < minSize; i++) { WidgetExtension* ext = children[i]; - sol::table newLayout = content.at(i); - if (ext->widget()->getTypeName() == widgetType(newLayout)) + sol::object child = content.at(i); + if (child.is()) { - updateWidget(ext, newLayout, depth); + std::shared_ptr element = child.as>(); + if (ext != element->mRoot) + destroyChild(ext); + result[i] = element->mRoot; + element->mRoot->updateCoord(); } else { - destroyWidget(ext); - ext = createWidget(newLayout, depth); + sol::table newLayout = child.as(); + if (ext->widget()->getTypeName() == widgetType(newLayout)) + { + updateWidget(ext, newLayout, depth); + } + else + { + destroyChild(ext); + ext = createWidget(newLayout, depth); + } + result[i] = ext; } - result[i] = ext; } for (size_t i = minSize; i < children.size(); i++) - destroyWidget(children[i]); + destroyChild(children[i]); for (size_t i = minSize; i < content.size(); i++) - result[i] = createWidget(content.at(i), depth); + { + sol::object child = content.at(i); + if (child.is()) + { + std::shared_ptr element = child.as>(); + result[i] = element->mRoot; + element->mRoot->updateCoord(); + } + else + { + result[i] = createWidget(child.as(), depth); + } + } return result; } @@ -130,7 +184,7 @@ namespace LuaUi WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget); + ext->initialize(layout.lua_state(), widget, depth == 0); updateWidget(ext, layout, depth); return ext; @@ -201,7 +255,7 @@ namespace LuaUi { if (mRoot->widget()->getTypeName() != widgetType(layout())) { - destroyWidget(mRoot); + destroyRoot(mRoot); mRoot = createWidget(layout(), 0); } else @@ -218,7 +272,7 @@ namespace LuaUi { if (mRoot) { - destroyWidget(mRoot); + destroyRoot(mRoot); mRoot = nullptr; mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index e3188f6136..2e505ff9a8 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -18,13 +18,15 @@ namespace LuaUi , mExternal(sol::nil) , mParent(nullptr) , mTemplateChild(false) + , mElementRoot(false) { } - void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self) + void WidgetExtension::initialize(lua_State* lua, MyGUI::Widget* self, bool isRoot) { mLua = lua; mWidget = self; + mElementRoot = isRoot; initialize(); updateTemplate(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 81698b0479..e5edd91113 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -26,13 +26,15 @@ namespace LuaUi virtual ~WidgetExtension() = default; // must be called after creating the underlying MyGUI::Widget - void initialize(lua_State* lua, MyGUI::Widget* self); + void initialize(lua_State* lua, MyGUI::Widget* self, bool isRoot); // must be called after before destroying the underlying MyGUI::Widget virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } WidgetExtension* slot() const { return mSlot; } + bool isRoot() const { return mElementRoot; } + void reset(); const std::vector& children() { return mChildren; } @@ -152,6 +154,7 @@ namespace LuaUi sol::object mExternal; WidgetExtension* mParent; bool mTemplateChild; + bool mElementRoot; void attach(WidgetExtension* ext); void attachTemplate(WidgetExtension* ext); From a36360cbde80084b86f489ca79c069ab910aae43 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 13:42:35 +0100 Subject: [PATCH 0520/2167] Update parent coords when updating element --- components/lua_ui/element.cpp | 10 ++++++++++ components/lua_ui/widget.hpp | 1 + 2 files changed, 11 insertions(+) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index b15647b1c4..996ba67bb4 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -79,6 +79,14 @@ namespace LuaUi destroyWidget(ext); } + void updateRootCoord(LuaUi::WidgetExtension* ext) + { + LuaUi::WidgetExtension* root = ext; + while (root->getParent()) + root = root->getParent(); + root->updateCoord(); + } + WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); @@ -246,6 +254,7 @@ namespace LuaUi mRoot = createWidget(layout(), 0); mLayer = setLayer(mRoot, layout()); updateAttachment(); + updateRootCoord(mRoot); } } @@ -264,6 +273,7 @@ namespace LuaUi } mLayer = setLayer(mRoot, layout()); updateAttachment(); + updateRootCoord(mRoot); } mUpdate = false; } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index e5edd91113..0902434e19 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -34,6 +34,7 @@ namespace LuaUi WidgetExtension* slot() const { return mSlot; } bool isRoot() const { return mElementRoot; } + WidgetExtension* getParent() const { return mParent; } void reset(); From 919e067ab7a6f2f07422cb23c916f63348c4af58 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 13:46:16 +0100 Subject: [PATCH 0521/2167] Error when encountering destroyed widgets --- components/lua_ui/element.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 996ba67bb4..dc8f2e6175 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -113,6 +113,8 @@ namespace LuaUi std::shared_ptr element = child.as>(); if (ext != element->mRoot) destroyChild(ext); + if (!element->mRoot) + throw std::logic_error("Using a destroyed element as a layout child"); result[i] = element->mRoot; element->mRoot->updateCoord(); } @@ -139,6 +141,8 @@ namespace LuaUi if (child.is()) { std::shared_ptr element = child.as>(); + if (!element->mRoot) + throw std::logic_error("Using a destroyed element as a layout child"); result[i] = element->mRoot; element->mRoot->updateCoord(); } From 4ba2aca3d38f381d221d703e7407604643cec619 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 13:51:22 +0100 Subject: [PATCH 0522/2167] Handle Element root changing type --- components/lua_ui/element.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index dc8f2e6175..cc75a258d5 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -269,7 +269,12 @@ namespace LuaUi if (mRoot->widget()->getTypeName() != widgetType(layout())) { destroyRoot(mRoot); + WidgetExtension* parent = mRoot->getParent(); + auto children = parent->children(); + auto it = std::find(children.begin(), children.end(), mRoot); mRoot = createWidget(layout(), 0); + *it = mRoot; + parent->setChildren(children); } else { From cf84386cc27bdb24aafe3a4ab1b24b9450d4d915 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 14:07:36 +0100 Subject: [PATCH 0523/2167] Use Element Content children for Settings UI --- components/lua_ui/adapter.cpp | 14 +++++++++---- components/lua_ui/container.hpp | 1 + components/lua_ui/element.cpp | 35 ++------------------------------- components/lua_ui/element.hpp | 5 ----- 4 files changed, 13 insertions(+), 42 deletions(-) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp index 6db9420398..c9afd4db0d 100644 --- a/components/lua_ui/adapter.cpp +++ b/components/lua_ui/adapter.cpp @@ -44,14 +44,20 @@ namespace LuaUi void LuaAdapter::attachElement() { - if (mElement.get()) - mElement->attachToWidget(mContainer); + if (!mElement.get()) + return; + if (!mElement->mRoot) + throw std::logic_error("Attempting to use a destroyed UI Element"); + mContainer->setChildren({ mElement->mRoot }); + mElement->mRoot->updateCoord(); + mContainer->updateCoord(); } void LuaAdapter::detachElement() { - if (mElement.get()) - mElement->detachFromWidget(); + mContainer->setChildren({}); + if (mElement && mElement->mRoot) + mElement->mRoot->widget()->detachFromWidget(); mElement = nullptr; } } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 79d3cd8fa8..16f19d3c12 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -9,6 +9,7 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaContainer) + public: MyGUI::IntSize calculateSize() override; void updateCoord() override; diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index cc75a258d5..013e6fc85d 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -53,6 +53,8 @@ namespace LuaUi { if (!ext->isRoot()) destroyWidget(ext); + else + ext->widget()->detachFromWidget(); } void detachElements(LuaUi::WidgetExtension* ext) @@ -235,7 +237,6 @@ namespace LuaUi Element::Element(sol::table layout) : mRoot(nullptr) - , mAttachedTo(nullptr) , mLayout(std::move(layout)) , mLayer() , mUpdate(false) @@ -257,7 +258,6 @@ namespace LuaUi { mRoot = createWidget(layout(), 0); mLayer = setLayer(mRoot, layout()); - updateAttachment(); updateRootCoord(mRoot); } } @@ -281,7 +281,6 @@ namespace LuaUi updateWidget(mRoot, layout(), 0); } mLayer = setLayer(mRoot, layout()); - updateAttachment(); updateRootCoord(mRoot); } mUpdate = false; @@ -297,34 +296,4 @@ namespace LuaUi } sAllElements.erase(this); } - - void Element::attachToWidget(WidgetExtension* w) - { - if (mAttachedTo) - throw std::logic_error("A UI element can't be attached to two widgets at once"); - mAttachedTo = w; - updateAttachment(); - } - - void Element::detachFromWidget() - { - if (mRoot) - mRoot->widget()->detachFromWidget(); - if (mAttachedTo) - mAttachedTo->setChildren({}); - mAttachedTo = nullptr; - } - - void Element::updateAttachment() - { - if (!mRoot) - return; - if (mAttachedTo) - { - if (!mLayer.empty()) - Log(Debug::Warning) << "Ignoring element's layer " << mLayer << " because it's attached to a widget"; - mAttachedTo->setChildren({ mRoot }); - mAttachedTo->updateCoord(); - } - } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index b57af92fee..5aadb1beab 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -17,7 +17,6 @@ namespace LuaUi } WidgetExtension* mRoot; - WidgetExtension* mAttachedTo; sol::object mLayout; std::string mLayer; bool mUpdate; @@ -31,14 +30,10 @@ namespace LuaUi friend void clearUserInterface(); - void attachToWidget(WidgetExtension* w); - void detachFromWidget(); - private: Element(sol::table layout); sol::table layout() { return LuaUtil::cast(mLayout); } static std::map> sAllElements; - void updateAttachment(); }; } From 86ea12a45820cafb4a956c0eb8ff74ab4ed74007 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 14:44:17 +0100 Subject: [PATCH 0524/2167] Handle moving element into another element layout --- components/lua_ui/element.cpp | 36 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 013e6fc85d..ede87bc991 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -88,6 +88,23 @@ namespace LuaUi root = root->getParent(); root->updateCoord(); } + WidgetExtension* pluckElementRoot(const sol::object& child) + { + std::shared_ptr element = child.as>(); + WidgetExtension* root = element->mRoot; + if (!root) + throw std::logic_error("Using a destroyed element as a layout child"); + WidgetExtension* parent = root->getParent(); + if (parent) + { + auto children = parent->children(); + std::remove(children.begin(), children.end(), root); + parent->setChildren(children); + root->widget()->detachFromWidget(); + } + root->updateCoord(); + return root; + } WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); @@ -112,13 +129,10 @@ namespace LuaUi sol::object child = content.at(i); if (child.is()) { - std::shared_ptr element = child.as>(); - if (ext != element->mRoot) + WidgetExtension* root = pluckElementRoot(child); + if (ext != root) destroyChild(ext); - if (!element->mRoot) - throw std::logic_error("Using a destroyed element as a layout child"); - result[i] = element->mRoot; - element->mRoot->updateCoord(); + result[i] = root; } else { @@ -141,15 +155,8 @@ namespace LuaUi { sol::object child = content.at(i); if (child.is()) - { - std::shared_ptr element = child.as>(); - if (!element->mRoot) - throw std::logic_error("Using a destroyed element as a layout child"); - result[i] = element->mRoot; - element->mRoot->updateCoord(); - } + result[i] = pluckElementRoot(child); else - { result[i] = createWidget(child.as(), depth); } } @@ -275,6 +282,7 @@ namespace LuaUi mRoot = createWidget(layout(), 0); *it = mRoot; parent->setChildren(children); + mRoot->updateCoord(); } else { From f3a7b087ebc988590acf486de7a96ec0eafd7f18 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 14:44:37 +0100 Subject: [PATCH 0525/2167] Clean up unncesesary namespace prefixes --- components/lua_ui/element.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ede87bc991..71f2ba9c96 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -43,13 +43,13 @@ namespace LuaUi return type; } - void destroyWidget(LuaUi::WidgetExtension* ext) + void destroyWidget(WidgetExtension* ext) { ext->deinitialize(); MyGUI::Gui::getInstancePtr()->destroyWidget(ext->widget()); } - void destroyChild(LuaUi::WidgetExtension* ext) + void destroyChild(WidgetExtension* ext) { if (!ext->isRoot()) destroyWidget(ext); @@ -57,7 +57,7 @@ namespace LuaUi ext->widget()->detachFromWidget(); } - void detachElements(LuaUi::WidgetExtension* ext) + void detachElements(WidgetExtension* ext) { for (auto* child : ext->children()) { @@ -75,19 +75,20 @@ namespace LuaUi } } - void destroyRoot(LuaUi::WidgetExtension* ext) + void destroyRoot(WidgetExtension* ext) { detachElements(ext); destroyWidget(ext); } - void updateRootCoord(LuaUi::WidgetExtension* ext) + void updateRootCoord(WidgetExtension* ext) { - LuaUi::WidgetExtension* root = ext; + WidgetExtension* root = ext; while (root->getParent()) root = root->getParent(); root->updateCoord(); } + WidgetExtension* pluckElementRoot(const sol::object& child) { std::shared_ptr element = child.as>(); @@ -158,7 +159,6 @@ namespace LuaUi result[i] = pluckElementRoot(child); else result[i] = createWidget(child.as(), depth); - } } return result; } @@ -172,7 +172,7 @@ namespace LuaUi ext->setTemplateChildren(updateContent(ext->templateChildren(), content, depth)); } - void setEventCallbacks(LuaUi::WidgetExtension* ext, const sol::object& eventsObj) + void setEventCallbacks(WidgetExtension* ext, const sol::object& eventsObj) { ext->clearCallbacks(); if (eventsObj == sol::nil) From 416fa331f15f44174c76b74133990f75b89d0e6b Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 15:38:04 +0100 Subject: [PATCH 0526/2167] Implement UI Element tostring --- apps/openmw/mwlua/uibindings.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index d42f7b0637..98a653949d 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -95,6 +95,13 @@ namespace MWLua MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); auto element = context.mLua->sol().new_usertype("Element"); + element[sol::meta_function::to_string] = [](const LuaUi::Element& element) { + std::stringstream res; + res << "UiElement"; + if (element.mLayer != "") + res << "[" << element.mLayer << "]"; + return res.str(); + }; element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { From 39df270ff2a604853210ecd761e309aecc39823d Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 11 Nov 2023 16:00:56 +0100 Subject: [PATCH 0527/2167] Update UI Content docs --- files/lua_api/openmw/ui.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 2fefe4fd84..53ff57d276 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -164,9 +164,9 @@ --- -- Content. An array-like container, which allows to reference elements by their name. --- Implements [iterables#List](iterables.html#List) of #Layout and [iterables#Map](iterables.html#Map) of #string to #Layout. +-- Implements [iterables#List](iterables.html#List) of #Layout or #Element and [iterables#Map](iterables.html#Map) of #string to #Layout or #Element. -- @type Content --- @list <#Layout> +-- @list <#any> -- @usage -- local content = ui.content { -- { name = 'input' }, @@ -200,27 +200,27 @@ -- @function [parent=#Content] __index -- @param self -- @param #string name --- @return #Layout +-- @return #any --- -- Puts the layout at given index by shifting all the elements after it -- @function [parent=#Content] insert -- @param self -- @param #number index --- @param #Layout layout +-- @param #any layoutOrElement --- -- Adds the layout at the end of the Content -- (same as calling insert with `last index + 1`) -- @function [parent=#Content] add -- @param self --- @param #Layout layout +-- @param #any layoutOrElement --- -- Finds the index of the given layout. If it is not in the container, returns nil -- @function [parent=#Content] indexOf -- @param self --- @param #Layout layout +-- @param #any layoutOrElement -- @return #number, #nil index --- From d214f6f6ef2ceb726e029d0be3b9df9f1e1a808b Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 17 Nov 2023 18:02:34 +0100 Subject: [PATCH 0528/2167] Get rid of unncesesary onCoordChange --- components/lua_ui/adapter.cpp | 7 ++++++- components/lua_ui/adapter.hpp | 2 ++ components/lua_ui/widget.cpp | 4 ---- components/lua_ui/widget.hpp | 7 ------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/components/lua_ui/adapter.cpp b/components/lua_ui/adapter.cpp index c9afd4db0d..44493f6f46 100644 --- a/components/lua_ui/adapter.cpp +++ b/components/lua_ui/adapter.cpp @@ -19,10 +19,15 @@ namespace LuaUi mContainer = MyGUI::Gui::getInstancePtr()->createWidget( "", MyGUI::IntCoord(), MyGUI::Align::Default, "", ""); mContainer->initialize(luaState, mContainer, false); - mContainer->onCoordChange([this](WidgetExtension* ext, MyGUI::IntCoord coord) { setSize(coord.size()); }); + mContainer->widget()->eventChangeCoord += MyGUI::newDelegate(this, &LuaAdapter::containerChangedCoord); mContainer->widget()->attachToWidget(this); } + void LuaAdapter::containerChangedCoord(MyGUI::Widget*) + { + setSize(mContainer->getSize()); + } + void LuaAdapter::attach(const std::shared_ptr& element) { detachElement(); diff --git a/components/lua_ui/adapter.hpp b/components/lua_ui/adapter.hpp index d699e4992f..1524a55425 100644 --- a/components/lua_ui/adapter.hpp +++ b/components/lua_ui/adapter.hpp @@ -25,6 +25,8 @@ namespace LuaUi void attachElement(); void detachElement(); + + void containerChangedCoord(MyGUI::Widget*); }; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 2e505ff9a8..855ba29b3c 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -42,8 +42,6 @@ namespace LuaUi clearCallbacks(); clearEvents(mWidget); - mOnCoordChange.reset(); - for (WidgetExtension* w : mChildren) w->deinitialize(); for (WidgetExtension* w : mTemplateChildren) @@ -264,8 +262,6 @@ namespace LuaUi if (oldCoord != newCoord) mWidget->setCoord(newCoord); updateChildrenCoord(); - if (oldCoord != newCoord && mOnCoordChange.has_value()) - mOnCoordChange.value()(this, newCoord); } void WidgetExtension::setProperties(const sol::object& props) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 0902434e19..1cda09b41b 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -69,11 +69,6 @@ namespace LuaUi return parseExternal(mExternal, name, defaultValue); } - void onCoordChange(const std::optional>& callback) - { - mOnCoordChange = callback; - } - virtual MyGUI::IntSize calculateSize(); virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); @@ -176,8 +171,6 @@ namespace LuaUi void focusGain(MyGUI::Widget*, MyGUI::Widget*); void focusLoss(MyGUI::Widget*, MyGUI::Widget*); - std::optional> mOnCoordChange; - void updateVisible(); }; From 9403f06618ff583ae7663fc3da2627fa8970cca1 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 17 Nov 2023 18:15:07 +0100 Subject: [PATCH 0529/2167] Fix visibility breaking after multiple updates --- components/lua_ui/widget.cpp | 11 ++++++----- components/lua_ui/widget.hpp | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 855ba29b3c..9550c9de73 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -9,6 +9,7 @@ namespace LuaUi : mForcePosition(false) , mForceSize(false) , mPropagateEvents(true) + , mVisible(true) , mLua(nullptr) , mWidget(nullptr) , mSlot(this) @@ -92,10 +93,9 @@ namespace LuaUi { // workaround for MyGUI bug // parent visibility doesn't affect added children - MyGUI::Widget* widget = this->widget(); - MyGUI::Widget* parent = widget->getParent(); - bool inheritedVisible = widget->getVisible() && (parent == nullptr || parent->getInheritedVisible()); - widget->setVisible(inheritedVisible); + MyGUI::Widget* parent = widget()->getParent(); + bool inheritedVisible = mVisible && (parent == nullptr || parent->getInheritedVisible()); + widget()->setVisible(inheritedVisible); } void WidgetExtension::attach(WidgetExtension* ext) @@ -278,7 +278,8 @@ namespace LuaUi mRelativeCoord = propertyValue("relativePosition", MyGUI::FloatPoint()); mRelativeCoord = propertyValue("relativeSize", MyGUI::FloatSize()); mAnchor = propertyValue("anchor", MyGUI::FloatSize()); - mWidget->setVisible(propertyValue("visible", true)); + mVisible = propertyValue("visible", true); + mWidget->setVisible(mVisible); mWidget->setPointer(propertyValue("pointer", std::string("arrow"))); mWidget->setAlpha(propertyValue("alpha", 1.f)); mWidget->setInheritsAlpha(propertyValue("inheritAlpha", true)); diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 1cda09b41b..c72b64ae3b 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -135,6 +135,7 @@ namespace LuaUi MyGUI::FloatSize mAnchor; bool mPropagateEvents; + bool mVisible; // used to implement updateVisible private: // use lua_State* instead of sol::state_view because MyGUI requires a default constructor From 2be3824d9ed3942c8229c6617b1ce53a5fc93c56 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 24 Nov 2023 20:41:54 +0100 Subject: [PATCH 0530/2167] Clarify child element update behavior in the documentation --- files/lua_api/openmw/ui.lua | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 53ff57d276..451f919077 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -228,10 +228,35 @@ -- @type Element --- --- Refreshes the rendered element to match the current layout state +-- Refreshes the rendered element to match the current layout state. +-- Refreshes positions and sizes, but not the layout of the child Elements. -- @function [parent=#Element] update -- @param self +-- @usage +-- local child = ui.create { +-- type = ui.TYPE.Text, +-- props = { +-- text = 'child 1', +-- }, +-- } +-- local parent = ui.create { +-- content = ui.content { +-- child, +-- { +-- type = ui.TYPE.Text, +-- props = { +-- text = 'parent 1', +-- }, +-- } +-- } +-- } +-- -- ... +-- child.layout.props.text = 'child 2' +-- parent.layout.content[2].props.text = 'parent 2' +-- parent:update() -- will show 'parent 2', but 'child 1' + + --- -- Destroys the element -- @function [parent=#Element] destroy From 9a43ca2d0047ff62136318b4f7a0c563665116f4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 24 Nov 2023 22:33:43 +0300 Subject: [PATCH 0531/2167] Move NiGeometry triangulation to NiGeometry --- components/nif/node.cpp | 96 ++++++++++++++++ components/nif/node.hpp | 13 ++- components/nifbullet/bulletnifloader.cpp | 138 ++--------------------- components/nifbullet/bulletnifloader.hpp | 2 +- 4 files changed, 121 insertions(+), 128 deletions(-) diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 28cd2cdbfe..01c0e1597d 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -2,13 +2,33 @@ #include +#include + +#include #include +#include #include "data.hpp" #include "exception.hpp" #include "physics.hpp" #include "property.hpp" +namespace +{ + + void triBasedGeomToBtTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriBasedGeomData& data) + { + // FIXME: copying vertices/indices individually is unreasonable + const std::vector& vertices = data.mVertices; + mesh.preallocateVertices(static_cast(vertices.size())); + for (const osg::Vec3f& vertex : vertices) + mesh.findOrAddVertex(Misc::Convert::toBullet(vertex), false); + + mesh.preallocateIndices(static_cast(data.mNumTriangles) * 3); + } + +} + namespace Nif { @@ -218,6 +238,82 @@ namespace Nif } } + std::unique_ptr NiTriShape::getCollisionShape() const + { + if (mData.empty() || mData->mVertices.empty()) + return nullptr; + + auto data = static_cast(mData.getPtr()); + if (data->mNumTriangles == 0 || data->mTriangles.empty()) + return nullptr; + + auto mesh = std::make_unique(); + triBasedGeomToBtTriangleMesh(*mesh, *data); + const std::vector& triangles = data->mTriangles; + for (std::size_t i = 0; i < triangles.size(); i += 3) + mesh->addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); + + if (mesh->getNumTriangles() == 0) + return nullptr; + + auto shape = std::make_unique(mesh.get(), true); + std::ignore = mesh.release(); + + return shape; + } + + std::unique_ptr NiTriStrips::getCollisionShape() const + { + if (mData.empty() || mData->mVertices.empty()) + return nullptr; + + auto data = static_cast(mData.getPtr()); + if (data->mNumTriangles == 0 || data->mStrips.empty()) + return nullptr; + + auto mesh = std::make_unique(); + triBasedGeomToBtTriangleMesh(*mesh, *data); + for (const std::vector& strip : data->mStrips) + { + if (strip.size() < 3) + continue; + + unsigned short a; + unsigned short b = strip[0]; + unsigned short c = strip[1]; + for (size_t i = 2; i < strip.size(); i++) + { + a = b; + b = c; + c = strip[i]; + if (a == b || b == c || a == c) + continue; + if (i % 2 == 0) + mesh->addTriangleIndices(a, b, c); + else + mesh->addTriangleIndices(a, c, b); + } + } + + if (mesh->getNumTriangles() == 0) + return nullptr; + + auto shape = std::make_unique(mesh.get(), true); + std::ignore = mesh.release(); + + return shape; + } + + std::unique_ptr NiLines::getCollisionShape() const + { + return nullptr; + } + + std::unique_ptr NiParticles::getCollisionShape() const + { + return nullptr; + } + void BSSegmentedTriShape::SegmentData::read(NIFStream* nif) { nif->read(mFlags); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 0aaad40ed4..32746f7a9f 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -8,6 +8,8 @@ #include "base.hpp" +class btCollisionShape; + namespace Nif { @@ -146,6 +148,11 @@ namespace Nif void read(NIFStream* nif) override; void post(Reader& nif) override; + + virtual std::unique_ptr getCollisionShape() const + { + throw std::runtime_error("NiGeometry::getCollisionShape() called on base class"); + } }; // Abstract triangle-based geometry @@ -155,6 +162,7 @@ namespace Nif struct NiTriShape : NiTriBasedGeom { + std::unique_ptr getCollisionShape() const override; }; struct BSSegmentedTriShape : NiTriShape @@ -175,17 +183,20 @@ namespace Nif struct NiTriStrips : NiTriBasedGeom { + std::unique_ptr getCollisionShape() const override; }; struct NiLines : NiTriBasedGeom { + std::unique_ptr getCollisionShape() const override; }; struct NiParticles : NiGeometry { + std::unique_ptr getCollisionShape() const override; }; - struct BSLODTriShape : NiTriBasedGeom + struct BSLODTriShape : NiTriShape { std::array mLOD; void read(NIFStream* nif) override; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index ec46afec41..96dff80004 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -6,22 +6,15 @@ #include #include -#include - #include - +#include #include - #include - -#include #include #include #include #include -#include - namespace { @@ -32,111 +25,6 @@ namespace return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } - bool isTypeNiGeometry(int type) - { - switch (type) - { - case Nif::RC_NiTriShape: - case Nif::RC_NiTriStrips: - case Nif::RC_BSLODTriShape: - case Nif::RC_BSSegmentedTriShape: - return true; - } - return false; - } - - bool isTypeTriShape(int type) - { - switch (type) - { - case Nif::RC_NiTriShape: - case Nif::RC_BSLODTriShape: - case Nif::RC_BSSegmentedTriShape: - return true; - } - - return false; - } - - void prepareTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriBasedGeomData& data) - { - // FIXME: copying vertices/indices individually is unreasonable - const std::vector& vertices = data.mVertices; - mesh.preallocateVertices(static_cast(vertices.size())); - for (const osg::Vec3f& vertex : vertices) - mesh.findOrAddVertex(Misc::Convert::toBullet(vertex), false); - - mesh.preallocateIndices(static_cast(data.mNumTriangles) * 3); - } - - void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data) - { - prepareTriangleMesh(mesh, data); - const std::vector& triangles = data.mTriangles; - for (std::size_t i = 0; i < triangles.size(); i += 3) - mesh.addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); - } - - void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data) - { - prepareTriangleMesh(mesh, data); - for (const std::vector& strip : data.mStrips) - { - if (strip.size() < 3) - continue; - - unsigned short a; - unsigned short b = strip[0]; - unsigned short c = strip[1]; - for (size_t i = 2; i < strip.size(); i++) - { - a = b; - b = c; - c = strip[i]; - if (a == b || b == c || a == c) - continue; - if (i % 2 == 0) - mesh.addTriangleIndices(a, b, c); - else - mesh.addTriangleIndices(a, c, b); - } - } - } - - template - auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) - -> decltype(function(static_cast(geometry.mData.get()))) - { - if (isTypeTriShape(geometry.recType)) - { - auto data = static_cast(geometry.mData.getPtr()); - if (data->mTriangles.empty()) - return {}; - - return function(static_cast(*data)); - } - - if (geometry.recType == Nif::RC_NiTriStrips) - { - auto data = static_cast(geometry.mData.getPtr()); - if (data->mStrips.empty()) - return {}; - - return function(static_cast(*data)); - } - - return {}; - } - - std::unique_ptr makeChildMesh(const Nif::NiGeometry& geometry) - { - return handleNiGeometry(geometry, [&](const auto& data) { - auto mesh = std::make_unique(); - fillTriangleMesh(*mesh, data); - return mesh; - }); - } - } namespace NifBullet @@ -336,8 +224,8 @@ namespace NifBullet return; // Otherwise we'll want to notify the user. - Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << mShape->mFileName - << ". Treating it as a common NiTriShape."; + Log(Debug::Info) << "BulletNifLoader: RootCollisionNode is not attached to the root node in " + << mShape->mFileName << ". Treating it as a NiNode."; } else { @@ -349,8 +237,12 @@ namespace NifBullet if (node.recType == Nif::RC_AvoidNode) args.mAvoid = true; - if ((args.mAutogenerated || args.mIsCollisionNode) && isTypeNiGeometry(node.recType)) - handleNiTriShape(static_cast(node), parent, args); + if (args.mAutogenerated || args.mIsCollisionNode) + { + auto geometry = dynamic_cast(&node); + if (geometry) + handleGeometry(*geometry, parent, args); + } // For NiNodes, loop through children if (const Nif::NiNode* ninode = dynamic_cast(&node)) @@ -367,7 +259,7 @@ namespace NifBullet } } - void BulletNifLoader::handleNiTriShape( + void BulletNifLoader::handleGeometry( const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, HandleNodeArgs args) { // This flag comes from BSXFlags @@ -378,20 +270,14 @@ namespace NifBullet if (args.mHasTriMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "Tri EditorMarker")) return; - if (niGeometry.mData.empty() || niGeometry.mData->mVertices.empty()) - return; - if (!niGeometry.mSkin.empty()) args.mAnimated = false; // TODO: handle NiSkinPartition - std::unique_ptr childMesh = makeChildMesh(niGeometry); - if (childMesh == nullptr || childMesh->getNumTriangles() == 0) + std::unique_ptr childShape = niGeometry.getCollisionShape(); + if (childShape == nullptr) return; - auto childShape = std::make_unique(childMesh.get(), true); - std::ignore = childMesh.release(); - osg::Matrixf transform = niGeometry.mTransform.toMatrix(); for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) transform *= parent->mNiNode.mTransform.toMatrix(); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index c87c1242de..a80e6fdc3d 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -62,7 +62,7 @@ namespace NifBullet void handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args); void handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args); - void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args); + void handleGeometry(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args); std::unique_ptr mCompoundShape; std::unique_ptr mAvoidCompoundShape; From 6db75a7b7e7abbd5feb493e971e5a88f20ef4ebd Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 25 Nov 2023 15:22:57 +0100 Subject: [PATCH 0532/2167] Fix servicesOffered types --- files/lua_api/openmw/types.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d2ba9a3ee4..0c4f19d19f 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -727,7 +727,7 @@ -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature --- @field #list<#string> servicesOffered The services of the creature, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. +-- @field #map<#string, #boolean> servicesOffered The services of the creature, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. --- @{#NPC} functions @@ -936,7 +936,7 @@ -- @field #number baseGold The base barter gold of the NPC -- @field #number baseDisposition NPC's starting disposition -- @field #bool isMale The gender setting of the NPC --- @field #list<#string> servicesOffered The services of the NPC, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. +-- @field #map<#string, #boolean> servicesOffered The services of the NPC, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. -------------------------------------------------------------------------------- @@ -959,7 +959,7 @@ -- @function [parent=#Player] getCrimeLevel -- @param openmw.core#GameObject player -- @return #number - + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished @@ -1097,7 +1097,7 @@ -- @param #ArmorRecord armor A Lua table with the fields of a ArmorRecord, with an additional field `template` that accepts a @{#ArmorRecord} as a base. -- @return #ArmorRecord A strongly typed Armor record. -- @usage local armorTemplate = types.Armor.record('orcish_cuirass') --- local armorTable = {name = "Better Orcish Cuirass",template = armorTemplate,baseArmor = armorTemplate.baseArmor + 10} +-- local armorTable = {name = "Better Orcish Cuirass",template = armorTemplate,baseArmor = armorTemplate.baseArmor + 10} -- --This is the new record we want to create, with a record provided as a template. -- local recordDraft = types.Armor.createRecordDraft(armorTable)--Need to convert the table into the record draft -- local newRecord = world.createRecord(recordDraft)--This creates the actual record @@ -1224,7 +1224,7 @@ -- @param #ClothingRecord clothing A Lua table with the fields of a ClothingRecord, with an additional field `template` that accepts a @{#ClothingRecord} as a base. -- @return #ClothingRecord A strongly typed clothing record. -- @usage local clothingTemplate = types.Clothing.record('exquisite_robe_01') --- local clothingTable = {name = "Better Exquisite Robe",template = clothingTemplate,enchantCapacity = clothingTemplate.enchantCapacity + 10} +-- local clothingTable = {name = "Better Exquisite Robe",template = clothingTemplate,enchantCapacity = clothingTemplate.enchantCapacity + 10} -- --This is the new record we want to create, with a record provided as a template. -- local recordDraft = types.Clothing.createRecordDraft(clothingTable)--Need to convert the table into the record draft -- local newRecord = world.createRecord(recordDraft)--This creates the actual record From 4d250263d73b5d8d2554eb9bd8a4507e158cb273 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 25 Nov 2023 17:01:26 +0100 Subject: [PATCH 0533/2167] Missing return type of split --- files/lua_api/openmw/core.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 609b6fffa5..18898b5002 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -265,6 +265,7 @@ -- @function [parent=#GameObject] split -- @param self -- @param #number count The number of items to return. +-- @return #GameObject -- @usage -- take 50 coins from `money` and put to the container `cont` -- money:split(50):moveInto(types.Container.content(cont)) From 1841341da2a1880e4d50241b00a6df941fbaf9cf Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 25 Nov 2023 17:39:00 +0100 Subject: [PATCH 0534/2167] Fix Lua remove interacting with restocking items --- apps/openmw/mwlua/objectbindings.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 2f3a020971..e938d90e5e 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -417,20 +417,23 @@ namespace MWLua using DelayedRemovalFn = std::function; auto removeFn = [](const MWWorld::Ptr ptr, int countToRemove) -> std::optional { - int currentCount = ptr.getRefData().getCount(); + int rawCount = ptr.getRefData().getCount(false); + int currentCount = std::abs(rawCount); + int signedCountToRemove = (rawCount < 0 ? -1 : 1) * countToRemove; + if (countToRemove <= 0 || countToRemove > currentCount) throw std::runtime_error("Can't remove " + std::to_string(countToRemove) + " of " + std::to_string(currentCount) + " items"); - ptr.getRefData().setCount(currentCount - countToRemove); // Immediately change count + ptr.getRefData().setCount(rawCount - signedCountToRemove); // Immediately change count if (!ptr.getContainerStore() && currentCount > countToRemove) return std::nullopt; // Delayed action to trigger side effects - return [countToRemove](MWWorld::Ptr ptr) { + return [signedCountToRemove](MWWorld::Ptr ptr) { // Restore the original count - ptr.getRefData().setCount(ptr.getRefData().getCount() + countToRemove); + ptr.getRefData().setCount(ptr.getRefData().getCount(false) + signedCountToRemove); // And now remove properly if (ptr.getContainerStore()) - ptr.getContainerStore()->remove(ptr, countToRemove, false); + ptr.getContainerStore()->remove(ptr, std::abs(signedCountToRemove), false); else { MWBase::Environment::get().getWorld()->disable(ptr); From c945735f3bda25ea4c0f7317e800919a2dab42a2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 26 Nov 2023 19:07:38 +0400 Subject: [PATCH 0535/2167] Fix resolution dropdown in the launcher --- apps/launcher/graphicspage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 952e0c9349..cf2f00fca4 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -309,11 +309,11 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) QString aspect = getAspect(mode.w, mode.h); if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) { - resolution.append(tr("\t(Wide ") + aspect + ")"); + resolution.append(tr(" (Wide ") + aspect + ")"); } else if (aspect == QLatin1String("4:3")) { - resolution.append(tr("\t(Standard 4:3)")); + resolution.append(tr(" (Standard 4:3)")); } result.append(resolution); From 3be0ee824afd300ebddbe1496e459b542a2de824 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 26 Nov 2023 21:57:41 +0300 Subject: [PATCH 0536/2167] niftest updates Properly read archives within the supplied data directories Don't print quote marks redundantly Reduce code duplication Improve logging --- apps/niftest/niftest.cpp | 141 +++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 004e45765c..77752d25bd 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -45,9 +45,6 @@ std::unique_ptr makeBsaArchive(const std::filesystem::path& path) { switch (Bsa::BSAFile::detectVersion(path)) { - case Bsa::BSAVER_UNKNOWN: - std::cerr << '"' << path << "\" is unknown BSA archive" << std::endl; - return nullptr; case Bsa::BSAVER_COMPRESSED: return std::make_unique::type>(path); case Bsa::BSAVER_BA2_GNRL: @@ -56,11 +53,11 @@ std::unique_ptr makeBsaArchive(const std::filesystem::path& path) return std::make_unique::type>(path); case Bsa::BSAVER_UNCOMPRESSED: return std::make_unique::type>(path); + case Bsa::BSAVER_UNKNOWN: + default: + std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl; + return nullptr; } - - std::cerr << '"' << path << "\" is unsupported BSA archive" << std::endl; - - return nullptr; } std::unique_ptr makeArchive(const std::filesystem::path& path) @@ -72,58 +69,85 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) return nullptr; } +void readNIF( + const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) +{ + if (!quiet) + { + std::cout << "Reading NIF file '" << Files::pathToUnicodeString(path) << "'"; + if (!source.empty()) + std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; + std::cout << std::endl; + } + std::filesystem::path fullPath = !source.empty() ? source / path : path; + try + { + Nif::NIFFile file(fullPath); + Nif::Reader reader(file); + if (vfs != nullptr) + reader.parse(vfs->get(Files::pathToUnicodeString(path))); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } + catch (std::exception& e) + { + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + } +} + /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. -void readVFS(std::unique_ptr&& anArchive, const std::filesystem::path& archivePath = {}) +void readVFS(std::unique_ptr&& archive, const std::filesystem::path& archivePath, bool quiet) { - if (anArchive == nullptr) + if (archive == nullptr) return; - VFS::Manager myManager; - myManager.addArchive(std::move(anArchive)); - myManager.buildIndex(); + if (!quiet) + std::cout << "Reading data source '" << Files::pathToUnicodeString(archivePath) << "'" << std::endl; - for (const auto& name : myManager.getRecursiveDirectoryIterator("")) + VFS::Manager vfs; + vfs.addArchive(std::move(archive)); + vfs.buildIndex(); + + for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - try + if (isNIF(name)) { - if (isNIF(name)) - { - // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile file(archivePath / name); - Nif::Reader reader(file); - reader.parse(myManager.get(name)); - } - else if (isBSA(name)) - { - if (!archivePath.empty() && !isBSA(archivePath)) - { - // std::cout << "Reading BSA File: " << name << std::endl; - readVFS(makeBsaArchive(archivePath / name), archivePath / name); - // std::cout << "Done with BSA File: " << name << std::endl; - } - } + readNIF(archivePath, name, &vfs, quiet); } - catch (std::exception& e) + } + + if (!archivePath.empty() && !isBSA(archivePath)) + { + Files::PathContainer dataDirs = { archivePath }; + const Files::Collections fileCollections = Files::Collections(dataDirs); + const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); + const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); + for (auto& file : bsaCol) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + readVFS(makeBsaArchive(file.second), file.second, quiet); + } + for (auto& file : ba2Col) + { + readVFS(makeBsaArchive(file.second), file.second, quiet); } } } -bool parseOptions(int argc, char** argv, std::vector& files, bool& writeDebugLog, - std::vector& archives) +bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, + bool& writeDebugLog, bool& quiet) { bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files Usages: - niftool - Scan the file or directories for nif errors. + niftest + Scan the file or directories for NIF errors. Allowed options)"); auto addOption = desc.add_options(); addOption("help,h", "print help message."); addOption("write-debug-log,v", "write debug log for unsupported nif files"); + addOption("quiet,q", "do not log read archives/files"); addOption("archives", bpo::value(), "path to archive files to provide files"); addOption("input-file", bpo::value(), "input file"); @@ -143,17 +167,18 @@ Allowed options)"); return false; } writeDebugLog = variables.count("write-debug-log") > 0; + quiet = variables.count("quiet") > 0; if (variables.count("input-file")) { - files = variables["input-file"].as(); + files = asPathContainer(variables["input-file"].as()); if (const auto it = variables.find("archives"); it != variables.end()) - archives = it->second.as(); + archives = asPathContainer(it->second.as()); return true; } } catch (std::exception& e) { - std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; + std::cout << "Error parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } @@ -164,64 +189,62 @@ Allowed options)"); int main(int argc, char** argv) { - std::vector files; + Files::PathContainer files, sources; bool writeDebugLog = false; - std::vector archives; - if (!parseOptions(argc, argv, files, writeDebugLog, archives)) + bool quiet = false; + if (!parseOptions(argc, argv, files, sources, writeDebugLog, quiet)) return 1; Nif::Reader::setLoadUnsupportedFiles(true); Nif::Reader::setWriteNifDebugLog(writeDebugLog); std::unique_ptr vfs; - if (!archives.empty()) + if (!sources.empty()) { vfs = std::make_unique(); - for (const std::filesystem::path& path : archives) + for (const std::filesystem::path& path : sources) { + if (!quiet) + std::cout << "Adding data source '" << Files::pathToUnicodeString(path) << "'" << std::endl; + try { if (auto archive = makeArchive(path)) vfs->addArchive(std::move(archive)); else - std::cerr << '"' << path << "\" is unsupported archive" << std::endl; - vfs->buildIndex(); + std::cerr << "Error: '" << Files::pathToUnicodeString(path) << "' is not an archive or directory" + << std::endl; } catch (std::exception& e) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; } } + + vfs->buildIndex(); } - // std::cout << "Reading Files" << std::endl; for (const auto& path : files) { try { if (isNIF(path)) { - // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile file(path); - Nif::Reader reader(file); - if (vfs != nullptr) - reader.parse(vfs->get(Files::pathToUnicodeString(path))); - else - reader.parse(Files::openConstrainedFileStream(path)); + readNIF({}, path, vfs.get(), quiet); } else if (auto archive = makeArchive(path)) { - readVFS(std::move(archive), path); + readVFS(std::move(archive), path, quiet); } else { - std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path) - << "\" is not a nif file, bsa/ba2 file, or directory!" << std::endl; + std::cerr << "Error: '" << Files::pathToUnicodeString(path) + << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; } } catch (std::exception& e) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; } } return 0; From 3bf5b150c54012121b852cdd746e412c3d174049 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 27 Nov 2023 00:58:05 +0300 Subject: [PATCH 0537/2167] bsatool: Support extracting files with forward slash paths --- apps/bsatool/bsatool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index de755e7d1d..28711df929 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -194,7 +194,8 @@ int extract(std::unique_ptr& bsa, Arguments& info) // Get a stream for the file to extract for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it) { - if (Misc::StringUtils::ciEqual(Misc::StringUtils::stringToU8String(it->name()), archivePath)) + auto streamPath = Misc::StringUtils::stringToU8String(it->name()); + if (Misc::StringUtils::ciEqual(streamPath, archivePath) || Misc::StringUtils::ciEqual(streamPath, extractPath)) { stream = bsa->getFile(&*it); break; From 9c94058727880eabc41c5d48b2f853eeb4c3e4a8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 4 Nov 2023 20:02:55 +0300 Subject: [PATCH 0538/2167] Support Oblivion parallax setup --- components/nifosg/nifloader.cpp | 1 + components/shader/shadervisitor.cpp | 10 +++++++ components/shader/shadervisitor.hpp | 1 + files/shaders/compatibility/objects.frag | 35 ++++++++++++++++-------- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 17c608f2d4..436f2e1d34 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2363,6 +2363,7 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); handleTextureProperty( texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); + node->setUserValue("applyMode", static_cast(texprop->mApplyMode)); break; } case Nif::RC_BSShaderPPLightingProperty: diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 96e3d42f78..a08652620d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -181,6 +181,7 @@ namespace Shader , mAlphaBlend(false) , mBlendFuncOverridden(false) , mAdditiveBlending(false) + , mDiffuseHeight(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) @@ -303,6 +304,14 @@ namespace Shader if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; + int applyMode; + // Oblivion parallax + if (node.getUserValue("applyMode", applyMode) && applyMode == 4) + { + mRequirements.back().mShaderRequired = true; + mRequirements.back().mDiffuseHeight = true; + } + // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -615,6 +624,7 @@ namespace Shader addedState->addUniform("useDiffuseMapForShadowAlpha"); } + defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 66bd8c2a9d..a8e79ec995 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -108,6 +108,7 @@ namespace Shader bool mBlendFuncOverridden; bool mAdditiveBlending; + bool mDiffuseHeight; // true if diffuse map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index b86678af87..6de1f6d02f 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -113,10 +113,6 @@ void main() applyOcclusionDiscard(orthoDepthMapCoord, texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r); #endif -#if @diffuseMap - vec2 adjustedDiffuseUV = diffuseMapUV; -#endif - vec3 normal = normalize(passNormal); vec3 viewVec = normalize(passViewPos.xyz); @@ -131,11 +127,24 @@ void main() normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if @parallax +#if !@diffuseMap + gl_FragData[0] = vec4(1.0); +#else + vec2 adjustedDiffuseUV = diffuseMapUV; + +#if @normalMap && (@parallax || @diffuseParallax) vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; vec3 eyeDir = normalize(cameraPos - objectPos); - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0.0) ? -1.f : 1.f); +#if @parallax + float height = normalTex.a; + float flipY = (passTangent.w > 0.0) ? -1.f : 1.f; +#else + float height = texture2D(diffuseMap, diffuseMapUV).a; + // FIXME: shouldn't be necessary, but in this path false-positives are common + float flipY = -1.f; +#endif + vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, height, flipY); adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set // TODO: check not working as the same UV buffer is being bound to different targets @@ -149,14 +158,16 @@ void main() #endif -vec3 viewNormal = normalize(gl_NormalMatrix * normal); - -#if @diffuseMap - gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); - gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); + vec4 diffuseTex = texture2D(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].xyz = diffuseTex.xyz; +#if !@diffuseParallax + gl_FragData[0].a = diffuseTex.a * coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); #else - gl_FragData[0] = vec4(1.0); + gl_FragData[0].a = 1.0; #endif +#endif + + vec3 viewNormal = normalize(gl_NormalMatrix * normal); vec4 diffuseColor = getDiffuseColor(); gl_FragData[0].a *= diffuseColor.a; From 81a6a7cd2f51ea3c64809e844fb0df6f02f736e2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 28 Nov 2023 18:03:04 +0400 Subject: [PATCH 0539/2167] Rework resolution selection (feature 7709) --- CHANGELOG.md | 1 + apps/launcher/graphicspage.cpp | 32 +---------- apps/openmw/mwgui/settingswindow.cpp | 22 +------- components/CMakeLists.txt | 2 +- components/misc/display.cpp | 82 ++++++++++++++++++++++++++++ components/misc/display.hpp | 11 ++++ files/ui/graphicspage.ui | 2 +- 7 files changed, 101 insertions(+), 51 deletions(-) create mode 100644 components/misc/display.cpp create mode 100644 components/misc/display.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab1c0213a..30ad2bae6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb Feature #7652: Sort inactive post processing shaders list properly + Feature #7709: Improve resolution selection in Launcher Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index cf2f00fca4..832cb0ef8f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -2,6 +2,7 @@ #include "sdlinit.hpp" +#include #include #include @@ -16,22 +17,6 @@ #include #include -#include - -QString getAspect(int x, int y) -{ - int gcd = std::gcd(x, y); - if (gcd == 0) - return QString(); - - int xaspect = x / gcd; - int yaspect = y / gcd; - // special case: 8 : 5 is usually referred to as 16:10 - if (xaspect == 8 && yaspect == 5) - return QString("16:10"); - - return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); -} Launcher::GraphicsPage::GraphicsPage(QWidget* parent) : QWidget(parent) @@ -304,19 +289,8 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) return result; } - QString resolution = QString::number(mode.w) + QString(" x ") + QString::number(mode.h); - - QString aspect = getAspect(mode.w, mode.h); - if (aspect == QLatin1String("16:9") || aspect == QLatin1String("16:10")) - { - resolution.append(tr(" (Wide ") + aspect + ")"); - } - else if (aspect == QLatin1String("4:3")) - { - resolution.append(tr(" (Standard 4:3)")); - } - - result.append(resolution); + auto str = Misc::getResolutionText(mode.w, mode.h); + result.append(QString(str.c_str())); } result.removeDuplicates(); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 696596a46f..2d9a9d8292 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -21,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -93,20 +93,6 @@ namespace return left.first > right.first; } - std::string getAspect(int x, int y) - { - int gcd = std::gcd(x, y); - if (gcd == 0) - return std::string(); - - int xaspect = x / gcd; - int yaspect = y / gcd; - // special case: 8 : 5 is usually referred to as 16:10 - if (xaspect == 8 && yaspect == 5) - return "16 : 10"; - return MyGUI::utility::toString(xaspect) + " : " + MyGUI::utility::toString(yaspect); - } - const std::string_view checkButtonType = "CheckButton"; const std::string_view sliderType = "Slider"; @@ -366,11 +352,7 @@ namespace MWGui std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { - std::string str - = MyGUI::utility::toString(resolution.first) + " x " + MyGUI::utility::toString(resolution.second); - std::string aspect = getAspect(resolution.first, resolution.second); - if (!aspect.empty()) - str = str + " (" + aspect + ")"; + std::string str = Misc::getResolutionText(resolution.first, resolution.second); if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c9c74957ec..7e3c7aea23 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -273,7 +273,7 @@ add_component_dir (esm4 ) add_component_dir (misc - barrier budgetmeasurement color compression constants convert coordinateconverter endianness float16 frameratelimiter + barrier budgetmeasurement color compression constants convert coordinateconverter display endianness float16 frameratelimiter guarded math mathutil messageformatparser notnullptr objectpool osguservalues progressreporter resourcehelpers rng strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows ) diff --git a/components/misc/display.cpp b/components/misc/display.cpp new file mode 100644 index 0000000000..7570914483 --- /dev/null +++ b/components/misc/display.cpp @@ -0,0 +1,82 @@ +#include "display.hpp" + +#include +#include + +#include + +namespace Misc +{ + std::string getResolutionText(int x, int y) + { + int gcd = std::gcd(x, y); + if (gcd == 0) + return std::string(); + + int xaspect = x / gcd; + int yaspect = y / gcd; + + // It is unclear how to handle 90-degree screen rotation properly. + // So far only swap aspects, apply custom formatting logic and then swap back. + // As result, 1920 x 1200 is displayed as "1200 x 1920 (10:16)" + bool flipped = false; + if (yaspect > xaspect) + { + flipped = true; + std::swap(xaspect, yaspect); + } + + // 683:384 (used in 1366 x 768) is usually referred as 16:9 + if (xaspect == 683 && yaspect == 384) + { + xaspect = 16; + yaspect = 9; + } + // 85:48 (used in 1360 x 768) is usually referred as 16:9 + else if (xaspect == 85 && yaspect == 48) + { + xaspect = 16; + yaspect = 9; + } + // 49:36 (used in 1176 x 864) is usually referred as 4:3 + else if (xaspect == 49 && yaspect == 36) + { + xaspect = 4; + yaspect = 3; + } + // 39:29 (used in 624 x 484) is usually referred as 4:3 + else if (xaspect == 39 && yaspect == 29) + { + xaspect = 4; + yaspect = 3; + } + // 8:5 (used in 1440 x 900) is usually referred as 16:10 + else if (xaspect == 8 && yaspect == 5) + { + xaspect = 16; + yaspect = 10; + } + // 5:3 (used in 1280 x 768) is usually referred as 15:9 + else if (xaspect == 5 && yaspect == 3) + { + xaspect = 15; + yaspect = 9; + } + else + { + // everything between 21:9 and 22:9 + // is usually referred as 21:9 + float ratio = static_cast(xaspect) / yaspect; + if (ratio >= 21 / 9.f && ratio < 22 / 9.f) + { + xaspect = 21; + yaspect = 9; + } + } + + if (flipped) + std::swap(xaspect, yaspect); + + return Misc::StringUtils::format("%i x %i (%i:%i)", x, y, xaspect, yaspect); + } +} diff --git a/components/misc/display.hpp b/components/misc/display.hpp new file mode 100644 index 0000000000..6f60a48cda --- /dev/null +++ b/components/misc/display.hpp @@ -0,0 +1,11 @@ +#ifndef OPENMW_COMPONENTS_MISC_DISPLAY_H +#define OPENMW_COMPONENTS_MISC_DISPLAY_H + +#include + +namespace Misc +{ + std::string getResolutionText(int x, int y); +} + +#endif diff --git a/files/ui/graphicspage.ui b/files/ui/graphicspage.ui index bb900ccb2d..70ab1f0728 100644 --- a/files/ui/graphicspage.ui +++ b/files/ui/graphicspage.ui @@ -22,7 +22,7 @@ - + From 99024d38267f96a2109acde1ee7342996bb07e32 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 27 Nov 2023 01:31:38 +0300 Subject: [PATCH 0540/2167] Revamp NIF debug logging Disabled by default Extend it to supported files Log more version info Reduce noise --- components/nif/niffile.cpp | 32 ++++++++++++------- .../reference/modding/settings/models.rst | 5 ++- files/settings-default.cfg | 4 +-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 37e40938d3..d6d063a254 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -512,6 +512,10 @@ namespace Nif void Reader::parse(Files::IStreamPtr&& stream) { + const bool writeDebug = sWriteNifDebugLog; + if (writeDebug) + Log(Debug::Verbose) << "NIF Debug: Reading file: '" << mFilename << "'"; + const std::array fileHash = Files::getHash(mFilename, *stream); mHash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); @@ -538,15 +542,9 @@ namespace Nif }; const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), mVersion) != supportedVers.end(); - const bool writeDebugLog = sWriteNifDebugLog; - if (!supportedVersion) - { - if (!sLoadUnsupportedFiles) - throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename); - if (writeDebugLog) - Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << versionToString(mVersion) - << ". Proceed with caution! File: " << mFilename; - } + + if (!supportedVersion && !sLoadUnsupportedFiles) + throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename); const bool hasEndianness = mVersion >= NIFStream::generateVersion(20, 0, 0, 4); const bool hasUserVersion = mVersion >= NIFStream::generateVersion(10, 0, 1, 8); @@ -603,6 +601,17 @@ namespace Nif } } + if (writeDebug) + { + std::stringstream versionInfo; + versionInfo << "NIF Debug: Version: " << versionToString(mVersion); + if (mUserVersion) + versionInfo << "\nUser version: " << mUserVersion; + if (mBethVersion) + versionInfo << "\nBSStream version: " << mBethVersion; + Log(Debug::Verbose) << versionInfo.str(); + } + if (hasRecTypeListings) { // TODO: 20.3.1.2 uses DJB hashes instead of strings @@ -658,9 +667,8 @@ namespace Nif r = entry->second(); - if (!supportedVersion && writeDebugLog) - Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" - << mFilename << ")"; + if (writeDebug) + Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i; assert(r != nullptr); assert(r->recType != RC_MISSING); diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst index 998be9e6ea..4cf8d4c0d6 100644 --- a/docs/source/reference/modding/settings/models.rst +++ b/docs/source/reference/modding/settings/models.rst @@ -269,7 +269,6 @@ write nif debug log :Type: boolean :Range: True/False -:Default: True +:Default: False -If enabled, log the loading process of unsupported NIF files. -:ref:`load unsupported nif files` setting must be enabled for this setting to have any effect. +If enabled, log the loading process of NIF files. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index da1c97519a..4a90a46cc5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1136,8 +1136,8 @@ weathersnow = meshes/snow.nif # Blizzard weather effect weatherblizzard = meshes/blizzard.nif -# Enable to write logs when loading unsupported nif file -write nif debug log = true +# Enable to write logs when loading NIF files +write nif debug log = false [Groundcover] From 623510c073e5f2034b32f200d5752a1549288030 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 28 Nov 2023 22:47:27 +0400 Subject: [PATCH 0541/2167] Use multiplication character in the launcher instead of 'x' --- apps/launcher/graphicspage.cpp | 6 +++--- apps/openmw/mwgui/settingswindow.cpp | 2 +- components/misc/display.cpp | 4 ++-- components/misc/display.hpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 832cb0ef8f..84d5049d6c 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -102,7 +102,7 @@ bool Launcher::GraphicsPage::loadSettings() const int width = Settings::video().mResolutionX; const int height = Settings::video().mResolutionY; - QString resolution = QString::number(width) + QString(" x ") + QString::number(height); + QString resolution = QString::number(width) + QString(" × ") + QString::number(height); screenComboBox->setCurrentIndex(Settings::video().mScreen); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -189,7 +189,7 @@ void Launcher::GraphicsPage::saveSettings() int cHeight = 0; if (standardRadioButton->isChecked()) { - QRegularExpression resolutionRe("^(\\d+) x (\\d+)"); + QRegularExpression resolutionRe("^(\\d+) × (\\d+)"); QRegularExpressionMatch match = resolutionRe.match(resolutionComboBox->currentText().simplified()); if (match.hasMatch()) { @@ -289,7 +289,7 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) return result; } - auto str = Misc::getResolutionText(mode.w, mode.h); + auto str = Misc::getResolutionText(mode.w, mode.h, "%i × %i (%i:%i)"); result.append(QString(str.c_str())); } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 2d9a9d8292..1060b3a20f 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -352,7 +352,7 @@ namespace MWGui std::sort(resolutions.begin(), resolutions.end(), sortResolutions); for (std::pair& resolution : resolutions) { - std::string str = Misc::getResolutionText(resolution.first, resolution.second); + std::string str = Misc::getResolutionText(resolution.first, resolution.second, "%i x %i (%i:%i)"); if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) mResolutionList->addItem(str); diff --git a/components/misc/display.cpp b/components/misc/display.cpp index 7570914483..ee78b2a0c9 100644 --- a/components/misc/display.cpp +++ b/components/misc/display.cpp @@ -7,7 +7,7 @@ namespace Misc { - std::string getResolutionText(int x, int y) + std::string getResolutionText(int x, int y, const std::string& format) { int gcd = std::gcd(x, y); if (gcd == 0) @@ -77,6 +77,6 @@ namespace Misc if (flipped) std::swap(xaspect, yaspect); - return Misc::StringUtils::format("%i x %i (%i:%i)", x, y, xaspect, yaspect); + return Misc::StringUtils::format(format, x, y, xaspect, yaspect); } } diff --git a/components/misc/display.hpp b/components/misc/display.hpp index 6f60a48cda..82037661c8 100644 --- a/components/misc/display.hpp +++ b/components/misc/display.hpp @@ -5,7 +5,7 @@ namespace Misc { - std::string getResolutionText(int x, int y); + std::string getResolutionText(int x, int y, const std::string& format); } #endif From 4beac9035a96511ed30a851d5f9218c58349b233 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 28 Nov 2023 19:58:41 -0600 Subject: [PATCH 0542/2167] Add bindings for controller cursor mode --- apps/openmw/mwbase/inputmanager.hpp | 1 + apps/openmw/mwinput/inputmanagerimp.cpp | 5 +++++ apps/openmw/mwinput/inputmanagerimp.hpp | 1 + apps/openmw/mwlua/inputbindings.cpp | 6 ++++++ files/lua_api/openmw/input.lua | 10 ++++++++++ 5 files changed, 23 insertions(+) diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index f52f9ea454..5ee20476b3 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -45,6 +45,7 @@ namespace MWBase virtual void processChangedSettings(const std::set>& changed) = 0; virtual void setDragDrop(bool dragDrop) = 0; + virtual bool isGamepadGuiCursorEnabled() = 0; virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index f9ca0a3432..d0d6e7023d 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -102,6 +102,11 @@ namespace MWInput mControllerManager->setGamepadGuiCursorEnabled(enabled); } + bool InputManager::isGamepadGuiCursorEnabled() + { + return mControllerManager->gamepadGuiCursorEnabled(); + } + void InputManager::changeInputMode(bool guiMode) { mControllerManager->setGuiCursorEnabled(guiMode); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index c5de579961..f8f1411ebf 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -68,6 +68,7 @@ namespace MWInput void setDragDrop(bool dragDrop) override; void setGamepadGuiCursorEnabled(bool enabled) override; + bool isGamepadGuiCursorEnabled() override; void toggleControlSwitch(std::string_view sw, bool value) override; bool getControlSwitch(std::string_view sw) override; diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 02babf0399..414bb575b7 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwinput/actions.hpp" namespace sol @@ -68,6 +69,11 @@ namespace MWLua }; api["isMouseButtonPressed"] = [](int button) -> bool { return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; + api["isGamepadCursorActive"] = [input]() -> bool { return input->isGamepadGuiCursorEnabled(); }; + api["setGamepadCursorActive"] = [input](bool v) { + input->setGamepadGuiCursorEnabled(v); + MWBase::Environment::get().getWindowManager()->setCursorActive(v); + }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getAxisValue"] = [input](int axis) { diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 4ca4e5af4e..fbb790ce49 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -29,6 +29,16 @@ -- @param #number buttonId Button index (see @{openmw.input#CONTROLLER_BUTTON}) -- @return #boolean +--- +-- Checks if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. +-- @function [parent=#input] isGamepadCursorActive +-- @return #boolean + +--- +-- Set if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. +-- @function [parent=#input] setGamepadCursorActive +-- @param #boolean value + --- -- Is `Shift` key pressed. -- @function [parent=#input] isShiftPressed From 5e96825e6bd2ceb0e94f60aa5721e4f61fb91606 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Nov 2023 11:14:44 +0400 Subject: [PATCH 0543/2167] Highlight new items in launcher by text formatting, not by color --- apps/launcher/datafilespage.cpp | 15 ++++++++++----- .../contentselector/model/contentmodel.cpp | 17 ++++++----------- files/ui/datafilespage.ui | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4d3f0cc64f..4ad99c99f1 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -301,12 +301,14 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); - // Display new content with green background + // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) { tooltip += "Will be added to the current profile\n"; - item->setBackground(Qt::green); - item->setForeground(Qt::black); + QFont font = item->font(); + font.setBold(true); + font.setItalic(true); + item->setFont(font); } // deactivate data-local and global data directory: they are always included @@ -737,8 +739,11 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel ui.archiveListWidget->item(row)->setCheckState(selected); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { - ui.archiveListWidget->item(row)->setBackground(Qt::green); - ui.archiveListWidget->item(row)->setForeground(Qt::black); + auto item = ui.archiveListWidget->item(row); + QFont font = item->font(); + font.setBold(true); + font.setItalic(true); + item->setFont(font); } } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 7b4f5db158..f8ecc67998 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -164,20 +165,14 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int return isLoadOrderError(file) ? mWarningIcon : QVariant(); } - case Qt::BackgroundRole: + case Qt::FontRole: { if (isNew(file->fileName())) { - return QVariant(QColor(Qt::green)); - } - return QVariant(); - } - - case Qt::ForegroundRole: - { - if (isNew(file->fileName())) - { - return QVariant(QColor(Qt::black)); + auto font = QFont(); + font.setBold(true); + font.setItalic(true); + return font; } return QVariant(); } diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 239df34961..249207123e 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -6,7 +6,7 @@ 0 0 - 571 + 573 384 @@ -30,7 +30,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -57,7 +57,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: directories that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -210,7 +210,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: archives that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> From 67421d67e2ced5139b6da59c799ec13e51049c16 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 29 Nov 2023 15:42:34 +0000 Subject: [PATCH 0544/2167] Allow not passing force in ItemUsage events --- files/data/scripts/omw/usehandlers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/usehandlers.lua b/files/data/scripts/omw/usehandlers.lua index 01203b225c..563e31b3b7 100644 --- a/files/data/scripts/omw/usehandlers.lua +++ b/files/data/scripts/omw/usehandlers.lua @@ -22,7 +22,7 @@ local function useItem(obj, actor, force) end end end - world._runStandardUseAction(obj, actor, force) + world._runStandardUseAction(obj, actor, options.force) end return { From f52e8f76c6157600a2d485e37e8e9028769f5841 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Nov 2023 16:59:01 +0100 Subject: [PATCH 0545/2167] Use std::erase instead of using std::remove without erasing --- components/lua_ui/element.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 71f2ba9c96..baa3438982 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -99,7 +99,7 @@ namespace LuaUi if (parent) { auto children = parent->children(); - std::remove(children.begin(), children.end(), root); + std::erase(children, root); parent->setChildren(children); root->widget()->detachFromWidget(); } From c5b16d1ba2ab5cdeb7e2b0172f788d5c2cb3b692 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Nov 2023 09:28:40 +0400 Subject: [PATCH 0546/2167] Do not include formatting spaces to localizable strings --- apps/launcher/importpage.cpp | 6 +-- apps/launcher/maindialog.cpp | 56 ++++++++++++------------ apps/wizard/componentselectionpage.cpp | 8 ++-- apps/wizard/conclusionpage.cpp | 17 +++---- apps/wizard/existinginstallationpage.cpp | 14 +++--- apps/wizard/installationpage.cpp | 24 ++++------ apps/wizard/installationtargetpage.cpp | 19 ++++---- apps/wizard/mainwizard.cpp | 36 +++++++-------- components/process/processinvoker.cpp | 30 ++++++------- 9 files changed, 103 insertions(+), 107 deletions(-) diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp index fa91ad1654..44c5867c0d 100644 --- a/apps/launcher/importpage.cpp +++ b/apps/launcher/importpage.cpp @@ -104,9 +104,9 @@ void Launcher::ImportPage::on_importerButton_clicked() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open or create %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open or create %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); msgBox.exec(); return; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index bba3bbe5e1..177d4fe88c 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -121,9 +121,9 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() if (!create_directories(userConfigDir)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not create directory %0

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not create directory %0

" + "Please make sure you have the right permissions " + "and try again.
") .arg(Files::pathToQString(canonical(userConfigDir)))); return FirstRunDialogResultFailure; } @@ -136,10 +136,10 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() msgBox.setIcon(QMessageBox::Question); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText( - tr("

Welcome to OpenMW!

\ -

It is recommended to run the Installation Wizard.

\ -

The Wizard will let you select an existing Morrowind installation, \ - or install Morrowind for OpenMW to use.

")); + tr("

Welcome to OpenMW!

" + "

It is recommended to run the Installation Wizard.

" + "

The Wizard will let you select an existing Morrowind installation, " + "or install Morrowind for OpenMW to use.

")); QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! @@ -297,9 +297,9 @@ bool Launcher::MainDialog::setupLauncherSettings() if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading:

%1

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open %0 for reading:

%1

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName()) .arg(file.errorString())); return false; @@ -327,9 +327,9 @@ bool Launcher::MainDialog::setupGameSettings() if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open %0 for reading

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return {}; } @@ -388,8 +388,8 @@ bool Launcher::MainDialog::setupGameData() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText( - tr("
Could not find the Data Files location

\ - The directory containing the data files was not found.")); + tr("
Could not find the Data Files location

" + "The directory containing the data files was not found.")); QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); @@ -419,8 +419,8 @@ bool Launcher::MainDialog::setupGraphicsSettings() catch (std::exception& e) { cfgError(tr("Error reading OpenMW configuration files"), - tr("
The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.
") + tr("
The problem may be due to an incomplete installation of OpenMW.
" + "Reinstalling OpenMW may resolve the problem.
") + e.what()); return false; } @@ -460,9 +460,9 @@ bool Launcher::MainDialog::writeSettings() if (!create_directories(userPath)) { cfgError(tr("Error creating OpenMW configuration directory"), - tr("
Could not create %0

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not create %0

" + "Please make sure you have the right permissions " + "and try again.
") .arg(Files::pathToQString(userPath))); return false; } @@ -479,9 +479,9 @@ bool Launcher::MainDialog::writeSettings() { // File cannot be opened or created cfgError(tr("Error writing OpenMW configuration file"), - tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open or create %0 for writing

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return false; } @@ -510,9 +510,9 @@ bool Launcher::MainDialog::writeSettings() { // File cannot be opened or created cfgError(tr("Error writing Launcher configuration file"), - tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open or create %0 for writing

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return false; } @@ -562,8 +562,8 @@ void Launcher::MainDialog::play() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("
You do not have a game file selected.

\ - OpenMW will not start without a game file selected.
")); + tr("
You do not have a game file selected.

" + "OpenMW will not start without a game file selected.
")); msgBox.exec(); return; } diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index e492f4b83a..63f2eff078 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -138,10 +138,10 @@ bool Wizard::ComponentSelectionPage::validatePage() msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText( - tr("

You are about to install Tribunal

\ -

Bloodmoon is already installed on your computer.

\ -

However, it is recommended that you install Tribunal before Bloodmoon.

\ -

Would you like to re-install Bloodmoon?

")); + tr("

You are about to install Tribunal

" + "

Bloodmoon is already installed on your computer.

" + "

However, it is recommended that you install Tribunal before Bloodmoon.

" + "

Would you like to re-install Bloodmoon?

")); QAbstractButton* reinstallButton = msgBox.addButton(tr("Re-install &Bloodmoon"), QMessageBox::ActionRole); diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index a184c745ee..4a4a4ef689 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -37,22 +37,23 @@ void Wizard::ConclusionPage::initializePage() if (field(QLatin1String("installation.retailDisc")).toBool() == true) { textLabel->setText( - tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\ -

Click Finish to close the Wizard.

")); + tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

" + "

Click Finish to close the Wizard.

")); } else { - textLabel->setText(tr( - "

The OpenMW Wizard successfully modified your existing Morrowind installation.

\ -

Click Finish to close the Wizard.

")); + textLabel->setText( + tr("

The OpenMW Wizard successfully modified your existing Morrowind " + "installation.

Click Finish to close the Wizard.

")); } } else { textLabel->setText( - tr("

The OpenMW Wizard failed to install Morrowind on your computer.

\ -

Please report any bugs you might have encountered to our \ - bug tracker.
Make sure to include the installation log.


")); + tr("

The OpenMW Wizard failed to install Morrowind on your computer.

" + "

Please report any bugs you might have encountered to our " + "bug tracker.
Make sure to include the " + "installation log.


")); } } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 71ae331a61..d5ba009799 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -58,9 +58,9 @@ bool Wizard::ExistingInstallationPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText( - QObject::tr("
Could not find Morrowind.ini

\ - The Wizard needs to update settings in this file.

\ - Press \"Browse...\" to specify the location manually.
")); + QObject::tr("
Could not find Morrowind.ini

" + "The Wizard needs to update settings in this file.

" + "Press \"Browse...\" to specify the location manually.
")); QAbstractButton* browseButton2 = msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); @@ -107,8 +107,8 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - QObject::tr("Morrowind.bsa is missing!
\ - Make sure your Morrowind installation is complete.")); + QObject::tr("Morrowind.bsa is missing!
" + "Make sure your Morrowind installation is complete.")); msgBox.exec(); return; } @@ -187,8 +187,8 @@ bool Wizard::ExistingInstallationPage::versionIsOK(QString directory_name) msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); msgBox.setText( - QObject::tr("
There may be a more recent version of Morrowind available.

\ - Do you wish to continue anyway?
")); + QObject::tr("
There may be a more recent version of Morrowind available.

" + "Do you wish to continue anyway?
")); int ret = msgBox.exec(); if (ret == QMessageBox::Yes) { diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index e06972332a..cf2e3671e1 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -175,8 +175,8 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) if (path.isEmpty()) { logTextEdit->appendHtml( - tr("


\ - Error: The installation was aborted by the user

")); + tr("


" + "Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; @@ -205,8 +205,8 @@ void Wizard::InstallationPage::showOldVersionDialog() if (ret == QMessageBox::No) { logTextEdit->appendHtml( - tr("


\ - Error: The installation was aborted by the user

")); + tr("


" + "Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; @@ -236,14 +236,8 @@ void Wizard::InstallationPage::installationError(const QString& text, const QStr { installProgressLabel->setText(tr("Installation failed!")); - logTextEdit->appendHtml( - tr("


\ - Error: %1

") - .arg(text)); - logTextEdit->appendHtml( - tr("

\ - %1

") - .arg(details)); + logTextEdit->appendHtml(tr("


Error: %1

").arg(text)); + logTextEdit->appendHtml(tr("

%1

").arg(details)); mWizard->addLogText(QLatin1String("Error: ") + text); mWizard->addLogText(details); @@ -254,9 +248,9 @@ void Wizard::InstallationPage::installationError(const QString& text, const QStr msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

The Wizard has encountered an error

\ -

The error reported was:

%1

\ -

Press "Show Details..." for more information.

") + tr("

The Wizard has encountered an error

" + "

The error reported was:

%1

" + "

Press "Show Details..." for more information.

") .arg(text)); msgBox.setDetailedText(details); diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index c32573184d..dc94d2d002 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -48,9 +48,9 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not create the destination directory

\ -

Please make sure you have the right permissions \ - and try again, or specify a different location.

")); + tr("

Could not create the destination directory

" + "

Please make sure you have the right permissions " + "and try again, or specify a different location.

")); msgBox.exec(); return false; } @@ -65,9 +65,9 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not write to the destination directory

\ -

Please make sure you have the right permissions \ - and try again, or specify a different location.

")); + tr("

Could not write to the destination directory

" + "

Please make sure you have the right permissions " + "and try again, or specify a different location.

")); msgBox.exec(); return false; } @@ -79,9 +79,10 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

The destination directory is not empty

\ -

An existing Morrowind installation is present in the specified location.

\ -

Please specify a different location, or go back and select the location as an existing installation.

")); + tr("

The destination directory is not empty

" + "

An existing Morrowind installation is present in the specified location.

" + "

Please specify a different location, or go back and select the location as an existing " + "installation.

")); msgBox.exec(); return false; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 9abb61cfd7..e9cce3db5e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -55,9 +55,9 @@ Wizard::MainWizard::MainWizard(QWidget* parent) &MainWizard::importerFinished); mLogError = tr( - "

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

"); + "

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

"); std::filesystem::create_directories(mCfgMgr.getUserConfigPath()); std::filesystem::create_directories(mCfgMgr.getUserDataPath()); @@ -139,9 +139,9 @@ void Wizard::MainWizard::addLogText(const QString& text) void Wizard::MainWizard::setupGameSettings() { QString message( - tr("

Could not open %1 for reading

\ -

Please make sure you have the right permissions \ - and try again.

")); + tr("

Could not open %1 for reading

" + "

Please make sure you have the right permissions " + "and try again.

")); // Load the user config file first, separately // So we can write it properly, uncontaminated @@ -210,9 +210,9 @@ void Wizard::MainWizard::setupLauncherSettings() path.append(QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); QString message( - tr("

Could not open %1 for reading

\ -

Please make sure you have the right permissions \ - and try again.

")); + tr("

Could not open %1 for reading

" + "

Please make sure you have the right permissions " + "and try again.

")); QFile file(path); @@ -427,9 +427,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not create %1

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not create %1

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(userPath)); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); @@ -448,9 +448,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); @@ -475,9 +475,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index 73e23eb9f9..9489076acb 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -76,9 +76,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not find %1

\ -

The application is not found.

\ -

Please make sure OpenMW is installed correctly and try again.

") + tr("

Could not find %1

" + "

The application is not found.

" + "

Please make sure OpenMW is installed correctly and try again.

") .arg(info.fileName())); msgBox.exec(); return false; @@ -91,9 +91,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not start %1

\ -

The application is not executable.

\ -

Please make sure you have the right permissions and try again.

") + tr("

Could not start %1

" + "

The application is not executable.

" + "

Please make sure you have the right permissions and try again.

") .arg(info.fileName())); msgBox.exec(); return false; @@ -109,9 +109,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not start %1

\ -

An error occurred while starting %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Could not start %1

" + "

An error occurred while starting %1.

" + "

Press \"Show Details...\" for more information.

") .arg(info.fileName())); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); @@ -168,9 +168,9 @@ void Process::ProcessInvoker::processError(QProcess::ProcessError error) msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Executable %1 returned an error

\ -

An error occurred while running %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Executable %1 returned an error

" + "

An error occurred while running %1.

" + "

Press \"Show Details...\" for more information.

") .arg(mName)); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); @@ -191,9 +191,9 @@ void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Executable %1 returned an error

\ -

An error occurred while running %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Executable %1 returned an error

" + "

An error occurred while running %1.

" + "

Press \"Show Details...\" for more information.

") .arg(mName)); msgBox.setDetailedText(error); msgBox.exec(); From b96600a7fb7c760ca073038a6efe081c61aa2913 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 28 Nov 2023 21:50:40 +0300 Subject: [PATCH 0547/2167] Make niftest exceptions more informative --- apps/niftest/niftest.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 77752d25bd..29488fb677 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -72,9 +72,10 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) void readNIF( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { + const std::string pathStr = Files::pathToUnicodeString(path); if (!quiet) { - std::cout << "Reading NIF file '" << Files::pathToUnicodeString(path) << "'"; + std::cout << "Reading NIF file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; @@ -85,13 +86,13 @@ void readNIF( Nif::NIFFile file(fullPath); Nif::Reader reader(file); if (vfs != nullptr) - reader.parse(vfs->get(Files::pathToUnicodeString(path))); + reader.parse(vfs->get(pathStr)); else reader.parse(Files::openConstrainedFileStream(fullPath)); } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; } } @@ -204,20 +205,20 @@ int main(int argc, char** argv) vfs = std::make_unique(); for (const std::filesystem::path& path : sources) { + const std::string pathStr = Files::pathToUnicodeString(path); if (!quiet) - std::cout << "Adding data source '" << Files::pathToUnicodeString(path) << "'" << std::endl; + std::cout << "Adding data source '" << pathStr << "'" << std::endl; try { if (auto archive = makeArchive(path)) vfs->addArchive(std::move(archive)); else - std::cerr << "Error: '" << Files::pathToUnicodeString(path) << "' is not an archive or directory" - << std::endl; + std::cerr << "Error: '" << pathStr << "' is not an archive or directory" << std::endl; } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to add data source '" << pathStr << "': " << e.what() << std::endl; } } @@ -226,6 +227,7 @@ int main(int argc, char** argv) for (const auto& path : files) { + const std::string pathStr = Files::pathToUnicodeString(path); try { if (isNIF(path)) @@ -238,13 +240,12 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << Files::pathToUnicodeString(path) - << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; + std::cerr << "Error: '" << pathStr << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; } } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to read '" << pathStr << "': " << e.what() << std::endl; } } return 0; From b7a4cb0c8385ab613bc131e53e33aaa0e92a693b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 18 Nov 2023 17:42:12 +0100 Subject: [PATCH 0548/2167] The anim queue should still update when underwater. CharState_SpecialIdle should be retained until the animation queue is done. --- apps/openmw/mwmechanics/character.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index dd7b97b6a5..df04e3cfaa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2306,6 +2306,7 @@ namespace MWMechanics jumpstate = JumpState_None; } + updateAnimQueue(); if (mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) { if (inwater) @@ -2315,8 +2316,8 @@ namespace MWMechanics else idlestate = CharState_Idle; } - else - updateAnimQueue(); + else if (!mAnimQueue.empty()) + idlestate = CharState_SpecialIdle; if (!mSkipAnim) { From f4cc16e469988fae2901970acb639164a6482130 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 28 Nov 2023 21:29:05 +0100 Subject: [PATCH 0549/2167] feedback --- apps/openmw/mwmechanics/character.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index df04e3cfaa..d9166aa683 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2307,17 +2307,15 @@ namespace MWMechanics } updateAnimQueue(); - if (mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) - { - if (inwater) - idlestate = CharState_IdleSwim; - else if (sneak && !mInJump) - idlestate = CharState_IdleSneak; - else - idlestate = CharState_Idle; - } - else if (!mAnimQueue.empty()) + if (!mAnimQueue.empty()) idlestate = CharState_SpecialIdle; + else if (sneak && !mInJump) + idlestate = CharState_IdleSneak; + else + idlestate = CharState_Idle; + + if (inwater) + idlestate = CharState_IdleSwim; if (!mSkipAnim) { From 23aacbd9147a8de57823f4974a36385dddf90a80 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Nov 2023 19:20:38 +0100 Subject: [PATCH 0550/2167] Introduce a minimum supported save game format --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 29 +++++++++++++++++-------- components/esm3/formatversion.hpp | 4 ++++ files/data/l10n/OMWEngine/de.yaml | 3 +++ files/data/l10n/OMWEngine/en.yaml | 3 +++ files/data/l10n/OMWEngine/fr.yaml | 3 +++ files/data/l10n/OMWEngine/ru.yaml | 3 +++ files/data/l10n/OMWEngine/sv.yaml | 3 +++ 8 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..eb61affa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,7 @@ Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds + Feature #6624: Drop support for old saves Feature #6726: Lua API for creating new objects Feature #6864: Lua file access API Feature #6922: Improve launcher appearance diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 826c0dbba6..c040dca8dd 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -411,10 +411,25 @@ void MWState::StateManager::loadGame(const Character* character, const std::file ESM::ESMReader reader; reader.open(filepath); - if (reader.getFormatVersion() > ESM::CurrentSaveGameFormatVersion) - throw VersionMismatchError( - "This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade " - "to the newest OpenMW version to load this file."); + ESM::FormatVersion version = reader.getFormatVersion(); + if (version > ESM::CurrentSaveGameFormatVersion) + throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}"); + else if (version < ESM::MinSupportedSaveGameFormatVersion) + { + const char* release; + // Report the last version still capable of reading this save + if (version <= ESM::OpenMW0_48SaveGameFormatVersion) + release = "OpenMW 0.48.0"; + else + { + // Insert additional else if statements above to cover future releases + static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); + release = "OpenMW 0.49.0"; + } + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); + throw VersionMismatchError(message); + } std::map contentFileMap = buildContentFileIndexMap(reader); reader.setContentFileMapping(&contentFileMap); @@ -607,11 +622,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file std::vector buttons; buttons.emplace_back("#{Interface:OK}"); - std::string error; - if (typeid(e) == typeid(VersionMismatchError)) - error = "#{OMWEngine:LoadingFailed}: #{OMWEngine:LoadingRequiresNewVersionError}"; - else - error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); + std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons); } diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 12a73fc12b..1b4bee0bc5 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,6 +26,10 @@ namespace ESM inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; + + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 0; + inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; + inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } #endif diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 26838bd93c..0f729d0077 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -25,6 +25,9 @@ BuildingNavigationMesh: "Baue Navigationsgitter" #LoadingRequiresNewVersionError: |- # This save file was created using a newer version of OpenMW and is thus not supported. # Please upgrade to the newest OpenMW version to load this file. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. #NewGameConfirmation: "Do you want to start a new game and lose the current one?" #SaveGameDenied: "The game cannot be saved right now." #SavingInProgress: "Saving..." diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index ee2a33ee71..09db2b496d 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Loading Save Game" LoadingRequiresNewVersionError: |- This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file. +LoadingRequiresOldVersionError: |- + This save file was created using an older version of OpenMW in a format that is no longer supported. + Load and save this file using {version} to upgrade it. NewGameConfirmation: "Do you want to start a new game and lose the current one?" SaveGameDenied: "The game cannot be saved right now." SavingInProgress: "Saving..." diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 689ccc59a5..f2772b017e 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Chargement de la sauvegarde" LoadingRequiresNewVersionError: |- Ce fichier de sauvegarde provient d'une version plus récente d'OpenMW, il n'est par consequent pas supporté. Mettez à jour votre version d'OpenMW afin de pouvoir charger cette sauvegarde. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index b645b681b1..cecd6fc37c 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Загрузка сохранения" LoadingRequiresNewVersionError: |- Это сохранение создано более новой версией OpenMW и поэтому не может быть загружено. Обновите OpenMW до последней версии, чтобы загрузить этот файл. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Вы хотите начать новую игру? Текущая игра будет потеряна." SaveGameDenied: "В данный момент игру нельзя сохранить." SavingInProgress: "Сохранение..." diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index f4c9db031a..dc65726fdd 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Laddar sparat spel" LoadingRequiresNewVersionError: |- Denna sparfil skapades i en nyare version av OpenMW och stöds därför inte. Uppgradera till den senaste versionen av OpenMW för att ladda filen. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." From 194bcb0187c4c12ddfb2681889fa957ccf50b89c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Nov 2023 22:08:30 +0100 Subject: [PATCH 0551/2167] Drop support for save game format 0 (pre 0.37) --- apps/openmw/mwclass/creature.cpp | 32 +++++------ apps/openmw/mwclass/npc.cpp | 24 +++----- apps/openmw/mwdialogue/journalimp.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/cellstore.cpp | 6 +- apps/openmw/mwworld/player.cpp | 7 +-- apps/openmw_test_suite/mwworld/test_store.cpp | 4 +- components/esm/defs.hpp | 2 - components/esm3/creaturestats.cpp | 11 +--- components/esm3/dialoguestate.cpp | 8 --- components/esm3/fogstate.cpp | 2 +- components/esm3/formatversion.hpp | 7 ++- components/esm3/inventorystate.cpp | 8 --- components/esm3/npcstats.cpp | 57 ------------------- components/esm3/npcstats.hpp | 2 - components/esm3/objectstate.cpp | 4 -- components/esm3/quickkeys.cpp | 6 -- components/esm3/statstate.cpp | 4 -- 18 files changed, 36 insertions(+), 151 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2628cd3905..ed601a9255 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -779,32 +779,26 @@ namespace MWClass const ESM::CreatureState& creatureState = state.asCreatureState(); - if (state.mVersion > 0) + if (!ptr.getRefData().getCustomData()) { - if (!ptr.getRefData().getCustomData()) + if (creatureState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); + else { - if (creatureState.mCreatureStats.mMissingACDT) - ensureCustomData(ptr); + // Create a CustomData, but don't fill it from ESM records (not needed) + auto data = std::make_unique(); + + if (hasInventoryStore(ptr)) + data->mContainerStore = std::make_unique(); else - { - // Create a CustomData, but don't fill it from ESM records (not needed) - auto data = std::make_unique(); + data->mContainerStore = std::make_unique(); - if (hasInventoryStore(ptr)) - data->mContainerStore = std::make_unique(); - else - data->mContainerStore = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mContainerStore->setPtr(ptr); - MWBase::Environment::get().getWorldModel()->registerPtr(ptr); - data->mContainerStore->setPtr(ptr); - - ptr.getRefData().setCustomData(std::move(data)); - } + ptr.getRefData().setCustomData(std::move(data)); } } - else - ensureCustomData( - ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b9e8bc8dfb..95a3b713fa 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1333,25 +1333,19 @@ namespace MWClass const ESM::NpcState& npcState = state.asNpcState(); - if (state.mVersion > 0) + if (!ptr.getRefData().getCustomData()) { - if (!ptr.getRefData().getCustomData()) + if (npcState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); + else { - if (npcState.mCreatureStats.mMissingACDT) - ensureCustomData(ptr); - else - { - // Create a CustomData, but don't fill it from ESM records (not needed) - auto data = std::make_unique(); - MWBase::Environment::get().getWorldModel()->registerPtr(ptr); - data->mInventoryStore.setPtr(ptr); - ptr.getRefData().setCustomData(std::move(data)); - } + // Create a CustomData, but don't fill it from ESM records (not needed) + auto data = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mInventoryStore.setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); } } - else - ensureCustomData( - ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index e4d9453c83..28a2e16699 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -250,7 +250,7 @@ namespace MWDialogue void Journal::readRecord(ESM::ESMReader& reader, uint32_t type) { - if (type == ESM::REC_JOUR || type == ESM::REC_JOUR_LEGACY) + if (type == ESM::REC_JOUR) { ESM::JournalEntry record; record.load(reader); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c040dca8dd..89b63c35e4 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -472,7 +472,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file break; case ESM::REC_JOUR: - case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt()); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 340def5859..930f26c1cc 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -252,16 +252,16 @@ namespace if (!record) return; - if (state.mVersion < 15) + if (state.mVersion <= ESM::MaxOldRestockingFormatVersion) fixRestocking(record, state); - if (state.mVersion < 17) + if (state.mVersion <= ESM::MaxClearModifiersFormatVersion) { if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } - else if (state.mVersion < 20) + else if (state.mVersion <= ESM::MaxOldCreatureStatsFormatVersion) { if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertStats(state.mCreatureStats); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0d7afb559f..b498bb488b 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -334,12 +334,7 @@ namespace MWWorld if (player.mObject.mNpcStats.mIsWerewolf) { - if (player.mObject.mNpcStats.mWerewolfDeprecatedData) - { - saveStats(); - setWerewolfStats(); - } - else if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion) + if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion) { setWerewolfStats(); if (player.mSetWerewolfAcrobatics) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index d8890bc5ab..f80c12917c 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -272,8 +272,8 @@ namespace ESM::CurrentContentFormatVersion, ESM::MaxOldWeatherFormatVersion, ESM::MaxOldDeathAnimationFormatVersion, - ESM::MaxOldForOfWarFormatVersion, - ESM::MaxWerewolfDeprecatedDataFormatVersion, + ESM::MaxOldFogOfWarFormatVersion, + ESM::MaxUnoptimizedCharacterDataFormatVersion, ESM::MaxOldTimeLeftFormatVersion, ESM::MaxIntFallbackFormatVersion, ESM::MaxClearModifiersFormatVersion, diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 96d70f6fea..55404ee768 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -146,8 +146,6 @@ namespace ESM // format 0 - saved games REC_SAVE = esm3Recname("SAVE"), - REC_JOUR_LEGACY = esm3Recname("\xa4UOR"), // "\xa4UOR", rather than "JOUR", little oversight when magic numbers - // were calculated by hand, needs to be supported for older files now REC_JOUR = esm3Recname("JOUR"), REC_QUES = esm3Recname("QUES"), REC_GSCR = esm3Recname("GSCR"), diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index d8fb0d6969..8281916e30 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -38,7 +38,7 @@ namespace ESM mHitRecovery = false; mBlock = false; mRecalcDynamicStats = false; - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion) + if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion) { esm.getHNOT(mDead, "DEAD"); esm.getHNOT(mDeathAnimationFinished, "DFNT"); @@ -46,13 +46,9 @@ namespace ESM mDeathAnimationFinished = true; esm.getHNOT(mDied, "DIED"); esm.getHNOT(mMurdered, "MURD"); - if (esm.isNextSub("FRHT")) - esm.skipHSub(); // Friendly hits, no longer used esm.getHNOT(mTalkedTo, "TALK"); esm.getHNOT(mAlarmed, "ALRM"); esm.getHNOT(mAttacked, "ATKD"); - if (esm.isNextSub("HOST")) - esm.skipHSub(); // Hostile, no longer used if (esm.isNextSub("ATCK")) esm.skipHSub(); // attackingOrSpell, no longer used esm.getHNOT(mKnockdown, "KNCK"); @@ -82,9 +78,6 @@ namespace ESM mMovementFlags = 0; esm.getHNOT(mMovementFlags, "MOVE"); - if (esm.isNextSub("ASTR")) - esm.skipHSub(); // attackStrength, no longer used - mFallHeight = 0; esm.getHNOT(mFallHeight, "FALL"); @@ -92,7 +85,7 @@ namespace ESM mLastHitAttemptObject = esm.getHNORefId("LHAT"); - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion) + if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion) esm.getHNOT(mRecalcDynamicStats, "CALC"); mDrawState = 0; diff --git a/components/esm3/dialoguestate.cpp b/components/esm3/dialoguestate.cpp index 88fbe9659a..7095e096cb 100644 --- a/components/esm3/dialoguestate.cpp +++ b/components/esm3/dialoguestate.cpp @@ -22,14 +22,6 @@ namespace ESM esm.getHNT(reaction, "INTV"); mChangedFactionReaction[faction][faction2] = reaction; } - - // no longer used - while (esm.isNextSub("REAC")) - { - esm.skipHSub(); - esm.getSubName(); - esm.skipHSub(); - } } } diff --git a/components/esm3/fogstate.cpp b/components/esm3/fogstate.cpp index 3ee4600c90..2c07438070 100644 --- a/components/esm3/fogstate.cpp +++ b/components/esm3/fogstate.cpp @@ -74,7 +74,7 @@ namespace ESM tex.mImageData.resize(imageSize); esm.getExact(tex.mImageData.data(), imageSize); - if (dataFormat <= MaxOldForOfWarFormatVersion) + if (dataFormat <= MaxOldFogOfWarFormatVersion) convertFogOfWar(tex.mImageData); mFogTextures.push_back(tex); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 1b4bee0bc5..e02e0176a9 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -11,10 +11,11 @@ namespace ESM inline constexpr FormatVersion CurrentContentFormatVersion = 1; inline constexpr FormatVersion MaxOldWeatherFormatVersion = 1; inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; - inline constexpr FormatVersion MaxOldForOfWarFormatVersion = 6; - inline constexpr FormatVersion MaxWerewolfDeprecatedDataFormatVersion = 7; + inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; + inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; inline constexpr FormatVersion MaxIntFallbackFormatVersion = 10; + inline constexpr FormatVersion MaxOldRestockingFormatVersion = 14; inline constexpr FormatVersion MaxClearModifiersFormatVersion = 16; inline constexpr FormatVersion MaxOldAiPackageFormatVersion = 17; inline constexpr FormatVersion MaxOldSkillsAndAttributesFormatVersion = 18; @@ -27,7 +28,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 0; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 1; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 84a52ff518..a6130af473 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -22,14 +22,6 @@ namespace ESM ObjectState state; - // obsolete - if (esm.isNextSub("SLOT")) - { - int32_t slot; - esm.getHT(slot); - mEquipmentSlots[index] = slot; - } - state.mRef.loadId(esm, true); state.load(esm); diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index a21ba807e4..dc221a5b43 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -41,46 +41,6 @@ namespace ESM for (auto& skill : mSkills) skill.load(esm, intFallback); - mWerewolfDeprecatedData = false; - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion && esm.peekNextSub("STBA")) - { - // we have deprecated werewolf skills, stored interleaved - // Load into one big vector, then remove every 2nd value - mWerewolfDeprecatedData = true; - std::vector> skills(mSkills.begin(), mSkills.end()); - - for (size_t i = 0; i < std::size(mSkills); ++i) - { - StatState skill; - skill.load(esm, intFallback); - skills.push_back(skill); - } - - int i = 0; - for (std::vector>::iterator it = skills.begin(); it != skills.end(); ++i) - { - if (i % 2 == 1) - it = skills.erase(it); - else - ++it; - } - if (skills.size() != std::size(mSkills)) - throw std::runtime_error( - "Invalid number of skill for werewolf deprecated data: " + std::to_string(skills.size())); - std::copy(skills.begin(), skills.end(), mSkills.begin()); - } - - // No longer used - bool hasWerewolfAttributes = false; - esm.getHNOT(hasWerewolfAttributes, "HWAT"); - if (hasWerewolfAttributes) - { - StatState dummy; - for (int i = 0; i < ESM::Attribute::Length; ++i) - dummy.load(esm, intFallback); - mWerewolfDeprecatedData = true; - } - mIsWerewolf = false; esm.getHNOT(mIsWerewolf, "WOLF"); @@ -93,14 +53,6 @@ namespace ESM mWerewolfKills = 0; esm.getHNOT(mWerewolfKills, "WKIL"); - // No longer used - if (esm.isNextSub("PROF")) - esm.skipHSub(); // int profit - - // No longer used - if (esm.isNextSub("ASTR")) - esm.skipHSub(); // attackStrength - mLevelProgress = 0; esm.getHNOT(mLevelProgress, "LPRO"); @@ -116,14 +68,6 @@ namespace ESM mTimeToStartDrowning = 0; esm.getHNOT(mTimeToStartDrowning, "DRTI"); - // No longer used - float lastDrowningHit = 0; - esm.getHNOT(lastDrowningHit, "DRLH"); - - // No longer used - float levelHealthBonus = 0; - esm.getHNOT(levelHealthBonus, "LVLH"); - mCrimeId = -1; esm.getHNOT(mCrimeId, "CRID"); } @@ -195,7 +139,6 @@ namespace ESM void NpcStats::blank() { - mWerewolfDeprecatedData = false; mIsWerewolf = false; mDisposition = 0; mBounty = 0; diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index ccb58a12ad..425a62162b 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -31,8 +31,6 @@ namespace ESM bool mIsWerewolf; - bool mWerewolfDeprecatedData; - std::map mFactions; int32_t mDisposition; std::array, ESM::Skill::Length> mSkills; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a7fe41d66c..fca4c64f5f 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -48,10 +48,6 @@ namespace ESM mFlags = 0; esm.getHNOT(mFlags, "FLAG"); - // obsolete - int32_t unused; - esm.getHNOT(unused, "LTIM"); - mAnimationState.load(esm); // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files diff --git a/components/esm3/quickkeys.cpp b/components/esm3/quickkeys.cpp index ababa535b7..7477fd24fa 100644 --- a/components/esm3/quickkeys.cpp +++ b/components/esm3/quickkeys.cpp @@ -8,9 +8,6 @@ namespace ESM void QuickKeys::load(ESMReader& esm) { - if (esm.isNextSub("KEY_")) - esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader - while (esm.isNextSub("TYPE")) { QuickKey key; @@ -18,9 +15,6 @@ namespace ESM key.mId = esm.getHNRefId("ID__"); mKeys.push_back(key); - - if (esm.isNextSub("KEY_")) - esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader } } diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index 7477d83e2d..b46c2e34fd 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -32,10 +32,6 @@ namespace ESM int32_t current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); - - int32_t oldDamage = 0; - esm.getHNOT(oldDamage, "STDA"); - mDamage = static_cast(oldDamage); } else { From 659d7fefa1eca36ac7f9ebe6a540c2a34cf8551a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 1 Dec 2023 16:38:38 +0100 Subject: [PATCH 0552/2167] Add Russian localization --- files/data/l10n/OMWEngine/ru.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index cecd6fc37c..2bcb76a442 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -22,9 +22,9 @@ LoadingInProgress: "Загрузка сохранения" LoadingRequiresNewVersionError: |- Это сохранение создано более новой версией OpenMW и поэтому не может быть загружено. Обновите OpenMW до последней версии, чтобы загрузить этот файл. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. +LoadingRequiresOldVersionError: |- + Это сохранение создано старой версией OpenMW и использует формат, который больше не поддерживается. + Загрузите и сохраните этот файл в {version}, чтобы обновить его. NewGameConfirmation: "Вы хотите начать новую игру? Текущая игра будет потеряна." SaveGameDenied: "В данный момент игру нельзя сохранить." SavingInProgress: "Сохранение..." From 88a6ecabae77854092a13e5cff070c62e6afaedf Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:11:49 +0100 Subject: [PATCH 0553/2167] Add lookup index to editor settings category Prevent adding duplicate settings there. --- apps/opencs/model/prefs/category.cpp | 12 ++++++++---- apps/opencs/model/prefs/category.hpp | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp index 5a82be08fc..9c8923f042 100644 --- a/apps/opencs/model/prefs/category.cpp +++ b/apps/opencs/model/prefs/category.cpp @@ -24,6 +24,9 @@ CSMPrefs::State* CSMPrefs::Category::getState() const void CSMPrefs::Category::addSetting(Setting* setting) { + if (!mIndex.emplace(setting->getKey(), setting).second) + throw std::logic_error("Category " + mKey + " already has setting: " + setting->getKey()); + mSettings.push_back(setting); } @@ -39,11 +42,12 @@ CSMPrefs::Category::Iterator CSMPrefs::Category::end() CSMPrefs::Setting& CSMPrefs::Category::operator[](const std::string& key) { - for (Iterator iter = mSettings.begin(); iter != mSettings.end(); ++iter) - if ((*iter)->getKey() == key) - return **iter; + const auto it = mIndex.find(key); - throw std::logic_error("Invalid user setting: " + key); + if (it != mIndex.end()) + return *it->second; + + throw std::logic_error("Invalid user setting in " + mKey + " category: " + key); } void CSMPrefs::Category::update() diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp index 5c75f99067..19b559b384 100644 --- a/apps/opencs/model/prefs/category.hpp +++ b/apps/opencs/model/prefs/category.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace CSMPrefs @@ -20,6 +21,7 @@ namespace CSMPrefs State* mParent; std::string mKey; Container mSettings; + std::unordered_map mIndex; public: Category(State* parent, const std::string& key); From 4c13ecea236bfe1ee7c0b5a554a198fff0d9831c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 00:52:09 +0100 Subject: [PATCH 0554/2167] Use settings values for editor --- apps/opencs/model/prefs/boolsetting.cpp | 16 ++---- apps/opencs/model/prefs/boolsetting.hpp | 6 +- apps/opencs/model/prefs/category.cpp | 6 ++ apps/opencs/model/prefs/category.hpp | 3 + apps/opencs/model/prefs/coloursetting.cpp | 17 ++---- apps/opencs/model/prefs/coloursetting.hpp | 7 ++- apps/opencs/model/prefs/doublesetting.cpp | 17 ++---- apps/opencs/model/prefs/doublesetting.hpp | 6 +- apps/opencs/model/prefs/enumsetting.cpp | 24 +++----- apps/opencs/model/prefs/enumsetting.hpp | 7 +-- apps/opencs/model/prefs/intsetting.cpp | 17 ++---- apps/opencs/model/prefs/intsetting.hpp | 6 +- apps/opencs/model/prefs/modifiersetting.cpp | 17 ++---- apps/opencs/model/prefs/modifiersetting.hpp | 6 +- apps/opencs/model/prefs/setting.cpp | 29 ++-------- apps/opencs/model/prefs/setting.hpp | 59 ++++++++++++++++--- apps/opencs/model/prefs/shortcutsetting.cpp | 17 ++---- apps/opencs/model/prefs/shortcutsetting.hpp | 6 +- apps/opencs/model/prefs/state.cpp | 64 ++++++--------------- apps/opencs/model/prefs/state.hpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 17 ++---- apps/opencs/model/prefs/stringsetting.hpp | 8 +-- apps/opencs/model/prefs/subcategory.cpp | 4 +- apps/opencs/model/prefs/subcategory.hpp | 4 +- apps/opencs/model/world/actoradapter.cpp | 7 ++- 25 files changed, 164 insertions(+), 208 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c1eb626969..2ca8315245 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,9 +11,8 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -27,7 +26,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { mWidget = new QCheckBox(getLabel(), parent); - mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); + mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) { @@ -44,17 +43,12 @@ void CSMPrefs::BoolSetting::updateWidget() { if (mWidget) { - mWidget->setCheckState( - Settings::Manager::getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked); + mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked); } } void CSMPrefs::BoolSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setBool(getKey(), getParent()->getKey(), value); - } - + setValue(value != Qt::Unchecked); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index 9d53f98e9e..fd67019a78 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -12,16 +12,16 @@ namespace CSMPrefs { class Category; - class BoolSetting : public Setting + class BoolSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - bool mDefault; QCheckBox* mWidget; public: - BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_); + explicit BoolSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp index 9c8923f042..3ae4826953 100644 --- a/apps/opencs/model/prefs/category.cpp +++ b/apps/opencs/model/prefs/category.cpp @@ -5,6 +5,7 @@ #include "setting.hpp" #include "state.hpp" +#include "subcategory.hpp" CSMPrefs::Category::Category(State* parent, const std::string& key) : mParent(parent) @@ -30,6 +31,11 @@ void CSMPrefs::Category::addSetting(Setting* setting) mSettings.push_back(setting); } +void CSMPrefs::Category::addSubcategory(Subcategory* setting) +{ + mSettings.push_back(setting); +} + CSMPrefs::Category::Iterator CSMPrefs::Category::begin() { return mSettings.begin(); diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp index 19b559b384..ef67c82138 100644 --- a/apps/opencs/model/prefs/category.hpp +++ b/apps/opencs/model/prefs/category.hpp @@ -10,6 +10,7 @@ namespace CSMPrefs { class State; class Setting; + class Subcategory; class Category { @@ -32,6 +33,8 @@ namespace CSMPrefs void addSetting(Setting* setting); + void addSubcategory(Subcategory* setting); + Iterator begin(); Iterator end(); diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index e2ece04722..61aa92063e 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,9 +14,8 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_) - : Setting(parent, mutex, key, label) - , mDefault(std::move(default_)) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -31,7 +30,7 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); - mWidget = new CSVWidget::ColorEditor(mDefault, parent); + mWidget = new CSVWidget::ColorEditor(toColor(), parent); if (!mTooltip.empty()) { @@ -48,18 +47,12 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) void CSMPrefs::ColourSetting::updateWidget() { if (mWidget) - { - mWidget->setColor(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - } + mWidget->setColor(toColor()); } void CSMPrefs::ColourSetting::valueChanged() { CSVWidget::ColorEditor& widget = dynamic_cast(*sender()); - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); - } - + setValue(widget.color().name().toStdString()); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index 5a1a7a2df2..f5af627491 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -20,16 +20,17 @@ namespace CSVWidget namespace CSMPrefs { class Category; - class ColourSetting : public Setting + + class ColourSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - QColor mDefault; CSVWidget::ColorEditor* mWidget; public: - ColourSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_); + explicit ColourSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 153298ce57..b275c2e9b7 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,12 +15,11 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_) - : Setting(parent, mutex, key, label) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mPrecision(2) , mMin(0) , mMax(std::numeric_limits::max()) - , mDefault(default_) , mWidget(nullptr) { } @@ -63,7 +62,7 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) mWidget = new QDoubleSpinBox(parent); mWidget->setDecimals(mPrecision); mWidget->setRange(mMin, mMax); - mWidget->setValue(mDefault); + mWidget->setValue(getValue()); if (!mTooltip.empty()) { @@ -80,17 +79,11 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) void CSMPrefs::DoubleSetting::updateWidget() { if (mWidget) - { - mWidget->setValue(Settings::Manager::getFloat(getKey(), getParent()->getKey())); - } + mWidget->setValue(getValue()); } void CSMPrefs::DoubleSetting::valueChanged(double value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setFloat(getKey(), getParent()->getKey(), value); - } - + setValue(value); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 33342d2f5b..add85cb9b3 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -9,7 +9,7 @@ namespace CSMPrefs { class Category; - class DoubleSetting : public Setting + class DoubleSetting final : public TypedSetting { Q_OBJECT @@ -17,11 +17,11 @@ namespace CSMPrefs double mMin; double mMax; std::string mTooltip; - double mDefault; QDoubleSpinBox* mWidget; public: - DoubleSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_); + explicit DoubleSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index d55e4005a4..1521c3cc13 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -45,9 +45,8 @@ CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const } CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -83,16 +82,18 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) mWidget = new QComboBox(parent); size_t index = 0; + const std::string value = getValue(); for (size_t i = 0; i < mValues.mValues.size(); ++i) { - if (mDefault.mValue == mValues.mValues[i].mValue) + if (value == mValues.mValues[i].mValue) index = i; mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str())); if (!mValues.mValues[i].mTooltip.empty()) - mWidget->setItemData(i, QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); + mWidget->setItemData( + static_cast(i), QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); } mWidget->setCurrentIndex(static_cast(index)); @@ -111,20 +112,11 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) void CSMPrefs::EnumSetting::updateWidget() { if (mWidget) - { - int index - = mWidget->findText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - - mWidget->setCurrentIndex(index); - } + mWidget->setCurrentIndex(mWidget->findText(QString::fromStdString(getValue()))); } void CSMPrefs::EnumSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), mValues.mValues.at(value).mValue); - } - + setValue(mValues.mValues.at(value).mValue); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index f430988aa6..51241d593f 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -34,18 +34,17 @@ namespace CSMPrefs EnumValues& add(const std::string& value, const std::string& tooltip); }; - class EnumSetting : public Setting + class EnumSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - EnumValue mDefault; EnumValues mValues; QComboBox* mWidget; public: - EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_); + explicit EnumSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); EnumSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 7d0b40a45a..023339b9c1 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,11 +15,10 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_) - : Setting(parent, mutex, key, label) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mMin(0) , mMax(std::numeric_limits::max()) - , mDefault(default_) , mWidget(nullptr) { } @@ -55,7 +54,7 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) mWidget = new QSpinBox(parent); mWidget->setRange(mMin, mMax); - mWidget->setValue(mDefault); + mWidget->setValue(getValue()); if (!mTooltip.empty()) { @@ -72,17 +71,11 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) void CSMPrefs::IntSetting::updateWidget() { if (mWidget) - { - mWidget->setValue(Settings::Manager::getInt(getKey(), getParent()->getKey())); - } + mWidget->setValue(getValue()); } void CSMPrefs::IntSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setInt(getKey(), getParent()->getKey(), value); - } - + setValue(value); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 8fb3bdb1f6..a1ed19ffd6 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -12,18 +12,18 @@ namespace CSMPrefs { class Category; - class IntSetting : public Setting + class IntSetting final : public TypedSetting { Q_OBJECT int mMin; int mMax; std::string mTooltip; - int mDefault; QSpinBox* mWidget; public: - IntSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_); + explicit IntSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 173092dc2b..53db4c8521 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -19,8 +19,9 @@ class QWidget; namespace CSMPrefs { - ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) - : Setting(parent, mutex, key, label) + ModifierSetting::ModifierSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) { @@ -53,7 +54,7 @@ namespace CSMPrefs { if (mButton) { - const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); + const std::string& shortcut = getValue(); int modifier; State::get().getShortcutManager().convertFromString(shortcut, modifier); @@ -131,15 +132,7 @@ namespace CSMPrefs void ModifierSetting::storeValue(int modifier) { State::get().getShortcutManager().setModifier(getKey(), modifier); - - // Convert to string and assign - std::string value = State::get().getShortcutManager().convertToString(modifier); - - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), value); - } - + setValue(State::get().getShortcutManager().convertToString(modifier)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index 9e308875fd..cf9ce8f149 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -15,12 +15,14 @@ class QPushButton; namespace CSMPrefs { class Category; - class ModifierSetting : public Setting + + class ModifierSetting final : public TypedSetting { Q_OBJECT public: - ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit ModifierSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index 666b4ba1b1..cc5077dc83 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "category.hpp" #include "state.hpp" @@ -14,12 +15,14 @@ QMutex* CSMPrefs::Setting::getMutex() return mMutex; } -CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) +CSMPrefs::Setting::Setting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) , mKey(key) , mLabel(label) + , mIndex(index) { } @@ -33,30 +36,6 @@ const std::string& CSMPrefs::Setting::getKey() const return mKey; } -int CSMPrefs::Setting::toInt() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getInt(mKey, mParent->getKey()); -} - -double CSMPrefs::Setting::toDouble() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getFloat(mKey, mParent->getKey()); -} - -std::string CSMPrefs::Setting::toString() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getString(mKey, mParent->getKey()); -} - -bool CSMPrefs::Setting::isTrue() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getBool(mKey, mParent->getKey()); -} - QColor CSMPrefs::Setting::toColor() const { // toString() handles lock diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index 882add20ca..f5d9e83ef1 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -4,8 +4,13 @@ #include #include +#include #include +#include + +#include "category.hpp" + class QWidget; class QColor; class QMutex; @@ -14,8 +19,6 @@ class QLabel; namespace CSMPrefs { - class Category; - struct SettingWidgets { QLabel* mLabel; @@ -31,12 +34,35 @@ namespace CSMPrefs QMutex* mMutex; std::string mKey; QString mLabel; + Settings::Index& mIndex; protected: QMutex* getMutex(); + template + void resetValueImpl() + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).reset(); + } + + template + T getValueImpl() const + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).get(); + } + + template + void setValueImpl(const T& value) + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).set(value); + } + public: - Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit Setting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); ~Setting() override = default; @@ -47,23 +73,42 @@ namespace CSMPrefs /// \note If make_widgets() has not been called yet then nothing happens. virtual void updateWidget() = 0; + virtual void reset() = 0; + const Category* getParent() const; const std::string& getKey() const; const QString& getLabel() const { return mLabel; } - int toInt() const; + int toInt() const { return getValueImpl(); } - double toDouble() const; + double toDouble() const { return getValueImpl(); } - std::string toString() const; + std::string toString() const { return getValueImpl(); } - bool isTrue() const; + bool isTrue() const { return getValueImpl(); } QColor toColor() const; }; + template + class TypedSetting : public Setting + { + public: + using Setting::Setting; + + void reset() final + { + resetValueImpl(); + updateWidget(); + } + + T getValue() const { return getValueImpl(); } + + void setValue(const T& value) { return setValueImpl(value); } + }; + // note: fullKeys have the format categoryKey/settingKey bool operator==(const Setting& setting, const std::string& fullKey); bool operator==(const std::string& fullKey, const Setting& setting); diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index aae290b3f4..42902f02cd 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -18,8 +18,9 @@ namespace CSMPrefs { - ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) - : Setting(parent, mutex, key, label) + ShortcutSetting::ShortcutSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) , mEditorPos(0) @@ -57,7 +58,7 @@ namespace CSMPrefs { if (mButton) { - const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); + const std::string shortcut = getValue(); QKeySequence sequence; State::get().getShortcutManager().convertFromString(shortcut, sequence); @@ -170,15 +171,7 @@ namespace CSMPrefs void ShortcutSetting::storeValue(const QKeySequence& sequence) { State::get().getShortcutManager().setSequence(getKey(), sequence); - - // Convert to string and assign - std::string value = State::get().getShortcutManager().convertToString(sequence); - - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), value); - } - + setValue(State::get().getShortcutManager().convertToString(sequence)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 74cad995d4..84857a9bc7 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -17,12 +17,14 @@ class QWidget; namespace CSMPrefs { class Category; - class ShortcutSetting : public Setting + + class ShortcutSetting final : public TypedSetting { Q_OBJECT public: - ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit ShortcutSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d74044f6d5..d55913fb3c 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -465,9 +465,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const setDefault(key, std::to_string(default_)); - default_ = Settings::Manager::getInt(key, mCurrentCategory->second.getKey()); - - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -483,10 +481,8 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, stream << default_; setDefault(key, stream.str()); - default_ = Settings::Manager::getFloat(key, mCurrentCategory->second.getKey()); - CSMPrefs::DoubleSetting* setting - = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -500,10 +496,7 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons setDefault(key, default_ ? "true" : "false"); - default_ = Settings::Manager::getBool(key, mCurrentCategory->second.getKey()); - - CSMPrefs::BoolSetting* setting - = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -517,10 +510,7 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, cons setDefault(key, default_.mValue); - default_.mValue = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - - CSMPrefs::EnumSetting* setting - = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -534,11 +524,8 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, setDefault(key, default_.name().toUtf8().data()); - default_.setNamedColor( - QString::fromUtf8(Settings::Manager::getString(key, mCurrentCategory->second.getKey()).c_str())); - CSMPrefs::ColourSetting* setting - = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -557,28 +544,26 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( // Setup with actual data QKeySequence sequence; - getShortcutManager().convertFromString( - Settings::Manager::getString(key, mCurrentCategory->second.getKey()), sequence); + getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), sequence); getShortcutManager().setSequence(key, sequence); - CSMPrefs::ShortcutSetting* setting = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label); + CSMPrefs::ShortcutSetting* setting + = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const QString& label, std::string default_) + const std::string& key, const QString& label, const std::string& default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); setDefault(key, default_); - default_ = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - CSMPrefs::StringSetting* setting - = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -596,11 +581,11 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k // Setup with actual data int modifier; - getShortcutManager().convertFromString( - Settings::Manager::getString(key, mCurrentCategory->second.getKey()), modifier); + getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), modifier); getShortcutManager().setModifier(key, modifier); - CSMPrefs::ModifierSetting* setting = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label); + CSMPrefs::ModifierSetting* setting + = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; @@ -611,7 +596,8 @@ void CSMPrefs::State::declareSubcategory(const QString& label) if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - mCurrentCategory->second.addSetting(new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label)); + mCurrentCategory->second.addSubcategory( + new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label, *mIndex)); } void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) @@ -693,27 +679,13 @@ CSMPrefs::State& CSMPrefs::State::get() void CSMPrefs::State::resetCategory(const std::string& category) { - for (Settings::CategorySettingValueMap::iterator i = Settings::Manager::mUserSettings.begin(); - i != Settings::Manager::mUserSettings.end(); ++i) - { - // if the category matches - if (i->first.first == category) - { - // mark the setting as changed - Settings::Manager::mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); - // reset the value to the default - i->second = Settings::Manager::mDefaultSettings[i->first]; - } - } - Collection::iterator container = mCategories.find(category); if (container != mCategories.end()) { - Category settings = container->second; - for (Category::Iterator i = settings.begin(); i != settings.end(); ++i) + for (Setting* setting : container->second) { - (*i)->updateWidget(); - update(**i); + setting->reset(); + update(*setting); } } } diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index e398f06e4a..97d657ddaf 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -74,7 +74,7 @@ namespace CSMPrefs ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const QString& label, std::string default_); + StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 0cba2d047d..d70324ba56 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,9 +12,8 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -27,7 +26,7 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { - mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent); + mWidget = new QLineEdit(QString::fromStdString(getValue()), parent); if (!mTooltip.empty()) { @@ -43,17 +42,11 @@ CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) void CSMPrefs::StringSetting::updateWidget() { if (mWidget) - { - mWidget->setText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - } + mWidget->setText(QString::fromStdString(getValue())); } void CSMPrefs::StringSetting::textChanged(const QString& text) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), text.toStdString()); - } - + setValue(text.toStdString()); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 5c03a6ea12..25e1a362ff 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -14,17 +14,17 @@ class QWidget; namespace CSMPrefs { class Category; - class StringSetting : public Setting + + class StringSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - std::string mDefault; QLineEdit* mWidget; public: - StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_); + explicit StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); StringSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp index cca558407c..ac5b78ee2c 100644 --- a/apps/opencs/model/prefs/subcategory.cpp +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -6,8 +6,8 @@ namespace CSMPrefs { class Category; - Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label) - : Setting(parent, mutex, "", label) + Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index) + : Setting(parent, mutex, "", label, index) { } diff --git a/apps/opencs/model/prefs/subcategory.hpp b/apps/opencs/model/prefs/subcategory.hpp index 4f62c1743c..4c661ad0fa 100644 --- a/apps/opencs/model/prefs/subcategory.hpp +++ b/apps/opencs/model/prefs/subcategory.hpp @@ -15,11 +15,13 @@ namespace CSMPrefs Q_OBJECT public: - explicit Subcategory(Category* parent, QMutex* mutex, const QString& label); + explicit Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override {} + + void reset() override {} }; } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index e68d3e833f..0e3725bbb7 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -132,11 +133,11 @@ namespace CSMWorld bool beast = mRaceData ? mRaceData->isBeast() : false; if (beast) - return Settings::Manager::getString("baseanimkna", "Models"); + return CSMPrefs::get()["Models"]["baseanimkna"].toString(); else if (mFemale) - return Settings::Manager::getString("baseanimfemale", "Models"); + return CSMPrefs::get()["Models"]["baseanimfemale"].toString(); else - return Settings::Manager::getString("baseanim", "Models"); + return CSMPrefs::get()["Models"]["baseanim"].toString(); } ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const From e1a68d8cf598e25c5348a7b91b05dbe7ea854b69 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:14:16 +0100 Subject: [PATCH 0555/2167] Ignore absent default setting value --- apps/opencs/model/prefs/state.cpp | 35 +------------------ apps/opencs/model/prefs/state.hpp | 2 -- .../openmw_test_suite/settings/testvalues.cpp | 34 ++++++++++++++++++ components/settings/settings.hpp | 9 +++++ components/settings/settingvalue.hpp | 4 +-- 5 files changed, 46 insertions(+), 38 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d55913fb3c..364f7b3320 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -463,8 +463,6 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, std::to_string(default_)); - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -477,10 +475,6 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::ostringstream stream; - stream << default_; - setDefault(key, stream.str()); - CSMPrefs::DoubleSetting* setting = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -494,8 +488,6 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_ ? "true" : "false"); - CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -508,8 +500,6 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, cons if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_.mValue); - CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -522,8 +512,6 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_.name().toUtf8().data()); - CSMPrefs::ColourSetting* setting = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -538,9 +526,6 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::string seqStr = getShortcutManager().convertToString(default_); - setDefault(key, seqStr); - // Setup with actual data QKeySequence sequence; @@ -560,8 +545,6 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_); - CSMPrefs::StringSetting* setting = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -575,9 +558,6 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::string modStr = getShortcutManager().convertToString(default_); - setDefault(key, modStr); - // Setup with actual data int modifier; @@ -600,33 +580,20 @@ void CSMPrefs::State::declareSubcategory(const QString& label) new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label, *mIndex)); } -void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) -{ - Settings::CategorySetting fullKey(mCurrentCategory->second.getKey(), key); - - Settings::CategorySettingValueMap::iterator iter = Settings::Manager::mDefaultSettings.find(fullKey); - - if (iter == Settings::Manager::mDefaultSettings.end()) - Settings::Manager::mDefaultSettings.insert(std::make_pair(fullKey, default_)); -} - CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager) : mConfigFile("openmw-cs.cfg") , mDefaultConfigFile("defaults-cs.bin") , mConfigurationManager(configurationManager) , mCurrentCategory(mCategories.end()) , mIndex(std::make_unique()) + , mValues(std::make_unique(*mIndex)) { if (sThis) throw std::logic_error("An instance of CSMPRefs::State already exists"); sThis = this; - Values values(*mIndex); - declare(); - - mValues = std::make_unique(std::move(values)); } CSMPrefs::State::~State() diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 97d657ddaf..018a81a8d9 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -80,8 +80,6 @@ namespace CSMPrefs void declareSubcategory(const QString& label); - void setDefault(const std::string& key, const std::string& default_); - public: State(const Files::ConfigurationManager& configurationManager); diff --git a/apps/openmw_test_suite/settings/testvalues.cpp b/apps/openmw_test_suite/settings/testvalues.cpp index 81af308795..236417b559 100644 --- a/apps/openmw_test_suite/settings/testvalues.cpp +++ b/apps/openmw_test_suite/settings/testvalues.cpp @@ -59,6 +59,33 @@ namespace Settings EXPECT_EQ(values.mCamera.mFieldOfView.get(), 1); } + TEST_F(SettingsValuesTest, constructorWithDefaultShouldDoLookup) + { + Manager::mUserSettings[std::make_pair("category", "value")] = "13"; + Index index; + SettingValue value{ index, "category", "value", 42 }; + EXPECT_EQ(value.get(), 13); + value.reset(); + EXPECT_EQ(value.get(), 42); + } + + TEST_F(SettingsValuesTest, constructorWithDefaultShouldSanitize) + { + Manager::mUserSettings[std::make_pair("category", "value")] = "2"; + Index index; + SettingValue value{ index, "category", "value", -1, Settings::makeClampSanitizerInt(0, 1) }; + EXPECT_EQ(value.get(), 1); + value.reset(); + EXPECT_EQ(value.get(), 0); + } + + TEST_F(SettingsValuesTest, constructorWithDefaultShouldFallbackToDefault) + { + Index index; + const SettingValue value{ index, "category", "value", 42 }; + EXPECT_EQ(value.get(), 42); + } + TEST_F(SettingsValuesTest, moveConstructorShouldSetDefaults) { Index index; @@ -79,6 +106,13 @@ namespace Settings EXPECT_EQ(values.mCamera.mFieldOfView.get(), 1); } + TEST_F(SettingsValuesTest, moveConstructorShouldThrowOnMissingSetting) + { + Index index; + SettingValue defaultValue{ index, "category", "value", 42 }; + EXPECT_THROW([&] { SettingValue value(std::move(defaultValue)); }(), std::runtime_error); + } + TEST_F(SettingsValuesTest, findShouldThrowExceptionOnTypeMismatch) { Index index; diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c061755bc1..bcdcbb16d1 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -77,6 +77,15 @@ namespace Settings static osg::Vec2f getVector2(std::string_view setting, std::string_view category); static osg::Vec3f getVector3(std::string_view setting, std::string_view category); + template + static T getOrDefault(std::string_view setting, std::string_view category, const T& defaultValue) + { + const auto key = std::make_pair(category, setting); + if (!mUserSettings.contains(key) && !mDefaultSettings.contains(key)) + return defaultValue; + return get(setting, category); + } + template static T get(std::string_view setting, std::string_view category) { diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 392e5b646f..8183e8c1ac 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -339,8 +339,8 @@ namespace Settings std::unique_ptr>&& sanitizer = nullptr) : BaseSettingValue(getSettingValueType(), category, name, index) , mSanitizer(std::move(sanitizer)) - , mDefaultValue(defaultValue) - , mValue(defaultValue) + , mDefaultValue(sanitize(defaultValue)) + , mValue(sanitize(Settings::Manager::getOrDefault(mName, mCategory, mDefaultValue))) { } From 6e7661ca879661bbabc6333fd376e4b1cb49f621 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Dec 2023 17:44:49 +0300 Subject: [PATCH 0556/2167] BulletNifLoader: Handle only the first child of NiSwitchNode and NiFltAnimationNode To prevent duplicated collisions in general cases when the node states are similar or only one child is ever active. For NiLODNode this is definitely not going to work --- components/nifbullet/bulletnifloader.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 96dff80004..2c7dd1b1c4 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -250,11 +250,16 @@ namespace NifBullet const Nif::Parent currentParent{ *ninode, parent }; for (const auto& child : ninode->mChildren) { - if (child.empty()) - continue; - - assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); - handleNode(child.get(), ¤tParent, args); + if (!child.empty()) + { + assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); + handleNode(child.get(), ¤tParent, args); + } + // For NiSwitchNodes and NiFltAnimationNodes, only use the first child + // TODO: must synchronize with the rendering scene graph somehow + // Doing this for NiLODNodes is unsafe (the first level might not be the closest) + if (node.recType == Nif::RC_NiSwitchNode || node.recType == Nif::RC_NiFltAnimationNode) + break; } } } From b93291840ee3c0996576ffabef29ff550a056a87 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Dec 2023 18:20:05 +0300 Subject: [PATCH 0557/2167] BulletNifLoader: Handle NiSkinPartition Add NiSkinPartition recovery helper method --- apps/openmw_test_suite/nif/node.hpp | 1 + components/nif/data.cpp | 11 +++ components/nif/data.hpp | 2 + components/nif/node.cpp | 104 +++++++++++++++++------ components/nifbullet/bulletnifloader.cpp | 1 - components/nifosg/nifloader.cpp | 12 +-- 6 files changed, 91 insertions(+), 40 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 76cc6ac687..4e21698501 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -53,6 +53,7 @@ namespace Nif::Testing { value.mData = NiSkinDataPtr(nullptr); value.mRoot = NiAVObjectPtr(nullptr); + value.mPartitions = NiSkinPartitionPtr(nullptr); } inline void init(NiTimeController& value) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index e653959bbc..fa66435aee 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -290,6 +290,17 @@ namespace Nif } } + const Nif::NiSkinPartition* NiSkinInstance::getPartitions() const + { + const Nif::NiSkinPartition* partitions = nullptr; + if (!mPartitions.empty()) + partitions = mPartitions.getPtr(); + else if (!mData.empty() && !mData->mPartitions.empty()) + partitions = mData->mPartitions.getPtr(); + + return partitions; + } + void BSDismemberSkinInstance::read(NIFStream* nif) { NiSkinInstance::read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 75c18d657a..c9daeef4d4 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -208,6 +208,8 @@ namespace Nif void read(NIFStream* nif) override; void post(Reader& nif) override; + + const Nif::NiSkinPartition* getPartitions() const; }; struct BSDismemberSkinInstance : public NiSkinInstance diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 01c0e1597d..b91f143d00 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -27,6 +27,37 @@ namespace mesh.preallocateIndices(static_cast(data.mNumTriangles) * 3); } + void trianglesToBtTriangleMesh(btTriangleMesh& mesh, const std::vector& triangles) + { + for (std::size_t i = 0; i < triangles.size(); i += 3) + mesh.addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); + } + + void stripsToBtTriangleMesh(btTriangleMesh& mesh, const std::vector>& strips) + { + for (const auto& strip : strips) + { + if (strip.size() < 3) + continue; + + unsigned short a; + unsigned short b = strip[0]; + unsigned short c = strip[1]; + for (size_t i = 2; i < strip.size(); i++) + { + a = b; + b = c; + c = strip[i]; + if (a == b || b == c || a == c) + continue; + if (i % 2 == 0) + mesh.addTriangleIndices(a, b, c); + else + mesh.addTriangleIndices(a, c, b); + } + } + } + } namespace Nif @@ -243,15 +274,33 @@ namespace Nif if (mData.empty() || mData->mVertices.empty()) return nullptr; + std::vector*> triangleLists; + std::vector>*> stripsLists; auto data = static_cast(mData.getPtr()); - if (data->mNumTriangles == 0 || data->mTriangles.empty()) - return nullptr; + const Nif::NiSkinPartition* partitions = nullptr; + if (!mSkin.empty()) + partitions = mSkin->getPartitions(); + if (partitions) + { + triangleLists.reserve(partitions->mPartitions.size()); + stripsLists.reserve(partitions->mPartitions.size()); + for (auto& partition : partitions->mPartitions) + { + triangleLists.push_back(&partition.mTrueTriangles); + stripsLists.push_back(&partition.mTrueStrips); + } + } + else if (data->mNumTriangles != 0) + triangleLists.push_back(&data->mTriangles); + + // This makes a perhaps dangerous assumption that NiSkinPartition will never have more than 65536 triangles. auto mesh = std::make_unique(); triBasedGeomToBtTriangleMesh(*mesh, *data); - const std::vector& triangles = data->mTriangles; - for (std::size_t i = 0; i < triangles.size(); i += 3) - mesh->addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); + for (const auto triangles : triangleLists) + trianglesToBtTriangleMesh(*mesh, *triangles); + for (const auto strips : stripsLists) + stripsToBtTriangleMesh(*mesh, *strips); if (mesh->getNumTriangles() == 0) return nullptr; @@ -267,33 +316,32 @@ namespace Nif if (mData.empty() || mData->mVertices.empty()) return nullptr; + std::vector*> triangleLists; + std::vector>*> stripsLists; auto data = static_cast(mData.getPtr()); - if (data->mNumTriangles == 0 || data->mStrips.empty()) - return nullptr; + const Nif::NiSkinPartition* partitions = nullptr; + if (!mSkin.empty()) + partitions = mSkin->getPartitions(); + + if (partitions) + { + triangleLists.reserve(partitions->mPartitions.size()); + stripsLists.reserve(partitions->mPartitions.size()); + for (auto& partition : partitions->mPartitions) + { + triangleLists.push_back(&partition.mTrueTriangles); + stripsLists.push_back(&partition.mTrueStrips); + } + } + else if (data->mNumTriangles != 0) + stripsLists.push_back(&data->mStrips); auto mesh = std::make_unique(); triBasedGeomToBtTriangleMesh(*mesh, *data); - for (const std::vector& strip : data->mStrips) - { - if (strip.size() < 3) - continue; - - unsigned short a; - unsigned short b = strip[0]; - unsigned short c = strip[1]; - for (size_t i = 2; i < strip.size(); i++) - { - a = b; - b = c; - c = strip[i]; - if (a == b || b == c || a == c) - continue; - if (i % 2 == 0) - mesh->addTriangleIndices(a, b, c); - else - mesh->addTriangleIndices(a, c, b); - } - } + for (const auto triangles : triangleLists) + trianglesToBtTriangleMesh(*mesh, *triangles); + for (const auto strips : stripsLists) + stripsToBtTriangleMesh(*mesh, *strips); if (mesh->getNumTriangles() == 0) return nullptr; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 2c7dd1b1c4..0bba9ee1a2 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -277,7 +277,6 @@ namespace NifBullet if (!niGeometry.mSkin.empty()) args.mAnimated = false; - // TODO: handle NiSkinPartition std::unique_ptr childShape = niGeometry.getCollisionShape(); if (childShape == nullptr) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..fba831d2a2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1381,17 +1381,7 @@ namespace NifOsg if (!niGeometry->mSkin.empty()) { const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); - const Nif::NiSkinData* data = nullptr; - const Nif::NiSkinPartition* partitions = nullptr; - if (!skin->mData.empty()) - { - data = skin->mData.getPtr(); - if (!data->mPartitions.empty()) - partitions = data->mPartitions.getPtr(); - } - if (!partitions && !skin->mPartitions.empty()) - partitions = skin->mPartitions.getPtr(); - + const Nif::NiSkinPartition* partitions = skin->getPartitions(); hasPartitions = partitions != nullptr; if (hasPartitions) { From 8cf99822edd15654e77236e5081725cbf55fd107 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 17:31:13 +0100 Subject: [PATCH 0558/2167] Add a death event to the Lua API --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/engineevents.cpp | 10 ++++++++++ apps/openmw/mwlua/engineevents.hpp | 7 ++++++- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 2 ++ apps/openmw/mwlua/luamanagerimp.hpp | 4 ++++ apps/openmw/mwmechanics/actors.cpp | 2 ++ .../source/reference/lua-scripting/engine_handlers.rst | 2 ++ 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 06a68efe4a..e4b16ff725 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -53,6 +53,7 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; + virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; // `arg` is either forwarded from MWGui::pushGuiMode or empty diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 0fbb13f1cf..7250acc857 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -86,6 +86,16 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } + void operator()(const OnDeath& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (!actor.isEmpty()) + { + if (auto* scripts = getLocalScripts(actor)) + scripts->onDeath(); + } + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 7c706edcd0..04a5ab4ebd 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -51,7 +51,12 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - using Event = std::variant; + struct OnDeath + { + ESM::RefNum mActor; + }; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8cf383e985..8dc8358aa8 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -170,7 +170,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers }); + &mOnTeleportedHandlers, &mOnDeathHandlers }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index b87b628a89..05b34cbb96 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -71,6 +71,7 @@ namespace MWLua void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); } + void onDeath() { callEngineHandlers(mOnDeathHandlers); } void applyStatsCache(); @@ -83,6 +84,7 @@ namespace MWLua EngineHandlerList mOnConsumeHandlers{ "onConsume" }; EngineHandlerList mOnActivatedHandlers{ "onActivated" }; EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; + EngineHandlerList mOnDeathHandlers{ "onDeath" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 404820cc6b..ed0b4e9257 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,6 +82,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); } + void actorDied(const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnDeath{ getId(actor) }); + } void objectTeleported(const MWWorld::Ptr& ptr) override; void questUpdated(const ESM::RefId& questId, int stage) override; void uiModeChanged(const MWWorld::Ptr& arg) override; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3e7b075e62..73bd331de2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1730,6 +1730,8 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).notifyDied(); ++mDeathCount[actor.getCellRef().getRefId()]; + + MWBase::Environment::get().getLuaManager()->actorDied(actor); } void Actors::resurrect(const MWWorld::Ptr& ptr) const diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 1ffa1820f3..b6e90a6bda 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -79,6 +79,8 @@ Engine handler is a function defined by a script, that can be called by the engi - | Called on an actor when they consume an item (e.g. a potion). | Similarly to onActivated, the item has already been removed | from the actor's inventory, and the count was set to zero. + * - onDeath() + - Called when the actor dies. Note that actors that start out dead are ignored. **Only for local scripts attached to a player** From ad68b7e18bf2f5a9b8f880fc9ecc658c498577d8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 4 Dec 2023 17:32:38 +0100 Subject: [PATCH 0559/2167] Turn onDeath the engine handler into OnDeath the regular event --- apps/openmw/mwlua/engineevents.cpp | 10 ---------- apps/openmw/mwlua/engineevents.hpp | 7 +------ apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 2 -- apps/openmw/mwlua/luamanagerimp.cpp | 7 +++++++ apps/openmw/mwlua/luamanagerimp.hpp | 5 +---- .../reference/lua-scripting/engine_handlers.rst | 2 -- docs/source/reference/lua-scripting/events.rst | 12 ++++++++++++ 8 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 7250acc857..0fbb13f1cf 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -86,16 +86,6 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } - void operator()(const OnDeath& event) const - { - MWWorld::Ptr actor = getPtr(event.mActor); - if (!actor.isEmpty()) - { - if (auto* scripts = getLocalScripts(actor)) - scripts->onDeath(); - } - } - private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 04a5ab4ebd..7c706edcd0 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -51,12 +51,7 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - struct OnDeath - { - ESM::RefNum mActor; - }; - using Event = std::variant; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8dc8358aa8..8cf383e985 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -170,7 +170,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnDeathHandlers }); + &mOnTeleportedHandlers }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 05b34cbb96..b87b628a89 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -71,7 +71,6 @@ namespace MWLua void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); } - void onDeath() { callEngineHandlers(mOnDeathHandlers); } void applyStatsCache(); @@ -84,7 +83,6 @@ namespace MWLua EngineHandlerList mOnConsumeHandlers{ "onConsume" }; EngineHandlerList mOnActivatedHandlers{ "onActivated" }; EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; - EngineHandlerList mOnDeathHandlers{ "onDeath" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 48b0c15381..66e8ff7538 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -344,6 +344,13 @@ namespace MWLua playerScripts->uiModeChanged(argId, false); } + void LuaManager::actorDied(const MWWorld::Ptr& actor) + { + if (actor.isEmpty()) + return; + mLuaEvents.addLocalEvent({ getId(actor), "OnDeath", {} }); + } + void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) { MWBase::Environment::get().getWorldModel()->registerPtr(object); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index ed0b4e9257..ae16689562 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,13 +82,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); } - void actorDied(const MWWorld::Ptr& actor) override - { - mEngineEvents.addToQueue(EngineEvents::OnDeath{ getId(actor) }); - } void objectTeleported(const MWWorld::Ptr& ptr) override; void questUpdated(const ESM::RefId& questId, int stage) override; void uiModeChanged(const MWWorld::Ptr& arg) override; + void actorDied(const MWWorld::Ptr& actor) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index b6e90a6bda..1ffa1820f3 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -79,8 +79,6 @@ Engine handler is a function defined by a script, that can be called by the engi - | Called on an actor when they consume an item (e.g. a potion). | Similarly to onActivated, the item has already been removed | from the actor's inventory, and the count was set to zero. - * - onDeath() - - Called when the actor dies. Note that actors that start out dead are ignored. **Only for local scripts attached to a player** diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 7f0a764b86..7a08ef4d6e 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -6,6 +6,18 @@ Built-in events Actor events ------------ +**OnDeath** + +This event is sent to an actor's local script when that actor dies. + +.. code-block:: Lua + + eventHandlers = { + OnDeath = function() + print('Alas, ye hardly knew me!') + end + } + **StartAIPackage, RemoveAIPackages** Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``. From efb819b9d2c3de689a6e183b2697286552c2e0fa Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 4 Dec 2023 17:50:18 +0100 Subject: [PATCH 0560/2167] Rename to Died --- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- docs/source/reference/lua-scripting/events.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 66e8ff7538..d34518d558 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -348,7 +348,7 @@ namespace MWLua { if (actor.isEmpty()) return; - mLuaEvents.addLocalEvent({ getId(actor), "OnDeath", {} }); + mLuaEvents.addLocalEvent({ getId(actor), "Died", {} }); } void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 7a08ef4d6e..282e3d1173 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -6,14 +6,14 @@ Built-in events Actor events ------------ -**OnDeath** +**Died** This event is sent to an actor's local script when that actor dies. .. code-block:: Lua eventHandlers = { - OnDeath = function() + Died = function() print('Alas, ye hardly knew me!') end } From e043ed935eae8cb65addd01b6ce6094df1b05369 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Tue, 5 Dec 2023 12:03:34 +0000 Subject: [PATCH 0561/2167] Add some missing changelog entries --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..98be6c60ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,8 @@ Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives + Bug #7354: Disabling post processing in-game causes a crash + Bug #7364: Post processing is not reflected in savegame previews Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies @@ -127,12 +129,14 @@ Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas + Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb Feature #7652: Sort inactive post processing shaders list properly + Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char From f6a6c278dd8c61af8e4f437de20c1cb9b4620571 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 5 Dec 2023 14:13:35 +0000 Subject: [PATCH 0562/2167] More cleanup of scripted animations --- CHANGELOG.md | 3 + apps/openmw/mwmechanics/character.cpp | 166 +++++++++++++------ apps/openmw/mwmechanics/character.hpp | 5 + apps/openmw/mwmechanics/weapontype.cpp | 16 ++ apps/openmw/mwmechanics/weapontype.hpp | 5 + apps/openmw/mwrender/animation.cpp | 34 +--- apps/openmw/mwrender/animation.hpp | 10 +- apps/openmw/mwrender/npcanimation.cpp | 17 +- apps/openmw/mwscript/animationextensions.cpp | 2 +- components/esm3/loadweap.hpp | 4 +- 10 files changed, 175 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..fca7982e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward + Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation + Bug #5066: Quirks with starting and stopping scripted animations Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X @@ -91,6 +93,7 @@ Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d9166aa683..77bc51423e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -20,6 +20,7 @@ #include "character.hpp" #include +#include #include #include @@ -1189,7 +1190,7 @@ namespace MWMechanics if (!animPlaying) { int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; - mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul); + mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else { @@ -1246,8 +1247,47 @@ namespace MWMechanics } } + bool CharacterController::isLoopingAnimation(std::string_view group) const + { + // In Morrowind, a some animation groups are always considered looping, regardless + // of loop start/stop keys. + // To be match vanilla behavior we probably only need to check this list, but we don't + // want to prevent modded animations with custom group names from looping either. + static const std::unordered_set loopingAnimations = { "walkforward", "walkback", "walkleft", + "walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback", + "runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward", + "sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright", + "spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7", + "idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand", + "inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" }; + static const std::vector shortGroups = getAllWeaponTypeShortGroups(); + + if (mAnimation && mAnimation->getTextKeyTime(std::string(group) + ": loop start") >= 0) + return true; + + // Most looping animations have variants for each weapon type shortgroup. + // Just remove the shortgroup instead of enumerating all of the possible animation groupnames. + // Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow" + // when the shortgroup is crossbow. + std::size_t suffixLength = 0; + for (std::string_view suffix : shortGroups) + { + if (suffix.length() > suffixLength && group.ends_with(suffix)) + { + suffixLength = suffix.length(); + } + } + group.remove_suffix(suffixLength); + + return loopingAnimations.count(group) > 0; + } + bool CharacterController::updateWeaponState() { + // If the current animation is scripted, we can't do anything here. + if (isScriptedAnimPlaying()) + return false; + const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1481,10 +1521,6 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with scripted animations obviously will be buggy - if (isScriptedAnimPlaying()) - return forcestateupdate; - float complete = 0.f; bool animPlaying = false; ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; @@ -1857,33 +1893,58 @@ namespace MWMechanics if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - // Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of - // the animation object. - if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0) + // Playing animations through mwscript is weird. If an animation is + // a looping animation (idle or other cyclical animations), then they + // will end as expected. However, if they are non-looping animations, they + // will stick around forever or until another animation appears in the queue. + bool shouldPlayOrRestart = mAnimQueue.size() > 1; + if (shouldPlayOrRestart || !mAnimQueue.front().mScripted + || (mAnimQueue.front().mLoopCount == 0 && mAnimQueue.front().mLooping)) { + mAnimation->setPlayScriptedOnly(false); mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); + shouldPlayOrRestart = true; } + else + // A non-looping animation will stick around forever, so only restart if the animation + // actually was removed for some reason. + shouldPlayOrRestart = !mAnimation->getInfo(mAnimQueue.front().mGroup) + && mAnimation->hasAnimation(mAnimQueue.front().mGroup); - if (!mAnimQueue.empty()) + if (shouldPlayOrRestart) { // Move on to the remaining items of the queue - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, - mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, - mAnimQueue.front().mLoopCount, loopfallback); + playAnimQueue(); } } else { - mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); + float complete; + size_t loopcount; + mAnimation->getInfo(mAnimQueue.front().mGroup, &complete, nullptr, &loopcount); + mAnimQueue.front().mLoopCount = loopcount; + mAnimQueue.front().mTime = complete; } if (!mAnimQueue.empty()) mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } + void CharacterController::playAnimQueue(bool loopStart) + { + if (!mAnimQueue.empty()) + { + clearStateAnimation(mCurrentIdle); + mIdleState = CharState_SpecialIdle; + auto priority = mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default; + mAnimation->setPlayScriptedOnly(mAnimQueue.front().mScripted); + mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::Animation::BlendMask_All, false, 1.0f, + (loopStart ? "loop start" : "start"), "stop", mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount, + mAnimQueue.front().mLooping); + } + } + void CharacterController::update(float duration) { MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -2455,10 +2516,11 @@ namespace MWMechanics if (iter == mAnimQueue.begin()) { - anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); float complete; - mAnimation->getInfo(anim.mGroup, &complete, nullptr); + size_t loopcount; + mAnimation->getInfo(anim.mGroup, &complete, nullptr, &loopcount); anim.mTime = complete; + anim.mLoopCount = loopcount; } else { @@ -2484,26 +2546,20 @@ namespace MWMechanics entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; entry.mScripted = true; + entry.mLooping = isLoopingAnimation(entry.mGroup); + entry.mTime = iter->mTime; + if (iter->mAbsolute) + { + float start = mAnimation->getTextKeyTime(iter->mGroup + ": start"); + float stop = mAnimation->getTextKeyTime(iter->mGroup + ": stop"); + float time = std::clamp(iter->mTime, start, stop); + entry.mTime = (time - start) / (stop - start); + } mAnimQueue.push_back(entry); } - const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); - float complete = anim.mTime; - if (anim.mAbsolute) - { - float start = mAnimation->getTextKeyTime(anim.mGroup + ": start"); - float stop = mAnimation->getTextKeyTime(anim.mGroup + ": stop"); - float time = std::clamp(anim.mTime, start, stop); - complete = (time - start) / (stop - start); - } - - clearStateAnimation(mCurrentIdle); - mIdleState = CharState_SpecialIdle; - - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", - "stop", complete, anim.mLoopCount, loopfallback); + playAnimQueue(); } } @@ -2516,13 +2572,14 @@ namespace MWMechanics if (isScriptedAnimPlaying() && !scripted) return true; - // If this animation is a looped animation (has a "loop start" key) that is already playing + bool looping = isLoopingAnimation(groupname); + + // If this animation is a looped animation that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and remove any other animations that were queued. // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners // correctly. - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname - && mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && looping && mAnimation->isPlaying(groupname)) { float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop stop"); @@ -2537,36 +2594,43 @@ namespace MWMechanics } } - count = std::max(count, 1); + // The loop count in vanilla is weird. + // if played with a count of 0, all objects play exactly once from start to stop. + // But if the count is x > 0, actors and non-actors behave differently. actors will loop + // exactly x times, while non-actors will loop x+1 instead. + if (mPtr.getClass().isActor()) + count--; + count = std::max(count, 0); AnimationQueueEntry entry; entry.mGroup = groupname; - entry.mLoopCount = count - 1; + entry.mLoopCount = count; + entry.mTime = 0.f; entry.mScripted = scripted; + entry.mLooping = looping; + + bool playImmediately = false; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { clearAnimQueue(scripted); - clearStateAnimation(mCurrentIdle); - - mIdleState = CharState_SpecialIdle; - bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, - count - 1, loopfallback); + playImmediately = true; } else { mAnimQueue.resize(1); } - // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + // "PlayGroup idle" is a special case, used to stop and remove scripted animations playing if (groupname == "idle") entry.mScripted = false; mAnimQueue.push_back(entry); + if (playImmediately) + playAnimQueue(mode == 2); + return true; } @@ -2577,11 +2641,10 @@ namespace MWMechanics bool CharacterController::isScriptedAnimPlaying() const { + // If the front of the anim queue is scripted, morrowind treats it as if it's + // still playing even if it's actually done. if (!mAnimQueue.empty()) - { - const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mScripted && isAnimPlaying(first.mGroup); - } + return mAnimQueue.front().mScripted; return false; } @@ -2611,6 +2674,7 @@ namespace MWMechanics if (clearScriptedAnims) { + mAnimation->setPlayScriptedOnly(false); mAnimQueue.clear(); return; } @@ -2645,6 +2709,8 @@ namespace MWMechanics playRandomDeath(); } + updateAnimQueue(); + mAnimation->runAnimation(0.f); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 63491ec776..ee26b61a25 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -135,6 +135,8 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; + float mTime; + bool mLooping; bool mScripted; }; typedef std::deque AnimationQueue; @@ -219,6 +221,7 @@ namespace MWMechanics bool isMovementAnimationControlled() const; void updateAnimQueue(); + void playAnimQueue(bool useLoopStart = false); void updateHeadTracking(float duration); @@ -245,6 +248,8 @@ namespace MWMechanics void prepareHit(); + bool isLoopingAnimation(std::string_view group) const; + public: CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 9dd5842f58..8c51629803 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -8,6 +8,8 @@ #include +#include + namespace MWMechanics { template @@ -416,4 +418,18 @@ namespace MWMechanics return &Weapon::getValue(); } + + std::vector getAllWeaponTypeShortGroups() + { + // Go via a set to eliminate duplicates. + std::set shortGroupSet; + for (int type = ESM::Weapon::Type::First; type <= ESM::Weapon::Type::Last; type++) + { + std::string_view shortGroup = getWeaponType(type)->mShortGroup; + if (!shortGroup.empty()) + shortGroupSet.insert(shortGroup); + } + + return std::vector(shortGroupSet.begin(), shortGroupSet.end()); + } } diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp index db7b3013f6..efe404d327 100644 --- a/apps/openmw/mwmechanics/weapontype.hpp +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -1,6 +1,9 @@ #ifndef GAME_MWMECHANICS_WEAPONTYPE_H #define GAME_MWMECHANICS_WEAPONTYPE_H +#include +#include + namespace ESM { struct WeaponType; @@ -21,6 +24,8 @@ namespace MWMechanics MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype); const ESM::WeaponType* getWeaponType(const int weaponType); + + std::vector getAllWeaponTypeShortGroups(); } #endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index bac9dbb56c..5b0e1f82bd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -529,6 +529,7 @@ namespace MWRender , mBodyPitchRadians(0.f) , mHasMagicEffects(false) , mAlpha(1.f) + , mPlayScriptedOnly(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -1020,7 +1021,7 @@ namespace MWRender return false; } - bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult, size_t* loopcount) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if (iter == mStates.end()) @@ -1029,6 +1030,8 @@ namespace MWRender *complete = 0.0f; if (speedmult) *speedmult = 0.0f; + if (loopcount) + *loopcount = 0; return false; } @@ -1042,6 +1045,9 @@ namespace MWRender } if (speedmult) *speedmult = iter->second.mSpeedMult; + + if (loopcount) + *loopcount = iter->second.mLoopCount; return true; } @@ -1054,15 +1060,6 @@ namespace MWRender return iter->second.getTime(); } - size_t Animation::getCurrentLoopCount(const std::string& groupname) const - { - AnimStateMap::const_iterator iter = mStates.find(groupname); - if (iter == mStates.end()) - return 0; - - return iter->second.mLoopCount; - } - void Animation::disable(std::string_view groupname) { AnimStateMap::iterator iter = mStates.find(groupname); @@ -1141,23 +1138,12 @@ namespace MWRender osg::Vec3f Animation::runAnimation(float duration) { - // If we have scripted animations, play only them - bool hasScriptedAnims = false; - for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) - { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) - { - hasScriptedAnims = true; - break; - } - } - osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) + if (mPlayScriptedOnly && !state.mPriority.contains(MWMechanics::Priority_Scripted)) { ++stateiter; continue; @@ -1263,10 +1249,6 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } - // Scripted animations should not cause movement - if (hasScriptedAnims) - return osg::Vec3f(0, 0, 0); - return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8615811cc3..24366889c4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -292,6 +292,8 @@ namespace MWRender osg::ref_ptr mLightListCallback; + bool mPlayScriptedOnly; + const NodeMap& getNodeMap() const; /* Sets the appropriate animations on the bone groups based on priority. @@ -441,7 +443,8 @@ namespace MWRender * \param speedmult Stores the animation speed multiplier * \return True if the animation is active, false otherwise. */ - bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr) const; + bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr, + size_t* loopcount = nullptr) const; /// Get the absolute position in the animation track of the first text key with the given group. float getStartTime(const std::string& groupname) const; @@ -453,8 +456,6 @@ namespace MWRender /// the given group. float getCurrentTime(const std::string& groupname) const; - size_t getCurrentLoopCount(const std::string& groupname) const; - /** Disables the specified animation group; * \param groupname Animation group to disable. */ @@ -477,6 +478,9 @@ namespace MWRender MWWorld::MovementDirectionFlags getSupportedMovementDirections( std::span prefixes) const; + bool getPlayScriptedOnly() const { return mPlayScriptedOnly; } + void setPlayScriptedOnly(bool playScriptedOnly) { mPlayScriptedOnly = playScriptedOnly; } + virtual bool useShieldAnimations() const { return false; } virtual bool getWeaponsShown() const { return false; } virtual void showWeapons(bool showWeapon) {} diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 669a6fae45..469978e6eb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -923,13 +923,18 @@ namespace MWRender if (mViewMode == VM_FirstPerson) { - NodeMap::iterator found = mNodeMap.find("bip01 neck"); - if (found != mNodeMap.end()) + // If there is no active animation, then the bip01 neck node will not be updated each frame, and the + // RotateController will accumulate rotations. + if (mStates.size() > 0) { - osg::MatrixTransform* node = found->second.get(); - mFirstPersonNeckController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mFirstPersonNeckController); - mActiveControllers.emplace_back(node, mFirstPersonNeckController); + NodeMap::iterator found = mNodeMap.find("bip01 neck"); + if (found != mNodeMap.end()) + { + osg::MatrixTransform* node = found->second.get(); + mFirstPersonNeckController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mFirstPersonNeckController); + mActiveControllers.emplace_back(node, mFirstPersonNeckController); + } } } else if (mViewMode == VM_Normal) diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 32d7e46527..8d439ec82b 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -91,7 +91,7 @@ namespace MWScript throw std::runtime_error("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops + 1, true); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops, true); } }; diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index e8355d0f55..ba1599b1df 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -24,6 +24,7 @@ namespace ESM enum Type { + First = -4, PickProbe = -4, HandToHand = -3, Spell = -2, @@ -41,7 +42,8 @@ namespace ESM MarksmanCrossbow = 10, MarksmanThrown = 11, Arrow = 12, - Bolt = 13 + Bolt = 13, + Last = 13 }; enum AttackType From 754c5a8e2a3bcd5b66e1a48350ddcd558fabaf94 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 4 Dec 2023 23:43:35 +0300 Subject: [PATCH 0563/2167] Restore animated collision shape rescaling --- apps/openmw/mwphysics/object.cpp | 12 +- .../nifloader/testbulletnifloader.cpp | 126 +++++++++++++----- components/nifbullet/bulletnifloader.cpp | 15 ++- components/resource/bulletshape.cpp | 6 +- components/resource/bulletshape.hpp | 19 ++- 5 files changed, 132 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 6e0e2cdc7f..53529ec729 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -137,6 +137,7 @@ namespace MWPhysics osg::NodePath& nodePath = nodePathFound->second; osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); + btVector3 scale = Misc::Convert::toBullet(matrix.getScale()); matrix.orthoNormalize(matrix); btTransform transform; @@ -145,8 +146,15 @@ namespace MWPhysics for (int j = 0; j < 3; ++j) transform.getBasis()[i][j] = matrix(j, i); // NB column/row major difference - // Note: we can not apply scaling here for now since we treat scaled shapes - // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now + btCollisionShape* childShape = compound->getChildShape(shapeIndex); + btVector3 newScale = compound->getLocalScaling() * scale; + + if (childShape->getLocalScaling() != newScale) + { + childShape->setLocalScaling(newScale); + result = true; + } + if (!(transform == compound->getChildTransform(shapeIndex))) { compound->updateChildTransform(shapeIndex, transform); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7dce21bac6..d44561a68b 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -144,6 +144,12 @@ namespace Resource return stream << "}}"; } + static std::ostream& operator<<(std::ostream& stream, const ScaledTriangleMeshShape& value) + { + return stream << "Resource::ScaledTriangleMeshShape {" << value.getLocalScaling() << ", " + << value.getChildShape() << "}"; + } + static bool operator==(const CollisionBox& l, const CollisionBox& r) { const auto tie = [](const CollisionBox& v) { return std::tie(v.mExtents, v.mCenter); }; @@ -169,6 +175,10 @@ static std::ostream& operator<<(std::ostream& stream, const btCollisionShape& va if (const auto casted = dynamic_cast(&value)) return stream << *casted; break; + case SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto casted = dynamic_cast(&value)) + return stream << *casted; + break; } return stream << "btCollisionShape {" << value.getShapeType() << "}"; } @@ -249,6 +259,12 @@ static bool operator==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMes && lhs.getOwnsBvh() == rhs.getOwnsBvh() && isNear(getTriangles(lhs), getTriangles(rhs)); } +static bool operator==(const btScaledBvhTriangleMeshShape& lhs, const btScaledBvhTriangleMeshShape& rhs) +{ + return isNear(lhs.getLocalScaling(), rhs.getLocalScaling()) + && compareObjects(lhs.getChildShape(), rhs.getChildShape()); +} + static bool operator==(const btCollisionShape& lhs, const btCollisionShape& rhs) { if (lhs.getShapeType() != rhs.getShapeType()) @@ -264,6 +280,11 @@ static bool operator==(const btCollisionShape& lhs, const btCollisionShape& rhs) if (const auto rhsCasted = dynamic_cast(&rhs)) return *lhsCasted == *rhsCasted; return false; + case SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto lhsCasted = dynamic_cast(&lhs)) + if (const auto rhsCasted = dynamic_cast(&rhs)) + return *lhsCasted == *rhsCasted; + return false; } return false; } @@ -572,7 +593,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -596,7 +619,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -619,7 +644,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -644,7 +671,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -669,9 +698,13 @@ namespace std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + auto triShape2 = std::make_unique(triangles2.release(), true); + compound->addChildShape( - btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape2.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -695,7 +728,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -717,9 +752,8 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransform, mesh.release()); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(3, 3, 3))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -744,9 +778,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -776,16 +810,14 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransform, mesh.release()); - shape->addChildShape(mTransform, mesh2.release()); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(3, 3, 3))); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(3, 3, 3))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -813,9 +845,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -849,16 +881,16 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); - shape->addChildShape(mTransformScale4, mesh2.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 1 } }; @@ -880,14 +912,17 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); compound->addChildShape( - btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -911,9 +946,11 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mAvoidCollisionShape.reset(compound.release()); @@ -973,8 +1010,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1002,8 +1041,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1029,8 +1070,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1057,8 +1100,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1083,8 +1128,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1116,8 +1163,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1178,8 +1227,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); triangles->addTriangle(btVector3(1, 0, 0), btVector3(0, 1, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1267,9 +1318,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1298,14 +1350,14 @@ namespace std::unique_ptr triangles1(new btTriangleMesh(false)); triangles1->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh1(new Resource::TriangleMeshShape(triangles1.release(), true)); - mesh1->setLocalScaling(btVector3(8, 8, 8)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale2, mesh1.release()); - shape->addChildShape(mTransformScale3, mesh2.release()); + shape->addChildShape( + mTransformScale2, new Resource::ScaledTriangleMeshShape(mesh1.release(), btVector3(8, 8, 8))); + shape->addChildShape( + mTransformScale3, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 96dff80004..c2cd915a8c 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -281,7 +281,20 @@ namespace NifBullet osg::Matrixf transform = niGeometry.mTransform.toMatrix(); for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) transform *= parent->mNiNode.mTransform.toMatrix(); - childShape->setLocalScaling(Misc::Convert::toBullet(transform.getScale())); + + if (childShape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + auto scaledShape = std::make_unique( + static_cast(childShape.get()), Misc::Convert::toBullet(transform.getScale())); + std::ignore = childShape.release(); + + childShape = std::move(scaledShape); + } + else + { + childShape->setLocalScaling(Misc::Convert::toBullet(transform.getScale())); + } + transform.orthoNormalize(transform); btTransform trans; diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index cc0a63359b..360b92ffc0 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -32,11 +32,11 @@ namespace Resource return newShape; } - if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + if (shape->getShapeType() == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) { - const btBvhTriangleMeshShape* trishape = static_cast(shape); + const btScaledBvhTriangleMeshShape* trishape = static_cast(shape); return CollisionShapePtr(new btScaledBvhTriangleMeshShape( - const_cast(trishape), btVector3(1.f, 1.f, 1.f))); + const_cast(trishape->getChildShape()), trishape->getLocalScaling())); } if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 9610fde49f..0a1b98bf7c 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -10,6 +10,7 @@ #include #include +#include class btCollisionShape; @@ -49,8 +50,8 @@ namespace Resource // collision box for creatures. For now, use one file <-> one resource for simplicity. CollisionBox mCollisionBox; - // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape - // will be a btCompoundShape (which consists of one or more child shapes). + // Stores animated collision shapes. + // mCollisionShape is a btCompoundShape (which consists of one or more child shapes). // In this map, for each animated collision shape, // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; @@ -61,6 +62,7 @@ namespace Resource VisualCollisionType mVisualCollisionType = VisualCollisionType::None; BulletShape() = default; + // Note this is always a shallow copy and the copy will not autodelete underlying vertex data BulletShape(const BulletShape& other, const osg::CopyOp& copyOp = osg::CopyOp()); META_Object(Resource, BulletShape) @@ -70,7 +72,7 @@ namespace Resource bool isAnimated() const { return !mAnimatedShapes.empty(); } }; - // An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. + // An instance of a BulletShape that may have its own unique scaling set on collision shapes. // Vertex data is shallow-copied where possible. A ref_ptr to the original shape is held to keep vertex pointers // intact. class BulletShapeInstance : public BulletShape @@ -102,6 +104,17 @@ namespace Resource } }; + // btScaledBvhTriangleMeshShape that auto-deletes the child shape + struct ScaledTriangleMeshShape : public btScaledBvhTriangleMeshShape + { + ScaledTriangleMeshShape(btBvhTriangleMeshShape* childShape, const btVector3& localScaling) + : btScaledBvhTriangleMeshShape(childShape, localScaling) + { + } + + ~ScaledTriangleMeshShape() override { delete getChildShape(); } + }; + } #endif From 6f68df9ac221ad62a36441efb23fc27c69156ba2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 6 Dec 2023 23:45:25 +0400 Subject: [PATCH 0564/2167] Avoid redundant copies --- apps/bulletobjecttool/main.cpp | 8 ++++---- apps/esmtool/esmtool.cpp | 2 +- apps/essimporter/main.cpp | 4 ++-- apps/navmeshtool/main.cpp | 8 ++++---- apps/opencs/view/world/dragrecordtable.cpp | 2 +- apps/openmw/mwlua/magicbindings.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index c522d69d70..7d87899f4a 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -145,12 +145,12 @@ namespace config.filterOutNonExistingPaths(dataDirs); - const auto resDir = variables["resources"].as(); + const auto& resDir = variables["resources"].as(); Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); - const auto fileCollections = Files::Collections(dataDirs); - const auto archives = variables["fallback-archive"].as(); - const auto contentFiles = variables["content"].as(); + const Files::Collections fileCollections(dataDirs); + const auto& archives = variables["fallback-archive"].as(); + const auto& contentFiles = variables["content"].as(); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 092be66e97..88342a2a5b 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -156,7 +156,7 @@ Allowed options)"); return false; }*/ - const auto inputFiles = variables["input-file"].as(); + const auto& inputFiles = variables["input-file"].as(); info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on // MSVC 14.26 due to implementation bugs. if (inputFiles.size() > 1) diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index 7d3ad10bb1..f0833e9d81 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -42,8 +42,8 @@ Allowed options)"); Files::ConfigurationManager cfgManager(true); cfgManager.readConfiguration(variables, desc); - const auto essFile = variables["mwsave"].as(); - const auto outputFile = variables["output"].as(); + const auto& essFile = variables["mwsave"].as(); + const auto& outputFile = variables["output"].as(); std::string encoding = variables["encoding"].as(); ESSImport::Importer importer(essFile, outputFile, encoding); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 793a08ba2c..8604bcdfb0 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -164,12 +164,12 @@ namespace NavMeshTool config.filterOutNonExistingPaths(dataDirs); - const auto resDir = variables["resources"].as(); + const auto& resDir = variables["resources"].as(); Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); - const auto fileCollections = Files::Collections(dataDirs); - const auto archives = variables["fallback-archive"].as(); - const auto contentFiles = variables["content"].as(); + const Files::Collections fileCollections(dataDirs); + const auto& archives = variables["fallback-archive"].as(); + const auto& contentFiles = variables["content"].as(); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index ae8c1cb708..11a0c9a540 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -92,7 +92,7 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent* event) if (CSVWorld::DragDropUtils::isTopicOrJournal(*event, display)) { const CSMWorld::TableMimeData* tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); - for (auto universalId : tableMimeData->getData()) + for (const auto& universalId : tableMimeData->getData()) { emit createNewInfoRecord(universalId.getId()); } diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index fb714c81c6..0a34c008a7 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -839,7 +839,7 @@ namespace MWLua // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a // spell), we still need to use the active spells store to purge this effect from active spells. - auto ptr = effects.mActor.ptr(); + const auto& ptr = effects.mActor.ptr(); auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); activeSpells.purgeEffect(ptr, key.mId, key.mArg); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3d96de6749..036287d3a9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -69,13 +69,13 @@ namespace osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr) { - const auto pos = ptr.getRefData().getPosition(); + const auto& pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos); } osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr) { - const auto pos = ptr.getRefData().getPosition(); + const auto& pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos); } From 81617719692dd410de135dd756d0c93502d65a59 Mon Sep 17 00:00:00 2001 From: Devin Alexander Torres Date: Thu, 7 Dec 2023 04:57:52 -0600 Subject: [PATCH 0565/2167] Add sol::lib::jit to actually enable JIT --- components/lua/luastate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 95b56fb020..0a350a2d9f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -179,6 +179,10 @@ namespace LuaUtil mSol.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug); +#ifndef NO_LUAJIT + mSol.open_libraries(sol::lib::jit); +#endif // NO_LUAJIT + mSol["math"]["randomseed"](static_cast(std::time(nullptr))); mSol["math"]["randomseed"] = [] {}; From 1a39ef07c86ca0c4bb6d1999becb8b478ee3ee24 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Dec 2023 00:00:54 +0100 Subject: [PATCH 0566/2167] Fix build with Lua 5.4.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp: In member function ‘void MWLua::LuaManager::update()’: /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:127:19: error: use of deleted function ‘Settings::SettingValue::SettingValue(const Settings::SettingValue&) [with T = int]’ 127 | lua_gc(mLua.sol(), LUA_GCSTEP, Settings::lua().mGcStepsPerFrame); | ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /home/elsid/dev/openmw/components/settings/categories/camera.hpp:5, from /home/elsid/dev/openmw/components/settings/values.hpp:4, from /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:16: /home/elsid/dev/openmw/components/settings/settingvalue.hpp:355:9: note: declared here 355 | SettingValue(const SettingValue& other) = delete; | ^~~~~~~~~~~~ --- apps/openmw/mwlua/luamanagerimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d34518d558..c324360287 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -123,8 +123,8 @@ namespace MWLua void LuaManager::update() { - if (Settings::lua().mGcStepsPerFrame > 0) - lua_gc(mLua.sol(), LUA_GCSTEP, Settings::lua().mGcStepsPerFrame); + if (const int steps = Settings::lua().mGcStepsPerFrame; steps > 0) + lua_gc(mLua.sol(), LUA_GCSTEP, steps); if (mPlayer.isEmpty()) return; // The game is not started yet. From c1088e5f70e8ce1ca7fe5099e3f04bfdb4ddc2ab Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Dec 2023 00:19:14 +0300 Subject: [PATCH 0567/2167] Streamline passing influence data to skinning --- components/nifosg/nifloader.cpp | 58 ++++++++++++++-------------- components/sceneutil/riggeometry.cpp | 37 +++++++++++++----- components/sceneutil/riggeometry.hpp | 32 +++++++-------- 3 files changed, 70 insertions(+), 57 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..a08231809b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1515,24 +1515,24 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geom); - // Assign bone weights - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); const Nif::NiSkinData* data = skin->mData.getPtr(); const Nif::NiAVObjectList& bones = skin->mBones; + + // Assign bone weights + std::vector boneInfo; + std::vector influences; + boneInfo.resize(bones.size()); + influences.resize(bones.size()); for (std::size_t i = 0; i < bones.size(); ++i) { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = data->mBones[i].mWeights; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); + boneInfo[i].mName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + boneInfo[i].mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + boneInfo[i].mBoundSphere = data->mBones[i].mBoundSphere; + influences[i] = data->mBones[i].mWeights; } - rig->setInfluenceMap(map); + rig->setBoneInfo(std::move(boneInfo)); + rig->setInfluences(influences); drawable = rig; } @@ -1671,29 +1671,29 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - - auto skin = static_cast(bsTriShape->mSkin.getPtr()); + const Nif::BSSkinInstance* skin = static_cast(bsTriShape->mSkin.getPtr()); const Nif::BSSkinBoneData* data = skin->mData.getPtr(); const Nif::NiAVObjectList& bones = skin->mBones; - std::vector> vertWeights(data->mBones.size()); - for (size_t i = 0; i < vertices.size(); i++) - for (int j = 0; j < 4; j++) - vertWeights[bsTriShape->mVertData[i].mBoneIndices[j]].emplace_back( - i, halfToFloat(bsTriShape->mVertData[i].mBoneWeights[j])); + std::vector boneInfo; + std::vector influences; + boneInfo.resize(bones.size()); + influences.resize(vertices.size()); for (std::size_t i = 0; i < bones.size(); ++i) { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = vertWeights[i]; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); + boneInfo[i].mName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + boneInfo[i].mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + boneInfo[i].mBoundSphere = data->mBones[i].mBoundSphere; } - rig->setInfluenceMap(map); + + for (size_t i = 0; i < vertices.size(); i++) + { + const Nif::BSVertexData& vertData = bsTriShape->mVertData[i]; + for (int j = 0; j < 4; j++) + influences[i].emplace_back(vertData.mBoneIndices[j], halfToFloat(vertData.mBoneWeights[j])); + } + rig->setBoneInfo(std::move(boneInfo)); + rig->setInfluences(influences); drawable = rig; } diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 1572fab338..92e316d412 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -301,23 +301,29 @@ namespace SceneUtil } } - void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) + void RigGeometry::setBoneInfo(std::vector&& bones) { - mData = new InfluenceData; - mData->mBones.reserve(influenceMap->mData.size()); + if (!mData) + mData = new InfluenceData; - std::unordered_map> vertexToInfluences; + mData->mBones = std::move(bones); + } + + void RigGeometry::setInfluences(const std::vector& influences) + { + if (!mData) + mData = new InfluenceData; + + std::unordered_map vertexToInfluences; size_t index = 0; - for (const auto& [boneName, bi] : influenceMap->mData) + for (const auto& influence : influences) { - mData->mBones.push_back({ boneName, bi.mBoundSphere, bi.mInvBindMatrix }); - - for (const auto& [vertex, weight] : bi.mWeights) + for (const auto& [vertex, weight] : influence) vertexToInfluences[vertex].emplace_back(index, weight); index++; } - std::map, VertexList> influencesToVertices; + std::map influencesToVertices; for (const auto& [vertex, weights] : vertexToInfluences) influencesToVertices[weights].emplace_back(vertex); @@ -325,6 +331,19 @@ namespace SceneUtil mData->mInfluences.assign(influencesToVertices.begin(), influencesToVertices.end()); } + void RigGeometry::setInfluences(const std::vector& influences) + { + if (!mData) + mData = new InfluenceData; + + std::map influencesToVertices; + for (size_t i = 0; i < influences.size(); i++) + influencesToVertices[influences[i]].emplace_back(i); + + mData->mInfluences.reserve(influencesToVertices.size()); + mData->mInfluences.assign(influencesToVertices.begin(), influencesToVertices.end()); + } + void RigGeometry::accept(osg::NodeVisitor& nv) { if (!nv.validNodeMask(*this)) diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index d1c077288d..64ea1e2519 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -36,21 +36,23 @@ namespace SceneUtil // static parts of the model. void compileGLObjects(osg::RenderInfo& renderInfo) const override {} - // TODO: Make InfluenceMap more similar to InfluenceData - struct BoneInfluence + struct BoneInfo { - osg::Matrixf mInvBindMatrix; + std::string mName; osg::BoundingSpheref mBoundSphere; - // - std::vector> mWeights; + osg::Matrixf mInvBindMatrix; }; - struct InfluenceMap : public osg::Referenced - { - std::vector> mData; - }; + using VertexWeight = std::pair; + using VertexWeights = std::vector; + using BoneWeight = std::pair; + using BoneWeights = std::vector; - void setInfluenceMap(osg::ref_ptr influenceMap); + void setBoneInfo(std::vector&& bones); + // Convert influences in vertex and weight list per bone format + void setInfluences(const std::vector& influences); + // Convert influences in bone and weight list per vertex format + void setInfluences(const std::vector& influences); /// Initialize this geometry from the source geometry. /// @note The source geometry will not be modified. @@ -89,19 +91,11 @@ namespace SceneUtil osg::ref_ptr mGeomToSkelMatrix; - struct BoneInfo - { - std::string mName; - osg::BoundingSpheref mBoundSphere; - osg::Matrixf mInvBindMatrix; - }; - - using BoneWeight = std::pair; using VertexList = std::vector; struct InfluenceData : public osg::Referenced { std::vector mBones; - std::vector, VertexList>> mInfluences; + std::vector> mInfluences; }; osg::ref_ptr mData; std::vector mNodes; From cedc5289d785e44bad706133dfc80a988b32fb66 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 1 Dec 2023 19:24:20 +0100 Subject: [PATCH 0568/2167] Dejank movement solver vs animation movement accumulation --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 +- apps/openmw/mwphysics/movementsolver.cpp | 2 + apps/openmw/mwphysics/mtphysics.cpp | 80 ++++++++++++++++++++--- apps/openmw/mwphysics/mtphysics.hpp | 7 +- apps/openmw/mwphysics/physicssystem.cpp | 16 +++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 15 ++++- apps/openmw/mwrender/animation.cpp | 68 +++++++++++++++++-- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwworld/datetimemanager.hpp | 5 ++ apps/openmw/mwworld/projectilemanager.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 14 files changed, 183 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 14e3b2b3b7..f67b9a0e05 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,7 +295,7 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 77bc51423e..9a9f62f9ac 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2420,7 +2420,7 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + world->queueMovement(mPtr, vec, duration); } movement = vec; @@ -2493,7 +2493,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement); + world->queueMovement(mPtr, movement, duration); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index c0b5014b31..ae958c60c7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -170,6 +170,8 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it + // TODO: This will cause instability in idle animations and other in-place animations. Should include a flag for + // this when queueing up movement if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 52b96d9d13..5df9a5c7a9 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -25,6 +25,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -35,6 +36,7 @@ #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" +#include "ptrholder.hpp" namespace MWPhysics { @@ -195,6 +197,51 @@ namespace void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} }; + struct InitMovement + { + float mSteps = 1.f; + float mDelta = 0.f; + float mSimulationTime = 0.f; + + // Returns how the actor or projectile wants to move between startTime and endTime + osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const + { + osg::Vec3f movement = osg::Vec3f(); + auto it = actor.movement().begin(); + while (it != actor.movement().end()) + { + float start = std::max(it->simulationTimeStart, startTime); + float stop = std::min(it->simulationTimeStop, endTime); + movement += it->velocity * (stop - start); + if (std::abs(stop - it->simulationTimeStop) < 0.0001f) + it = actor.movement().erase(it); + else + it++; + } + + return movement; + } + + void operator()(auto& sim) const + { + if (mSteps == 0 || mDelta < 0.00001f) + return; + + auto locked = sim.lock(); + if (!locked.has_value()) + return; + + auto& [ptrHolder, frameDataRef] = *locked; + + // Because takeMovement() returns movement instead of velocity, convert it back to velocity for the + // movement solver + osg::Vec3f velocity + = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); + + frameDataRef.get().mMovement += velocity; + } + }; + struct PreStep { btCollisionWorld* mCollisionWorld; @@ -501,18 +548,18 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime, + std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { assert(mSimulations != &simulations); waitForWorkers(); - prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); + prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } - void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, + void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. @@ -522,6 +569,9 @@ namespace MWPhysics double timeStart = mTimer->tick(); + // The simulation time when the movement solving begins. + float simulationTimeStart = simulationTime - timeAccum; + // start by finishing previous background computation if (mNumThreads != 0) { @@ -536,10 +586,15 @@ namespace MWPhysics timeAccum -= numSteps * newDelta; // init - const Visitors::InitPosition vis{ mCollisionWorld }; + const Visitors::InitPosition initPositionVisitor{ mCollisionWorld }; for (auto& sim : simulations) { - std::visit(vis, sim); + std::visit(initPositionVisitor, sim); + } + const Visitors::InitMovement initMovementVisitor{ numSteps, newDelta, simulationTimeStart }; + for (auto& sim : simulations) + { + std::visit(initMovementVisitor, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -552,10 +607,11 @@ namespace MWPhysics mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) + { + mNextJobSimTime = simulationTimeStart + (numSteps * newDelta); mWorldFrameData = std::make_unique(); - - if (mAdvanceSimulation) mBudgetCursor += 1; + } if (mNumThreads == 0) { @@ -756,6 +812,7 @@ namespace MWPhysics mLockingPolicy }; for (Simulation& sim : *mSimulations) std::visit(vis, sim); + mCurrentJobSimTime += mPhysicsDt; } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -864,6 +921,10 @@ namespace MWPhysics std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } + + // On paper, mCurrentJobSimTime should have added up to mNextJobSimTime already + // But to avoid accumulating floating point errors, assign this anyway. + mCurrentJobSimTime = mNextJobSimTime; mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); @@ -878,6 +939,9 @@ namespace MWPhysics std::visit(vis, sim); mSimulations->clear(); mSimulations = nullptr; + const float interpolationFactor = std::clamp(mTimeAccum / mPhysicsDt, 0.0f, 1.0f); + MWBase::Environment::get().getWorld()->getTimeManager()->setPhysicsSimulationTime( + mCurrentJobSimTime - mPhysicsDt * (1.f - interpolationFactor)); } // Attempt to acquire unique lock on mSimulationMutex while not all worker diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 57f3711096..d2eb320ea4 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,7 +46,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,7 +87,7 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; @@ -96,6 +96,9 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; + float mNextJobSimTime = 0.f; + float mCurrentJobSimTime = 0.f; + float mPreviousJobSimTime = 0.f; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2196834a50..3c451497e1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -43,6 +43,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "actor.hpp" #include "collisiontype.hpp" @@ -623,18 +624,19 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) { + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->setVelocity(velocity); + found->second->queueMovement(velocity, start, start + duration); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { - actor->setVelocity(osg::Vec3f()); + actor->clearMovement(); actor->setInertialForce(osg::Vec3f()); } } @@ -722,8 +724,10 @@ namespace MWPhysics { std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); + float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt; // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements( + mTimeAccum, simulationTime, simulations, frameStart, frameNumber, stats); } } @@ -907,7 +911,7 @@ namespace MWPhysics ->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() - , mMovement(actor.velocity()) + , mMovement() , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) @@ -922,7 +926,7 @@ namespace MWPhysics ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) - , mMovement(projectile.velocity()) + , mMovement() , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ad56581eb3..3babdef9aa 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index fc8fd94c30..270d2af37b 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,13 @@ namespace MWPhysics { + struct Movement + { + osg::Vec3f velocity = osg::Vec3f(); + float simulationTimeStart = 0.f; // The time at which this movement begun + float simulationTimeStop = 0.f; // The time at which this movement finished + }; + class PtrHolder { public: @@ -32,9 +40,10 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } + void clearMovement() { mMovement = { }; } + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) { mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); } - osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } + std::deque& movement() { return mMovement; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -53,7 +62,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - osg::Vec3f mVelocity; + std::deque mMovement; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b0e1f82bd..b9d6759dc7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -50,6 +50,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -489,12 +490,21 @@ namespace MWRender class ResetAccumRootCallback : public SceneUtil::NodeCallback { + struct AccumulatedMovement + { + osg::Vec3f mMovement = osg::Vec3f(); + float mSimStartTime = 0.f; + float mSimStopTime = 0.f; + }; + public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); + // Add back the offset that the movement solver has not consumed yet + position += computeRemainder(); mat.setTrans(position); transform->setMatrix(mat); @@ -509,7 +519,35 @@ namespace MWRender mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } + void accumulate(const osg::Vec3f& movement, float dt) + { + if (dt < 0.00001f) + return; + + float simTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + mMovement.emplace_back(AccumulatedMovement{ movement, simTime, simTime + dt }); + } + + const osg::Vec3f computeRemainder() + { + float physSimTime = MWBase::Environment::get().getWorld()->getTimeManager()->getPhysicsSimulationTime(); + // Start by erasing all movement that has been fully consumed by the physics code + std::erase_if(mMovement, + [physSimTime](const AccumulatedMovement& movement) { return movement.mSimStopTime <= physSimTime; }); + + // Accumulate all the movement that hasn't been consumed. + osg::Vec3f movement; + for (const auto& m : mMovement) + { + float startTime = std::max(physSimTime, m.mSimStartTime); + float fraction = (m.mSimStopTime - startTime) / (m.mSimStopTime - m.mSimStartTime); + movement += m.mMovement * fraction; + } + return movement; + } + private: + std::deque mMovement; osg::Vec3f mResetAxes; }; @@ -1129,11 +1167,27 @@ namespace MWRender return velocity; } - void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement) { // Get the difference from the last update, and move the position - osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); - position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + if (!hasMovement) + { + // When animations have no velocity, the character should have zero net movement through a complete loop or + // animation sequence. Although any subsequence of the animation may move. This works because each sequence + // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move + // the character by just moving it from the position Bip01 was last frame to where it is this frame, without + // needing to accumulate anything in-between. + position += offset - mPreviousPosition; + mPreviousPosition = offset; + } + else + { + // When animations have velocity, net movement is expected. The above block would negate that movement every time + // the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works because + // oldtime < newtime is a guarantee even when the animation has looped. + position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + } } osg::Vec3f Animation::runAnimation(float duration) @@ -1151,6 +1205,7 @@ namespace MWRender const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); + bool hasMovement = getVelocity(stateiter->first) > 0.001f; float timepassed = duration * state.mSpeedMult; while (state.mPlaying) @@ -1161,13 +1216,13 @@ namespace MWRender if (textkey == textkeys.end() || textkey->first > targetTime) { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement); + updatePosition(state.getTime(), targetTime, movement, hasMovement); state.setTime(std::min(targetTime, state.mStopTime)); } else { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement); + updatePosition(state.getTime(), textkey->first, movement, hasMovement); state.setTime(textkey->first); } @@ -1249,6 +1304,9 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } + + if (mResetAccumRootCallback) + mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 24366889c4..990910fc50 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -276,6 +276,7 @@ namespace MWRender float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; + osg::Vec3f mPreviousPosition; osg::ref_ptr addRotateController(std::string_view bone); @@ -304,7 +305,7 @@ namespace MWRender /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index af62d9ba3f..20d968f4ea 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,6 +29,10 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio + // Physics simulation time + double getPhysicsSimulationTime() const { return mPhysicsSimulationTime; } + void setPhysicsSimulationTime(double t) { mPhysicsSimulationTime = t; } + // Rendering simulation time (summary simulation time of rendering frames since application start). double getRenderingSimulationTime() const { return mRenderingSimulationTime; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } @@ -70,6 +74,7 @@ namespace MWWorld float mSimulationTimeScale = 1.0; double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; + double mPhysicsSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d873f16a59..3496b5dab5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -33,6 +33,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -457,7 +458,8 @@ namespace MWWorld } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - projectile->setVelocity(direction * speed); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(direction * speed, start, start + duration); update(magicBoltState, duration); @@ -485,7 +487,8 @@ namespace MWWorld projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - projectile->setVelocity(projectileState.mVelocity); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(projectileState.mVelocity, start, start + duration); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting // direction. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d945aa5848..9db72d1604 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) { - mPhysics->queueObjectMovement(ptr, velocity); + mPhysics->queueObjectMovement(ptr, velocity, duration); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b9a0ccb98..486e240fdc 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,7 +383,7 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. From 81095686bf42ffbf33e447e8d9167cbc7ad0b1bc Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 13:06:09 +0100 Subject: [PATCH 0569/2167] Reset mPreviousAccumulatePosition when not accumulating to avoid an instant transition when resuming idle anims. --- apps/openmw/mwmechanics/character.cpp | 5 +++-- apps/openmw/mwrender/animation.cpp | 18 ++++++++++++------ apps/openmw/mwrender/animation.hpp | 5 +++-- apps/openmw/mwrender/creatureanimation.cpp | 4 ++-- apps/openmw/mwrender/creatureanimation.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.hpp | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9a9f62f9ac..2ef3479cd9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2446,10 +2446,11 @@ namespace MWMechanics } } + bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation - = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration, doMovementAccumulation); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && doMovementAccumulation && !isScriptedAnimPlaying()) { if (duration > 0.0f) movementFromAnimation /= duration; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b9d6759dc7..cb51bcd00f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1178,8 +1178,11 @@ namespace MWRender // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move // the character by just moving it from the position Bip01 was last frame to where it is this frame, without // needing to accumulate anything in-between. - position += offset - mPreviousPosition; - mPreviousPosition = offset; + if (mPreviousAccumulatePosition) + { + position += offset - mPreviousAccumulatePosition.value(); + } + mPreviousAccumulatePosition = offset; } else { @@ -1190,8 +1193,11 @@ namespace MWRender } } - osg::Vec3f Animation::runAnimation(float duration) + osg::Vec3f Animation::runAnimation(float duration, bool accumulateMovement) { + if (!accumulateMovement) + mPreviousAccumulatePosition = std::nullopt; + osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) @@ -1215,13 +1221,13 @@ namespace MWRender float targetTime = state.getTime() + timepassed; if (textkey == textkeys.end() || textkey->first > targetTime) { - if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), targetTime, movement, hasMovement); state.setTime(std::min(targetTime, state.mStopTime)); } else { - if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), textkey->first, movement, hasMovement); state.setTime(textkey->first); } @@ -1305,7 +1311,7 @@ namespace MWRender } - if (mResetAccumRootCallback) + if (accumulateMovement && mResetAccumRootCallback) mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 990910fc50..0e748180e8 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace ESM { @@ -265,6 +266,7 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::Vec3f mAccumulate; + std::optional mPreviousAccumulatePosition; TextKeyListener* mTextKeyListener; @@ -276,7 +278,6 @@ namespace MWRender float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; - osg::Vec3f mPreviousPosition; osg::ref_ptr addRotateController(std::string_view bone); @@ -465,7 +466,7 @@ namespace MWRender /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(std::string_view groupname) const; - virtual osg::Vec3f runAnimation(float duration); + virtual osg::Vec3f runAnimation(float duration, bool accumulateMovement = false); void setLoopingEnabled(std::string_view groupname, bool enabled); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7767520715..65f48f5ade 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -258,9 +258,9 @@ namespace MWRender WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } - osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) + osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration, bool accumulateMovement) { - osg::Vec3f ret = Animation::runAnimation(duration); + osg::Vec3f ret = Animation::runAnimation(duration, accumulateMovement); WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 05235e5191..9ff3d55375 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -59,7 +59,7 @@ namespace MWRender void addControllers() override; - osg::Vec3f runAnimation(float duration) override; + osg::Vec3f runAnimation(float duration, bool accumulateMovement = false) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..951d16aefe 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -693,9 +693,9 @@ namespace MWRender return std::make_unique(attached); } - osg::Vec3f NpcAnimation::runAnimation(float timepassed) + osg::Vec3f NpcAnimation::runAnimation(float timepassed, bool accumulateMovement) { - osg::Vec3f ret = Animation::runAnimation(timepassed); + osg::Vec3f ret = Animation::runAnimation(timepassed, accumulateMovement); mHeadAnimationTime->update(timepassed); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index a03ee28f3a..0c2d67db86 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -130,7 +130,7 @@ namespace MWRender void setWeaponGroup(const std::string& group, bool relativeDuration) override; - osg::Vec3f runAnimation(float timepassed) override; + osg::Vec3f runAnimation(float timepassed, bool accumulateMovement = false) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. From 28eeef59bca179b10bd252473ce7b127228563d8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 13:35:02 +0100 Subject: [PATCH 0570/2167] Reduce movement solver same-position epsilon size. The previous value causes very stable idles to very slightly slide. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index ae958c60c7..02d24e735c 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -214,7 +214,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > 0.0001) + if ((newPosition - nextpos).length2() > 0.00001) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 18a6422c1c3b5cc371837f772dde8f8010eaeac5 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 14:21:14 +0100 Subject: [PATCH 0571/2167] clang format --- apps/openmw/mwphysics/ptrholder.hpp | 9 ++++++--- apps/openmw/mwrender/animation.cpp | 8 ++++---- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 270d2af37b..5884372e42 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,10 +1,10 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H +#include #include #include #include -#include #include @@ -40,8 +40,11 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void clearMovement() { mMovement = { }; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) { mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); } + void clearMovement() { mMovement = {}; } + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) + { + mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); + } std::deque& movement() { return mMovement; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cb51bcd00f..e12e8212e6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -49,8 +49,8 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/datetimemanager.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -1186,9 +1186,9 @@ namespace MWRender } else { - // When animations have velocity, net movement is expected. The above block would negate that movement every time - // the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works because - // oldtime < newtime is a guarantee even when the animation has looped. + // When animations have velocity, net movement is expected. The above block would negate that movement every + // time the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works + // because oldtime < newtime is a guarantee even when the animation has looped. position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 0e748180e8..8e159700ea 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -10,11 +10,11 @@ #include #include +#include #include #include #include #include -#include namespace ESM { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3496b5dab5..b3a01e8c3e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -30,10 +30,10 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" From aa30ec81d6d11721a04b3ba139fed232ab5aff7e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 16:27:21 +0100 Subject: [PATCH 0572/2167] more clang format --- apps/openmw/mwphysics/mtphysics.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index d2eb320ea4..83d3010359 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,8 +46,8 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, - unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,8 +87,8 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, - unsigned int frameNumber, osg::Stats& stats); + void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector* mSimulations = nullptr; From 3683f24b2818e3b49a70900e85f15db428fde707 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 17:47:18 +0100 Subject: [PATCH 0573/2167] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..531e51c812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #2623: Snowy Granius doesn't prioritize conjuration spells + Bug #3330: Backward displacement of the character when attacking in 3rd person Bug #3438: NPCs can't hit bull netch with melee weapons Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy From edf8c3b81c2b64648388687cfccb997631b631ca Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 17:53:40 +0100 Subject: [PATCH 0574/2167] mSteps should be an int. --- apps/openmw/mwphysics/mtphysics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 5df9a5c7a9..09eb309593 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -199,7 +199,7 @@ namespace struct InitMovement { - float mSteps = 1.f; + int mSteps = 0; float mDelta = 0.f; float mSimulationTime = 0.f; @@ -224,7 +224,7 @@ namespace void operator()(auto& sim) const { - if (mSteps == 0 || mDelta < 0.00001f) + if (mSteps <= 0 || mDelta < 0.00001f) return; auto locked = sim.lock(); From 0037fd78c17ef2977e4a16cb1f6a7f10471f3b8b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:08:42 +0100 Subject: [PATCH 0575/2167] Use std::numeric_limits::epsilon() instead of picking our own epsilon. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 02d24e735c..4952a561e7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -214,7 +214,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > 0.00001) + if ((newPosition - nextpos).length2() > std::numeric_limits::epsilon()) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 26817e9cc59dc4ae7e42262f23bef2ffca48161e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:36:22 +0100 Subject: [PATCH 0576/2167] Change the comparison of positions to avoid a problem if both positions are large numbers. --- apps/openmw/mwphysics/movementsolver.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 4952a561e7..d67758a618 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -202,7 +202,8 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { - osg::Vec3f nextpos = newPosition + velocity * remainingTime; + osg::Vec3f diff = velocity * remainingTime; + osg::Vec3f nextpos = newPosition + diff; bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air @@ -214,7 +215,9 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > std::numeric_limits::epsilon()) + // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely + // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. + if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 15c143e27258a9b37d63d7f5031a1d755535c774 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:53:45 +0100 Subject: [PATCH 0577/2167] Comment --- apps/openmw/mwphysics/movementsolver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index d67758a618..d187470801 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -217,6 +217,8 @@ namespace MWPhysics // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. + // Note that, although both these comparisons to 1e-6 are logically the same, they test separate floating point + // accuracy cases. if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions From af9312d869683dad87a2c0ad53c858207e304e5a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:56:04 +0100 Subject: [PATCH 0578/2167] clang format --- apps/openmw/mwphysics/movementsolver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index d187470801..63ffb055dd 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -215,10 +215,10 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely - // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. - // Note that, although both these comparisons to 1e-6 are logically the same, they test separate floating point - // accuracy cases. + // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely + // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle + // animations. Note that, although both these comparisons to 1e-6 are logically the same, they test separate + // floating point accuracy cases. if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions From 9cdaf2c29be1875fc3abe532ccae083c2098a6b2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Dec 2023 22:53:00 +0300 Subject: [PATCH 0579/2167] Handle NiFogProperty (feature #5173) --- CHANGELOG.md | 1 + components/CMakeLists.txt | 2 +- components/nif/property.hpp | 11 +++++++++++ components/nifosg/fog.cpp | 31 ++++++++++++++++++++++++++++++ components/nifosg/fog.hpp | 29 ++++++++++++++++++++++++++++ components/nifosg/nifloader.cpp | 28 ++++++++++++++++++++++++++- components/sceneutil/serialize.cpp | 15 +++++++++++++++ 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 components/nifosg/fog.cpp create mode 100644 components/nifosg/fog.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..e94bf28c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples + Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7e3c7aea23..f61a5bd0b2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -130,7 +130,7 @@ add_component_dir (nif ) add_component_dir (nifosg - nifloader controller particle matrixtransform + nifloader controller particle matrixtransform fog ) add_component_dir (nifbullet diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 2506633867..7d420f1650 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -461,11 +461,22 @@ namespace Nif struct NiFogProperty : NiProperty { + enum Flags : uint16_t + { + Enabled = 0x02, + Radial = 0x08, + VertexAlpha = 0x10, + }; + uint16_t mFlags; float mFogDepth; osg::Vec3f mColour; void read(NIFStream* nif) override; + + bool enabled() const { return mFlags & Flags::Enabled; } + bool radial() const { return mFlags & Flags::Radial; } + bool vertexAlpha() const { return mFlags & Flags::VertexAlpha; } }; struct NiMaterialProperty : NiProperty diff --git a/components/nifosg/fog.cpp b/components/nifosg/fog.cpp new file mode 100644 index 0000000000..497193ec42 --- /dev/null +++ b/components/nifosg/fog.cpp @@ -0,0 +1,31 @@ +#include "fog.hpp" + +#include +#include + +namespace NifOsg +{ + + Fog::Fog() + : osg::Fog() + { + } + + Fog::Fog(const Fog& copy, const osg::CopyOp& copyop) + : osg::Fog(copy, copyop) + , mDepth(copy.mDepth) + { + } + + void Fog::apply(osg::State& state) const + { + osg::Fog::apply(state); +#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE + float fov, aspect, near, far; + state.getProjectionMatrix().getPerspective(fov, aspect, near, far); + glFogf(GL_FOG_START, near * mDepth + far * (1.f - mDepth)); + glFogf(GL_FOG_END, far); +#endif + } + +} diff --git a/components/nifosg/fog.hpp b/components/nifosg/fog.hpp new file mode 100644 index 0000000000..5c49392a24 --- /dev/null +++ b/components/nifosg/fog.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_FOG_H +#define OPENMW_COMPONENTS_NIFOSG_FOG_H + +#include + +namespace NifOsg +{ + + // osg::Fog-based wrapper for NiFogProperty that autocalculates the fog start and end distance. + class Fog : public osg::Fog + { + public: + Fog(); + Fog(const Fog& copy, const osg::CopyOp& copyop); + + META_StateAttribute(NifOsg, Fog, FOG) + + void setDepth(float depth) { mDepth = depth; } + float getDepth() const { return mDepth; } + + void apply(osg::State& state) const override; + + private: + float mDepth{ 1.f }; + }; + +} + +#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..c55d580e36 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -56,6 +56,7 @@ #include #include +#include "fog.hpp" #include "matrixtransform.hpp" #include "particle.hpp" @@ -2495,10 +2496,35 @@ namespace NifOsg handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } + case Nif::RC_NiFogProperty: + { + const Nif::NiFogProperty* fogprop = static_cast(property); + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Vertex alpha mode appears to be broken + if (!fogprop->vertexAlpha() && fogprop->enabled()) + { + osg::ref_ptr fog = new NifOsg::Fog; + fog->setMode(osg::Fog::LINEAR); + fog->setColor(osg::Vec4f(fogprop->mColour, 1.f)); + fog->setDepth(fogprop->mFogDepth); + stateset->setAttributeAndModes(fog, osg::StateAttribute::ON); + // Intentionally ignoring radial fog flag + // We don't really want to override the global setting + } + else + { + osg::ref_ptr fog = new osg::Fog; + // Shaders don't respect glDisable(GL_FOG) + fog->setMode(osg::Fog::LINEAR); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } + break; + } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: - case Nif::RC_NiFogProperty: { break; } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 784dafafa5..8d8acacae4 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -123,6 +124,19 @@ namespace SceneUtil } }; + class FogSerializer : public osgDB::ObjectWrapper + { + public: + FogSerializer() + : osgDB::ObjectWrapper( + createInstanceFunc, "NifOsg::Fog", "osg::Object osg::StateAttribute osg::Fog NifOsg::Fog") + { + addSerializer(new osgDB::PropByValSerializer( + "Depth", 1.f, &NifOsg::Fog::getDepth, &NifOsg::Fog::setDepth), + osgDB::BaseSerializer::RW_FLOAT); + } + }; + osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) { return new osgDB::ObjectWrapper(createInstanceFunc, classname, "osg::Object"); @@ -153,6 +167,7 @@ namespace SceneUtil mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); mgr->addWrapper(new MatrixTransformSerializer); + mgr->addWrapper(new FogSerializer); // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of // vertex data that would make the file large and hard to read. From 0da620b3f9eb73f3ac7e1260a41dc64a8fb64235 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 15:40:23 +0100 Subject: [PATCH 0580/2167] Don't crash on spells or enchantments without effects --- CHANGELOG.md | 1 + apps/openmw/mwgui/hud.cpp | 26 +++++++++++++++----------- apps/openmw/mwgui/itemwidget.cpp | 2 +- apps/openmw/mwgui/itemwidget.hpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 14 ++++++++------ apps/openmw/mwmechanics/character.cpp | 19 +++++++------------ apps/openmw/mwworld/class.cpp | 4 +--- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..f549eac686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders + Bug #7712: Casting doesn't support spells and enchantments with no effects Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index bd38174183..1c8aad5447 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -423,17 +423,21 @@ namespace MWGui mSpellBox->setUserString("Spell", spellId.serialize()); mSpellBox->setUserData(MyGUI::Any::Null); - // use the icon of the first effect - const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - spell->mEffects.mList.front().mEffectID); - - std::string icon = effect->mIcon; - std::replace(icon.begin(), icon.end(), '/', '\\'); - int slashPos = icon.rfind('\\'); - icon.insert(slashPos + 1, "b_"); - icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); - - mSpellImage->setSpellIcon(icon); + if (!spell->mEffects.mList.empty()) + { + // use the icon of the first effect + const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( + spell->mEffects.mList.front().mEffectID); + std::string icon = effect->mIcon; + std::replace(icon.begin(), icon.end(), '/', '\\'); + size_t slashPos = icon.rfind('\\'); + icon.insert(slashPos + 1, "b_"); + icon = Misc::ResourceHelpers::correctIconPath( + icon, MWBase::Environment::get().getResourceSystem()->getVFS()); + mSpellImage->setSpellIcon(icon); + } + else + mSpellImage->setSpellIcon({}); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 5ee74c6e87..05fff2d40f 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -202,7 +202,7 @@ namespace MWGui setIcon(ptr); } - void SpellWidget::setSpellIcon(const std::string& icon) + void SpellWidget::setSpellIcon(std::string_view icon) { if (mFrame && !mCurrentFrame.empty()) { diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 29b0063203..63837ae92f 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -58,7 +58,7 @@ namespace MWGui { MYGUI_RTTI_DERIVED(SpellWidget) public: - void setSpellIcon(const std::string& icon); + void setSpellIcon(std::string_view icon); }; } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdcc4e76d7..a13cfe4d77 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -237,13 +237,15 @@ namespace MWGui params.mNoTarget = false; effects.push_back(params); } - if (MWMechanics::spellIncreasesSkill( - spell)) // display school of spells that contribute to skill progress + // display school of spells that contribute to skill progress + if (MWMechanics::spellIncreasesSkill(spell)) { - MWWorld::Ptr player = MWMechanics::getPlayer(); - const auto& school - = store->get().find(MWMechanics::getSpellSchool(spell, player))->mSchool; - info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); + ESM::RefId id = MWMechanics::getSpellSchool(spell, MWMechanics::getPlayer()); + if (!id.empty()) + { + const auto& school = store->get().find(id)->mSchool; + info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); + } } auto cost = focus->getUserString("SpellCost"); if (!cost.empty() && cost != "0") diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 77bc51423e..262283b365 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1605,7 +1605,7 @@ namespace MWMechanics effects = &spell->mEffects.mList; cast.playSpellCastingEffects(spell); } - if (mCanCast) + if (mCanCast && !effects->empty()) { const ESM::MagicEffect* effect = store.get().find( effects->back().mEffectID); // use last effect of list for color of VFX_Hands @@ -1615,18 +1615,13 @@ namespace MWMechanics const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (!effects->empty()) - { - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 L Hand", effect->mParticle); + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), + -1, false, "Bip01 L Hand", effect->mParticle); - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 R Hand", effect->mParticle); - } + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), + -1, false, "Bip01 R Hand", effect->mParticle); } const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index de3c2b011d..e6080ce447 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -524,11 +524,9 @@ namespace MWWorld const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().search(enchantmentName); - if (!enchantment) + if (!enchantment || enchantment->mEffects.mList.empty()) return result; - assert(enchantment->mEffects.mList.size()); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( enchantment->mEffects.mList.front().mEffectID); if (!magicEffect) From 32d391f5485cb63853bb5139b2ab07e2c6a05ce6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 14:29:26 +0100 Subject: [PATCH 0581/2167] Revert accumulating movement in the reset accum root callback. --- apps/openmw/mwmechanics/character.cpp | 4 +- apps/openmw/mwphysics/mtphysics.cpp | 8 -- apps/openmw/mwphysics/mtphysics.hpp | 3 - apps/openmw/mwrender/animation.cpp | 89 ++++------------------ apps/openmw/mwrender/animation.hpp | 6 +- apps/openmw/mwrender/creatureanimation.cpp | 4 +- apps/openmw/mwrender/creatureanimation.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwrender/npcanimation.hpp | 2 +- apps/openmw/mwworld/datetimemanager.hpp | 5 -- 10 files changed, 23 insertions(+), 104 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ef3479cd9..3eb392daf1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2448,9 +2448,9 @@ namespace MWMechanics bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation - = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration, doMovementAccumulation); + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && doMovementAccumulation && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { if (duration > 0.0f) movementFromAnimation /= duration; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 09eb309593..deef837992 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -608,7 +608,6 @@ namespace MWPhysics if (mAdvanceSimulation) { - mNextJobSimTime = simulationTimeStart + (numSteps * newDelta); mWorldFrameData = std::make_unique(); mBudgetCursor += 1; } @@ -812,7 +811,6 @@ namespace MWPhysics mLockingPolicy }; for (Simulation& sim : *mSimulations) std::visit(vis, sim); - mCurrentJobSimTime += mPhysicsDt; } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -922,9 +920,6 @@ namespace MWPhysics mLOSCache.end()); } - // On paper, mCurrentJobSimTime should have added up to mNextJobSimTime already - // But to avoid accumulating floating point errors, assign this anyway. - mCurrentJobSimTime = mNextJobSimTime; mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); @@ -939,9 +934,6 @@ namespace MWPhysics std::visit(vis, sim); mSimulations->clear(); mSimulations = nullptr; - const float interpolationFactor = std::clamp(mTimeAccum / mPhysicsDt, 0.0f, 1.0f); - MWBase::Environment::get().getWorld()->getTimeManager()->setPhysicsSimulationTime( - mCurrentJobSimTime - mPhysicsDt * (1.f - interpolationFactor)); } // Attempt to acquire unique lock on mSimulationMutex while not all worker diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 83d3010359..986f2be973 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -96,9 +96,6 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; - float mNextJobSimTime = 0.f; - float mCurrentJobSimTime = 0.f; - float mPreviousJobSimTime = 0.f; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e12e8212e6..e780a30fbb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -49,7 +49,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -490,23 +489,14 @@ namespace MWRender class ResetAccumRootCallback : public SceneUtil::NodeCallback { - struct AccumulatedMovement - { - osg::Vec3f mMovement = osg::Vec3f(); - float mSimStartTime = 0.f; - float mSimStopTime = 0.f; - }; - public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::Matrix mat = transform->getMatrix(); - osg::Vec3f position = mat.getTrans(); - position = osg::componentMultiply(mResetAxes, position); - // Add back the offset that the movement solver has not consumed yet - position += computeRemainder(); - mat.setTrans(position); - transform->setMatrix(mat); + osg::Matrix mat = transform->getMatrix(); + osg::Vec3f position = mat.getTrans(); + position = osg::componentMultiply(mResetAxes, position); + mat.setTrans(position); + transform->setMatrix(mat); traverse(transform, nv); } @@ -519,35 +509,7 @@ namespace MWRender mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } - void accumulate(const osg::Vec3f& movement, float dt) - { - if (dt < 0.00001f) - return; - - float simTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - mMovement.emplace_back(AccumulatedMovement{ movement, simTime, simTime + dt }); - } - - const osg::Vec3f computeRemainder() - { - float physSimTime = MWBase::Environment::get().getWorld()->getTimeManager()->getPhysicsSimulationTime(); - // Start by erasing all movement that has been fully consumed by the physics code - std::erase_if(mMovement, - [physSimTime](const AccumulatedMovement& movement) { return movement.mSimStopTime <= physSimTime; }); - - // Accumulate all the movement that hasn't been consumed. - osg::Vec3f movement; - for (const auto& m : mMovement) - { - float startTime = std::max(physSimTime, m.mSimStartTime); - float fraction = (m.mSimStopTime - startTime) / (m.mSimStopTime - m.mSimStartTime); - movement += m.mMovement * fraction; - } - return movement; - } - private: - std::deque mMovement; osg::Vec3f mResetAxes; }; @@ -1167,37 +1129,15 @@ namespace MWRender return velocity; } - void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) { // Get the difference from the last update, and move the position - osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); - if (!hasMovement) - { - // When animations have no velocity, the character should have zero net movement through a complete loop or - // animation sequence. Although any subsequence of the animation may move. This works because each sequence - // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move - // the character by just moving it from the position Bip01 was last frame to where it is this frame, without - // needing to accumulate anything in-between. - if (mPreviousAccumulatePosition) - { - position += offset - mPreviousAccumulatePosition.value(); - } - mPreviousAccumulatePosition = offset; - } - else - { - // When animations have velocity, net movement is expected. The above block would negate that movement every - // time the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works - // because oldtime < newtime is a guarantee even when the animation has looped. - position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); - } + osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); } - osg::Vec3f Animation::runAnimation(float duration, bool accumulateMovement) + osg::Vec3f Animation::runAnimation(float duration) { - if (!accumulateMovement) - mPreviousAccumulatePosition = std::nullopt; - osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) @@ -1211,7 +1151,6 @@ namespace MWRender const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); - bool hasMovement = getVelocity(stateiter->first) > 0.001f; float timepassed = duration * state.mSpeedMult; while (state.mPlaying) @@ -1221,14 +1160,14 @@ namespace MWRender float targetTime = state.getTime() + timepassed; if (textkey == textkeys.end() || textkey->first > targetTime) { - if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement, hasMovement); + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), targetTime, movement); state.setTime(std::min(targetTime, state.mStopTime)); } else { - if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement, hasMovement); + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), textkey->first, movement); state.setTime(textkey->first); } @@ -1311,8 +1250,6 @@ namespace MWRender } - if (accumulateMovement && mResetAccumRootCallback) - mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8e159700ea..24366889c4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -266,7 +265,6 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::Vec3f mAccumulate; - std::optional mPreviousAccumulatePosition; TextKeyListener* mTextKeyListener; @@ -306,7 +304,7 @@ namespace MWRender /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If @@ -466,7 +464,7 @@ namespace MWRender /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(std::string_view groupname) const; - virtual osg::Vec3f runAnimation(float duration, bool accumulateMovement = false); + virtual osg::Vec3f runAnimation(float duration); void setLoopingEnabled(std::string_view groupname, bool enabled); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 65f48f5ade..7767520715 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -258,9 +258,9 @@ namespace MWRender WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } - osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration, bool accumulateMovement) + osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) { - osg::Vec3f ret = Animation::runAnimation(duration, accumulateMovement); + osg::Vec3f ret = Animation::runAnimation(duration); WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 9ff3d55375..05235e5191 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -59,7 +59,7 @@ namespace MWRender void addControllers() override; - osg::Vec3f runAnimation(float duration, bool accumulateMovement = false) override; + osg::Vec3f runAnimation(float duration) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 951d16aefe..469978e6eb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -693,9 +693,9 @@ namespace MWRender return std::make_unique(attached); } - osg::Vec3f NpcAnimation::runAnimation(float timepassed, bool accumulateMovement) + osg::Vec3f NpcAnimation::runAnimation(float timepassed) { - osg::Vec3f ret = Animation::runAnimation(timepassed, accumulateMovement); + osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 0c2d67db86..a03ee28f3a 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -130,7 +130,7 @@ namespace MWRender void setWeaponGroup(const std::string& group, bool relativeDuration) override; - osg::Vec3f runAnimation(float timepassed, bool accumulateMovement = false) override; + osg::Vec3f runAnimation(float timepassed) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index 20d968f4ea..af62d9ba3f 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,10 +29,6 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio - // Physics simulation time - double getPhysicsSimulationTime() const { return mPhysicsSimulationTime; } - void setPhysicsSimulationTime(double t) { mPhysicsSimulationTime = t; } - // Rendering simulation time (summary simulation time of rendering frames since application start). double getRenderingSimulationTime() const { return mRenderingSimulationTime; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } @@ -74,7 +70,6 @@ namespace MWWorld float mSimulationTimeScale = 1.0; double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; - double mPhysicsSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; }; From 4062f0225be17ef4f929be713747d49bd767f323 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 16:13:56 +0100 Subject: [PATCH 0582/2167] Use the right getContainerStore --- apps/openmw/mwclass/container.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 0efbbc84fd..a985fe6d71 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -306,7 +306,7 @@ namespace MWClass if (newPtr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - newPtr.getContainerStore()->setPtr(newPtr); + getContainerStore(newPtr).setPtr(newPtr); } return newPtr; } From c79446818ef27a802d016e4e41e2e40d1970afe7 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 16:48:04 +0100 Subject: [PATCH 0583/2167] Add a flag for jump when queueing movement, so inertia can be added accurately. --- apps/openmw/mwbase/world.hpp | 15 +++++++++---- apps/openmw/mwmechanics/character.cpp | 7 ++++--- apps/openmw/mwphysics/mtphysics.cpp | 28 ++++++++++++++++++++++++- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 5 +++-- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 4 +++- 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f67b9a0e05..8670202965 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,9 +295,13 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) = 0; + virtual void queueMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) + = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. + /// \param duration The duration this speed shall be held, starting at current simulation time + /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead virtual void updateAnimatedCollisionShape(const MWWorld::Ptr& ptr) = 0; @@ -567,7 +571,8 @@ namespace MWBase virtual DetourNavigator::Navigator* getNavigator() const = 0; virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const + = 0; virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; @@ -576,10 +581,12 @@ namespace MWBase virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; virtual bool hasCollisionWithDoor( - const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; + const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const + = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - std::span ignore, std::vector* occupyingActors = nullptr) const = 0; + std::span ignore, std::vector* occupyingActors = nullptr) const + = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3eb392daf1..2aece5f86f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2420,7 +2420,9 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec, duration); + { + world->queueMovement(mPtr, vec, duration, mInJump && mJumpState == JumpState_None); + } } movement = vec; @@ -2446,7 +2448,6 @@ namespace MWMechanics } } - bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); @@ -2494,7 +2495,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement, duration); + world->queueMovement(mPtr, movement, duration, mInJump && mJumpState == JumpState_None); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index deef837992..cc0c38a471 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -210,6 +210,13 @@ namespace auto it = actor.movement().begin(); while (it != actor.movement().end()) { + if (it->jump) + { + // Adjusting inertia is instant and should not be performed over time like other movement is. + it++; + continue; + } + float start = std::max(it->simulationTimeStart, startTime); float stop = std::min(it->simulationTimeStop, endTime); movement += it->velocity * (stop - start); @@ -222,6 +229,23 @@ namespace return movement; } + std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const + { + std::optional inertia = std::nullopt; + auto it = actor.movement().begin(); + while (it != actor.movement().end()) + { + if (it->jump && it->simulationTimeStart >= startTime) + { + inertia = it->velocity; + it = actor.movement().erase(it); + } + else + it++; + } + return inertia; + } + void operator()(auto& sim) const { if (mSteps <= 0 || mDelta < 0.00001f) @@ -237,8 +261,10 @@ namespace // movement solver osg::Vec3f velocity = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); + // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a jump + auto inertia = takeInertia(*ptrHolder, mSimulationTime); - frameDataRef.get().mMovement += velocity; + frameDataRef.get().mMovement += inertia.value_or(velocity); } }; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 3c451497e1..6f57c7db01 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -624,12 +624,12 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->queueMovement(velocity, start, start + duration); + found->second->queueMovement(velocity, start, start + duration, jump); } void PhysicsSystem::clearQueuedMovement() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3babdef9aa..7729f274f1 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 5884372e42..d7a6f887bb 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -19,6 +19,7 @@ namespace MWPhysics osg::Vec3f velocity = osg::Vec3f(); float simulationTimeStart = 0.f; // The time at which this movement begun float simulationTimeStop = 0.f; // The time at which this movement finished + bool jump = false; }; class PtrHolder @@ -41,9 +42,9 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } void clearMovement() { mMovement = {}; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop, bool jump = false) { - mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); + mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } std::deque& movement() { return mMovement; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9db72d1604..b6b7de24b4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { - mPhysics->queueObjectMovement(ptr, velocity, duration); + mPhysics->queueObjectMovement(ptr, velocity, duration, jump); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 486e240fdc..aa5f9d56f0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,9 +383,11 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. + /// \param duration The duration this speed shall be held, starting at current simulation time + /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead void updateAnimatedCollisionShape(const Ptr& ptr) override; From 521cff08f83e7e616a8d87980550f7030c0a9862 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 17:22:11 +0100 Subject: [PATCH 0584/2167] Drop support for save game format 1 --- apps/openmw/mwworld/weather.cpp | 47 +++++++++++++------------------ components/esm3/creaturestats.cpp | 2 -- components/esm3/formatversion.hpp | 3 +- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5d739a9161..aa75730b40 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -898,36 +898,27 @@ namespace MWWorld { if (ESM::REC_WTHR == type) { - if (reader.getFormatVersion() <= ESM::MaxOldWeatherFormatVersion) + ESM::WeatherState state; + state.load(reader); + + std::swap(mCurrentRegion, state.mCurrentRegion); + mTimePassed = state.mTimePassed; + mFastForward = state.mFastForward; + mWeatherUpdateTime = state.mWeatherUpdateTime; + mTransitionFactor = state.mTransitionFactor; + mCurrentWeather = state.mCurrentWeather; + mNextWeather = state.mNextWeather; + mQueuedWeather = state.mQueuedWeather; + + mRegions.clear(); + importRegions(); + + for (auto it = state.mRegions.begin(); it != state.mRegions.end(); ++it) { - // Weather state isn't really all that important, so to preserve older save games, we'll just discard - // the older weather records, rather than fail to handle the record. - reader.skipRecord(); - } - else - { - ESM::WeatherState state; - state.load(reader); - - std::swap(mCurrentRegion, state.mCurrentRegion); - mTimePassed = state.mTimePassed; - mFastForward = state.mFastForward; - mWeatherUpdateTime = state.mWeatherUpdateTime; - mTransitionFactor = state.mTransitionFactor; - mCurrentWeather = state.mCurrentWeather; - mNextWeather = state.mNextWeather; - mQueuedWeather = state.mQueuedWeather; - - mRegions.clear(); - importRegions(); - - for (auto it = state.mRegions.begin(); it != state.mRegions.end(); ++it) + auto found = mRegions.find(it->first); + if (found != mRegions.end()) { - auto found = mRegions.find(it->first); - if (found != mRegions.end()) - { - found->second = RegionWeather(it->second); - } + found->second = RegionWeather(it->second); } } diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index 8281916e30..a393da697f 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -49,8 +49,6 @@ namespace ESM esm.getHNOT(mTalkedTo, "TALK"); esm.getHNOT(mAlarmed, "ALRM"); esm.getHNOT(mAttacked, "ATKD"); - if (esm.isNextSub("ATCK")) - esm.skipHSub(); // attackingOrSpell, no longer used esm.getHNOT(mKnockdown, "KNCK"); esm.getHNOT(mKnockdownOneFrame, "KNC1"); esm.getHNOT(mKnockdownOverOneFrame, "KNCO"); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index e02e0176a9..82fff88614 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,7 +9,6 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; - inline constexpr FormatVersion MaxOldWeatherFormatVersion = 1; inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; @@ -28,7 +27,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 1; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 2; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } From becc5ef8fa96640b7952d65420708174a61cd5a4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 17:45:42 +0100 Subject: [PATCH 0585/2167] Drop support for save game format 2 --- components/esm3/creaturestats.cpp | 2 -- components/esm3/formatversion.hpp | 3 +-- components/esm3/inventorystate.cpp | 18 ++++-------------- components/esm3/inventorystate.hpp | 2 -- components/esm3/objectstate.cpp | 3 --- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index a393da697f..44c3bd993b 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -42,8 +42,6 @@ namespace ESM { esm.getHNOT(mDead, "DEAD"); esm.getHNOT(mDeathAnimationFinished, "DFNT"); - if (esm.getFormatVersion() <= MaxOldDeathAnimationFormatVersion && mDead) - mDeathAnimationFinished = true; esm.getHNOT(mDied, "DIED"); esm.getHNOT(mMurdered, "MURD"); esm.getHNOT(mTalkedTo, "TALK"); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 82fff88614..854502b220 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,7 +9,6 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; - inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; @@ -27,7 +26,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 2; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 3; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index a6130af473..ded2d1c33b 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -52,20 +52,17 @@ namespace ESM mItems.push_back(state); } + std::map, int32_t> levelledItemMap; // Next item is Levelled item while (esm.isNextSub("LEVM")) { // Get its name ESM::RefId id = esm.getRefId(); int32_t count; - std::string parentGroup; // Then get its count esm.getHNT(count, "COUN"); - // Old save formats don't have information about parent group; check for that - if (esm.isNextSub("LGRP")) - // Newest saves contain parent group - parentGroup = esm.getHString(); - mLevelledItemMap[std::make_pair(id, parentGroup)] = count; + std::string parentGroup = esm.getHNString("LGRP"); + levelledItemMap[std::make_pair(id, parentGroup)] = count; } while (esm.isNextSub("MAGI")) @@ -117,7 +114,7 @@ namespace ESM // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities - for (const auto& entry : mLevelledItemMap) + for (const auto& entry : levelledItemMap) { const ESM::RefId& id = entry.first.first; const int count = entry.second; @@ -141,13 +138,6 @@ namespace ESM } } - for (auto it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) - { - esm.writeHNRefId("LEVM", it->first.first); - esm.writeHNT("COUN", it->second); - esm.writeHNString("LGRP", it->first.second); - } - for (const auto& [id, params] : mPermanentMagicEffectMagnitudes) { esm.writeHNRefId("MAGI", id); diff --git a/components/esm3/inventorystate.hpp b/components/esm3/inventorystate.hpp index 050d1eb92f..814236ce46 100644 --- a/components/esm3/inventorystate.hpp +++ b/components/esm3/inventorystate.hpp @@ -22,8 +22,6 @@ namespace ESM // std::map mEquipmentSlots; - std::map, int32_t> mLevelledItemMap; - std::map>> mPermanentMagicEffectMagnitudes; std::optional mSelectedEnchantItem; // For inventories only diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index fca4c64f5f..6e2621df29 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -42,9 +42,6 @@ namespace ESM else mPosition = mRef.mPos; - if (esm.isNextSub("LROT")) - esm.skipHSub(); // local rotation, no longer used - mFlags = 0; esm.getHNOT(mFlags, "FLAG"); From b0ef42ae3ccbd5cbbb2acc6e09586c114f4c89c2 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 18:05:57 +0100 Subject: [PATCH 0586/2167] Drop support for save game format 3 --- components/esm3/animationstate.cpp | 13 +------------ components/esm3/formatversion.hpp | 2 +- components/esm3/projectilestate.cpp | 7 ------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/components/esm3/animationstate.cpp b/components/esm3/animationstate.cpp index 14edf01a83..79dcad7578 100644 --- a/components/esm3/animationstate.cpp +++ b/components/esm3/animationstate.cpp @@ -21,18 +21,7 @@ namespace ESM anim.mGroup = esm.getHString(); esm.getHNOT(anim.mTime, "TIME"); esm.getHNOT(anim.mAbsolute, "ABST"); - - esm.getSubNameIs("COUN"); - // workaround bug in earlier version where size_t was used - esm.getSubHeader(); - if (esm.getSubSize() == 8) - esm.getT(anim.mLoopCount); - else - { - uint32_t loopcount; - esm.getT(loopcount); - anim.mLoopCount = (uint64_t)loopcount; - } + esm.getHNT(anim.mLoopCount, "COUN"); mScriptedAnims.push_back(anim); } diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 854502b220..949cdadc38 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,7 +26,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 3; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index 15ff5fff64..bed9073999 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -37,18 +37,11 @@ namespace ESM BaseProjectileState::load(esm); mSpellId = esm.getHNRefId("SPEL"); - if (esm.isNextSub("SRCN")) // for backwards compatibility - esm.skipHSub(); - EffectList().load(esm); // for backwards compatibility esm.getHNT(mSpeed, "SPED"); if (esm.peekNextSub("ITEM")) mItem = esm.getFormId(true, "ITEM"); if (esm.isNextSub("SLOT")) // for backwards compatibility esm.skipHSub(); - if (esm.isNextSub("STCK")) // for backwards compatibility - esm.skipHSub(); - if (esm.isNextSub("SOUN")) // for backwards compatibility - esm.skipHSub(); } void ProjectileState::save(ESMWriter& esm) const From 41dc409238c59d9159f5f6aa95cb99b9cb7ef40d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 18:20:10 +0100 Subject: [PATCH 0587/2167] Don't consider empty effect lists exceptional --- apps/openmw/mwmechanics/character.cpp | 104 ++++++++++++++------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 262283b365..713add719b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1605,61 +1605,67 @@ namespace MWMechanics effects = &spell->mEffects.mList; cast.playSpellCastingEffects(spell); } - if (mCanCast && !effects->empty()) + if (!effects->empty()) { - const ESM::MagicEffect* effect = store.get().find( - effects->back().mEffectID); // use last effect of list for color of VFX_Hands - - const ESM::Static* castStatic - = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); - - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), - -1, false, "Bip01 L Hand", effect->mParticle); - - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), - -1, false, "Bip01 R Hand", effect->mParticle); - } - - const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation - - std::string startKey; - std::string stopKey; - if (isRandomAttackAnimation(mCurrentWeapon)) - { - startKey = "start"; - stopKey = "stop"; if (mCanCast) - world->castSpell( - mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; - mCanCast = false; - } - else - { - switch (firstEffect.mRange) { - case 0: - mAttackType = "self"; - break; - case 1: - mAttackType = "touch"; - break; - case 2: - mAttackType = "target"; - break; + const ESM::MagicEffect* effect = store.get().find( + effects->back().mEffectID); // use last effect of list for color of VFX_Hands + + const ESM::Static* castStatic + = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); + + const VFS::Manager* const vfs + = MWBase::Environment::get().getResourceSystem()->getVFS(); + + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 R Hand", effect->mParticle); + } + // first effect used for casting animation + const ESM::ENAMstruct& firstEffect = effects->front(); + + std::string startKey; + std::string stopKey; + if (isRandomAttackAnimation(mCurrentWeapon)) + { + startKey = "start"; + stopKey = "stop"; + if (mCanCast) + world->castSpell( + mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + mCastingManualSpell = false; + mCanCast = false; + } + else + { + switch (firstEffect.mRange) + { + case 0: + mAttackType = "self"; + break; + case 1: + mAttackType = "touch"; + break; + case 2: + mAttackType = "target"; + break; + } + + startKey = mAttackType + " start"; + stopKey = mAttackType + " stop"; } - startKey = mAttackType + " start"; - stopKey = mAttackType + " stop"; + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, + 1, startKey, stopKey, 0.0f, 0); + mUpperBodyState = UpperBodyState::Casting; } - - mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1, - startKey, stopKey, 0.0f, 0); - mUpperBodyState = UpperBodyState::Casting; } else { From 7b8c0d1d88d8ba5f2d3661e443d4279bc2e9addc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 19:00:42 +0100 Subject: [PATCH 0588/2167] Remove dropped formats from tests --- apps/openmw_test_suite/mwworld/test_store.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f80c12917c..b63c0902ab 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -270,8 +270,6 @@ namespace std::vector result({ ESM::DefaultFormatVersion, ESM::CurrentContentFormatVersion, - ESM::MaxOldWeatherFormatVersion, - ESM::MaxOldDeathAnimationFormatVersion, ESM::MaxOldFogOfWarFormatVersion, ESM::MaxUnoptimizedCharacterDataFormatVersion, ESM::MaxOldTimeLeftFormatVersion, From f269b25bd07fb1830984e6f1a7607288ef8772e2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 9 Dec 2023 22:00:35 +0300 Subject: [PATCH 0589/2167] Remove unused field --- apps/openmw/mwrender/globalmap.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index e0582b20fa..07d7731e31 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -111,8 +111,6 @@ namespace MWRender ImageDestMap mPendingImageDest; - std::vector> mExploredCells; - osg::ref_ptr mBaseTexture; osg::ref_ptr mAlphaTexture; From 76232c49df5458a67a7161ebeb6f6bfeb2efb2f8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 20:08:14 +0100 Subject: [PATCH 0590/2167] clang format --- apps/openmw/mwbase/world.hpp | 9 +++------ apps/openmw/mwphysics/mtphysics.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.hpp | 3 ++- apps/openmw/mwrender/animation.cpp | 11 +++++------ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8670202965..23961554ca 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -571,8 +571,7 @@ namespace MWBase virtual DetourNavigator::Navigator* getNavigator() const = 0; virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const - = 0; + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; @@ -581,12 +580,10 @@ namespace MWBase virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; virtual bool hasCollisionWithDoor( - const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const - = 0; + const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - std::span ignore, std::vector* occupyingActors = nullptr) const - = 0; + std::span ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index cc0c38a471..9a4ec2cba2 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -261,7 +261,8 @@ namespace // movement solver osg::Vec3f velocity = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); - // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a jump + // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a + // jump auto inertia = takeInertia(*ptrHolder, mSimulationTime); frameDataRef.get().mMovement += inertia.value_or(velocity); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6f57c7db01..9a85ee009f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -624,7 +624,8 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void PhysicsSystem::queueObjectMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 7729f274f1..7758c6dfd7 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,8 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); + void queueObjectMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e780a30fbb..5b0e1f82bd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -492,11 +492,11 @@ namespace MWRender public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::Matrix mat = transform->getMatrix(); - osg::Vec3f position = mat.getTrans(); - position = osg::componentMultiply(mResetAxes, position); - mat.setTrans(position); - transform->setMatrix(mat); + osg::Matrix mat = transform->getMatrix(); + osg::Vec3f position = mat.getTrans(); + position = osg::componentMultiply(mResetAxes, position); + mat.setTrans(position); + transform->setMatrix(mat); traverse(transform, nv); } @@ -1249,7 +1249,6 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } - return movement; } From 69bb65e47bd3c6dcd37beb1535f24034274fb339 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 13 Aug 2023 16:51:15 +0100 Subject: [PATCH 0591/2167] Allow bookart to be in texutres and texutres to be in bookart. Rebased to account for upstream normalising slashes to turn forward slashes into backslashes. This simplifies some conditions that previously needed to check for both kinds. --- components/misc/resourcehelpers.cpp | 26 ++++++++++++++++---------- components/misc/resourcehelpers.hpp | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ce552df4f7..762a8b88d2 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -46,8 +46,8 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) return changeExtension(path, ".dds"); } -std::string Misc::ResourceHelpers::correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs) +std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, + const VFS::Manager* vfs, const std::vector& alternativeDirectories) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -69,12 +69,18 @@ std::string Misc::ResourceHelpers::correctResourcePath( if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() || correctedPath[topLevelDirectory.size()] != '\\') { - std::string topLevelPrefix = std::string{ topLevelDirectory } + '\\'; - size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); - if (topLevelPos == std::string::npos) - correctedPath = topLevelPrefix + correctedPath; - else - correctedPath.erase(0, topLevelPos + 1); + bool needsPrefix = true; + for (std::string_view alternativeDirectory : alternativeDirectories) + { + if (!correctedPath.starts_with(alternativeDirectory) || correctedPath.size() <= alternativeDirectory.size() + || correctedPath[alternativeDirectory.size()] != '\\') + { + needsPrefix = false; + break; + } + } + if (needsPrefix) + correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath; } std::string origExt = correctedPath; @@ -110,7 +116,7 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("textures", resPath, vfs); + return correctResourcePath("textures", resPath, vfs, { "bookart" }); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) @@ -120,7 +126,7 @@ std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, con std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("bookart", resPath, vfs); + return correctResourcePath("bookart", resPath, vfs, { "textures" }); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 478569ed14..f45a52a23c 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -22,8 +22,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs); + std::string correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, + const VFS::Manager* vfs, const std::vector& alternativeDirectories = {}); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 575367bc18bfd030335166deedef58bd680a1c54 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 13 Aug 2023 23:35:25 +0100 Subject: [PATCH 0592/2167] v e c t o r --- components/misc/resourcehelpers.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index f45a52a23c..dc0b487a59 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace VFS { From f30676cbc73b9ee5135f93a3567753a89d684831 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 14 Aug 2023 14:10:45 +0100 Subject: [PATCH 0593/2167] Invert condition Rebased to account for upstream normalising slashes to replace forward slashes with backslashes, simplifying the part that needed to check for both variants. Perhaps if it'd been like that in the first place, I wouldn't have made the mistake that made the original version of this commit necessary. --- components/misc/resourcehelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 762a8b88d2..32f65872c7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -72,8 +72,8 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel bool needsPrefix = true; for (std::string_view alternativeDirectory : alternativeDirectories) { - if (!correctedPath.starts_with(alternativeDirectory) || correctedPath.size() <= alternativeDirectory.size() - || correctedPath[alternativeDirectory.size()] != '\\') + if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() + && correctedPath[alternativeDirectory.size()] == '\\') { needsPrefix = false; break; From bf0756c5b35536949b2026d41b5ebdb3aa3c6a42 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 14 Aug 2023 22:09:51 +0100 Subject: [PATCH 0594/2167] c h a n g e l o g Rebased to account for other new changelog entries. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..dd6f7d68c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ Bug #7472: Crash when enchanting last projectiles Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size + Bug #7535: Bookart paths for textures in OpenMW vs vanilla Morrowind Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default From 3a71a78d9ea32708b996b7f51fe7717ca3ad7316 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Dec 2023 19:01:30 +0000 Subject: [PATCH 0595/2167] Combine topLevelDirectory and alternativeDirectories --- components/misc/resourcehelpers.cpp | 34 +++++++++++++---------------- components/misc/resourcehelpers.hpp | 4 ++-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 32f65872c7..b602001913 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -46,8 +46,8 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) return changeExtension(path, ".dds"); } -std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, - const VFS::Manager* vfs, const std::vector& alternativeDirectories) +std::string Misc::ResourceHelpers::correctResourcePath( + const std::vector& topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -66,22 +66,18 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel correctedPath.erase(0, 1); // Handle top level directory - if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() - || correctedPath[topLevelDirectory.size()] != '\\') + bool needsPrefix = true; + for (std::string_view alternativeDirectory : topLevelDirectories) { - bool needsPrefix = true; - for (std::string_view alternativeDirectory : alternativeDirectories) + if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() + && correctedPath[alternativeDirectory.size()] == '\\') { - if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() - && correctedPath[alternativeDirectory.size()] == '\\') - { - needsPrefix = false; - break; - } + needsPrefix = false; + break; } - if (needsPrefix) - correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath; } + if (needsPrefix) + correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath; std::string origExt = correctedPath; @@ -96,7 +92,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel return origExt; // fall back to a resource in the top level directory if it exists - std::string fallback{ topLevelDirectory }; + std::string fallback{ topLevelDirectories.front() }; fallback += '\\'; fallback += getBasename(correctedPath); if (vfs->exists(fallback)) @@ -104,7 +100,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel if (changedToDds) { - fallback = topLevelDirectory; + fallback = topLevelDirectories.front(); fallback += '\\'; fallback += getBasename(origExt); if (vfs->exists(fallback)) @@ -116,17 +112,17 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("textures", resPath, vfs, { "bookart" }); + return correctResourcePath({ "textures", "bookart" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("icons", resPath, vfs); + return correctResourcePath({ "icons" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("bookart", resPath, vfs, { "textures" }); + return correctResourcePath({ "bookart", "textures" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index dc0b487a59..c98840dd61 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -23,8 +23,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, - const VFS::Manager* vfs, const std::vector& alternativeDirectories = {}); + std::string correctResourcePath(const std::vector& topLevelDirectories, + std::string_view resPath, const VFS::Manager* vfs); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 4d0aece001739f9d2fd72b2b0e54fbc55b66c8e7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 11 Dec 2023 00:05:41 +0000 Subject: [PATCH 0596/2167] Clarify variable name --- components/misc/resourcehelpers.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index b602001913..7386dceb9f 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -67,10 +67,10 @@ std::string Misc::ResourceHelpers::correctResourcePath( // Handle top level directory bool needsPrefix = true; - for (std::string_view alternativeDirectory : topLevelDirectories) + for (std::string_view potentialTopLevelDirectory : topLevelDirectories) { - if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() - && correctedPath[alternativeDirectory.size()] == '\\') + if (correctedPath.starts_with(potentialTopLevelDirectory) && correctedPath.size() > potentialTopLevelDirectory.size() + && correctedPath[potentialTopLevelDirectory.size()] == '\\') { needsPrefix = false; break; From ca19f7006c7e6f129ce2c03241eb214f3c7fd20f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 2 Dec 2023 11:15:54 +0400 Subject: [PATCH 0597/2167] Make hardcoded strings in Launcher and Wizard localizable --- apps/launcher/datafilespage.cpp | 27 +++++++++++++------ apps/launcher/graphicspage.cpp | 18 ++++++++++--- apps/wizard/installationpage.cpp | 3 ++- apps/wizard/languageselectionpage.cpp | 12 +++++---- apps/wizard/mainwizard.cpp | 5 ++-- .../contentselector/view/contentselector.cpp | 2 +- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4ad99c99f1..dc2c07d9bd 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -164,11 +164,14 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); - QStringList languages; - languages << tr("English") << tr("French") << tr("German") << tr("Italian") << tr("Polish") << tr("Russian") - << tr("Spanish"); + QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, + { "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") }, + { "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } }; - mSelector->languageBox()->addItems(languages); + for (auto lang : languages) + { + mSelector->languageBox()->addItem(lang.second, lang.first); + } mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this); @@ -254,9 +257,17 @@ bool Launcher::DataFilesPage::loadSettings() if (!currentProfile.isEmpty()) addProfile(currentProfile, true); - const int index = mSelector->languageBox()->findText(mLauncherSettings.getLanguage()); - if (index != -1) - mSelector->languageBox()->setCurrentIndex(index); + auto language = mLauncherSettings.getLanguage(); + + for (int i = 0; i < mSelector->languageBox()->count(); ++i) + { + QString languageItem = mSelector->languageBox()->itemData(i).toString(); + if (language == languageItem) + { + mSelector->languageBox()->setCurrentIndex(i); + break; + } + } return true; } @@ -386,7 +397,7 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); - QString language(mSelector->languageBox()->currentText()); + QString language(mSelector->languageBox()->currentData().toString()); mLauncherSettings.setLanguage(language); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 84d5049d6c..fa9d5eb479 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -154,8 +154,13 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex( - shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str())))); + auto boundMethod = Settings::shadows().mComputeSceneBounds.get(); + if (boundMethod == "bounds") + shadowComputeSceneBoundsComboBox->setCurrentIndex(0); + else if (boundMethod == "primitives") + shadowComputeSceneBoundsComboBox->setCurrentIndex(1); + else + shadowComputeSceneBoundsComboBox->setCurrentIndex(2); const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; if (shadowDistLimit > 0) @@ -254,7 +259,14 @@ void Launcher::GraphicsPage::saveSettings() Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); - Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString()); + + auto index = shadowComputeSceneBoundsComboBox->currentIndex(); + if (index == 0) + Settings::shadows().mComputeSceneBounds.set("bounds"); + else if (index == 1) + Settings::shadows().mComputeSceneBounds.set("primitives"); + else + Settings::shadows().mComputeSceneBounds.set("none"); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index cf2e3671e1..2dd796ab3f 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,5 +1,6 @@ #include "installationpage.hpp" +#include #include #include #include @@ -123,7 +124,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini - QString language(field(QLatin1String("installation.language")).toString()); + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); if (language == QLatin1String("Polish")) { diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 9808d3c56c..38050b1cab 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -14,12 +14,14 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) void Wizard::LanguageSelectionPage::initializePage() { - QStringList languages; - languages << QLatin1String("English") << QLatin1String("French") << QLatin1String("German") - << QLatin1String("Italian") << QLatin1String("Polish") << QLatin1String("Russian") - << QLatin1String("Spanish"); + QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, + { "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") }, + { "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } }; - languageComboBox->addItems(languages); + for (auto lang : languages) + { + languageComboBox->addItem(lang.second, lang.first); + } } int Wizard::LanguageSelectionPage::nextId() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e9cce3db5e..5fd316d17a 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -270,8 +270,7 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--encoding")); // Set encoding - QString language(field(QLatin1String("installation.language")).toString()); - + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); if (language == QLatin1String("Polish")) { arguments.append(QLatin1String("win1250")); @@ -392,7 +391,7 @@ void Wizard::MainWizard::reject() void Wizard::MainWizard::writeSettings() { // Write the encoding and language settings - QString language(field(QLatin1String("installation.language")).toString()); + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); mLauncherSettings.setLanguage(language); if (language == QLatin1String("Polish")) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 2f01200927..62d476b944 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -32,7 +32,7 @@ void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->addItem(""); + ui.gameFileView->addItem(tr("")); ui.gameFileView->setVisible(true); connect(ui.gameFileView, qOverload(&ComboBox::currentIndexChanged), this, From 4a9688532381ac5813da164ba287f653bdf97213 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Dec 2023 21:45:15 +0300 Subject: [PATCH 0598/2167] Untangle normals and parallax handling Move tangent space generation to the vertex shaders Support diffuse parallax when no normal map is present Don't use diffuse parallax if there's no diffuse map Generalize normal-to-view conversion Rewrite parallax --- components/shader/shadervisitor.cpp | 18 +++--- files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++--- files/shaders/compatibility/bs/default.vert | 15 +++-- files/shaders/compatibility/groundcover.frag | 22 ++----- files/shaders/compatibility/groundcover.vert | 12 ++-- files/shaders/compatibility/normals.glsl | 14 ++++ files/shaders/compatibility/objects.frag | 67 +++++++------------- files/shaders/compatibility/objects.vert | 23 ++++--- files/shaders/compatibility/terrain.frag | 38 +++-------- files/shaders/compatibility/terrain.vert | 16 +++-- files/shaders/lib/material/parallax.glsl | 7 +- 12 files changed, 118 insertions(+), 130 deletions(-) create mode 100644 files/shaders/compatibility/normals.glsl diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a08652620d..70464f571e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -304,14 +304,6 @@ namespace Shader if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; - int applyMode; - // Oblivion parallax - if (node.getUserValue("applyMode", applyMode) && applyMode == 4) - { - mRequirements.back().mShaderRequired = true; - mRequirements.back().mDiffuseHeight = true; - } - // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -359,7 +351,17 @@ namespace Shader normalMap = texture; } else if (texName == "diffuseMap") + { + int applyMode; + // Oblivion parallax + if (node.getUserValue("applyMode", applyMode) && applyMode == 4) + { + mRequirements.back().mShaderRequired = true; + mRequirements.back().mDiffuseHeight = true; + mRequirements.back().mTexStageRequiringTangents = unit; + } diffuseMap = texture; + } else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 1b73acf758..6ead738477 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -45,6 +45,7 @@ set(SHADER_FILES compatibility/shadowcasting.vert compatibility/shadowcasting.frag compatibility/vertexcolors.glsl + compatibility/normals.glsl compatibility/multiview_resolve.vert compatibility/multiview_resolve.frag compatibility/depthclipped.vert diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index f1be8da80c..7e2be9aa8f 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -24,7 +24,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif varying float euclideanDepth; @@ -46,11 +45,10 @@ uniform bool useTreeAnim; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" #include "compatibility/fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); @@ -65,15 +63,10 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); float shadowing = unshadowedLightRatio(linearDepth); vec3 diffuseLight, ambientLight; diff --git a/files/shaders/compatibility/bs/default.vert b/files/shaders/compatibility/bs/default.vert index 712a3f3d0c..d9d47843c0 100644 --- a/files/shaders/compatibility/bs/default.vert +++ b/files/shaders/compatibility/bs/default.vert @@ -36,6 +36,7 @@ varying vec3 passNormal; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_vertex.glsl" +#include "compatibility/normals.glsl" void main(void) { @@ -45,6 +46,14 @@ void main(void) gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw, passNormal); +#endif #if @diffuseMap diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; @@ -56,15 +65,11 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; #if @shadows_enabled - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index 9dfd71b32e..f87beaf447 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -18,7 +18,6 @@ varying vec2 diffuseMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -44,23 +43,10 @@ varying vec3 passNormal; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else @@ -72,6 +58,12 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index dd8d083a47..c1bb35da05 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -21,7 +21,6 @@ varying vec2 diffuseMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -41,6 +40,7 @@ centroid varying vec3 shadowDiffuseLighting; varying vec3 passNormal; #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -149,8 +149,14 @@ void main(void) linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passNormal = rotation3(rotation) * gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw * rotation, passNormal); +#endif + #if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); #endif #if @diffuseMap @@ -159,10 +165,8 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw * rotation; #endif - passNormal = rotation3(rotation) * gl_Normal.xyz; #if PER_PIXEL_LIGHTING passViewPos = viewPos.xyz; #else diff --git a/files/shaders/compatibility/normals.glsl b/files/shaders/compatibility/normals.glsl new file mode 100644 index 0000000000..8df16a4b12 --- /dev/null +++ b/files/shaders/compatibility/normals.glsl @@ -0,0 +1,14 @@ +varying mat3 normalToViewMatrix; + +mat3 generateTangentSpace(vec4 tangent, vec3 normal) +{ + vec3 normalizedNormal = normalize(normal); + vec3 normalizedTangent = normalize(tangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * tangent.w; + return mat3(normalizedTangent, binormal, normalizedNormal); +} + +vec3 normalToView(vec3 normal) +{ + return normalize(normalToViewMatrix * normal); +} diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 6de1f6d02f..dd9c3e5f3b 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -37,7 +37,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -79,6 +78,9 @@ uniform float emissiveMult; uniform float specStrength; varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #if @additiveBlending #define ADDITIVE_BLENDING @@ -91,6 +93,7 @@ varying vec3 passNormal; #include "fog.glsl" #include "vertexcolors.glsl" #include "shadows_fragment.glsl" +#include "compatibility/normals.glsl" #if @softParticles #include "lib/particle/soft.glsl" @@ -113,62 +116,32 @@ void main() applyOcclusionDiscard(orthoDepthMapCoord, texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r); #endif - vec3 normal = normalize(passNormal); - vec3 viewVec = normalize(passViewPos.xyz); + // only offset diffuse and normal maps for now, other textures are more likely to be using a completely different UV set + vec2 offset = vec2(0.0); -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - -#if !@diffuseMap - gl_FragData[0] = vec4(1.0); -#else - vec2 adjustedDiffuseUV = diffuseMapUV; - -#if @normalMap && (@parallax || @diffuseParallax) - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); +#if @parallax || @diffuseParallax #if @parallax - float height = normalTex.a; + float height = texture2D(normalMap, normalMapUV).a; float flipY = (passTangent.w > 0.0) ? -1.f : 1.f; #else float height = texture2D(diffuseMap, diffuseMapUV).a; // FIXME: shouldn't be necessary, but in this path false-positives are common float flipY = -1.f; #endif - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, height, flipY); - adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set - - // TODO: check not working as the same UV buffer is being bound to different targets - // if diffuseMapUV == normalMapUV -#if 1 - // fetch a new normal using updated coordinates - normalTex = texture2D(normalMap, adjustedDiffuseUV); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif -#endif - - vec4 diffuseTex = texture2D(diffuseMap, adjustedDiffuseUV); - gl_FragData[0].xyz = diffuseTex.xyz; -#if !@diffuseParallax - gl_FragData[0].a = diffuseTex.a * coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); -#else +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); +#if @diffuseParallax gl_FragData[0].a = 1.0; +#else + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV + offset); #endif +#else + gl_FragData[0] = vec4(1.0); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseColor = getDiffuseColor(); gl_FragData[0].a *= diffuseColor.a; @@ -179,6 +152,14 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + + vec3 viewVec = normalize(passViewPos); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 1ea4a1553f..1ec0917ea8 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -31,7 +31,6 @@ varying vec2 emissiveMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -59,9 +58,13 @@ uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -84,9 +87,18 @@ void main(void) vec4 viewPos = modelToView(gl_Vertex); gl_ClipVertex = viewPos; + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; -#if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); +#if @normalMap || @diffuseParallax + passTangent = gl_MultiTexCoord7.xyzw; + normalToViewMatrix *= generateTangentSpace(passTangent, passNormal); +#endif + +#if @envMap || !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); #endif #if @envMap @@ -118,7 +130,6 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif #if @bumpMap @@ -133,10 +144,6 @@ void main(void) glossMapUV = (gl_TextureMatrix[@glossMapUV] * gl_MultiTexCoord@glossMapUV).xy; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; - #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 744a56d18b..734a358590 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -40,49 +40,31 @@ uniform float far; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, adjustedUV); - - vec3 normalizedNormal = normal; - vec3 tangent = vec3(1.0, 0.0, 0.0); - vec3 binormal = normalize(cross(tangent, normalizedNormal)); - tangent = normalize(cross(normalizedNormal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal - mat3 tbnTranspose = mat3(tangent, binormal, normalizedNormal); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); -#endif - #if @parallax - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); - adjustedUV += getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, 1.f); - - // update normal using new coordinates - normalTex = texture2D(normalMap, adjustedUV); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); + adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f); #endif - - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + #if @blendMap vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index 5e154d912a..f74bc1a95f 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -24,6 +24,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -37,13 +38,20 @@ void main(void) euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); -#if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); -#endif - passColor = gl_Color; passNormal = gl_Normal.xyz; passViewPos = viewPos.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal); + tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + normalToViewMatrix *= tbnMatrix; +#endif + +#if !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); +#endif #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; diff --git a/files/shaders/lib/material/parallax.glsl b/files/shaders/lib/material/parallax.glsl index 7f4ce2d1dc..5525281f75 100644 --- a/files/shaders/lib/material/parallax.glsl +++ b/files/shaders/lib/material/parallax.glsl @@ -4,10 +4,9 @@ #define PARALLAX_SCALE 0.04 #define PARALLAX_BIAS -0.02 -vec2 getParallaxOffset(vec3 eyeDir, mat3 tbnTranspose, float height, float flipY) +vec2 getParallaxOffset(vec3 eyeDir, float height, float flipY) { - vec3 TSeyeDir = normalize(eyeDir * tbnTranspose); - return vec2(TSeyeDir.x, TSeyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); + return vec2(eyeDir.x, eyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); } -#endif \ No newline at end of file +#endif From f80cd06256d860436ea382a67779662d2d3ba48c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 12 Dec 2023 22:06:52 +0100 Subject: [PATCH 0599/2167] Don't count the actor we're following as siding with us if we're in combat with them but they aren't in combat with us --- apps/openmw/mwmechanics/actors.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 73bd331de2..3efed1d540 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2090,6 +2090,10 @@ namespace MWMechanics if (ally.getClass().getCreatureStats(ally).getAiSequence().getCombatTargets(enemies) && std::find(enemies.begin(), enemies.end(), actorPtr) != enemies.end()) break; + enemies.clear(); + if (actorPtr.getClass().getCreatureStats(actorPtr).getAiSequence().getCombatTargets(enemies) + && std::find(enemies.begin(), enemies.end(), ally) != enemies.end()) + break; } list.push_back(package->getTarget()); } From a0694d4134566e5db0f5ba0a03bfdb557c4f514c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 12 Dec 2023 22:11:32 +0100 Subject: [PATCH 0600/2167] Don't assert that spells have a school --- apps/openmw/mwmechanics/autocalcspell.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 6581aacdd2..a2f6d479f1 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -74,7 +74,8 @@ namespace MWMechanics ESM::RefId school; float skillTerm; calcWeakestSchool(&spell, actorSkills, school, skillTerm); - assert(!school.empty()); + if (school.empty()) + continue; SchoolCaps& cap = schoolCaps[school]; if (cap.mReachedLimit && spellCost <= cap.mMinCost) From 5b02b77a392c4f5079fc7ba4aeda3390a597522c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Dec 2023 20:02:26 +0300 Subject: [PATCH 0601/2167] Base AiFollow activation range on follow distance (bug #7685) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aifollow.cpp | 32 +++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..cb3abc7315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders + Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index b78c1fd6ee..5c07c18166 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -119,21 +119,6 @@ namespace MWMechanics const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f targetDir = targetPos - actorPos; - // AiFollow requires the target to be in range and within sight for the initial activation - if (!mActive) - { - storage.mTimer -= duration; - - if (storage.mTimer < 0) - { - if (targetDir.length2() < 500 * 500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) - mActive = true; - storage.mTimer = 0.5f; - } - } - if (!mActive) - return false; - // In the original engine the first follower stays closer to the player than any subsequent followers. // Followers beyond the first usually attempt to stand inside each other. osg::Vec3f::value_type floatingDistance = 0; @@ -152,6 +137,23 @@ namespace MWMechanics floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); + // AiFollow requires the target to be in range and within sight for the initial activation + if (!mActive) + { + storage.mTimer -= duration; + + if (storage.mTimer < 0) + { + float activeRange = followDistance + 384.f; + if (targetDir.length2() < activeRange * activeRange + && MWBase::Environment::get().getWorld()->getLOS(actor, target)) + mActive = true; + storage.mTimer = 0.5f; + } + } + if (!mActive) + return false; + if (!mAlwaysFollow) // Update if you only follow for a bit { // Check if we've run out of time From 2628b02b4e05c3af7438af7e54948a334009e812 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Dec 2023 20:59:31 +0300 Subject: [PATCH 0602/2167] NpcAnimation: Assign parent animation time sources to body part controllers (bug #4822) --- CHANGELOG.md | 1 + apps/openmw/mwrender/actoranimation.cpp | 5 ++--- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..6364d0fb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached + Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5066: Quirks with starting and stopping scripted animations diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 600ae6f0ed..390ac12ea7 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -315,6 +315,7 @@ namespace MWRender if (node == nullptr) return; + // This is used to avoid playing animations intended for equipped weapons on holstered weapons. SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared()); node->accept(removeVisitor); } @@ -346,9 +347,7 @@ namespace MWRender if (mesh.empty() || boneName.empty()) return; - // If the scabbard is not found, use a weapon mesh as fallback. - // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. - // We use the similar approach for other bodyparts. + // If the scabbard is not found, use the weapon mesh as fallback. scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7767520715..040ba320a1 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -168,7 +168,7 @@ namespace MWRender if (slot == MWWorld::InventoryStore::Slot_CarriedRight) source = mWeaponAnimationTime; else - source = std::make_shared(); + source = mAnimationTimePtr[0]; SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); attached->accept(assignVisitor); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..1b599fb0c4 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -851,7 +851,7 @@ namespace MWRender if (type == ESM::PRT_Weapon) src = mWeaponAnimationTime; else - src = std::make_shared(); + src = mAnimationTimePtr[0]; SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); node->accept(assignVisitor); } From 2a747529bb9e308ddca1143c730db105b0efe809 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:44:32 -0600 Subject: [PATCH 0603/2167] Feat(CS): Add new shortcut for duplicating instances --- apps/opencs/model/prefs/state.cpp | 1 + apps/opencs/model/prefs/values.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 364f7b3320..5838afb2c3 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -410,6 +410,7 @@ void CSMPrefs::State::declare() declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); + declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); declareSubcategory("1st/Free Camera"); declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 247c025e80..4683258e57 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -464,6 +464,7 @@ namespace CSMPrefs "scene-instance-drop-terrain-separately", "" }; Settings::SettingValue mSceneInstanceDropCollisionSeparately{ mIndex, sName, "scene-instance-drop-collision-separately", "" }; + Settings::SettingValue mSceneDuplicate{ mIndex, sName, "scene-duplicate", "Shift+C" }; Settings::SettingValue mSceneLoadCamCell{ mIndex, sName, "scene-load-cam-cell", "Keypad+5" }; Settings::SettingValue mSceneLoadCamEastcell{ mIndex, sName, "scene-load-cam-eastcell", "Keypad+6" }; From 2bb8ceef5655ab898bf3b50815d2f02677f93520 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:59:13 -0600 Subject: [PATCH 0604/2167] Fix(CS): Correct invalid refNum for cloned objects so they actually appear ingame --- apps/opencs/model/world/refcollection.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 1afa9027a9..642edcfb64 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -297,6 +297,7 @@ void CSMWorld::RefCollection::cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { auto copy = std::make_unique>(); + int index = getAppendIndex(ESM::RefId(), type); copy->mModified = getRecord(origin).get(); copy->mState = RecordBase::State_ModifiedOnly; @@ -304,6 +305,15 @@ void CSMWorld::RefCollection::cloneRecord( copy->get().mId = destination; copy->get().mIdNum = extractIdNum(destination.getRefIdString()); + if (copy->get().mRefNum.mContentFile != 0) + { + mRefIndex.insert(std::make_pair(static_cast*>(copy.get())->get().mIdNum, index)); + copy->get().mRefNum.mContentFile = 0; + copy->get().mRefNum.mIndex = index; + } + else + copy->get().mRefNum.mIndex = copy->get().mIdNum; + insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() } From 7069a970ae2390f815491d19f0ef922e747c1528 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:59:49 -0600 Subject: [PATCH 0605/2167] Feat(CS): Implement instance cloning --- apps/opencs/view/render/instancemode.cpp | 28 ++++++++++++++++++++++++ apps/opencs/view/render/instancemode.hpp | 1 + 2 files changed, 29 insertions(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index df5bb02332..f55bc9a8e8 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -205,12 +205,19 @@ CSVRender::InstanceMode::InstanceMode( connect( deleteShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); + CSMPrefs::Shortcut* duplicateShortcut = new CSMPrefs::Shortcut("scene-duplicate", worldspaceWidget); + + connect( + duplicateShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::cloneSelectedInstances); + // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and // Qt5.14 CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); + connect(dropToCollisionShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToCollision); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); connect(dropToTerrainLevelShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, @@ -1087,6 +1094,27 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection(Mask_Reference); } +void CSVRender::InstanceMode::cloneSelectedInstances() +{ + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + return; + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& referencesTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); + QUndoStack& undoStack = document.getUndoStack(); + + CSMWorld::CommandMacro macro(undoStack, "Clone Instances"); + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + macro.push(new CSMWorld::CloneCommand(referencesTable, objectTag->mObject->getReferenceId(), + "ref#" + std::to_string(referencesTable.rowCount()), CSMWorld::UniversalId::Type_Reference)); + } + // getWorldspaceWidget().clearSelection(Mask_Reference); +} + void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { object->setEdited(Object::Override_Position); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 5055d08d5b..67af7854ef 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -132,6 +132,7 @@ namespace CSVRender void subModeChanged(const std::string& id); void deleteSelectedInstances(bool active); + void cloneSelectedInstances(); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToCollisionSeparately(); From bc662aeb63ad928934e78da9f28bf4377ac04fea Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 16:06:12 -0600 Subject: [PATCH 0606/2167] Fix(CS): Fix minor issue in deleteSelectedInstances impl which caused it to run twice --- apps/opencs/view/render/instancemode.cpp | 5 ++--- apps/opencs/view/render/instancemode.hpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index f55bc9a8e8..7332d9c84b 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -202,8 +202,7 @@ CSVRender::InstanceMode::InstanceMode( connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); - connect( - deleteShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); + connect(deleteShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); CSMPrefs::Shortcut* duplicateShortcut = new CSMPrefs::Shortcut("scene-duplicate", worldspaceWidget); @@ -1075,7 +1074,7 @@ void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) mDragMode = DragMode_None; } -void CSVRender::InstanceMode::deleteSelectedInstances(bool active) +void CSVRender::InstanceMode::deleteSelectedInstances() { std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 67af7854ef..917fde301a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -131,7 +131,7 @@ namespace CSVRender private slots: void subModeChanged(const std::string& id); - void deleteSelectedInstances(bool active); + void deleteSelectedInstances(); void cloneSelectedInstances(); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); From 11db9eec1d0fd9676cb473e31af707f20b2bc7b1 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:23:06 +0100 Subject: [PATCH 0607/2167] Use settings values to declare modifier settings --- apps/opencs/model/prefs/modifiersetting.cpp | 2 +- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 2 +- apps/opencs/model/prefs/setting.hpp | 2 +- apps/opencs/model/prefs/shortcutmanager.cpp | 2 +- apps/opencs/model/prefs/shortcutmanager.hpp | 6 +++--- apps/opencs/model/prefs/state.cpp | 11 ++++++----- apps/opencs/model/prefs/state.hpp | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 53db4c8521..86a8dc6274 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -20,7 +20,7 @@ class QWidget; namespace CSMPrefs { ModifierSetting::ModifierSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index cf9ce8f149..76a3a82e71 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs public: explicit ModifierSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index cc5077dc83..3c2ac65c94 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -16,7 +16,7 @@ QMutex* CSMPrefs::Setting::getMutex() } CSMPrefs::Setting::Setting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f5d9e83ef1..0f4840f713 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -62,7 +62,7 @@ namespace CSMPrefs public: explicit Setting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); ~Setting() override = default; diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index a6f1da4f85..3089281c91 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -91,7 +91,7 @@ namespace CSMPrefs return false; } - void ShortcutManager::setModifier(const std::string& name, int modifier) + void ShortcutManager::setModifier(std::string_view name, int modifier) { // Add to map/modify ModifierMap::iterator item = mModifiers.find(name); diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp index fc8db3f2b0..87d2f2256c 100644 --- a/apps/opencs/model/prefs/shortcutmanager.hpp +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -32,7 +32,7 @@ namespace CSMPrefs void setSequence(const std::string& name, const QKeySequence& sequence); bool getModifier(const std::string& name, int& modifier) const; - void setModifier(const std::string& name, int modifier); + void setModifier(std::string_view name, int modifier); std::string convertToString(const QKeySequence& sequence) const; std::string convertToString(int modifier) const; @@ -49,9 +49,9 @@ namespace CSMPrefs private: // Need a multimap in case multiple shortcuts share the same name - typedef std::multimap ShortcutMap; + typedef std::multimap> ShortcutMap; typedef std::map SequenceMap; - typedef std::map ModifierMap; + typedef std::map> ModifierMap; typedef std::map NameMap; typedef std::map KeyMap; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 364f7b3320..2d511c3b50 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -396,7 +396,7 @@ void CSMPrefs::State::declare() "scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); declareShortcut( "scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton)); - declareModifier("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); + declareModifier(mValues->mKeyBindings.mSceneSpeedModifier, "Speed Modifier"); declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); @@ -553,7 +553,8 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( return *setting; } -CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const QString& label, int default_) +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier( + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -561,11 +562,11 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k // Setup with actual data int modifier; - getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), modifier); - getShortcutManager().setModifier(key, modifier); + getShortcutManager().convertFromString(value.get(), modifier); + getShortcutManager().setModifier(value.mName, modifier); CSMPrefs::ModifierSetting* setting - = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 018a81a8d9..5f82389c82 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -76,7 +76,7 @@ namespace CSMPrefs StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); - ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); + ModifierSetting& declareModifier(Settings::SettingValue& value, const QString& label); void declareSubcategory(const QString& label); From a29ae07957a0a80be8090f618778337698c6aa4b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Dec 2023 00:28:43 +0100 Subject: [PATCH 0608/2167] Fix CS Key Binding settings page This got broken by e07d8f3066. Creating QGridLayout with parent and setting it later has not the same effect. --- apps/opencs/model/prefs/boolsetting.cpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 2 +- apps/opencs/model/prefs/doublesetting.cpp | 2 +- apps/opencs/model/prefs/enumsetting.cpp | 2 +- apps/opencs/model/prefs/intsetting.cpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 2 +- apps/opencs/model/prefs/setting.hpp | 1 - apps/opencs/model/prefs/shortcutsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/subcategory.cpp | 5 +---- apps/opencs/view/prefs/keybindingpage.cpp | 6 +++--- 11 files changed, 12 insertions(+), 16 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index 2ca8315245..c7520c415d 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -36,7 +36,7 @@ CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged); - return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; } void CSMPrefs::BoolSetting::updateWidget() diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 61aa92063e..9e237dd7a8 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -41,7 +41,7 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::ColourSetting::updateWidget() diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index b275c2e9b7..679c8d6a92 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -73,7 +73,7 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::DoubleSetting::updateWidget() diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 1521c3cc13..938da874e1 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -106,7 +106,7 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::EnumSetting::updateWidget() diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 023339b9c1..54fd13233e 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -65,7 +65,7 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QSpinBox::valueChanged), this, &IntSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::IntSetting::updateWidget() diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 53db4c8521..99caedb2c8 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -47,7 +47,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled); - return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = widget }; } void ModifierSetting::updateWidget() diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f5d9e83ef1..72b070b2fc 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -23,7 +23,6 @@ namespace CSMPrefs { QLabel* mLabel; QWidget* mInput; - QGridLayout* mLayout; }; class Setting : public QObject diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 42902f02cd..33bb521290 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -51,7 +51,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled); - return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = widget }; } void ShortcutSetting::updateWidget() diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index d70324ba56..56c46bb6af 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -36,7 +36,7 @@ CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; } void CSMPrefs::StringSetting::updateWidget() diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp index ac5b78ee2c..815025daec 100644 --- a/apps/opencs/model/prefs/subcategory.cpp +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -13,9 +13,6 @@ namespace CSMPrefs SettingWidgets Subcategory::makeWidgets(QWidget* /*parent*/) { - QGridLayout* const layout = new QGridLayout(); - layout->setSizeConstraint(QLayout::SetMinAndMaxSize); - - return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr, .mLayout = layout }; + return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr }; } } diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index b88627aca1..f292fa4cf5 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -81,12 +81,12 @@ namespace CSVPrefs int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.mInput, next, 0, 1, 2); } - else if (widgets.mLayout != nullptr) + else { // Create new page QWidget* pageWidget = new QWidget(); - mPageLayout = widgets.mLayout; - mPageLayout->setParent(pageWidget); + mPageLayout = new QGridLayout(pageWidget); + mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); mStackedLayout->addWidget(pageWidget); From 27bd70a976096671a1408a5760f326be1f1fd8a9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Dec 2023 15:32:34 +0300 Subject: [PATCH 0609/2167] For constant enchantments, allow on-self range for any effect (bug #7643) --- CHANGELOG.md | 1 + apps/openmw/mwgui/spellcreationdialog.cpp | 28 +++++------------------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..665a8fbc4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically + Bug #7643: Can't enchant items with constant effect on self magic effects for non-player character Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 1618f34a7a..bb97ccb82f 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -119,7 +119,7 @@ namespace MWGui void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) { - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; setMagicEffect(effect); @@ -240,7 +240,7 @@ namespace MWGui // cycle through range types until we find something that's allowed // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect // dialog) - bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (mEffect.mRange == ESM::RT_Self && !allowSelf) @@ -629,7 +629,7 @@ namespace MWGui const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; @@ -751,25 +751,9 @@ namespace MWGui void EffectEditorBase::setConstantEffect(bool constant) { mAddEffectDialog.setConstantEffect(constant); + if (!mConstantEffect && constant) + for (ESM::ENAMstruct& effect : mEffects) + effect.mRange = ESM::RT_Self; mConstantEffect = constant; - - if (!constant) - return; - - for (auto it = mEffects.begin(); it != mEffects.end();) - { - if (it->mRange != ESM::RT_Self) - { - auto& store = *MWBase::Environment::get().getESMStore(); - auto magicEffect = store.get().find(it->mEffectID); - if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0) - { - it = mEffects.erase(it); - continue; - } - it->mRange = ESM::RT_Self; - } - ++it; - } } } From d1274fd3db5bf117d48c0edce346a0412f7eec3d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 09:17:24 +0300 Subject: [PATCH 0610/2167] Deduplicate lambert calculations, consolidate doLighting --- files/shaders/lib/light/lighting.glsl | 77 +++++++++------------------ 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 8351fce8a0..689aee0911 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -3,15 +3,13 @@ #include "lighting_util.glsl" -void perLightSun(out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) +float calcLambert(vec3 viewNormal, vec3 lightDir, vec3 viewDir) { - vec3 lightDir = normalize(lcalcPosition(0)); - float lambert = dot(viewNormal.xyz, lightDir); - + float lambert = dot(viewNormal, lightDir); #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else - float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + float eyeCosine = dot(viewNormal, viewDir); if (lambert < 0.0) { lambert = -lambert; @@ -19,46 +17,7 @@ void perLightSun(out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - - diffuseOut = lcalcDiffuse(0).xyz * lambert; -} - -void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) -{ - vec3 lightPos = lcalcPosition(lightIndex) - viewPos; - float lightDistance = length(lightPos); - -// cull non-FFP point lighting by radius, light is guaranteed to not fall outside this bound with our cutoff -#if !@lightingMethodFFP - float radius = lcalcRadius(lightIndex); - - if (lightDistance > radius * 2.0) - { - ambientOut = vec3(0.0); - diffuseOut = vec3(0.0); - return; - } -#endif - - lightPos = normalize(lightPos); - - float illumination = lcalcIllumination(lightIndex, lightDistance); - ambientOut = lcalcAmbient(lightIndex) * illumination; - float lambert = dot(viewNormal.xyz, lightPos) * illumination; - -#ifndef GROUNDCOVER - lambert = max(lambert, 0.0); -#else - float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); - if (lambert < 0.0) - { - lambert = -lambert; - eyeCosine = -eyeCosine; - } - lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); -#endif - - diffuseOut = lcalcDiffuse(lightIndex) * lambert; + return lambert; } #if PER_PIXEL_LIGHTING @@ -67,26 +26,38 @@ void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuse void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse) #endif { - vec3 ambientOut, diffuseOut; + vec3 viewDir = normalize(viewPos); - perLightSun(diffuseOut, viewPos, viewNormal); + diffuseLight = lcalcDiffuse(0).xyz * calcLambert(viewNormal, normalize(lcalcPosition(0)), viewDir); ambientLight = gl_LightModel.ambient.xyz; #if PER_PIXEL_LIGHTING - diffuseLight = diffuseOut * shadowing; + diffuseLight *= shadowing; #else - shadowDiffuse = diffuseOut; + shadowDiffuse = diffuseLight; diffuseLight = vec3(0.0); #endif for (int i = @startLight; i < @endLight; ++i) { #if @lightingMethodUBO - perLightPoint(ambientOut, diffuseOut, PointLightIndex[i], viewPos, viewNormal); + int lightIndex = PointLightIndex[i]; #else - perLightPoint(ambientOut, diffuseOut, i, viewPos, viewNormal); + int lightIndex = i; #endif - ambientLight += ambientOut; - diffuseLight += diffuseOut; + vec3 lightPos = lcalcPosition(lightIndex) - viewPos; + float lightDistance = length(lightPos); + + // cull non-FFP point lighting by radius, light is guaranteed to not fall outside this bound with our cutoff +#if !@lightingMethodFFP + if (lightDistance > lcalcRadius(lightIndex) * 2.0) + continue; +#endif + + vec3 lightDir = lightPos / lightDistance; + + float illumination = lcalcIllumination(lightIndex, lightDistance); + ambientLight += lcalcAmbient(lightIndex) * illumination; + diffuseLight += lcalcDiffuse(lightIndex) * calcLambert(viewNormal, lightDir, viewDir) * illumination; } } From 93ea9dbc3bd809509cd32449d0d9e48a68a48681 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 10:23:10 +0300 Subject: [PATCH 0611/2167] Do all lighting calculations in one place, support per-vertex specularity Force PPL when specular maps are used --- files/shaders/compatibility/bs/default.frag | 22 +++--- files/shaders/compatibility/groundcover.frag | 4 +- files/shaders/compatibility/groundcover.vert | 5 +- files/shaders/compatibility/objects.frag | 43 ++++++------ files/shaders/compatibility/objects.vert | 14 ++-- files/shaders/compatibility/terrain.frag | 35 +++++----- files/shaders/compatibility/terrain.vert | 10 ++- files/shaders/lib/light/lighting.glsl | 70 +++++++------------- 8 files changed, 88 insertions(+), 115 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 7e2be9aa8f..70d9ef7ba7 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -61,34 +61,30 @@ void main() gl_FragData[0].a *= diffuseColor.a; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); + vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + specularColor *= normalTex.a; #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif float shadowing = unshadowedLightRatio(linearDepth); - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, gl_FrontMaterial.shininess, shadowing, diffuseLight, ambientLight, specularLight); + vec3 diffuse = diffuseColor.xyz * diffuseLight; + vec3 ambient = getAmbientColor().xyz * ambientLight; vec3 emission = getEmissionColor().xyz * emissiveMult; #if @emissiveMap emission *= texture2D(emissiveMap, emissiveMapUV).xyz; #endif - vec3 lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + vec3 lighting = diffuse + ambient + emission; + vec3 specular = specularColor * specularLight * specStrength; clampLightingResult(lighting); - gl_FragData[0].xyz *= lighting; - - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz * specStrength; -#if @normalMap - matSpec *= normalTex.a; -#endif - - if (matSpec != vec3(0.0)) - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index f87beaf447..dfdd6518c3 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -70,8 +70,8 @@ void main() #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, gl_FrontMaterial.shininess, shadowing, diffuseLight, ambientLight, specularLight); lighting = diffuseLight + ambientLight; #endif diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index c1bb35da05..95a17b5084 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -170,8 +170,9 @@ void main(void) #if PER_PIXEL_LIGHTING passViewPos = viewPos.xyz; #else - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + vec3 diffuseLight, ambientLight, specularLight; + vec3 unusedShadowSpecular; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, unusedShadowSpecular); passLighting = diffuseLight + ambientLight; clampLightingResult(passLighting); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index dd9c3e5f3b..80de6b0e9d 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -67,15 +67,17 @@ uniform float near; uniform float far; uniform float alphaRef; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #else uniform float emissiveMult; -#endif uniform float specStrength; +#endif varying vec3 passViewPos; varying vec3 passNormal; #if @normalMap || @diffuseParallax @@ -200,19 +202,27 @@ void main() #endif float shadowing = unshadowedLightRatio(-passViewPos.z); - vec3 lighting; + vec3 lighting, specular; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; + specular = passSpecular + shadowSpecularLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); - vec3 emission = getEmissionColor().xyz * emissiveMult; - lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; +#if @specularMap + vec4 specTex = texture2D(specularMap, specularMapUV); + float shininess = specTex.a * 255.0; + vec3 specularColor = specTex.xyz; +#else + float shininess = gl_FrontMaterial.shininess; + vec3 specularColor = getSpecularColor().xyz; +#endif + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz * emissiveMult; + specular = specularColor * specularLight * specStrength; #endif clampLightingResult(lighting); - - gl_FragData[0].xyz *= lighting; + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; #if @envMap && !@preLightEnv gl_FragData[0].xyz += envEffect; @@ -222,21 +232,6 @@ void main() gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; #endif -#if @specularMap - vec4 specTex = texture2D(specularMap, specularMapUV); - float shininess = specTex.a * 255.0; - vec3 matSpec = specTex.xyz; -#else - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz; -#endif - - matSpec *= specStrength; - if (matSpec != vec3(0.0)) - { - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); - } - gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); vec2 screenCoords = gl_FragCoord.xy / screenRes; diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 1ec0917ea8..2bebcd60bf 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -49,12 +49,15 @@ varying vec2 specularMapUV; varying vec2 glossMapUV; #endif -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; uniform float emissiveMult; +uniform float specStrength; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -145,12 +148,13 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); - vec3 emission = getEmissionColor().xyz * emissiveMult; - passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + vec3 diffuseLight, ambientLight, specularLight; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, shadowSpecularLighting); + passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz * emissiveMult; + passSpecular = getSpecularColor().xyz * specularLight * specStrength; clampLightingResult(passLighting); shadowDiffuseLighting *= getDiffuseColor().xyz; + shadowSpecularLighting *= getSpecularColor().xyz * specStrength; #endif #if (@shadows_enabled) diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 734a358590..38b985223e 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -23,11 +23,13 @@ uniform sampler2D blendMap; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -67,31 +69,26 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); - vec3 lighting; + vec3 lighting, specular; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; + specular = passSpecular + shadowSpecularLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); +#if @specularMap + float shininess = 128.0; // TODO: make configurable + vec3 specularColor = vec3(diffuseTex.a); +#else + float shininess = gl_FrontMaterial.shininess; + vec3 specularColor = getSpecularColor().xyz; +#endif + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + specular = specularColor * specularLight; #endif clampLightingResult(lighting); - - gl_FragData[0].xyz *= lighting; - -#if @specularMap - float shininess = 128.0; // TODO: make configurable - vec3 matSpec = vec3(diffuseTex.a); -#else - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz; -#endif - - if (matSpec != vec3(0.0)) - { - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); - } + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index f74bc1a95f..3b2cb16db4 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -13,11 +13,13 @@ varying vec2 uv; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -54,11 +56,13 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, shadowSpecularLighting); passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + passSpecular = getSpecularColor().xyz * specularLight; clampLightingResult(passLighting); shadowDiffuseLighting *= getDiffuseColor().xyz; + shadowSpecularLighting *= getSpecularColor().xyz; #endif uv = gl_MultiTexCoord0.xy; diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 689aee0911..8bb6ba148f 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -20,21 +20,39 @@ float calcLambert(vec3 viewNormal, vec3 lightDir, vec3 viewDir) return lambert; } +float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) +{ + if (dot(viewNormal, lightDir) > 0.0) + { + vec3 halfVec = normalize(lightDir - viewDir); + float NdotH = max(dot(viewNormal, halfVec), 0.0); + return pow(NdotH, shininess); + } + + return 0.0; +} + #if PER_PIXEL_LIGHTING -void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuseLight, out vec3 ambientLight) +void doLighting(vec3 viewPos, vec3 viewNormal, float shininess, float shadowing, out vec3 diffuseLight, out vec3 ambientLight, out vec3 specularLight) #else -void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse) +void doLighting(vec3 viewPos, vec3 viewNormal, float shininess, out vec3 diffuseLight, out vec3 ambientLight, out vec3 specularLight, out vec3 shadowDiffuse, out vec3 shadowSpecular) #endif { vec3 viewDir = normalize(viewPos); + shininess = max(shininess, 1e-4); - diffuseLight = lcalcDiffuse(0).xyz * calcLambert(viewNormal, normalize(lcalcPosition(0)), viewDir); + vec3 sunDir = normalize(lcalcPosition(0)); + diffuseLight = lcalcDiffuse(0) * calcLambert(viewNormal, sunDir, viewDir); ambientLight = gl_LightModel.ambient.xyz; + specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, sunDir); #if PER_PIXEL_LIGHTING diffuseLight *= shadowing; + specularLight *= shadowing; #else shadowDiffuse = diffuseLight; + shadowSpecular = specularLight; diffuseLight = vec3(0.0); + specularLight = vec3(0.0); #endif for (int i = @startLight; i < @endLight; ++i) @@ -56,52 +74,10 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a vec3 lightDir = lightPos / lightDistance; float illumination = lcalcIllumination(lightIndex, lightDistance); - ambientLight += lcalcAmbient(lightIndex) * illumination; diffuseLight += lcalcDiffuse(lightIndex) * calcLambert(viewNormal, lightDir, viewDir) * illumination; + ambientLight += lcalcAmbient(lightIndex) * illumination; + specularLight += lcalcSpecular(lightIndex).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, lightDir) * illumination; } } -float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) -{ - if (dot(viewNormal, lightDir) > 0.0) - { - vec3 halfVec = normalize(lightDir - viewDir); - float NdotH = max(dot(viewNormal, halfVec), 0.0); - return pow(NdotH, shininess); - } - - return 0.0; -} - -vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, float shadowing) -{ - shininess = max(shininess, 1e-4); - vec3 viewDir = normalize(viewPos); - vec3 specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lcalcPosition(0))); - specularLight *= shadowing; - - for (int i = @startLight; i < @endLight; ++i) - { -#if @lightingMethodUBO - int lightIndex = PointLightIndex[i]; -#else - int lightIndex = i; -#endif - - vec3 lightPos = lcalcPosition(lightIndex) - viewPos; - float lightDistance = length(lightPos); - -#if !@lightingMethodFFP - if (lightDistance > lcalcRadius(lightIndex) * 2.0) - continue; -#endif - - float illumination = lcalcIllumination(lightIndex, lightDistance); - float intensity = calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lightPos)); - specularLight += lcalcSpecular(lightIndex).xyz * intensity * illumination; - } - - return specularLight; -} - #endif From 82982bbc0513e6486bd9e247b4cb29a47ef51446 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 21:28:09 +0300 Subject: [PATCH 0612/2167] Outlaw vampires and werewolves (bugs #7723, #7724) --- CHANGELOG.md | 2 ++ apps/openmw/mwmechanics/actors.cpp | 35 ++++++++++++------- .../mwmechanics/mechanicsmanagerimp.cpp | 11 +++--- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3abc7315..a2d72644c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects + Bug #7723: Assaulting vampires and werewolves shouldn't be a crime + Bug #7724: Guards don't help vs werewolves Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 73bd331de2..e0baac0764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -707,10 +707,9 @@ namespace MWMechanics } } - // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter + // Make guards go aggressive with creatures and werewolves that are in combat const auto world = MWBase::Environment::get().getWorld(); - if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() - && creatureStats2.getAiSequence().isInCombat()) + if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far static const float fAlarmRadius @@ -718,20 +717,30 @@ namespace MWMechanics if (sqrDist > fAlarmRadius * fAlarmRadius) return; - bool followerOrEscorter = false; - for (const auto& package : creatureStats2.getAiSequence()) + bool targetIsCreature = !actor2.getClass().isNpc(); + if (targetIsCreature || actor2.getClass().getNpcStats(actor2).isWerewolf()) { - // The follow package must be first or have nothing but combat before it - if (package->sideWithTarget()) + bool followerOrEscorter = false; + // ...unless the creature has allies + if (targetIsCreature) { - followerOrEscorter = true; - break; + for (const auto& package : creatureStats2.getAiSequence()) + { + // The follow package must be first or have nothing but combat before it + if (package->sideWithTarget()) + { + followerOrEscorter = true; + break; + } + else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) + break; + } } - else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) - break; + // Morrowind also checks "known werewolf" flag, but the player is never in combat + // so this code is unreachable for the player + if (!followerOrEscorter) + aggressive = true; } - if (!followerOrEscorter) - aggressive = true; } // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f95df16855..0bb76add16 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1466,10 +1466,13 @@ namespace MWMechanics bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr& target, const MWWorld::Ptr& attacker) { - const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); - return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) - && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); + const MWWorld::Class& cls = target.getClass(); + const MWMechanics::CreatureStats& stats = cls.getCreatureStats(target); + const MWMechanics::AiSequence& seq = stats.getAiSequence(); + return cls.isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) + && !seq.isEngagedWithActor() && !stats.getAiSequence().isInPursuit() + && !cls.getNpcStats(target).isWerewolf() + && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude() <= 0; } void MechanicsManager::actorKilled(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) From a9e6e63c4ee2a2fda7f92558990d8fb09237e0a3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 13:00:14 +0100 Subject: [PATCH 0613/2167] Remove fixed size reads --- apps/esmtool/record.cpp | 26 ++++---- apps/opencs/model/tools/racecheck.cpp | 8 +-- apps/opencs/model/world/columnimp.hpp | 31 ++++++--- .../model/world/nestedcoladapterimp.cpp | 16 ++--- apps/openmw/mwclass/npc.cpp | 24 +++---- apps/openmw/mwmechanics/character.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 8 +-- apps/openmw/mwworld/projectilemanager.cpp | 2 +- components/esm/defs.hpp | 6 +- components/esm3/cellref.cpp | 4 +- components/esm3/esmreader.cpp | 5 +- components/esm3/esmreader.hpp | 2 +- components/esm3/loadfact.cpp | 47 ++++++++++++-- components/esm3/loadfact.hpp | 6 ++ components/esm3/loadrace.cpp | 64 ++++++++++++++++--- components/esm3/loadrace.hpp | 24 +++---- components/esm3/loadtes3.cpp | 3 +- components/esm3/loadtes3.hpp | 5 -- components/esm3/player.cpp | 4 +- components/esm3/projectilestate.cpp | 6 +- components/esm3/savedgame.cpp | 2 +- 21 files changed, 190 insertions(+), 105 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 96c418c0c4..71158299aa 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1169,19 +1169,23 @@ namespace EsmTool std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl; - for (int i = 0; i < 2; ++i) + std::cout << " Male:" << std::endl; + for (int j = 0; j < ESM::Attribute::Length; ++j) { - bool male = i == 0; - - std::cout << (male ? " Male:" : " Female:") << std::endl; - - for (int j = 0; j < ESM::Attribute::Length; ++j) - std::cout << " " << ESM::Attribute::indexToRefId(j) << ": " - << mData.mData.mAttributeValues[j].getValue(male) << std::endl; - - std::cout << " Height: " << mData.mData.mHeight.getValue(male) << std::endl; - std::cout << " Weight: " << mData.mData.mWeight.getValue(male) << std::endl; + ESM::RefId id = ESM::Attribute::indexToRefId(j); + std::cout << " " << id << ": " << mData.mData.getAttribute(id, true) << std::endl; } + std::cout << " Height: " << mData.mData.mMaleHeight << std::endl; + std::cout << " Weight: " << mData.mData.mMaleWeight << std::endl; + + std::cout << " Female:" << std::endl; + for (int j = 0; j < ESM::Attribute::Length; ++j) + { + ESM::RefId id = ESM::Attribute::indexToRefId(j); + std::cout << " " << id << ": " << mData.mData.getAttribute(id, false) << std::endl; + } + std::cout << " Height: " << mData.mData.mFemaleHeight << std::endl; + std::cout << " Weight: " << mData.mData.mFemaleWeight << std::endl; for (const auto& bonus : mData.mData.mBonus) // Not all races have 7 skills. diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 78f72f44c5..8f0df823c3 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -41,17 +41,17 @@ void CSMTools::RaceCheckStage::performPerRecord(int stage, CSMDoc::Messages& mes messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); // test for positive height - if (race.mData.mHeight.mMale <= 0) + if (race.mData.mMaleHeight <= 0) messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error); - if (race.mData.mHeight.mFemale <= 0) + if (race.mData.mFemaleHeight <= 0) messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error); // test for non-negative weight - if (race.mData.mWeight.mMale < 0) + if (race.mData.mMaleWeight < 0) messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error); - if (race.mData.mWeight.mFemale < 0) + if (race.mData.mFemaleWeight < 0) messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 5e5ff83fcf..da805d5c6d 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -570,19 +570,34 @@ namespace CSMWorld QVariant get(const Record& record) const override { - const ESM::Race::MaleFemaleF& value = mWeight ? record.get().mData.mWeight : record.get().mData.mHeight; - - return mMale ? value.mMale : value.mFemale; + if (mWeight) + { + if (mMale) + return record.get().mData.mMaleWeight; + return record.get().mData.mFemaleWeight; + } + if (mMale) + return record.get().mData.mMaleHeight; + return record.get().mData.mFemaleHeight; } void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - - ESM::Race::MaleFemaleF& value = mWeight ? record2.mData.mWeight : record2.mData.mHeight; - - (mMale ? value.mMale : value.mFemale) = data.toFloat(); - + if (mWeight) + { + if (mMale) + record2.mData.mMaleWeight = data.toFloat(); + else + record2.mData.mFemaleWeight = data.toFloat(); + } + else + { + if (mMale) + record2.mData.mMaleHeight = data.toFloat(); + else + record2.mData.mFemaleHeight = data.toFloat(); + } record.setModified(record2); } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b96cf46465..13ae821a77 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -741,8 +741,8 @@ namespace CSMWorld QVariant RaceAttributeAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - - if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) + ESM::RefId attribute = ESM::Attribute::indexToRefId(subRowIndex); + if (attribute.empty()) throw std::runtime_error("index out of range"); switch (subColIndex) @@ -750,9 +750,9 @@ namespace CSMWorld case 0: return subRowIndex; case 1: - return race.mData.mAttributeValues[subRowIndex].mMale; + return race.mData.getAttribute(attribute, true); case 2: - return race.mData.mAttributeValues[subRowIndex].mFemale; + return race.mData.getAttribute(attribute, false); default: throw std::runtime_error("Race Attribute subcolumn index out of range"); } @@ -762,8 +762,8 @@ namespace CSMWorld Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - - if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) + ESM::RefId attribute = ESM::Attribute::indexToRefId(subRowIndex); + if (attribute.empty()) throw std::runtime_error("index out of range"); switch (subColIndex) @@ -771,10 +771,10 @@ namespace CSMWorld case 0: return; // throw an exception here? case 1: - race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); + race.mData.setAttribute(attribute, true, value.toInt()); break; case 2: - race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); + race.mData.setAttribute(attribute, false, value.toInt()); break; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 95a3b713fa..c6276753de 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -92,13 +92,7 @@ namespace const auto& attributes = MWBase::Environment::get().getESMStore()->get(); int level = creatureStats.getLevel(); for (const ESM::Attribute& attribute : attributes) - { - auto index = ESM::Attribute::refIdToIndex(attribute.mId); - assert(index >= 0); - - const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; - creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); - } + creatureStats.setAttribute(attribute.mId, race->mData.getAttribute(attribute.mId, male)); // class bonus const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); @@ -1199,24 +1193,24 @@ namespace MWClass if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) { if (ref->mBase->isMale()) - scale *= race->mData.mHeight.mMale; + scale *= race->mData.mMaleHeight; else - scale *= race->mData.mHeight.mFemale; + scale *= race->mData.mFemaleHeight; return; } if (ref->mBase->isMale()) { - scale.x() *= race->mData.mWeight.mMale; - scale.y() *= race->mData.mWeight.mMale; - scale.z() *= race->mData.mHeight.mMale; + scale.x() *= race->mData.mMaleWeight; + scale.y() *= race->mData.mMaleWeight; + scale.z() *= race->mData.mMaleHeight; } else { - scale.x() *= race->mData.mWeight.mFemale; - scale.y() *= race->mData.mWeight.mFemale; - scale.z() *= race->mData.mHeight.mFemale; + scale.x() *= race->mData.mFemaleWeight; + scale.y() *= race->mData.mFemaleWeight; + scale.z() *= race->mData.mFemaleHeight; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 713add719b..3d9f4352ec 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1966,7 +1966,7 @@ namespace MWMechanics { const ESM::NPC* npc = mPtr.get()->mBase; const ESM::Race* race = world->getStore().get().find(npc->mRace); - float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + float weight = npc->isMale() ? race->mData.mMaleWeight : race->mData.mFemaleWeight; scale *= weight; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f95df16855..9a5b09ffe2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -152,13 +152,7 @@ namespace MWMechanics bool male = (player->mFlags & ESM::NPC::Female) == 0; for (const ESM::Attribute& attribute : esmStore.get()) - { - auto index = ESM::Attribute::refIdToIndex(attribute.mId); - assert(index >= 0); - - const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; - creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); - } + creatureStats.setAttribute(attribute.mId, race->mData.getAttribute(attribute.mId, male)); for (const ESM::Skill& skill : esmStore.get()) { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d873f16a59..b36d2a3221 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -453,7 +453,7 @@ namespace MWWorld { const auto npc = caster.get()->mBase; const auto race = store.get().find(npc->mRace); - speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + speed *= npc->isMale() ? race->mData.mMaleWeight : race->mData.mFemaleWeight; } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 55404ee768..6254830f63 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -18,9 +18,9 @@ namespace ESM struct EpochTimeStamp { float mGameHour; - int mDay; - int mMonth; - int mYear; + int32_t mDay; + int32_t mMonth; + int32_t mYear; }; // Pixel color value. Standard four-byte rr,gg,bb,aa format. diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index c4c2fca986..42edec8f1f 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -116,7 +116,7 @@ namespace ESM cellRef.mTeleport = true; } else - esm.skipHTSized<24, ESM::Position>(); + esm.skipHSub(); break; case fourCC("DNAM"): getHStringOrSkip(cellRef.mDestCell); @@ -134,7 +134,7 @@ namespace ESM if constexpr (load) esm.getHT(cellRef.mPos.pos, cellRef.mPos.rot); else - esm.skipHTSized<24, decltype(cellRef.mPos)>(); + esm.skipHSub(); break; case fourCC("NAM0"): { diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 77468f22d5..92a04fb487 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -263,7 +263,7 @@ namespace ESM { FormId res; if (wide) - getHNTSized<8>(res, tag); + getHNT(tag, res.mIndex, res.mContentFile); else getHNT(res.mIndex, tag); return res; @@ -496,7 +496,8 @@ namespace ESM case RefIdType::FormId: { FormId formId{}; - getTSized<8>(formId); + getT(formId.mIndex); + getT(formId.mContentFile); if (applyContentFileMapping(formId)) return RefId(formId); else diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 63db1634fc..53af8f69e6 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -333,7 +333,7 @@ namespace ESM mEsm->read(static_cast(x), static_cast(size)); } - void getName(NAME& name) { getTSized<4>(name); } + void getName(NAME& name) { getT(name.mData); } void getUint(uint32_t& u) { getT(u); } std::string getMaybeFixedStringSize(std::size_t size); diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 9d4b832f97..1dd1fe1a0e 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -17,6 +17,47 @@ namespace ESM return mSkills.at(index); } + void RankData::load(ESMReader& esm) + { + esm.getT(mAttribute1); + esm.getT(mAttribute2); + esm.getT(mPrimarySkill); + esm.getT(mFavouredSkill); + esm.getT(mFactReaction); + } + + void RankData::save(ESMWriter& esm) const + { + esm.writeT(mAttribute1); + esm.writeT(mAttribute2); + esm.writeT(mPrimarySkill); + esm.writeT(mFavouredSkill); + esm.writeT(mFactReaction); + } + + void Faction::FADTstruct::load(ESMReader& esm) + { + esm.getSubHeader(); + esm.getT(mAttribute); + for (auto& rank : mRankData) + rank.load(esm); + esm.getT(mSkills); + esm.getT(mIsHidden); + if (mIsHidden > 1) + esm.fail("Unknown flag!"); + } + + void Faction::FADTstruct::save(ESMWriter& esm) const + { + esm.startSubRecord("FADT"); + esm.writeT(mAttribute); + for (const auto& rank : mRankData) + rank.save(esm); + esm.writeT(mSkills); + esm.writeT(mIsHidden); + esm.endRecord("FADT"); + } + void Faction::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -47,9 +88,7 @@ namespace ESM mRanks[rankCounter++] = esm.getHString(); break; case fourCC("FADT"): - esm.getHTSized<240>(mData); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); + mData.load(esm); hasData = true; break; case fourCC("ANAM"): @@ -101,7 +140,7 @@ namespace ESM esm.writeHNString("RNAM", rank, 32); } - esm.writeHNT("FADT", mData, 240); + mData.save(esm); for (auto it = mReactions.begin(); it != mReactions.end(); ++it) { diff --git a/components/esm3/loadfact.hpp b/components/esm3/loadfact.hpp index 2359d276a2..eef2126514 100644 --- a/components/esm3/loadfact.hpp +++ b/components/esm3/loadfact.hpp @@ -30,6 +30,9 @@ namespace ESM int32_t mPrimarySkill, mFavouredSkill; int32_t mFactReaction; // Reaction from faction members + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; }; struct Faction @@ -60,6 +63,9 @@ namespace ESM int32_t getSkill(size_t index, bool ignored = false) const; ///< Throws an exception for invalid values of \a index. + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; }; // 240 bytes FADTstruct mData; diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index eb8faf40e9..8c7b89d07d 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -3,16 +3,61 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { - int Race::MaleFemale::getValue(bool male) const + int32_t Race::RADTstruct::getAttribute(ESM::RefId attribute, bool male) const { - return male ? mMale : mFemale; + int index = ESM::Attribute::refIdToIndex(attribute); + if (index < 0) + return 0; + if (!male) + index++; + return mAttributeValues[static_cast(index)]; } - float Race::MaleFemaleF::getValue(bool male) const + void Race::RADTstruct::setAttribute(ESM::RefId attribute, bool male, int32_t value) { - return male ? mMale : mFemale; + int index = ESM::Attribute::refIdToIndex(attribute); + if (index < 0) + return; + if (!male) + index++; + mAttributeValues[static_cast(index)] = value; + } + + void Race::RADTstruct::load(ESMReader& esm) + { + esm.getSubHeader(); + for (auto& bonus : mBonus) + { + esm.getT(bonus.mSkill); + esm.getT(bonus.mBonus); + } + esm.getT(mAttributeValues); + esm.getT(mMaleHeight); + esm.getT(mFemaleHeight); + esm.getT(mMaleWeight); + esm.getT(mFemaleWeight); + esm.getT(mFlags); + } + + void Race::RADTstruct::save(ESMWriter& esm) const + { + esm.startSubRecord("RADT"); + for (const auto& bonus : mBonus) + { + esm.writeT(bonus.mSkill); + esm.writeT(bonus.mBonus); + } + esm.writeT(mAttributeValues); + esm.writeT(mMaleHeight); + esm.writeT(mFemaleHeight); + esm.writeT(mMaleWeight); + esm.writeT(mFemaleWeight); + esm.writeT(mFlags); + esm.endRecord("RADT"); } void Race::load(ESMReader& esm, bool& isDeleted) @@ -37,7 +82,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RADT"): - esm.getHTSized<140>(mData); + mData.load(esm); hasData = true; break; case fourCC("DESC"): @@ -71,7 +116,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); + mData.save(esm); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); } @@ -90,11 +135,10 @@ namespace ESM bonus.mBonus = 0; } - for (auto& attribute : mData.mAttributeValues) - attribute.mMale = attribute.mFemale = 1; + mData.mAttributeValues.fill(1); - mData.mHeight.mMale = mData.mHeight.mFemale = 1; - mData.mWeight.mMale = mData.mWeight.mFemale = 1; + mData.mMaleHeight = mData.mFemaleHeight = 1; + mData.mMaleWeight = mData.mFemaleWeight = 1; mData.mFlags = 0; } diff --git a/components/esm3/loadrace.hpp b/components/esm3/loadrace.hpp index 8cb9d76118..4493240ac8 100644 --- a/components/esm3/loadrace.hpp +++ b/components/esm3/loadrace.hpp @@ -31,20 +31,6 @@ namespace ESM int32_t mBonus; }; - struct MaleFemale - { - int32_t mMale, mFemale; - - int getValue(bool male) const; - }; - - struct MaleFemaleF - { - float mMale, mFemale; - - float getValue(bool male) const; - }; - enum Flags { Playable = 0x01, @@ -57,14 +43,20 @@ namespace ESM std::array mBonus; // Attribute values for male/female - std::array mAttributeValues; + std::array mAttributeValues; // The actual eye level height (in game units) is (probably) given // as 'height' times 128. This has not been tested yet. - MaleFemaleF mHeight, mWeight; + float mMaleHeight, mFemaleHeight, mMaleWeight, mFemaleWeight; int32_t mFlags; // 0x1 - playable, 0x2 - beast race + int32_t getAttribute(ESM::RefId attribute, bool male) const; + void setAttribute(ESM::RefId attribute, bool male, int32_t value); + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; + }; // Size = 140 bytes RADTstruct mData; diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index 86b62234da..131510ce89 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -45,7 +45,8 @@ namespace ESM if (esm.isNextSub("GMDT")) { - esm.getHTSized<124>(mGameData); + esm.getHT(mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, + mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); } if (esm.isNextSub("SCRD")) { diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 8b14d41645..2f7493e15f 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -11,9 +11,6 @@ namespace ESM class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - struct Data { /* File format version. This is actually a float, the supported @@ -38,8 +35,6 @@ namespace ESM NAME32 mPlayerName; }; -#pragma pack(pop) - /// \brief File header record struct Header { diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 3b52f8d779..901b52ce1d 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -13,12 +13,12 @@ namespace ESM mCellId = esm.getCellId(); - esm.getHNTSized<12>(mLastKnownExteriorPosition, "LKEP"); + esm.getHNT("LKEP", mLastKnownExteriorPosition); if (esm.isNextSub("MARK")) { mHasMark = true; - esm.getHTSized<24>(mMarkedPosition); + esm.getHT(mMarkedPosition.pos, mMarkedPosition.rot); mMarkedCell = esm.getCellId(); } else diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index bed9073999..e20cefa882 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -17,8 +17,8 @@ namespace ESM void BaseProjectileState::load(ESMReader& esm) { mId = esm.getHNRefId("ID__"); - esm.getHNTSized<12>(mPosition, "VEC3"); - esm.getHNTSized<16>(mOrientation, "QUAT"); + esm.getHNT("VEC3", mPosition.mValues); + esm.getHNT("QUAT", mOrientation.mValues); esm.getHNT(mActorId, "ACTO"); } @@ -58,7 +58,7 @@ namespace ESM BaseProjectileState::load(esm); mBowId = esm.getHNRefId("BOW_"); - esm.getHNTSized<12>(mVelocity, "VEL_"); + esm.getHNT("VEL_", mVelocity.mValues); mAttackStrength = 1.f; esm.getHNOT(mAttackStrength, "STR_"); diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index cec2b5e189..3ffe062d76 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -17,7 +17,7 @@ namespace ESM mPlayerCellName = esm.getHNRefId("PLCE").toString(); else mPlayerCellName = esm.getHNString("PLCE"); - esm.getHNTSized<16>(mInGameTime, "TSTM"); + esm.getHNT("TSTM", mInGameTime.mGameHour, mInGameTime.mDay, mInGameTime.mMonth, mInGameTime.mYear); esm.getHNT(mTimePlayed, "TIME"); mDescription = esm.getHNString("DESC"); From dbf9d42cc5ce3f3f782a47daa98a80c7b959d470 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 14:03:45 +0100 Subject: [PATCH 0614/2167] Remove sized reads from essimporter --- apps/essimporter/converter.cpp | 8 ++++---- apps/essimporter/importacdt.hpp | 7 ++----- apps/essimporter/importcellref.cpp | 19 +++++++++++++------ apps/essimporter/importcntc.hpp | 2 +- apps/essimporter/importcrec.hpp | 2 +- apps/essimporter/importdial.cpp | 4 ++-- apps/essimporter/importdial.hpp | 2 +- apps/essimporter/importgame.cpp | 22 +++++++++++----------- apps/essimporter/importgame.hpp | 8 ++++---- apps/essimporter/importinventory.cpp | 10 +++++----- apps/essimporter/importinventory.hpp | 6 +++--- apps/essimporter/importklst.cpp | 2 +- apps/essimporter/importklst.hpp | 4 ++-- apps/essimporter/importnpcc.cpp | 2 +- apps/essimporter/importnpcc.hpp | 2 +- apps/essimporter/importplayer.cpp | 14 ++++++++++---- apps/essimporter/importplayer.hpp | 27 ++++++++++++--------------- apps/essimporter/importproj.cpp | 4 +++- apps/essimporter/importproj.h | 5 +---- apps/essimporter/importscpt.cpp | 3 ++- apps/essimporter/importscpt.hpp | 2 +- apps/essimporter/importscri.cpp | 6 +++--- apps/essimporter/importsplm.cpp | 6 ++++-- apps/essimporter/importsplm.h | 16 ++++++---------- 24 files changed, 94 insertions(+), 89 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4751fd9497..00c24514ea 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -90,14 +90,14 @@ namespace ESSImport struct MAPH { - unsigned int size; - unsigned int value; + uint32_t size; + uint32_t value; }; void ConvertFMAP::read(ESM::ESMReader& esm) { MAPH maph; - esm.getHNTSized<8>(maph, "MAPH"); + esm.getHNT("MAPH", maph.size, maph.value); std::vector data; esm.getSubNameIs("MAPD"); esm.getSubHeader(); @@ -278,7 +278,7 @@ namespace ESSImport while (esm.isNextSub("MPCD")) { float notepos[3]; - esm.getHTSized<3 * sizeof(float)>(notepos); + esm.getHT(notepos); // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. // This seems to be the reason markers can't be placed everywhere in interior cells, diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 785e988200..54910ac82c 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -25,14 +25,12 @@ namespace ESSImport }; /// Actor data, shared by (at least) REFR and CellRef -#pragma pack(push) -#pragma pack(1) struct ACDT { // Note, not stored at *all*: // - Level changes are lost on reload, except for the player (there it's in the NPC record). unsigned char mUnknown[12]; - unsigned int mFlags; + uint32_t mFlags; float mBreathMeter; // Seconds left before drowning unsigned char mUnknown2[20]; float mDynamic[3][2]; @@ -41,7 +39,7 @@ namespace ESSImport float mMagicEffects[27]; // Effect attributes: // https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes unsigned char mUnknown4[4]; - unsigned int mGoldPool; + uint32_t mGoldPool; unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe // this one is for respawning? unsigned char mUnknown5[3]; @@ -60,7 +58,6 @@ namespace ESSImport unsigned char mUnknown[3]; float mTime; }; -#pragma pack(pop) struct ActorData { diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index a900440a96..d9639eb0dd 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -48,14 +48,18 @@ namespace ESSImport if (esm.isNextSub("ACDT")) { mActorData.mHasACDT = true; - esm.getHTSized<264>(mActorData.mACDT); + esm.getHT(mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, + mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, + mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, + mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); } mActorData.mHasACSC = false; if (esm.isNextSub("ACSC")) { mActorData.mHasACSC = true; - esm.getHTSized<112>(mActorData.mACSC); + esm.getHT(mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, mActorData.mACSC.mUnknown2, + mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); } if (esm.isNextSub("ACSL")) @@ -137,7 +141,7 @@ namespace ESSImport if (esm.isNextSub("ANIS")) { mActorData.mHasANIS = true; - esm.getHTSized<8>(mActorData.mANIS); + esm.getHT(mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); } if (esm.isNextSub("LVCR")) @@ -155,13 +159,16 @@ namespace ESSImport // DATA should occur for all references, except levelled creature spawners // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess - esm.getHNOTSized<24>(mPos, "DATA"); - esm.getHNOTSized<24>(mPos, "DATA"); + for (int i = 0; i < 2; ++i) + { + if (esm.isNextSub("DATA")) + esm.getHNT("DATA", mPos.pos, mPos.rot); + } mDeleted = 0; if (esm.isNextSub("DELE")) { - unsigned int deleted; + uint32_t deleted; esm.getHT(deleted); mDeleted = ((deleted >> 24) & 0x2) != 0; // the other 3 bytes seem to be uninitialized garbage } diff --git a/apps/essimporter/importcntc.hpp b/apps/essimporter/importcntc.hpp index 1bc7d94bd5..6ee843805e 100644 --- a/apps/essimporter/importcntc.hpp +++ b/apps/essimporter/importcntc.hpp @@ -14,7 +14,7 @@ namespace ESSImport /// Changed container contents struct CNTC { - int mIndex; + int32_t mIndex; Inventory mInventory; diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 77933eafe8..87a2d311c8 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -15,7 +15,7 @@ namespace ESSImport /// Creature changes struct CREC { - int mIndex; + int32_t mIndex; Inventory mInventory; ESM::AIPackageList mAiPackages; diff --git a/apps/essimporter/importdial.cpp b/apps/essimporter/importdial.cpp index 6c45f9d059..43905738a1 100644 --- a/apps/essimporter/importdial.cpp +++ b/apps/essimporter/importdial.cpp @@ -8,11 +8,11 @@ namespace ESSImport void DIAL::load(ESM::ESMReader& esm) { // See ESM::Dialogue::Type enum, not sure why we would need this here though - int type = 0; + int32_t type = 0; esm.getHNOT(type, "DATA"); // Deleted dialogue in a savefile. No clue what this means... - int deleted = 0; + int32_t deleted = 0; esm.getHNOT(deleted, "DELE"); mIndex = 0; diff --git a/apps/essimporter/importdial.hpp b/apps/essimporter/importdial.hpp index 9a1e882332..2a09af9f2a 100644 --- a/apps/essimporter/importdial.hpp +++ b/apps/essimporter/importdial.hpp @@ -10,7 +10,7 @@ namespace ESSImport struct DIAL { - int mIndex; // Journal index + int32_t mIndex; // Journal index void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importgame.cpp b/apps/essimporter/importgame.cpp index 5295d2a1e8..8161a20031 100644 --- a/apps/essimporter/importgame.cpp +++ b/apps/essimporter/importgame.cpp @@ -9,17 +9,17 @@ namespace ESSImport { esm.getSubNameIs("GMDT"); esm.getSubHeader(); - if (esm.getSubSize() == 92) - { - esm.getExact(&mGMDT, 92); - mGMDT.mSecundaPhase = 0; - } - else if (esm.getSubSize() == 96) - { - esm.getTSized<96>(mGMDT); - } - else - esm.fail("unexpected subrecord size for GAME.GMDT"); + bool hasSecundaPhase = esm.getSubSize() == 96; + esm.getT(mGMDT.mCellName); + esm.getT(mGMDT.mFogColour); + esm.getT(mGMDT.mFogDensity); + esm.getT(mGMDT.mCurrentWeather); + esm.getT(mGMDT.mNextWeather); + esm.getT(mGMDT.mWeatherTransition); + esm.getT(mGMDT.mTimeOfNextTransition); + esm.getT(mGMDT.mMasserPhase); + if (hasSecundaPhase) + esm.getT(mGMDT.mSecundaPhase); mGMDT.mWeatherTransition &= (0x000000ff); mGMDT.mSecundaPhase &= (0x000000ff); diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index 8b26b9d8bd..e880166b45 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -15,12 +15,12 @@ namespace ESSImport struct GMDT { char mCellName[64]{}; - int mFogColour{ 0 }; + int32_t mFogColour{ 0 }; float mFogDensity{ 0.f }; - int mCurrentWeather{ 0 }, mNextWeather{ 0 }; - int mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage + int32_t mCurrentWeather{ 0 }, mNextWeather{ 0 }; + int32_t mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage float mTimeOfNextTransition{ 0.f }; // weather changes when gamehour == timeOfNextTransition - int mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage + int32_t mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage }; GMDT mGMDT; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 9d71c04f2a..f1db301bd0 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -12,7 +12,7 @@ namespace ESSImport while (esm.isNextSub("NPCO")) { ContItem contItem; - esm.getHTSized<36>(contItem); + esm.getHT(contItem.mCount, contItem.mItem.mData); InventoryItem item; item.mId = contItem.mItem.toString(); @@ -28,7 +28,7 @@ namespace ESSImport bool newStack = esm.isNextSub("XIDX"); if (newStack) { - unsigned int idx; + uint32_t idx; esm.getHT(idx); separateStacks = true; item.mCount = 1; @@ -40,7 +40,7 @@ namespace ESSImport bool isDeleted = false; item.ESM::CellRef::loadData(esm, isDeleted); - int charge = -1; + int32_t charge = -1; esm.getHNOT(charge, "XHLT"); item.mChargeInt = charge; @@ -60,7 +60,7 @@ namespace ESSImport // this is currently not handled properly. esm.getSubHeader(); - int itemIndex; // index of the item in the NPCO list + int32_t itemIndex; // index of the item in the NPCO list esm.getT(itemIndex); if (itemIndex < 0 || itemIndex >= int(mItems.size())) @@ -68,7 +68,7 @@ namespace ESSImport // appears to be a relative index for only the *possible* slots this item can be equipped in, // i.e. 0 most of the time - int slotIndex; + int32_t slotIndex; esm.getT(slotIndex); mItems[itemIndex].mRelativeEquipmentSlot = slotIndex; diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index 7a11b3f0a0..9e0dcbb30a 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -19,7 +19,7 @@ namespace ESSImport struct ContItem { - int mCount; + int32_t mCount; ESM::NAME32 mItem; }; @@ -28,8 +28,8 @@ namespace ESSImport struct InventoryItem : public ESM::CellRef { std::string mId; - int mCount; - int mRelativeEquipmentSlot; + int32_t mCount; + int32_t mRelativeEquipmentSlot; SCRI mSCRI; }; std::vector mItems; diff --git a/apps/essimporter/importklst.cpp b/apps/essimporter/importklst.cpp index d4cfc7f769..2d5e09e913 100644 --- a/apps/essimporter/importklst.cpp +++ b/apps/essimporter/importklst.cpp @@ -10,7 +10,7 @@ namespace ESSImport while (esm.isNextSub("KNAM")) { std::string refId = esm.getHString(); - int count; + int32_t count; esm.getHNT(count, "CNAM"); mKillCounter[refId] = count; } diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp index 7c1ff03bb6..8a098ac501 100644 --- a/apps/essimporter/importklst.hpp +++ b/apps/essimporter/importklst.hpp @@ -18,9 +18,9 @@ namespace ESSImport void load(ESM::ESMReader& esm); /// RefId, kill count - std::map mKillCounter; + std::map mKillCounter; - int mWerewolfKills; + int32_t mWerewolfKills; }; } diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index c115040074..c1a53b6cef 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -7,7 +7,7 @@ namespace ESSImport void NPCC::load(ESM::ESMReader& esm) { - esm.getHNTSized<8>(mNPDT, "NPDT"); + esm.getHNT("NPDT", mNPDT.mDisposition, mNPDT.unknown, mNPDT.mReputation, mNPDT.unknown2, mNPDT.mIndex); while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index 762add1906..f3532f2103 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -21,7 +21,7 @@ namespace ESSImport unsigned char unknown; unsigned char mReputation; unsigned char unknown2; - int mIndex; + int32_t mIndex; } mNPDT; Inventory mInventory; diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 165926d15a..b9e1d4f291 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -19,7 +19,12 @@ namespace ESSImport mMNAM = esm.getHString(); } - esm.getHNTSized<212>(mPNAM, "PNAM"); + esm.getHNT("PNAM", mPNAM.mPlayerFlags, mPNAM.mLevelProgress, mPNAM.mSkillProgress, mPNAM.mSkillIncreases, + mPNAM.mTelekinesisRangeBonus, mPNAM.mVisionBonus, mPNAM.mDetectKeyMagnitude, + mPNAM.mDetectEnchantmentMagnitude, mPNAM.mDetectAnimalMagnitude, mPNAM.mMarkLocation.mX, + mPNAM.mMarkLocation.mY, mPNAM.mMarkLocation.mZ, mPNAM.mMarkLocation.mRotZ, mPNAM.mMarkLocation.mCellX, + mPNAM.mMarkLocation.mCellY, mPNAM.mUnknown3, mPNAM.mVerticalRotation.mData, mPNAM.mSpecIncreases, + mPNAM.mUnknown4); if (esm.isNextSub("SNAM")) esm.skipHSub(); @@ -54,7 +59,7 @@ namespace ESSImport if (esm.isNextSub("ENAM")) { mHasENAM = true; - esm.getHTSized<8>(mENAM); + esm.getHT(mENAM.mCellX, mENAM.mCellY); } if (esm.isNextSub("LNAM")) @@ -63,7 +68,8 @@ namespace ESSImport while (esm.isNextSub("FNAM")) { FNAM fnam; - esm.getHTSized<44>(fnam); + esm.getHT( + fnam.mRank, fnam.mUnknown1, fnam.mReputation, fnam.mFlags, fnam.mUnknown2, fnam.mFactionName.mData); mFactions.push_back(fnam); } @@ -71,7 +77,7 @@ namespace ESSImport if (esm.isNextSub("AADT")) // Attack animation data? { mHasAADT = true; - esm.getHTSized<44>(mAADT); + esm.getHT(mAADT.animGroupIndex, mAADT.mUnknown5); } if (esm.isNextSub("KNAM")) diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 0fb820cb64..3602abd9c3 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -17,7 +17,7 @@ namespace ESSImport /// Other player data struct PCDT { - int mBounty; + int32_t mBounty; std::string mBirthsign; std::vector mKnownDialogueTopics; @@ -41,13 +41,11 @@ namespace ESSImport PlayerFlags_LevitationDisabled = 0x80000 }; -#pragma pack(push) -#pragma pack(1) struct FNAM { unsigned char mRank; unsigned char mUnknown1[3]; - int mReputation; + int32_t mReputation; unsigned char mFlags; // 0x1: unknown, 0x2: expelled unsigned char mUnknown2[3]; ESM::NAME32 mFactionName; @@ -59,7 +57,7 @@ namespace ESSImport { float mX, mY, mZ; // worldspace position float mRotZ; // Z angle in radians - int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) + int32_t mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) }; struct Rotation @@ -67,15 +65,15 @@ namespace ESSImport float mData[3][3]; }; - int mPlayerFlags; // controls, camera and draw state - unsigned int mLevelProgress; + int32_t mPlayerFlags; // controls, camera and draw state + uint32_t mLevelProgress; float mSkillProgress[27]; // skill progress, non-uniform scaled unsigned char mSkillIncreases[8]; // number of skill increases for each attribute - int mTelekinesisRangeBonus; // in units; seems redundant + int32_t mTelekinesisRangeBonus; // in units; seems redundant float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus - int mDetectKeyMagnitude; // seems redundant - int mDetectEnchantmentMagnitude; // seems redundant - int mDetectAnimalMagnitude; // seems redundant + int32_t mDetectKeyMagnitude; // seems redundant + int32_t mDetectEnchantmentMagnitude; // seems redundant + int32_t mDetectAnimalMagnitude; // seems redundant MarkLocation mMarkLocation; unsigned char mUnknown3[4]; Rotation mVerticalRotation; @@ -85,16 +83,15 @@ namespace ESSImport struct ENAM { - int mCellX; - int mCellY; + int32_t mCellX; + int32_t mCellY; }; struct AADT // 44 bytes { - int animGroupIndex; // See convertANIS() for the mapping. + int32_t animGroupIndex; // See convertANIS() for the mapping. unsigned char mUnknown5[40]; }; -#pragma pack(pop) std::vector mFactions; PNAM mPNAM; diff --git a/apps/essimporter/importproj.cpp b/apps/essimporter/importproj.cpp index f9a92095e0..a09ade81dd 100644 --- a/apps/essimporter/importproj.cpp +++ b/apps/essimporter/importproj.cpp @@ -10,7 +10,9 @@ namespace ESSImport while (esm.isNextSub("PNAM")) { PNAM pnam; - esm.getHTSized<184>(pnam); + esm.getHT(pnam.mAttackStrength, pnam.mSpeed, pnam.mUnknown, pnam.mFlightTime, pnam.mSplmIndex, + pnam.mUnknown2, pnam.mVelocity.mValues, pnam.mPosition.mValues, pnam.mUnknown3, pnam.mActorId.mData, + pnam.mArrowId.mData, pnam.mBowId.mData); mProjectiles.push_back(pnam); } } diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h index d1c544f66f..01592af3e3 100644 --- a/apps/essimporter/importproj.h +++ b/apps/essimporter/importproj.h @@ -16,15 +16,13 @@ namespace ESSImport struct PROJ { -#pragma pack(push) -#pragma pack(1) struct PNAM // 184 bytes { float mAttackStrength; float mSpeed; unsigned char mUnknown[4 * 2]; float mFlightTime; - int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) + int32_t mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) unsigned char mUnknown2[4]; ESM::Vector3 mVelocity; ESM::Vector3 mPosition; @@ -35,7 +33,6 @@ namespace ESSImport bool isMagic() const { return mSplmIndex != 0; } }; -#pragma pack(pop) std::vector mProjectiles; diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp index 746d0b90e7..8fe4afd336 100644 --- a/apps/essimporter/importscpt.cpp +++ b/apps/essimporter/importscpt.cpp @@ -7,7 +7,8 @@ namespace ESSImport void SCPT::load(ESM::ESMReader& esm) { - esm.getHNTSized<52>(mSCHD, "SCHD"); + esm.getHNT("SCHD", mSCHD.mName.mData, mSCHD.mData.mNumShorts, mSCHD.mData.mNumLongs, mSCHD.mData.mNumFloats, + mSCHD.mData.mScriptDataSize, mSCHD.mData.mStringTableSize); mSCRI.load(esm); diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index 8f60532447..a75a3e38da 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -29,7 +29,7 @@ namespace ESSImport SCRI mSCRI; bool mRunning; - int mRefNum; // Targeted reference, -1: no reference + int32_t mRefNum; // Targeted reference, -1: no reference void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp index b6c1d4094c..c0425cef32 100644 --- a/apps/essimporter/importscri.cpp +++ b/apps/essimporter/importscri.cpp @@ -9,7 +9,7 @@ namespace ESSImport { mScript = esm.getHNOString("SCRI"); - int numShorts = 0, numLongs = 0, numFloats = 0; + int32_t numShorts = 0, numLongs = 0, numFloats = 0; if (esm.isNextSub("SLCS")) { esm.getSubHeader(); @@ -23,7 +23,7 @@ namespace ESSImport esm.getSubHeader(); for (int i = 0; i < numShorts; ++i) { - short val; + int16_t val; esm.getT(val); mShorts.push_back(val); } @@ -35,7 +35,7 @@ namespace ESSImport esm.getSubHeader(); for (int i = 0; i < numLongs; ++i) { - int val; + int32_t val; esm.getT(val); mLongs.push_back(val); } diff --git a/apps/essimporter/importsplm.cpp b/apps/essimporter/importsplm.cpp index a0478f4d92..6019183f83 100644 --- a/apps/essimporter/importsplm.cpp +++ b/apps/essimporter/importsplm.cpp @@ -11,13 +11,15 @@ namespace ESSImport { ActiveSpell spell; esm.getHT(spell.mIndex); - esm.getHNTSized<160>(spell.mSPDT, "SPDT"); + esm.getHNT("SPDT", spell.mSPDT.mType, spell.mSPDT.mId.mData, spell.mSPDT.mUnknown, + spell.mSPDT.mCasterId.mData, spell.mSPDT.mSourceId.mData, spell.mSPDT.mUnknown2); spell.mTarget = esm.getHNOString("TNAM"); while (esm.isNextSub("NPDT")) { ActiveEffect effect; - esm.getHTSized<56>(effect.mNPDT); + esm.getHT(effect.mNPDT.mAffectedActorId.mData, effect.mNPDT.mUnknown, effect.mNPDT.mMagnitude, + effect.mNPDT.mSecondsActive, effect.mNPDT.mUnknown2); // Effect-specific subrecords can follow: // - INAM for disintegration and bound effects diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 8187afb131..39405db6d3 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -15,11 +15,9 @@ namespace ESSImport struct SPLM { -#pragma pack(push) -#pragma pack(1) struct SPDT // 160 bytes { - int mType; // 1 = spell, 2 = enchantment, 3 = potion + int32_t mType; // 1 = spell, 2 = enchantment, 3 = potion ESM::NAME32 mId; // base ID of a spell/enchantment/potion unsigned char mUnknown[4 * 4]; ESM::NAME32 mCasterId; @@ -31,31 +29,29 @@ namespace ESSImport { ESM::NAME32 mAffectedActorId; unsigned char mUnknown[4 * 2]; - int mMagnitude; + int32_t mMagnitude; float mSecondsActive; unsigned char mUnknown2[4 * 2]; }; struct INAM // 40 bytes { - int mUnknown; + int32_t mUnknown; unsigned char mUnknown2; ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration }; struct CNAM // 36 bytes { - int mUnknown; // seems to always be 0 + int32_t mUnknown; // seems to always be 0 ESM::NAME32 mSummonedOrCommandedActor[32]; }; struct VNAM // 4 bytes { - int mUnknown; + int32_t mUnknown; }; -#pragma pack(pop) - struct ActiveEffect { NPDT mNPDT; @@ -63,7 +59,7 @@ namespace ESSImport struct ActiveSpell { - int mIndex; + int32_t mIndex; SPDT mSPDT; std::string mTarget; std::vector mActiveEffects; From c10b9297f0b7ee7208e891d34759d870c6b34a69 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 14:05:10 +0100 Subject: [PATCH 0615/2167] Remove Sized methods from ESMReader --- components/esm3/esmreader.hpp | 49 ++++------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 53af8f69e6..d753023645 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -131,10 +131,10 @@ namespace ESM ESM::RefId getCellId(); // Read data of a given type, stored in a subrecord of a given name - template >> + template void getHNT(X& x, NAME name) { - getHNTSized(x, name); + getHNT(name, x); } template @@ -149,26 +149,11 @@ namespace ESM } // Optional version of getHNT - template >> + template void getHNOT(X& x, NAME name) - { - getHNOTSized(x, name); - } - - // Version with extra size checking, to make sure the compiler - // doesn't mess up our struct padding. - template - void getHNTSized(X& x, NAME name) - { - getSubNameIs(name); - getHTSized(x); - } - - template - void getHNOTSized(X& x, NAME name) { if (isNextSub(name)) - getHTSized(x); + getHT(x); } // Get data of a given type/size, including subrecord header @@ -185,37 +170,13 @@ namespace ESM template >> void skipHT() { - skipHTSized(); - } - - // Version with extra size checking, to make sure the compiler - // doesn't mess up our struct padding. - template - void getHTSized(X& x) - { - getSubHeader(); - if (mCtx.leftSub != size) - reportSubSizeMismatch(size, mCtx.leftSub); - getTSized(x); - } - - template - void skipHTSized() - { - static_assert(sizeof(T) == size); + constexpr size_t size = sizeof(T); getSubHeader(); if (mCtx.leftSub != size) reportSubSizeMismatch(size, mCtx.leftSub); skip(size); } - template - void getTSized(X& x) - { - static_assert(sizeof(X) == size); - getExact(&x, size); - } - // Read a string by the given name if it is the next record. std::string getHNOString(NAME name); From 37415b0382df3853caa3aac689db74e41d26176d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 15:16:32 +0100 Subject: [PATCH 0616/2167] Don't use getExact to read structs --- apps/esmtool/record.cpp | 10 +--- .../opencs/model/tools/referenceablecheck.cpp | 22 ++----- apps/opencs/model/world/refidadapterimp.cpp | 59 ++----------------- apps/openmw/mwclass/npc.cpp | 10 +--- .../mwmechanics/mechanicsmanagerimp.cpp | 11 +--- components/esm3/loadnpc.cpp | 31 ++++++---- components/esm3/loadnpc.hpp | 3 +- components/esm3/loadpgrd.cpp | 7 ++- components/esm3/loadrace.cpp | 2 + 9 files changed, 47 insertions(+), 108 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 71158299aa..044fbf9f93 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1084,14 +1084,8 @@ namespace EsmTool std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl; - std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl; - std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl; - std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl; - std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl; - std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl; - std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl; - std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl; + for (size_t i = 0; i != mData.mNpdt.mAttributes.size(); i++) + std::cout << " " << attributeLabel(i) << ": " << int(mData.mNpdt.mAttributes[i]) << std::endl; std::cout << " Skills:" << std::endl; for (size_t i = 0; i != mData.mNpdt.mSkills.size(); i++) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index e2e178f90a..d25568fd0a 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -693,22 +693,12 @@ void CSMTools::ReferenceableCheckStage::npcCheck( } else if (npc.mNpdt.mHealth != 0) { - if (npc.mNpdt.mStrength == 0) - messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mIntelligence == 0) - messages.add(id, "Intelligence is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mWillpower == 0) - messages.add(id, "Willpower is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mAgility == 0) - messages.add(id, "Agility is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mSpeed == 0) - messages.add(id, "Speed is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mEndurance == 0) - messages.add(id, "Endurance is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mPersonality == 0) - messages.add(id, "Personality is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mLuck == 0) - messages.add(id, "Luck is equal to zero", "", CSMDoc::Message::Severity_Warning); + for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i) + { + if (npc.mNpdt.mAttributes[i] == 0) + messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {}, + CSMDoc::Message::Severity_Warning); + } } if (level <= 0) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 0ddfbbb051..c6179facb8 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -938,30 +938,9 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData( if (subColIndex == 0) return subRowIndex; - else if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - return static_cast(npcStruct.mStrength); - case 1: - return static_cast(npcStruct.mIntelligence); - case 2: - return static_cast(npcStruct.mWillpower); - case 3: - return static_cast(npcStruct.mAgility); - case 4: - return static_cast(npcStruct.mSpeed); - case 5: - return static_cast(npcStruct.mEndurance); - case 6: - return static_cast(npcStruct.mPersonality); - case 7: - return static_cast(npcStruct.mLuck); - default: - return QVariant(); // throw an exception here? - } - else - return QVariant(); // throw an exception here? + else if (subColIndex == 1 && subRowIndex >= 0 && subRowIndex < ESM::Attribute::Length) + return static_cast(npcStruct.mAttributes[subRowIndex]); + return QVariant(); // throw an exception here? } void CSMWorld::NpcAttributesRefIdAdapter::setNestedData( @@ -972,36 +951,8 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedData( ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; - if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - npcStruct.mStrength = static_cast(value.toInt()); - break; - case 1: - npcStruct.mIntelligence = static_cast(value.toInt()); - break; - case 2: - npcStruct.mWillpower = static_cast(value.toInt()); - break; - case 3: - npcStruct.mAgility = static_cast(value.toInt()); - break; - case 4: - npcStruct.mSpeed = static_cast(value.toInt()); - break; - case 5: - npcStruct.mEndurance = static_cast(value.toInt()); - break; - case 6: - npcStruct.mPersonality = static_cast(value.toInt()); - break; - case 7: - npcStruct.mLuck = static_cast(value.toInt()); - break; - default: - return; // throw an exception here? - } + if (subColIndex == 1 && subRowIndex >= 0 && subRowIndex < ESM::Attribute::Length) + npcStruct.mAttributes[subRowIndex] = static_cast(value.toInt()); else return; // throw an exception here? diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c6276753de..7c6f16b06f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -313,14 +313,8 @@ namespace MWClass for (size_t i = 0; i < ref->mBase->mNpdt.mSkills.size(); ++i) data->mNpcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(ref->mBase->mNpdt.mSkills[i]); - data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); - data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); - data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower); - data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility); - data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed); - data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance); - data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality); - data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck); + for (size_t i = 0; i < ref->mBase->mNpdt.mAttributes.size(); ++i) + data->mNpcStats.setAttribute(ESM::Attribute::indexToRefId(i), ref->mBase->mNpdt.mAttributes[i]); data->mNpcStats.setHealth(ref->mBase->mNpdt.mHealth); data->mNpcStats.setMagicka(ref->mBase->mNpdt.mMana); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9a5b09ffe2..39b5286139 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -134,14 +134,9 @@ namespace MWMechanics for (size_t i = 0; i < player->mNpdt.mSkills.size(); ++i) npcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(player->mNpdt.mSkills[i]); - creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength); - creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence); - creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt.mWillpower); - creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt.mAgility); - creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt.mSpeed); - creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt.mEndurance); - creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt.mPersonality); - creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt.mLuck); + for (size_t i = 0; i < player->mNpdt.mAttributes.size(); ++i) + npcStats.setAttribute(ESM::Attribute::indexToRefId(i), player->mNpdt.mSkills[i]); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // race diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index d844f7d2bc..8a86780fe2 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -59,23 +59,31 @@ namespace ESM if (esm.getSubSize() == 52) { mNpdtType = NPC_DEFAULT; - esm.getExact(&mNpdt, 52); + esm.getT(mNpdt.mLevel); + esm.getT(mNpdt.mAttributes); + esm.getT(mNpdt.mSkills); + esm.getT(mNpdt.mUnknown1); + esm.getT(mNpdt.mHealth); + esm.getT(mNpdt.mMana); + esm.getT(mNpdt.mFatigue); + esm.getT(mNpdt.mDisposition); + esm.getT(mNpdt.mReputation); + esm.getT(mNpdt.mRank); + esm.getT(mNpdt.mUnknown2); + esm.getT(mNpdt.mGold); } else if (esm.getSubSize() == 12) { - // Reading into temporary NPDTstruct12 object - NPDTstruct12 npdt12; mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - esm.getExact(&npdt12, 12); // Clearing the mNdpt struct to initialize all values blankNpdt(); - // Swiching to an internal representation - mNpdt.mLevel = npdt12.mLevel; - mNpdt.mDisposition = npdt12.mDisposition; - mNpdt.mReputation = npdt12.mReputation; - mNpdt.mRank = npdt12.mRank; - mNpdt.mGold = npdt12.mGold; + esm.getT(mNpdt.mLevel); + esm.getT(mNpdt.mDisposition); + esm.getT(mNpdt.mReputation); + esm.getT(mNpdt.mRank); + esm.skip(3); + esm.getT(mNpdt.mGold); } else esm.fail("NPC_NPDT must be 12 or 52 bytes long"); @@ -213,8 +221,7 @@ namespace ESM void NPC::blankNpdt() { mNpdt.mLevel = 0; - mNpdt.mStrength = mNpdt.mIntelligence = mNpdt.mWillpower = mNpdt.mAgility = mNpdt.mSpeed = mNpdt.mEndurance - = mNpdt.mPersonality = mNpdt.mLuck = 0; + mNpdt.mAttributes.fill(0); mNpdt.mSkills.fill(0); mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index af8c2a8574..c50dd3414d 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -6,6 +6,7 @@ #include #include "aipackage.hpp" +#include "components/esm/attr.hpp" #include "components/esm/defs.hpp" #include "components/esm/refid.hpp" #include "loadcont.hpp" @@ -80,7 +81,7 @@ namespace ESM struct NPDTstruct52 { int16_t mLevel; - unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; + std::array mAttributes; // mSkill can grow up to 200, it must be unsigned std::array mSkills; diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 8d60d25524..4f0a62a9d4 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -70,7 +70,12 @@ namespace ESM for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; - esm.getExact(&p, sizeof(Point)); + esm.getT(p.mX); + esm.getT(p.mY); + esm.getT(p.mZ); + esm.getT(p.mAutogenerated); + esm.getT(p.mConnectionNum); + esm.getT(p.mUnknown); mPoints.push_back(p); edgeCount += p.mConnectionNum; } diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index 8c7b89d07d..0996a5ac48 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -12,6 +12,7 @@ namespace ESM int index = ESM::Attribute::refIdToIndex(attribute); if (index < 0) return 0; + index *= 2; if (!male) index++; return mAttributeValues[static_cast(index)]; @@ -22,6 +23,7 @@ namespace ESM int index = ESM::Attribute::refIdToIndex(attribute); if (index < 0) return; + index *= 2; if (!male) index++; mAttributeValues[static_cast(index)] = value; From 88731f864e46b8f7f7cd38802738465e2c6f835b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 15:21:12 +0100 Subject: [PATCH 0617/2167] Add imports --- apps/essimporter/converter.cpp | 1 + apps/essimporter/importacdt.hpp | 1 + apps/essimporter/importcellref.cpp | 1 + apps/essimporter/importcntc.cpp | 1 + apps/essimporter/importcrec.hpp | 1 + apps/essimporter/importdial.hpp | 3 +++ apps/essimporter/importgame.hpp | 2 ++ apps/essimporter/importinventory.hpp | 1 + apps/essimporter/importklst.hpp | 1 + apps/essimporter/importnpcc.hpp | 1 + apps/essimporter/importplayer.hpp | 1 + apps/essimporter/importproj.h | 1 + apps/essimporter/importscpt.hpp | 2 ++ apps/essimporter/importscri.hpp | 1 + apps/essimporter/importsplm.h | 1 + 15 files changed, 19 insertions(+) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 00c24514ea..b44d376842 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -1,6 +1,7 @@ #include "converter.hpp" #include +#include #include #include diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 54910ac82c..65519c6a6c 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_ACDT_H #define OPENMW_ESSIMPORT_ACDT_H +#include #include #include "importscri.hpp" diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index d9639eb0dd..756770d0b5 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,6 +1,7 @@ #include "importcellref.hpp" #include +#include namespace ESSImport { diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp index 41f4e50101..34c99babef 100644 --- a/apps/essimporter/importcntc.cpp +++ b/apps/essimporter/importcntc.cpp @@ -1,6 +1,7 @@ #include "importcntc.hpp" #include +#include namespace ESSImport { diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 87a2d311c8..5217f4edc4 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -3,6 +3,7 @@ #include "importinventory.hpp" #include +#include namespace ESM { diff --git a/apps/essimporter/importdial.hpp b/apps/essimporter/importdial.hpp index 2a09af9f2a..b8b6fd536a 100644 --- a/apps/essimporter/importdial.hpp +++ b/apps/essimporter/importdial.hpp @@ -1,5 +1,8 @@ #ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H #define OPENMW_ESSIMPORT_IMPORTDIAL_H + +#include + namespace ESM { class ESMReader; diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index e880166b45..276060ae4c 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_ESSIMPORT_GAME_H #define OPENMW_ESSIMPORT_GAME_H +#include + namespace ESM { class ESMReader; diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index 9e0dcbb30a..7261e64f68 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H #define OPENMW_ESSIMPORT_IMPORTINVENTORY_H +#include #include #include diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp index 8a098ac501..9cdb2d701b 100644 --- a/apps/essimporter/importklst.hpp +++ b/apps/essimporter/importklst.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_KLST_H #define OPENMW_ESSIMPORT_KLST_H +#include #include #include diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index f3532f2103..47925226e4 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_NPCC_H #include +#include #include "importinventory.hpp" diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 3602abd9c3..89957bf4b4 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_PLAYER_H #define OPENMW_ESSIMPORT_PLAYER_H +#include #include #include diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h index 01592af3e3..a2e03b5ba3 100644 --- a/apps/essimporter/importproj.h +++ b/apps/essimporter/importproj.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace ESM diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index a75a3e38da..af383b674c 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -3,6 +3,8 @@ #include "importscri.hpp" +#include + #include #include diff --git a/apps/essimporter/importscri.hpp b/apps/essimporter/importscri.hpp index 73d8942f81..0c83a4d3be 100644 --- a/apps/essimporter/importscri.hpp +++ b/apps/essimporter/importscri.hpp @@ -3,6 +3,7 @@ #include +#include #include namespace ESM diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 39405db6d3..762e32d9da 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_IMPORTSPLM_H #include +#include #include namespace ESM From 9f38ee82f45707ec5d9f937f6359ca8495797ab3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 21:30:04 +0100 Subject: [PATCH 0618/2167] Fix misaligned address --- components/esm3/loadnpc.cpp | 34 +++++++++++++++++++++++----------- components/esm3/loadnpc.hpp | 15 --------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 8a86780fe2..4a30649372 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -154,20 +154,32 @@ namespace ESM if (mNpdtType == NPC_DEFAULT) { - esm.writeHNT("NPDT", mNpdt, 52); + esm.startSubRecord("NPDT"); + esm.writeT(mNpdt.mLevel); + esm.writeT(mNpdt.mAttributes); + esm.writeT(mNpdt.mSkills); + esm.writeT(mNpdt.mUnknown1); + esm.writeT(mNpdt.mHealth); + esm.writeT(mNpdt.mMana); + esm.writeT(mNpdt.mFatigue); + esm.writeT(mNpdt.mDisposition); + esm.writeT(mNpdt.mReputation); + esm.writeT(mNpdt.mRank); + esm.writeT(mNpdt.mUnknown2); + esm.writeT(mNpdt.mGold); + esm.endRecord("NPDT"); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { - NPDTstruct12 npdt12; - npdt12.mLevel = mNpdt.mLevel; - npdt12.mDisposition = mNpdt.mDisposition; - npdt12.mReputation = mNpdt.mReputation; - npdt12.mRank = mNpdt.mRank; - npdt12.mUnknown1 = 0; - npdt12.mUnknown2 = 0; - npdt12.mUnknown3 = 0; - npdt12.mGold = mNpdt.mGold; - esm.writeHNT("NPDT", npdt12, 12); + esm.startSubRecord("NPDT"); + esm.writeT(mNpdt.mLevel); + esm.writeT(mNpdt.mDisposition); + esm.writeT(mNpdt.mReputation); + esm.writeT(mNpdt.mRank); + constexpr char padding[] = { 0, 0, 0 }; + esm.writeT(padding); + esm.writeT(mNpdt.mGold); + esm.endRecord("NPDT"); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index c50dd3414d..76930365c8 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -75,9 +75,6 @@ namespace ESM NPC_DEFAULT = 52 }; -#pragma pack(push) -#pragma pack(1) - struct NPDTstruct52 { int16_t mLevel; @@ -93,18 +90,6 @@ namespace ESM int32_t mGold; }; // 52 bytes - // Structure for autocalculated characters. - // This is only used for load and save operations. - struct NPDTstruct12 - { - int16_t mLevel; - // see above - unsigned char mDisposition, mReputation, mRank; - char mUnknown1, mUnknown2, mUnknown3; - int32_t mGold; - }; // 12 bytes -#pragma pack(pop) - unsigned char mNpdtType; // Worth noting when saving the struct: // Although we might read a NPDTstruct12 in, we use NPDTstruct52 internally From 77cf9284b784b5a0bbcfbd1ef1c7b7fbeaa1e086 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 18 Dec 2023 21:52:17 +0100 Subject: [PATCH 0619/2167] Allow ModPCCrimeLevel to clear crimes and cap bounties --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 6 ++++-- apps/openmw/mwscript/statsextensions.cpp | 8 +++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..5e455532db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies + Bug #7416: Modpccrimelevel is different from vanilla Bug #7428: AutoCalc flag is not used to calculate enchantment costs Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..12282a515d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1152,7 +1152,7 @@ namespace MWMechanics if (npcStats.getCrimeId() != -1) { // if you've paid for your crimes and I haven't noticed - if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId() || playerStats.getBounty() <= 0) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0bb76add16..d35e812d27 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1358,7 +1358,8 @@ namespace MWMechanics if (reported) { - player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); + player.getClass().getNpcStats(player).setBounty( + std::max(0, player.getClass().getNpcStats(player).getBounty() + arg)); // If committing a crime against a faction member, expell from the faction if (!victim.isEmpty() && victim.getClass().isNpc()) @@ -1923,7 +1924,8 @@ namespace MWMechanics if (reported) { - npcStats.setBounty(npcStats.getBounty() + gmst.find("iWereWolfBounty")->mValue.getInteger()); + npcStats.setBounty( + std::max(0, npcStats.getBounty() + gmst.find("iWereWolfBounty")->mValue.getInteger())); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 745286e109..d617a02b9a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -445,10 +445,12 @@ namespace MWScript { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - - player.getClass().getNpcStats(player).setBounty( - static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); + int bounty = std::max( + 0, static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); + player.getClass().getNpcStats(player).setBounty(bounty); runtime.pop(); + if (bounty == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; From 94b129cc62cf3b5a609552706a36ab6b27f1a464 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 18 Dec 2023 22:18:26 +0100 Subject: [PATCH 0620/2167] Stop combat when stacking a new AI package --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..2b708e4969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7661: Player followers should stop attacking newly recruited actors Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index af35be3763..1f3f4e2ea1 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -6,6 +6,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "actorutil.hpp" #include "aiactivate.hpp" @@ -365,7 +367,7 @@ namespace MWMechanics // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) - stopCombat(); + MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. From 5a6dbf87149a421638cce92577e3306b28633f3d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 18 Dec 2023 22:28:38 +0100 Subject: [PATCH 0621/2167] Comments --- apps/openmw/mwphysics/mtphysics.cpp | 42 +++++++++++------------------ apps/openmw/mwphysics/ptrholder.hpp | 14 +++++----- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 9a4ec2cba2..86760a67c6 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -208,23 +208,16 @@ namespace { osg::Vec3f movement = osg::Vec3f(); auto it = actor.movement().begin(); - while (it != actor.movement().end()) - { - if (it->jump) - { - // Adjusting inertia is instant and should not be performed over time like other movement is. - it++; - continue; - } - - float start = std::max(it->simulationTimeStart, startTime); - float stop = std::min(it->simulationTimeStop, endTime); - movement += it->velocity * (stop - start); - if (std::abs(stop - it->simulationTimeStop) < 0.0001f) - it = actor.movement().erase(it); - else - it++; - } + std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + if (v.mJump) + return false; + float start = std::max(v.mSimulationTimeStart, startTime); + float stop = std::min(v.mSimulationTimeStop, endTime); + movement += v.mVelocity * (stop - start); + if (std::abs(stop - v.mSimulationTimeStop) < 0.0001f) + return true; + return false; + }); return movement; } @@ -232,17 +225,14 @@ namespace std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const { std::optional inertia = std::nullopt; - auto it = actor.movement().begin(); - while (it != actor.movement().end()) - { - if (it->jump && it->simulationTimeStart >= startTime) + std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + if (v.mJump && v.mSimulationTimeStart >= startTime) { - inertia = it->velocity; - it = actor.movement().erase(it); + inertia = v.mVelocity; + return true; } - else - it++; - } + return false; + }); return inertia; } diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index d7a6f887bb..ecc626b44c 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H -#include +#include #include #include #include @@ -16,10 +16,10 @@ namespace MWPhysics { struct Movement { - osg::Vec3f velocity = osg::Vec3f(); - float simulationTimeStart = 0.f; // The time at which this movement begun - float simulationTimeStop = 0.f; // The time at which this movement finished - bool jump = false; + osg::Vec3f mVelocity = osg::Vec3f(); + float mSimulationTimeStart = 0.f; // The time at which this movement begun + float mSimulationTimeStop = 0.f; // The time at which this movement finished + bool mJump = false; }; class PtrHolder @@ -47,7 +47,7 @@ namespace MWPhysics mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } - std::deque& movement() { return mMovement; } + std::list& movement() { return mMovement; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -66,7 +66,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - std::deque mMovement; + std::list mMovement; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; From 00b1cd8c08caeacb56fbe8d053175103a649ebfa Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 18 Dec 2023 22:50:17 +0100 Subject: [PATCH 0622/2167] Replace movement() with eraseMovementIf() --- apps/openmw/mwphysics/mtphysics.cpp | 5 ++--- apps/openmw/mwphysics/ptrholder.hpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 86760a67c6..238d00deac 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -207,8 +207,7 @@ namespace osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const { osg::Vec3f movement = osg::Vec3f(); - auto it = actor.movement().begin(); - std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + actor.eraseMovementIf([&](MWPhysics::Movement& v) { if (v.mJump) return false; float start = std::max(v.mSimulationTimeStart, startTime); @@ -225,7 +224,7 @@ namespace std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const { std::optional inertia = std::nullopt; - std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + actor.eraseMovementIf([&](MWPhysics::Movement& v) { if (v.mJump && v.mSimulationTimeStart >= startTime) { inertia = v.mVelocity; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index ecc626b44c..16c3db0691 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -47,7 +47,7 @@ namespace MWPhysics mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } - std::list& movement() { return mMovement; } + void eraseMovementIf(const auto& predicate) { std::erase_if(mMovement, predicate); } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } From 8d06a9950739367de8cd5549473d16d745c3c78d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 10:20:31 +0400 Subject: [PATCH 0623/2167] Register language selector properly --- apps/wizard/installationpage.cpp | 2 +- apps/wizard/languageselectionpage.cpp | 2 +- apps/wizard/mainwizard.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 2dd796ab3f..60e9f3ccf9 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -124,7 +124,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 38050b1cab..7dcf642dd6 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -9,7 +9,7 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) setupUi(this); - registerField(QLatin1String("installation.language"), languageComboBox); + registerField(QLatin1String("installation.language"), languageComboBox, "currentData", "currentDataChanged"); } void Wizard::LanguageSelectionPage::initializePage() diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 5fd316d17a..2f1f373cfd 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -270,7 +270,7 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--encoding")); // Set encoding - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { arguments.append(QLatin1String("win1250")); @@ -391,7 +391,7 @@ void Wizard::MainWizard::reject() void Wizard::MainWizard::writeSettings() { // Write the encoding and language settings - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); mLauncherSettings.setLanguage(language); if (language == QLatin1String("Polish")) From 2e041073fccae6144d7d364d8ba9b30459575aec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 15:21:08 +0400 Subject: [PATCH 0624/2167] Move *.ui files for different applications to different folders --- apps/launcher/CMakeLists.txt | 14 +++++++------- {files => apps/launcher}/ui/datafilespage.ui | 0 {files => apps/launcher}/ui/directorypicker.ui | 0 {files => apps/launcher}/ui/graphicspage.ui | 0 {files => apps/launcher}/ui/importpage.ui | 0 {files => apps/launcher}/ui/mainwindow.ui | 0 {files => apps/launcher}/ui/settingspage.ui | 0 apps/opencs/CMakeLists.txt | 4 ++-- {files => apps/opencs}/ui/filedialog.ui | 0 apps/wizard/CMakeLists.txt | 18 +++++++++--------- .../wizard/ui}/componentselectionpage.ui | 0 .../wizard/ui}/conclusionpage.ui | 0 .../wizard/ui}/existinginstallationpage.ui | 0 .../ui/wizard => apps/wizard/ui}/importpage.ui | 0 .../wizard/ui}/installationpage.ui | 0 .../wizard/ui}/installationtargetpage.ui | 0 .../ui/wizard => apps/wizard/ui}/intropage.ui | 0 .../wizard/ui}/languageselectionpage.ui | 0 .../wizard/ui}/methodselectionpage.ui | 0 components/CMakeLists.txt | 2 +- .../contentselector}/contentselector.ui | 0 21 files changed, 19 insertions(+), 19 deletions(-) rename {files => apps/launcher}/ui/datafilespage.ui (100%) rename {files => apps/launcher}/ui/directorypicker.ui (100%) rename {files => apps/launcher}/ui/graphicspage.ui (100%) rename {files => apps/launcher}/ui/importpage.ui (100%) rename {files => apps/launcher}/ui/mainwindow.ui (100%) rename {files => apps/launcher}/ui/settingspage.ui (100%) rename {files => apps/opencs}/ui/filedialog.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/componentselectionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/conclusionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/existinginstallationpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/importpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/installationpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/installationtargetpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/intropage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/languageselectionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/methodselectionpage.ui (100%) rename {files/ui => components/contentselector}/contentselector.ui (100%) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index daae65dc66..f81870052d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -35,13 +35,13 @@ set(LAUNCHER_HEADER # Headers that must be pre-processed set(LAUNCHER_UI - ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui - ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui - ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui - ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui - ${CMAKE_SOURCE_DIR}/files/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui - ${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/datafilespage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/graphicspage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/mainwindow.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/importpage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/settingspage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/directorypicker.ui + ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/files/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui similarity index 100% rename from files/ui/datafilespage.ui rename to apps/launcher/ui/datafilespage.ui diff --git a/files/ui/directorypicker.ui b/apps/launcher/ui/directorypicker.ui similarity index 100% rename from files/ui/directorypicker.ui rename to apps/launcher/ui/directorypicker.ui diff --git a/files/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui similarity index 100% rename from files/ui/graphicspage.ui rename to apps/launcher/ui/graphicspage.ui diff --git a/files/ui/importpage.ui b/apps/launcher/ui/importpage.ui similarity index 100% rename from files/ui/importpage.ui rename to apps/launcher/ui/importpage.ui diff --git a/files/ui/mainwindow.ui b/apps/launcher/ui/mainwindow.ui similarity index 100% rename from files/ui/mainwindow.ui rename to apps/launcher/ui/mainwindow.ui diff --git a/files/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui similarity index 100% rename from files/ui/settingspage.ui rename to apps/launcher/ui/settingspage.ui diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b040980529..68d5502890 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -139,8 +139,8 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc ) set (OPENCS_UI - ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui - ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui + ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui + ${CMAKE_SOURCE_DIR}/apps/opencs/ui/filedialog.ui ) source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) diff --git a/files/ui/filedialog.ui b/apps/opencs/ui/filedialog.ui similarity index 100% rename from files/ui/filedialog.ui rename to apps/opencs/ui/filedialog.ui diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 588b100ef2..943252151f 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -34,20 +34,20 @@ set(WIZARD_HEADER ) set(WIZARD_UI - ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/languageselectionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/componentselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/conclusionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/existinginstallationpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/importpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationtargetpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/intropage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/languageselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/methodselectionpage.ui ) if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) + set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) diff --git a/files/ui/wizard/componentselectionpage.ui b/apps/wizard/ui/componentselectionpage.ui similarity index 100% rename from files/ui/wizard/componentselectionpage.ui rename to apps/wizard/ui/componentselectionpage.ui diff --git a/files/ui/wizard/conclusionpage.ui b/apps/wizard/ui/conclusionpage.ui similarity index 100% rename from files/ui/wizard/conclusionpage.ui rename to apps/wizard/ui/conclusionpage.ui diff --git a/files/ui/wizard/existinginstallationpage.ui b/apps/wizard/ui/existinginstallationpage.ui similarity index 100% rename from files/ui/wizard/existinginstallationpage.ui rename to apps/wizard/ui/existinginstallationpage.ui diff --git a/files/ui/wizard/importpage.ui b/apps/wizard/ui/importpage.ui similarity index 100% rename from files/ui/wizard/importpage.ui rename to apps/wizard/ui/importpage.ui diff --git a/files/ui/wizard/installationpage.ui b/apps/wizard/ui/installationpage.ui similarity index 100% rename from files/ui/wizard/installationpage.ui rename to apps/wizard/ui/installationpage.ui diff --git a/files/ui/wizard/installationtargetpage.ui b/apps/wizard/ui/installationtargetpage.ui similarity index 100% rename from files/ui/wizard/installationtargetpage.ui rename to apps/wizard/ui/installationtargetpage.ui diff --git a/files/ui/wizard/intropage.ui b/apps/wizard/ui/intropage.ui similarity index 100% rename from files/ui/wizard/intropage.ui rename to apps/wizard/ui/intropage.ui diff --git a/files/ui/wizard/languageselectionpage.ui b/apps/wizard/ui/languageselectionpage.ui similarity index 100% rename from files/ui/wizard/languageselectionpage.ui rename to apps/wizard/ui/languageselectionpage.ui diff --git a/files/ui/wizard/methodselectionpage.ui b/apps/wizard/ui/methodselectionpage.ui similarity index 100% rename from files/ui/wizard/methodselectionpage.ui rename to apps/wizard/ui/methodselectionpage.ui diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..e762bd4905 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -490,7 +490,7 @@ else () ) endif() -set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui +set (ESM_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) if (USE_QT) diff --git a/files/ui/contentselector.ui b/components/contentselector/contentselector.ui similarity index 100% rename from files/ui/contentselector.ui rename to components/contentselector/contentselector.ui From e6690bbcc7f6dedd9f7d1fa524c1734e53076efa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 19:58:52 +0400 Subject: [PATCH 0625/2167] Use CMAKE_CURRENT_SOURCE_DIR instead of CMAKE_SOURCE_DIR --- apps/launcher/CMakeLists.txt | 12 ++++++------ apps/opencs/CMakeLists.txt | 2 +- apps/wizard/CMakeLists.txt | 18 +++++++++--------- components/CMakeLists.txt | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index f81870052d..8d2208c9df 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -35,12 +35,12 @@ set(LAUNCHER_HEADER # Headers that must be pre-processed set(LAUNCHER_UI - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/datafilespage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/graphicspage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/mainwindow.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/settingspage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/directorypicker.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/datafilespage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/graphicspage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/mainwindow.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/settingspage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/directorypicker.ui ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 68d5502890..cea2b66331 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -140,7 +140,7 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc set (OPENCS_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui - ${CMAKE_SOURCE_DIR}/apps/opencs/ui/filedialog.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/filedialog.ui ) source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 943252151f..8c459f4f9c 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -34,20 +34,20 @@ set(WIZARD_HEADER ) set(WIZARD_UI - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/componentselectionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/conclusionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/existinginstallationpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationtargetpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/intropage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/languageselectionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/methodselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/componentselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/conclusionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/existinginstallationpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/installationtargetpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/intropage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/languageselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/methodselectionpage.ui ) if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationpage.ui) + set (WIZARD_UI ${WIZARD_UI} ${CMAKE_CURRENT_SOURCE_DIR}/ui/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e762bd4905..a335f81de0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -490,7 +490,7 @@ else () ) endif() -set (ESM_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui +set (ESM_UI ${CMAKE_CURRENT_SOURCE_DIR}/contentselector/contentselector.ui ) if (USE_QT) From 8a1ca870ebf02ec9c6ffa57ee20ac3df58d9bcd9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 19 Dec 2023 21:23:10 +0100 Subject: [PATCH 0626/2167] Stop infighting when gaining new allies --- apps/openmw/mwmechanics/aisequence.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 1f3f4e2ea1..5d6f25ecb8 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -19,6 +19,7 @@ #include "aipursue.hpp" #include "aitravel.hpp" #include "aiwander.hpp" +#include "creaturestats.hpp" namespace MWMechanics { @@ -367,7 +368,20 @@ namespace MWMechanics // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) - MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); + { + if (package.getTypeId() == MWMechanics::AiPackageTypeId::Follow + || package.getTypeId() == MWMechanics::AiPackageTypeId::Escort) + { + const auto& mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + std::vector newAllies = mechanicsManager->getActorsSidingWith(package.getTarget()); + std::vector allies = mechanicsManager->getActorsSidingWith(actor); + for (const auto& ally : allies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(newAllies); + for (const auto& ally : newAllies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(allies); + } + stopCombat(); + } // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. From 1223d12b29bbe7d8783473d5e09cd6aaf477fe68 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 11:56:12 +0100 Subject: [PATCH 0627/2167] Make ingredient order affect effect order --- CHANGELOG.md | 1 + apps/openmw/mwgui/alchemywindow.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 95 ++++++++++++++---------- apps/openmw/mwmechanics/alchemy.hpp | 3 +- apps/openmw/mwmechanics/magiceffects.cpp | 5 ++ apps/openmw/mwmechanics/magiceffects.hpp | 1 + 6 files changed, 66 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..681c721137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7660: Some inconsistencies regarding Invisibility breaking Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound + Bug #7676: Incorrect magic effect order in alchemy Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 333722a149..de3e0c19e0 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -433,7 +433,7 @@ namespace MWGui mItemView->update(); - std::set effectIds = mAlchemy->listEffects(); + std::vector effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; unsigned int effectIndex = 0; for (const MWMechanics::EffectKey& effectKey : effectIds) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 5f8ffc1750..e9662f395f 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -24,45 +24,64 @@ #include "creaturestats.hpp" #include "magiceffects.hpp" +namespace +{ + constexpr size_t sNumEffects = 4; + + std::optional toKey(const ESM::Ingredient& ingredient, size_t i) + { + if (ingredient.mData.mEffectID[i] < 0) + return {}; + ESM::RefId arg = ESM::Skill::indexToRefId(ingredient.mData.mSkills[i]); + if (arg.empty()) + arg = ESM::Attribute::indexToRefId(ingredient.mData.mAttributes[i]); + return MWMechanics::EffectKey(ingredient.mData.mEffectID[i], arg); + } + + bool containsEffect(const ESM::Ingredient& ingredient, const MWMechanics::EffectKey& effect) + { + for (size_t j = 0; j < sNumEffects; ++j) + { + if (toKey(ingredient, j) == effect) + return true; + } + return false; + } +} + MWMechanics::Alchemy::Alchemy() : mValue(0) - , mPotionName("") { } -std::set MWMechanics::Alchemy::listEffects() const +std::vector MWMechanics::Alchemy::listEffects() const { - std::map effects; - - for (TIngredientsIterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) + // We care about the order of these effects as each effect can affect the next when applied. + // The player can affect effect order by placing ingredients into different slots + std::vector effects; + for (size_t slotI = 0; slotI < mIngredients.size() - 1; ++slotI) { - if (!iter->isEmpty()) + if (mIngredients[slotI].isEmpty()) + continue; + const ESM::Ingredient* ingredient = mIngredients[slotI].get()->mBase; + for (size_t slotJ = slotI + 1; slotJ < mIngredients.size(); ++slotJ) { - const MWWorld::LiveCellRef* ingredient = iter->get(); - - std::set seenEffects; - - for (int i = 0; i < 4; ++i) - if (ingredient->mBase->mData.mEffectID[i] != -1) + if (mIngredients[slotJ].isEmpty()) + continue; + const ESM::Ingredient* ingredient2 = mIngredients[slotJ].get()->mBase; + for (size_t i = 0; i < sNumEffects; ++i) + { + if (const auto key = toKey(*ingredient, i)) { - ESM::RefId arg = ESM::Skill::indexToRefId(ingredient->mBase->mData.mSkills[i]); - if (arg.empty()) - arg = ESM::Attribute::indexToRefId(ingredient->mBase->mData.mAttributes[i]); - EffectKey key(ingredient->mBase->mData.mEffectID[i], arg); - - if (seenEffects.insert(key).second) - ++effects[key]; + if (std::ranges::find(effects, *key) != effects.end()) + continue; + if (containsEffect(*ingredient2, *key)) + effects.push_back(*key); } + } } } - - std::set effects2; - - for (std::map::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) - if (iter->second > 1) - effects2.insert(iter->first); - - return effects2; + return effects; } void MWMechanics::Alchemy::applyTools(int flags, float& value) const @@ -133,7 +152,7 @@ void MWMechanics::Alchemy::updateEffects() return; // find effects - std::set effects(listEffects()); + std::vector effects = listEffects(); // general alchemy factor float x = getAlchemyFactor(); @@ -150,14 +169,14 @@ void MWMechanics::Alchemy::updateEffects() x * MWBase::Environment::get().getESMStore()->get().find("iAlchemyMod")->mValue.getFloat()); // build quantified effect list - for (std::set::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) + for (const auto& effectKey : effects) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(iter->mId); + = MWBase::Environment::get().getESMStore()->get().find(effectKey.mId); if (magicEffect->mData.mBaseCost <= 0) { - const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId); + const std::string os = "invalid base cost for magic effect " + std::to_string(effectKey.mId); throw std::runtime_error(os); } @@ -198,15 +217,15 @@ void MWMechanics::Alchemy::updateEffects() if (magnitude > 0 && duration > 0) { ESM::ENAMstruct effect; - effect.mEffectID = iter->mId; + effect.mEffectID = effectKey.mId; effect.mAttribute = -1; effect.mSkill = -1; if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effect.mSkill = ESM::Skill::refIdToIndex(iter->mArg); + effect.mSkill = ESM::Skill::refIdToIndex(effectKey.mArg); else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effect.mAttribute = ESM::Attribute::refIdToIndex(iter->mArg); + effect.mAttribute = ESM::Attribute::refIdToIndex(effectKey.mArg); effect.mRange = 0; effect.mArea = 0; @@ -241,7 +260,7 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co bool mismatch = false; - for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) + for (size_t i = 0; i < iter->mEffects.mList.size(); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; @@ -578,7 +597,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle() std::string MWMechanics::Alchemy::suggestPotionName() { - std::set effects = listEffects(); + std::vector effects = listEffects(); if (effects.empty()) return {}; @@ -595,11 +614,11 @@ std::vector MWMechanics::Alchemy::effectsDescription(const MWWorld: const static auto fWortChanceValue = store->get().find("fWortChanceValue")->mValue.getFloat(); const auto& data = item->mData; - for (auto i = 0; i < 4; ++i) + for (size_t i = 0; i < sNumEffects; ++i) { const auto effectID = data.mEffectID[i]; - if (alchemySkill < fWortChanceValue * (i + 1)) + if (alchemySkill < fWortChanceValue * static_cast(i + 1)) break; if (effectID != -1) diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 1b76e400f5..373ca8b887 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWMECHANICS_ALCHEMY_H #define GAME_MWMECHANICS_ALCHEMY_H -#include #include #include @@ -110,7 +109,7 @@ namespace MWMechanics void setPotionName(const std::string& name); ///< Set name of potion to create - std::set listEffects() const; + std::vector listEffects() const; ///< List all effects shared by at least two ingredients. int addIngredient(const MWWorld::Ptr& ingredient); diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index bba6e7361d..c2afef7c0d 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -64,6 +64,11 @@ namespace MWMechanics return left.mArg < right.mArg; } + bool operator==(const EffectKey& left, const EffectKey& right) + { + return left.mId == right.mId && left.mArg == right.mArg; + } + float EffectParam::getMagnitude() const { return mBase + mModifier; diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index b9831c0250..4fe5d9fd4e 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -38,6 +38,7 @@ namespace MWMechanics }; bool operator<(const EffectKey& left, const EffectKey& right); + bool operator==(const EffectKey& left, const EffectKey& right); struct EffectParam { From 66b1745520d9a794fb50db5839e4f7d10ef1372c Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:08:06 +0100 Subject: [PATCH 0628/2167] Use settings values to declare int settings --- apps/opencs/model/prefs/intsetting.cpp | 2 +- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 39 ++++++++++++++------------ apps/opencs/model/prefs/state.hpp | 3 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 54fd13233e..a593b6f688 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mMin(0) , mMax(std::numeric_limits::max()) diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index a1ed19ffd6..e2926456aa 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -23,7 +23,7 @@ namespace CSMPrefs public: explicit IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 61d64b783e..d44c9de888 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -32,10 +32,10 @@ CSMPrefs::State* CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::declare() { declareCategory("Windows"); - declareInt("default-width", "Default window width", 800) + declareInt(mValues->mWindows.mDefaultWidth, "Default window width") .setTooltip("Newly opened top-level windows will open with this width.") .setMin(80); - declareInt("default-height", "Default window height", 600) + declareInt(mValues->mWindows.mDefaultHeight, "Default window height") .setTooltip("Newly opened top-level windows will open with this height.") .setMin(80); declareBool("show-statusbar", "Show Status Bar", true) @@ -46,7 +46,7 @@ void CSMPrefs::State::declare() .setTooltip( "When a new subview is requested and a matching subview already " " exist, do not open a new subview and use the existing one instead."); - declareInt("max-subviews", "Maximum number of subviews per top-level window", 256) + declareInt(mValues->mWindows.mMaxSubviews, "Maximum number of subviews per top-level window") .setTooltip( "If the maximum number is reached and a new subview is opened " "it will be placed into a new top-level window.") @@ -56,7 +56,7 @@ void CSMPrefs::State::declare() "When a view contains only a single subview, hide the subview title " "bar and if this subview is closed also close the view (unless it is the last " "view for this document)"); - declareInt("minimum-width", "Minimum subview width", 325) + declareInt(mValues->mWindows.mMinimumWidth, "Minimum subview width") .setTooltip("Minimum width of subviews.") .setRange(50, 10000); EnumValue scrollbarOnly("Scrollbar Only", @@ -134,9 +134,9 @@ void CSMPrefs::State::declare() declareBool("ignore-base-records", "Ignore base records in verifier", false); declareCategory("Search & Replace"); - declareInt("char-before", "Characters before search string", 10) + declareInt(mValues->mSearchAndReplace.mCharBefore, "Characters before search string") .setTooltip("Maximum number of character to display in search result before the searched text"); - declareInt("char-after", "Characters after search string", 10) + declareInt(mValues->mSearchAndReplace.mCharAfter, "Characters after search string") .setTooltip("Maximum number of character to display in search result after the searched text"); declareBool("auto-delete", "Delete row from result table after a successful replace", true); @@ -147,17 +147,19 @@ void CSMPrefs::State::declare() "The current row and column numbers of the text cursor are shown at the bottom."); declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor."); declareBool("mono-font", "Use monospace font", true); - declareInt("tab-width", "Tab Width", 4).setTooltip("Number of characters for tab width").setRange(1, 10); + declareInt(mValues->mScripts.mTabWidth, "Tab Width") + .setTooltip("Number of characters for tab width") + .setRange(1, 10); EnumValue warningsNormal("Normal", "Report warnings as warning"); declareEnum("warnings", "Warning Mode", warningsNormal) .addValue("Ignore", "Do not report warning") .addValue(warningsNormal) .addValue("Strict", "Promote warning to an error"); declareBool("toolbar", "Show toolbar", true); - declareInt("compile-delay", "Delay between updating of source errors", 100) + declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") .setRange(0, 10000); - declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000); + declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); @@ -201,12 +203,12 @@ void CSMPrefs::State::declare() declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory("Rendering"); - declareInt("framerate-limit", "FPS limit", 60) + declareInt(mValues->mRendering.mFramerateLimit, "FPS limit") .setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".") .setRange(0, 10000); - declareInt("camera-fov", "Camera FOV", 90).setRange(10, 170); + declareInt(mValues->mRendering.mCameraFov, "Camera FOV").setRange(10, 170); declareBool("camera-ortho", "Orthographic projection for camera", false); - declareInt("camera-ortho-size", "Orthographic projection size parameter", 100) + declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); @@ -231,7 +233,7 @@ void CSMPrefs::State::declare() declareCategory("Tooltips"); declareBool("scene", "Show Tooltips in 3D scenes", true); declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false); - declareInt("scene-delay", "Tooltip delay in milliseconds", 500).setMin(1); + declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); EnumValue createAndInsert("Create cell and insert"); EnumValue showAndInsert("Show cell and insert"); @@ -263,7 +265,7 @@ void CSMPrefs::State::declare() declareDouble("gridsnap-movement", "Grid snap size", 16); declareDouble("gridsnap-rotation", "Angle snap size", 15); declareDouble("gridsnap-scale", "Scale snap size", 0.25); - declareInt("distance", "Drop Distance", 50) + declareInt(mValues->mSceneEditing.mDistance, "Drop Distance") .setTooltip( "If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); @@ -276,8 +278,8 @@ void CSMPrefs::State::declare() declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit) .setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.") .addValues(landeditOutsideVisibleCell); - declareInt("texturebrush-maximumsize", "Maximum texture brush size", 50).setMin(1); - declareInt("shapebrush-maximumsize", "Maximum height edit brush size", 100) + declareInt(mValues->mSceneEditing.mTexturebrushMaximumsize, "Maximum texture brush size").setMin(1); + declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") .setMin(1); declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false) @@ -459,12 +461,13 @@ void CSMPrefs::State::declareCategory(const std::string& key) } } -CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const QString& label, int default_) +CSMPrefs::IntSetting& CSMPrefs::State::declareInt(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::IntSetting* setting + = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 5f82389c82..1d44f4b71a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -63,7 +63,8 @@ namespace CSMPrefs void declareCategory(const std::string& key); - IntSetting& declareInt(const std::string& key, const QString& label, int default_); + IntSetting& declareInt(Settings::SettingValue& value, const QString& label); + DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); From 3e101ab409b25a5dd475527f7971d7f9482e108c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 12:28:34 +0100 Subject: [PATCH 0629/2167] Add a variadic getHNOT --- apps/essimporter/importcellref.cpp | 36 ++++++++---------------------- apps/essimporter/importplayer.cpp | 14 ++---------- components/esm3/aisequence.cpp | 7 +----- components/esm3/cellid.cpp | 14 +++--------- components/esm3/esmreader.hpp | 12 +++++++++- components/esm3/loadtes3.cpp | 13 ++++------- components/esm3/objectstate.cpp | 11 ++------- components/esm3/player.cpp | 9 ++------ 8 files changed, 34 insertions(+), 82 deletions(-) diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 756770d0b5..56e888d3f6 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -45,23 +45,14 @@ namespace ESSImport bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); - mActorData.mHasACDT = false; - if (esm.isNextSub("ACDT")) - { - mActorData.mHasACDT = true; - esm.getHT(mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, + mActorData.mHasACDT + = esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); - } - mActorData.mHasACSC = false; - if (esm.isNextSub("ACSC")) - { - mActorData.mHasACSC = true; - esm.getHT(mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, mActorData.mACSC.mUnknown2, - mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); - } + mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, + mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); @@ -127,23 +118,17 @@ namespace ESSImport } // FIXME: not all actors have this, add flag - if (esm.isNextSub("CHRD")) // npc only - esm.getHExact(mActorData.mSkills, 27 * 2 * sizeof(int)); + esm.getHNOT("CHRD", mActorData.mSkills); // npc only - if (esm.isNextSub("CRED")) // creature only - esm.getHExact(mActorData.mCombatStats, 3 * 2 * sizeof(int)); + esm.getHNOT("CRED", mActorData.mCombatStats); // creature only mActorData.mSCRI.load(esm); if (esm.isNextSub("ND3D")) esm.skipHSub(); - mActorData.mHasANIS = false; - if (esm.isNextSub("ANIS")) - { - mActorData.mHasANIS = true; - esm.getHT(mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); - } + mActorData.mHasANIS + = esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); if (esm.isNextSub("LVCR")) { @@ -161,10 +146,7 @@ namespace ESSImport // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess for (int i = 0; i < 2; ++i) - { - if (esm.isNextSub("DATA")) - esm.getHNT("DATA", mPos.pos, mPos.rot); - } + esm.getHNOT("DATA", mPos.pos, mPos.rot); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index b9e1d4f291..f4c280541d 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -55,12 +55,7 @@ namespace ESSImport if (esm.isNextSub("NAM3")) esm.skipHSub(); - mHasENAM = false; - if (esm.isNextSub("ENAM")) - { - mHasENAM = true; - esm.getHT(mENAM.mCellX, mENAM.mCellY); - } + mHasENAM = esm.getHNOT("ENAM", mENAM.mCellX, mENAM.mCellY); if (esm.isNextSub("LNAM")) esm.skipHSub(); @@ -73,12 +68,7 @@ namespace ESSImport mFactions.push_back(fnam); } - mHasAADT = false; - if (esm.isNextSub("AADT")) // Attack animation data? - { - mHasAADT = true; - esm.getHT(mAADT.animGroupIndex, mAADT.mUnknown5); - } + mHasAADT = esm.getHNOT("AADT", mAADT.animGroupIndex, mAADT.mUnknown5); // Attack animation data? if (esm.isNextSub("KNAM")) esm.skipHSub(); // assigned Quick Keys, I think diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 21973acd1d..bbd42c400e 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -15,12 +15,7 @@ namespace ESM { esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime - mStoredInitialActorPosition = false; - if (esm.isNextSub("POS_")) - { - mStoredInitialActorPosition = true; - esm.getHT(mInitialActorPosition.mValues); - } + mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } void AiWander::save(ESMWriter& esm) const diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 3df1336c6c..9a5be3aada 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -11,17 +11,9 @@ namespace ESM { mWorldspace = esm.getHNString("SPAC"); - if (esm.isNextSub("CIDX")) - { - esm.getHT(mIndex.mX, mIndex.mY); - mPaged = true; - } - else - { - mPaged = false; - mIndex.mX = 0; - mIndex.mY = 0; - } + mIndex.mX = 0; + mIndex.mY = 0; + mPaged = esm.getHNOT("CIDX", mIndex.mX, mIndex.mY); } void CellId::save(ESMWriter& esm) const diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index d753023645..bafc89a74c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -151,9 +151,19 @@ namespace ESM // Optional version of getHNT template void getHNOT(X& x, NAME name) + { + getHNOT(name, x); + } + + template + bool getHNOT(NAME name, Args&... args) { if (isNextSub(name)) - getHT(x); + { + getHT(args...); + return true; + } + return false; } // Get data of a given type/size, including subrecord header diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index 131510ce89..b6fbe76553 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -20,10 +20,8 @@ namespace ESM void Header::load(ESMReader& esm) { - if (esm.isNextSub("FORM")) - esm.getHT(mFormatVersion); - else - mFormatVersion = DefaultFormatVersion; + mFormatVersion = DefaultFormatVersion; + esm.getHNOT("FORM", mFormatVersion); if (esm.isNextSub("HEDR")) { @@ -43,11 +41,8 @@ namespace ESM mMaster.push_back(m); } - if (esm.isNextSub("GMDT")) - { - esm.getHT(mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, - mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); - } + esm.getHNOT("GMDT", mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, + mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); if (esm.isNextSub("SCRD")) { esm.getSubHeader(); diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 6e2621df29..a46200944a 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -32,15 +32,8 @@ namespace ESM mCount = 1; esm.getHNOT(mCount, "COUN"); - if (esm.isNextSub("POS_")) - { - std::array pos; - esm.getHT(pos); - memcpy(mPosition.pos, pos.data(), sizeof(float) * 3); - memcpy(mPosition.rot, pos.data() + 3, sizeof(float) * 3); - } - else - mPosition = mRef.mPos; + mPosition = mRef.mPos; + esm.getHNOT("POS_", mPosition.pos, mPosition.rot); mFlags = 0; esm.getHNOT(mFlags, "FLAG"); diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 901b52ce1d..aa6b59abb9 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -15,14 +15,9 @@ namespace ESM esm.getHNT("LKEP", mLastKnownExteriorPosition); - if (esm.isNextSub("MARK")) - { - mHasMark = true; - esm.getHT(mMarkedPosition.pos, mMarkedPosition.rot); + mHasMark = esm.getHNOT("MARK", mMarkedPosition.pos, mMarkedPosition.rot); + if (mHasMark) mMarkedCell = esm.getCellId(); - } - else - mHasMark = false; // Automove, no longer used. if (esm.isNextSub("AMOV")) From 532a330aac1d1eb31c5e419a840f415b53147b96 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 13:58:43 +0100 Subject: [PATCH 0630/2167] mac plz --- apps/openmw/mwmechanics/alchemy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index e9662f395f..f8e0046e19 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -73,7 +73,7 @@ std::vector MWMechanics::Alchemy::listEffects() const { if (const auto key = toKey(*ingredient, i)) { - if (std::ranges::find(effects, *key) != effects.end()) + if (std::find(effects.begin(), effects.end(), *key) != effects.end()) continue; if (containsEffect(*ingredient2, *key)) effects.push_back(*key); From 491525d17355ca9f2749286aa61b903271871929 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 20 Dec 2023 22:37:41 +0100 Subject: [PATCH 0631/2167] Add shebangs to bash scripts To specify used interpreter and set exit on error mode. --- CI/teal_ci.sh | 2 +- docs/source/install_luadocumentor_in_docker.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CI/teal_ci.sh b/CI/teal_ci.sh index 5ea312e88c..8117e93443 100755 --- a/CI/teal_ci.sh +++ b/CI/teal_ci.sh @@ -1,4 +1,4 @@ -set -e +#!/bin/bash -e docs/source/install_luadocumentor_in_docker.sh PATH=$PATH:~/luarocks/bin diff --git a/docs/source/install_luadocumentor_in_docker.sh b/docs/source/install_luadocumentor_in_docker.sh index a1ec253600..6d24f3e9f3 100755 --- a/docs/source/install_luadocumentor_in_docker.sh +++ b/docs/source/install_luadocumentor_in_docker.sh @@ -1,3 +1,5 @@ +#!/bin/bash -e + if [ ! -f /.dockerenv ] && [ ! -f /home/docs/omw_luadoc_docker ]; then echo 'This script installs lua-5.1, luarocks, and openmwluadocumentor to $HOME. Should be used only in docker.' exit 1 From 29e8e7ba076df8facd5b4ee925501efe94f4f23a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 01:50:58 +0100 Subject: [PATCH 0632/2167] Update recasnagivation to c393777d26d2ff6519ac23612abf8af42678c9dd --- extern/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 6f55e4f1c6..10d75c1057 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -173,11 +173,10 @@ if(NOT OPENMW_USE_SYSTEM_RECASTNAVIGATION) set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "") set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "") - # master on 12 Oct 2022 include(FetchContent) FetchContent_Declare(recastnavigation - URL https://github.com/recastnavigation/recastnavigation/archive/405cc095ab3a2df976a298421974a2af83843baf.zip - URL_HASH SHA512=39580ca258783ab3bda237843facc918697266e729c85065cdae1f0ed3d0ed1429a7ed08b18b926ba64402d9875a18f52904a87f43fe4fe30252f23edcfa6c70 + URL https://github.com/recastnavigation/recastnavigation/archive/c393777d26d2ff6519ac23612abf8af42678c9dd.zip + URL_HASH SHA512=48f20cee7a70c2f20f4c68bb74d5af11a1434be85294e37f5fe7b7aae820fbcdff4f35d3be286eaf6f9cbce0aed4201fcc090df409a5bd04aec5fd7c29b3ad94 SOURCE_DIR fetched/recastnavigation ) FetchContent_MakeAvailableExcludeFromAll(recastnavigation) From 78a0e0eb3b0fc65d6541946d75a5d857c5c8da67 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 02:19:14 +0100 Subject: [PATCH 0633/2167] Handle and log some controller related SDL errors SDL_GameControllerNameForIndex may return nullptr indicating an error which causes a crash when passed to log. --- apps/openmw/mwinput/controllermanager.cpp | 38 ++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 7054f72c8f..6d82b1e226 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -34,16 +34,27 @@ namespace MWInput { if (!controllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(controllerBindingsFile).c_str()); + const int result + = SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(controllerBindingsFile).c_str()); + if (result < 0) + Log(Debug::Error) << "Failed to add game controller mappings from file \"" << controllerBindingsFile + << "\": " << SDL_GetError(); } if (!userControllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(userControllerBindingsFile).c_str()); + const int result + = SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(userControllerBindingsFile).c_str()); + if (result < 0) + Log(Debug::Error) << "Failed to add game controller mappings from user file \"" + << userControllerBindingsFile << "\": " << SDL_GetError(); } // Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); + const int numSticks = SDL_NumJoysticks(); + if (numSticks < 0) + Log(Debug::Error) << "Failed to get number of joysticks: " << SDL_GetError(); + for (int i = 0; i < numSticks; i++) { if (SDL_IsGameController(i)) @@ -52,11 +63,17 @@ namespace MWInput evt.which = i; static const int fakeDeviceID = 1; ControllerManager::controllerAdded(fakeDeviceID, evt); - Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); + if (const char* name = SDL_GameControllerNameForIndex(i)) + Log(Debug::Info) << "Detected game controller: " << name; + else + Log(Debug::Warning) << "Detected game controller without a name: " << SDL_GetError(); } else { - Log(Debug::Info) << "Detected unusable controller: " << SDL_JoystickNameForIndex(i); + if (const char* name = SDL_JoystickNameForIndex(i)) + Log(Debug::Info) << "Detected unusable controller: " << name; + else + Log(Debug::Warning) << "Detected unusable controller without a name: " << SDL_GetError(); } } @@ -336,8 +353,11 @@ namespace MWInput return; if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) return; - if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + if (const int result = SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE); result < 0) + { + Log(Debug::Error) << "Failed to enable game controller sensor: " << SDL_GetError(); return; + } mGyroAvailable = true; #endif } @@ -353,7 +373,11 @@ namespace MWInput #if SDL_VERSION_ATLEAST(2, 0, 14) SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); if (cntrl && mGyroAvailable) - SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + { + const int result = SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + if (result < 0) + Log(Debug::Error) << "Failed to get game controller sensor data: " << SDL_GetError(); + } #endif return std::array({ gyro[0], gyro[1], gyro[2] }); } From 187f63d3d3568fd8dfc7104ddf72d35f94acb4ba Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 10 Nov 2023 08:02:53 -0800 Subject: [PATCH 0634/2167] support postprocess distortion --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/distortion.cpp | 28 ++++ apps/openmw/mwrender/distortion.hpp | 28 ++++ apps/openmw/mwrender/npcanimation.cpp | 9 +- apps/openmw/mwrender/pingpongcanvas.cpp | 4 + apps/openmw/mwrender/pingpongcanvas.hpp | 3 + apps/openmw/mwrender/postprocessor.cpp | 120 +++++++++++------- apps/openmw/mwrender/postprocessor.hpp | 14 +- apps/openmw/mwrender/renderbin.hpp | 3 +- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/fx/pass.cpp | 1 + components/fx/technique.hpp | 4 + components/nif/property.hpp | 4 + components/nifosg/nifloader.cpp | 4 + components/resource/scenemanager.cpp | 1 + components/sceneutil/extradata.cpp | 19 +++ components/sceneutil/extradata.hpp | 1 + .../modding/custom-shader-effects.rst | 35 +++++ files/data/CMakeLists.txt | 1 + files/data/shaders/internal_distortion.omwfx | 25 ++++ files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++- files/shaders/compatibility/objects.frag | 17 ++- files/shaders/lib/util/distortion.glsl | 32 +++++ files/shaders/lib/util/quickstep.glsl | 4 +- 25 files changed, 311 insertions(+), 65 deletions(-) create mode 100644 apps/openmw/mwrender/distortion.cpp create mode 100644 apps/openmw/mwrender/distortion.hpp create mode 100644 files/data/shaders/internal_distortion.omwfx create mode 100644 files/shaders/lib/util/distortion.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index db44b91159..373de3683d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -24,7 +24,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples - actorutil + actorutil distortion ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/distortion.cpp b/apps/openmw/mwrender/distortion.cpp new file mode 100644 index 0000000000..2ca2ace65b --- /dev/null +++ b/apps/openmw/mwrender/distortion.cpp @@ -0,0 +1,28 @@ +#include "distortion.hpp" + +#include + +namespace MWRender +{ + void DistortionCallback::drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + osg::State* state = renderInfo.getState(); + size_t frameId = state->getFrameStamp()->getFrameNumber() % 2; + + mFBO[frameId]->apply(*state); + + const osg::Texture* tex + = mFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture(); + + glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight()); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + bin->drawImplementation(renderInfo, previous); + + tex = mOriginalFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture(); + glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight()); + mOriginalFBO[frameId]->apply(*state); + } +} diff --git a/apps/openmw/mwrender/distortion.hpp b/apps/openmw/mwrender/distortion.hpp new file mode 100644 index 0000000000..736f4ea6f2 --- /dev/null +++ b/apps/openmw/mwrender/distortion.hpp @@ -0,0 +1,28 @@ +#include + +#include + +namespace osg +{ + class FrameBufferObject; +} + +namespace MWRender +{ + class DistortionCallback : public osgUtil::RenderBin::DrawCallback + { + public: + void drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; + + void setFBO(const osg::ref_ptr& fbo, size_t frameId) { mFBO[frameId] = fbo; } + void setOriginalFBO(const osg::ref_ptr& fbo, size_t frameId) + { + mOriginalFBO[frameId] = fbo; + } + + private: + std::array, 2> mFBO; + std::array, 2> mOriginalFBO; + }; +} diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..d1cd5fed60 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -345,11 +345,9 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + primaryFBO->apply(*state); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); - else - primaryFBO->apply(*state); + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); // depth accumulation pass osg::ref_ptr restore = bin->getStateSet(); @@ -357,8 +355,7 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); bin->setStateSet(restore); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - primaryFBO->apply(*state); + primaryFBO->apply(*state); state->checkGLErrors("after DepthClearCallback::drawImplementation"); } diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 4fecdf87f9..9c8b08adfd 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -242,6 +242,10 @@ namespace MWRender if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); + if (mTextureDistortion) + node.mRootStateSet->setTextureAttribute( + PostProcessor::TextureUnits::Unit_Distortion, mTextureDistortion); + state.pushStateSet(node.mRootStateSet); state.apply(); diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index a03e3591ae..f7212a3f18 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -48,6 +48,8 @@ namespace MWRender void setTextureNormals(osg::ref_ptr tex) { mTextureNormals = tex; } + void setTextureDistortion(osg::ref_ptr tex) { mTextureDistortion = tex; } + void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; } void setPostProcessing(bool enabled) { mPostprocessing = enabled; } @@ -69,6 +71,7 @@ namespace MWRender osg::ref_ptr mTextureScene; osg::ref_ptr mTextureDepth; osg::ref_ptr mTextureNormals; + osg::ref_ptr mTextureDistortion; mutable bool mDirty = false; mutable std::vector mDirtyAttachments; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 2c77981244..1aaeb460b7 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -29,7 +29,9 @@ #include "../mwgui/postprocessorhud.hpp" +#include "distortion.hpp" #include "pingpongcull.hpp" +#include "renderbin.hpp" #include "renderingmanager.hpp" #include "sky.hpp" #include "transparentpass.hpp" @@ -103,6 +105,8 @@ namespace return Stereo::createMultiviewCompatibleAttachment(texture); } + + constexpr float DistortionRatio = 0.25; } namespace MWRender @@ -118,6 +122,7 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) + , mDistortionCallback(new DistortionCallback) { auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); @@ -141,18 +146,45 @@ namespace MWRender mHUDCamera->setCullCallback(new HUDCullCallback); mViewer->getCamera()->addCullCallback(mPingPongCull); - if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) - { - mTransparentDepthPostPass - = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); - } + // resolves the multisampled depth buffer and optionally draws an additional depth postpass + mTransparentDepthPostPass + = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), + Settings::postProcessing().mTransparentPostpass); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); + + osg::ref_ptr distortionRenderBin + = new osgUtil::RenderBin(osgUtil::RenderBin::SORT_BACK_TO_FRONT); + // This is silly to have to do, but if nothing is drawn then the drawcallback is never called and the distortion + // texture will never be cleared + osg::ref_ptr dummyNodeToClear = new osg::Node; + dummyNodeToClear->setCullingActive(false); + dummyNodeToClear->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Distortion, "Distortion"); + rootNode->addChild(dummyNodeToClear); + distortionRenderBin->setDrawCallback(mDistortionCallback); + distortionRenderBin->getStateSet()->setDefine("DISTORTION", "1", osg::StateAttribute::ON); + + // Give the renderbin access to the opaque depth sampler so it can write its occlusion + // Distorted geometry is drawn with ALWAYS depth function and depths writes disbled. + const int unitSoftEffect + = shaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); + distortionRenderBin->getStateSet()->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); + + osgUtil::RenderBin::addRenderBinPrototype("Distortion", distortionRenderBin); + + auto defines = shaderManager.getGlobalDefines(); + defines["distorionRTRatio"] = std::to_string(DistortionRatio); + shaderManager.setGlobalDefines(defines); createObjectsForFrame(0); createObjectsForFrame(1); populateTechniqueFiles(); + auto distortion = loadTechnique("internal_distortion"); + distortion->setInternal(true); + distortion->setLocked(true); + mInternalTechniques.push_back(distortion); + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -171,19 +203,6 @@ namespace MWRender else Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; - if (Settings::shaders().mSoftParticles) - { - for (int i = 0; i < 2; ++i) - { - if (Stereo::getMultiview()) - mTextures[i][Tex_OpaqueDepth] = new osg::Texture2DArray; - else - mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; - mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - } - } - mGLSLVersion = ext->glslLanguageVersion * 100; mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); @@ -281,17 +300,15 @@ namespace MWRender mCanvases[frameId]->setCalculateAvgLum(mHDR); mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId)); - if (mTransparentDepthPostPass) - mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); - else - mCanvases[frameId]->setTextureDepth(getTexture(Tex_Depth, frameId)); + mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); + mCanvases[frameId]->setTextureDistortion(getTexture(Tex_Distortion, frameId)); - if (mTransparentDepthPostPass) - { - mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; - mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; - mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; - } + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; + mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; + + mDistortionCallback->setFBO(mFbos[frameId][FBO_Distortion], frameId); + mDistortionCallback->setOriginalFBO(mFbos[frameId][FBO_Primary], frameId); size_t frame = cv->getTraversalNumber(); @@ -441,6 +458,13 @@ namespace MWRender textures[Tex_Normal]->setSourceFormat(GL_RGB); textures[Tex_Normal]->setInternalFormat(GL_RGB); + textures[Tex_Distortion]->setSourceFormat(GL_RGB); + textures[Tex_Distortion]->setInternalFormat(GL_RGB); + + Stereo::setMultiviewCompatibleTextureSize( + textures[Tex_Distortion], width * DistortionRatio, height * DistortionRatio); + textures[Tex_Distortion]->dirtyTextureObject(); + auto setupDepth = [](osg::Texture* tex) { tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); @@ -448,16 +472,8 @@ namespace MWRender }; setupDepth(textures[Tex_Depth]); - - if (!mTransparentDepthPostPass) - { - textures[Tex_OpaqueDepth] = nullptr; - } - else - { - setupDepth(textures[Tex_OpaqueDepth]); - textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); - } + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); auto& fbos = mFbos[frameId]; @@ -487,6 +503,7 @@ namespace MWRender auto normalRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); } auto depthRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); @@ -510,12 +527,13 @@ namespace MWRender Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); } - if (textures[Tex_OpaqueDepth]) - { - fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; - fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, - Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); - } + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); + + fbos[FBO_Distortion] = new osg::FrameBufferObject; + fbos[FBO_Distortion]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Distortion])); #ifdef __APPLE__ if (textures[Tex_OpaqueDepth]) @@ -575,6 +593,7 @@ namespace MWRender node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); if (mNormals) node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); @@ -582,6 +601,8 @@ namespace MWRender if (technique->getHDR()) node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); + int texUnit = Unit_NextFree; // user-defined samplers @@ -681,7 +702,7 @@ namespace MWRender disableTechnique(technique, false); - int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + int pos = std::min(location.value_or(mTechniques.size()) + mInternalTechniques.size(), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); @@ -747,6 +768,11 @@ namespace MWRender { mTechniques.clear(); + for (const auto& technique : mInternalTechniques) + { + mTechniques.push_back(technique); + } + for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) { if (techniqueName.empty()) @@ -764,7 +790,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique || technique->getDynamic()) + if (!technique || technique->getDynamic() || technique->getInternal()) continue; chain.push_back(technique->getName()); } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index e9f19bf6b5..2630467f95 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -50,12 +50,13 @@ namespace MWRender class PingPongCull; class PingPongCanvas; class TransparentDepthBinCallback; + class DistortionCallback; class PostProcessor : public osg::Group { public: - using FBOArray = std::array, 5>; - using TextureArray = std::array, 5>; + using FBOArray = std::array, 6>; + using TextureArray = std::array, 6>; using TechniqueList = std::vector>; enum TextureIndex @@ -64,7 +65,8 @@ namespace MWRender Tex_Scene_LDR, Tex_Depth, Tex_OpaqueDepth, - Tex_Normal + Tex_Normal, + Tex_Distortion, }; enum FBOIndex @@ -73,7 +75,8 @@ namespace MWRender FBO_Multisample, FBO_FirstPerson, FBO_OpaqueDepth, - FBO_Intercept + FBO_Intercept, + FBO_Distortion, }; enum TextureUnits @@ -83,6 +86,7 @@ namespace MWRender Unit_Depth, Unit_EyeAdaptation, Unit_Normals, + Unit_Distortion, Unit_NextFree }; @@ -223,6 +227,7 @@ namespace MWRender TechniqueList mTechniques; TechniqueList mTemplates; TechniqueList mQueuedTemplates; + TechniqueList mInternalTechniques; std::unordered_map mTechniqueFileMap; @@ -258,6 +263,7 @@ namespace MWRender osg::ref_ptr mPingPongCull; std::array, 2> mCanvases; osg::ref_ptr mTransparentDepthPostPass; + osg::ref_ptr mDistortionCallback; fx::DispatchArray mTemplateData; }; diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp index c14f611426..6f4ae0819b 100644 --- a/apps/openmw/mwrender/renderbin.hpp +++ b/apps/openmw/mwrender/renderbin.hpp @@ -13,7 +13,8 @@ namespace MWRender RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN RenderBin_OcclusionQuery = 11, RenderBin_FirstPerson = 12, - RenderBin_SunGlare = 13 + RenderBin_SunGlare = 13, + RenderBin_Distortion = 14, }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c175817fa8..7df5671eec 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -502,6 +502,7 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); mFog = std::make_unique(); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 76b54d55a5..cf50d20fe2 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -91,6 +91,7 @@ uniform @builtinSampler omw_SamplerLastShader; uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerNormals; +uniform @builtinSampler omw_SamplerDistortion; uniform vec4 omw_PointLights[@pointLightCount]; uniform int omw_PointLightsCount; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index ed356e0a37..0d17128e56 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -175,6 +175,9 @@ namespace fx void setLocked(bool locked) { mLocked = locked; } bool getLocked() const { return mLocked; } + void setInternal(bool internal) { mInternal = internal; } + bool getInternal() const { return mInternal; } + private: [[noreturn]] void error(const std::string& msg); @@ -295,6 +298,7 @@ namespace fx bool mDynamic = false; bool mLocked = false; + bool mInternal = false; }; template <> diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 7d420f1650..fbc7e8294c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -108,6 +108,8 @@ namespace Nif enum BSShaderFlags1 { BSSFlag1_Specular = 0x00000001, + BSSFlag1_Refraction = 0x00008000, + BSSFlag1_FireRefraction = 0x00010000, BSSFlag1_Decal = 0x04000000, BSSFlag1_DepthTest = 0x80000000, }; @@ -148,6 +150,8 @@ namespace Nif bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } + bool refraction() const { return mShaderFlags1 & BSSFlag1_Refraction; } + bool fireRefraction() const { return mShaderFlags1 & BSSFlag1_FireRefraction; } }; struct BSShaderLightingProperty : BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c30add7f77..ea4c16e402 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2381,6 +2381,8 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2438,6 +2440,8 @@ namespace NifOsg if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefractionStrength); break; } case Nif::RC_BSEffectShaderProperty: diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b3c9eee5e7..25abcfd0d8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -649,6 +649,7 @@ namespace Resource node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1, 1, 1, 1))); node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); } node->setUserValue(Misc::OsgUserValues::sFileHash, diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 720e032a61..bd82e9abba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -29,6 +29,19 @@ namespace SceneUtil node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); } + void setupDistortion(osg::Node& node, float distortionStrength) + { + static const osg::ref_ptr depth + = new SceneUtil::AutoDepth(osg::Depth::ALWAYS, 0, 1, false); + + osg::StateSet* stateset = node.getOrCreateStateSet(); + + stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength)); + + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + void ProcessExtraDataVisitor::apply(osg::Node& node) { if (!mSceneMgr->getSoftParticles()) @@ -54,6 +67,12 @@ namespace SceneUtil setupSoftEffect(node, size, falloff, falloffDepth); } + else if (key == "distortion") + { + auto strength = it.second["strength"].as(0.1f); + + setupDistortion(node, strength); + } } node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index 9b1563b78a..7054ac91b3 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -16,6 +16,7 @@ namespace osg namespace SceneUtil { void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); + void setupDistortion(osg::Node& node, float distortionStrength); class ProcessExtraDataVisitor : public osg::NodeVisitor { diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 5ea711953d..0bd1fbec85 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -54,3 +54,38 @@ Example usage. } } } + +Distortion +---------- + +This effect is used to imitate effects such as refraction and heat distortion. A common use case is to assign a normal map to the +diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture. +Blue and alpha channels are ignored. + +To use this feature the :ref:`post processing` setting must be enabled. +This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: + +:: + + [Post Processing] + enabled = false + +Variables. + ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| strength| The strength of the distortion effect. Scales linearly. | float | 0.1 | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ + +Example usage. + +:: + + omw:data { + "shader" : { + "distortion" : { + "strength": 0.12, + } + } + } diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..4e3354807c 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -96,6 +96,7 @@ set(BUILTIN_DATA_FILES shaders/adjustments.omwfx shaders/bloomlinear.omwfx shaders/debug.omwfx + shaders/internal_distortion.omwfx mygui/core.skin mygui/core.xml diff --git a/files/data/shaders/internal_distortion.omwfx b/files/data/shaders/internal_distortion.omwfx new file mode 100644 index 0000000000..b641bb6711 --- /dev/null +++ b/files/data/shaders/internal_distortion.omwfx @@ -0,0 +1,25 @@ +fragment main { + + omw_In vec2 omw_TexCoord; + + void main() + { + const float multiplier = 0.14; + + vec2 offset = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord).rg; + offset *= multiplier; + offset = clamp(offset, vec2(-1.0), vec2(1.0)); + + float occlusionFactor = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord+offset).b; + + omw_FragColor = mix(omw_GetLastShader(omw_TexCoord + offset), omw_GetLastShader(omw_TexCoord), occlusionFactor); + } +} + +technique { + description = "Internal refraction shader for OpenMW"; + version = "1.0"; + author = "OpenMW"; + passes = main; + flags = hidden; +} diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 6ead738477..ca0c264ade 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,7 @@ set(SHADER_FILES lib/particle/occlusion.glsl lib/util/quickstep.glsl lib/util/coordinates.glsl + lib/util/distortion.glsl lib/core/fragment.glsl lib/core/fragment.h.glsl lib/core/fragment_multiview.glsl diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 70d9ef7ba7..ec5f5c8978 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -26,6 +26,8 @@ uniform sampler2D normalMap; varying vec2 normalMapUV; #endif +uniform sampler2D opaqueDepthTex; + varying float euclideanDepth; varying float linearDepth; @@ -38,9 +40,11 @@ uniform float alphaRef; uniform float emissiveMult; uniform float specStrength; uniform bool useTreeAnim; +uniform float distortionStrength; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" @@ -51,6 +55,15 @@ void main() { #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + +#if defined(DISTORTION) && DISTORTION + vec2 screenCoords = gl_FragCoord.xy / (screenRes * @distorionRTRatio); + gl_FragData[0].a = getDiffuseColor().a; + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords).x); + + return; +#endif + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); #else gl_FragData[0] = vec4(1.0); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 80de6b0e9d..043aa266d8 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -66,6 +66,7 @@ uniform vec2 screenRes; uniform float near; uniform float far; uniform float alphaRef; +uniform float distortionStrength; #define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) @@ -91,6 +92,7 @@ varying vec4 passTangent; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "fog.glsl" #include "vertexcolors.glsl" @@ -100,7 +102,6 @@ varying vec4 passTangent; #if @softParticles #include "lib/particle/soft.glsl" -uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; uniform float softFalloffDepth; @@ -112,6 +113,8 @@ uniform sampler2D orthoDepthMap; varying vec3 orthoDepthMapCoord; #endif +uniform sampler2D opaqueDepthTex; + void main() { #if @particleOcclusion @@ -133,8 +136,17 @@ void main() offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif +vec2 screenCoords = gl_FragCoord.xy / screenRes; + #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); + +#if defined(DISTORTION) && DISTORTION + gl_FragData[0].a = getDiffuseColor().a; + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords / @distorionRTRatio).x); + return; +#endif + #if @diffuseParallax gl_FragData[0].a = 1.0; #else @@ -234,7 +246,6 @@ void main() gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); - vec2 screenCoords = gl_FragCoord.xy / screenRes; #if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade( viewVec, diff --git a/files/shaders/lib/util/distortion.glsl b/files/shaders/lib/util/distortion.glsl new file mode 100644 index 0000000000..e0ccf6f2ec --- /dev/null +++ b/files/shaders/lib/util/distortion.glsl @@ -0,0 +1,32 @@ +#ifndef LIB_UTIL_DISTORTION +#define LIB_UTIL_DISTORTION + +vec4 applyDistortion(in vec4 color, in float strength, in float pixelDepth, in float sceneDepth) +{ + vec4 distortion = color; + float invOcclusion = 1.0; + + // TODO: Investigate me. Alpha-clipping is enabled for refraction for what seems an arbitrary threshold, even when + // there are no associated NIF properties. + if (distortion.a < 0.1) + discard; + + distortion.b = 0.0; + +#if @reverseZ + if (pixelDepth < sceneDepth) +#else + if (pixelDepth > sceneDepth) +#endif + { + invOcclusion = 0.0; + distortion.b = 1.0; + } + distortion.rg = color.rg * 2.0 - 1.0; + + distortion.rg *= invOcclusion * strength; + + return distortion; +} + +#endif diff --git a/files/shaders/lib/util/quickstep.glsl b/files/shaders/lib/util/quickstep.glsl index 2baa0a7430..e505886337 100644 --- a/files/shaders/lib/util/quickstep.glsl +++ b/files/shaders/lib/util/quickstep.glsl @@ -4,8 +4,8 @@ float quickstep(float x) { x = clamp(x, 0.0, 1.0); - x = 1.0 - x*x; - x = 1.0 - x*x; + x = 1.0 - x * x; + x = 1.0 - x * x; return x; } From 995f0e48657ee96be702cad3e9c26378ab19cad5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 14:43:34 +0100 Subject: [PATCH 0635/2167] Fix unused-but-set-variable warning components/esm3/inventorystate.cpp:18:18: warning: variable 'index' set but not used [-Wunused-but-set-variable] uint32_t index = 0; ^ --- components/esm3/inventorystate.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index ded2d1c33b..4175159ad5 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -15,7 +15,6 @@ namespace ESM void InventoryState::load(ESMReader& esm) { // obsolete - uint32_t index = 0; while (esm.isNextSub("IOBJ")) { esm.skipHT(); @@ -29,8 +28,6 @@ namespace ESM continue; mItems.push_back(state); - - ++index; } uint32_t itemsCount = 0; From 4067e10f3f92c64e17520adfb55e16fa0dc30c1c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 19:24:02 +0100 Subject: [PATCH 0636/2167] Use gamepad cursor speed setting --- apps/openmw/mwinput/controllermanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 6d82b1e226..8e6496ddf1 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -94,7 +94,7 @@ namespace MWInput // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - const float gamepadCursorSpeed = Settings::input().mEnableController; + const float gamepadCursorSpeed = Settings::input().mGamepadCursorSpeed; const float xMove = xAxis * dt * 1500.0f / uiScale * gamepadCursorSpeed; const float yMove = yAxis * dt * 1500.0f / uiScale * gamepadCursorSpeed; From 099c39ae87bc7f699a0ca7627d6bbb25e310460a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Dec 2023 14:48:06 +0300 Subject: [PATCH 0637/2167] Use fallback weather ripple settings (bug #7292) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwrender/sky.cpp | 7 ++++++ apps/openmw/mwrender/sky.hpp | 3 +++ apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwrender/water.cpp | 30 +++++++++++++++++------ apps/openmw/mwrender/water.hpp | 5 ++-- apps/openmw/mwworld/weather.cpp | 4 +++ apps/openmw/mwworld/weather.hpp | 2 ++ components/fallback/validate.cpp | 9 ++++--- files/shaders/compatibility/water.frag | 3 ++- 11 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a92188e11..e5b84b7296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack + Bug #7292: Weather settings for disabling or enabling snow and rain ripples don't work Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c175817fa8..bb67345ba7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -847,6 +847,7 @@ namespace MWRender float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); + mWater->setRainRipplesEnabled(mSky->getRainRipplesEnabled()); mWater->update(dt, paused); if (!paused) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a38030738a..87c6d1f2e7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -257,6 +257,7 @@ namespace MWRender , mRainMaxHeight(0.f) , mRainEntranceSpeed(1.f) , mRainMaxRaindrops(0) + , mRipples(false) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) @@ -516,6 +517,11 @@ namespace MWRender return mRainNode != nullptr; } + bool SkyManager::getRainRipplesEnabled() const + { + return mRipples; + } + float SkyManager::getPrecipitationAlpha() const { if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) @@ -630,6 +636,7 @@ namespace MWRender mRainMinHeight = weather.mRainMinHeight; mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; + mRipples = weather.mRipples; mWindSpeed = weather.mWindSpeed; mBaseWindSpeed = weather.mBaseWindSpeed; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 75c6a10a50..9e4801ad7d 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -79,6 +79,8 @@ namespace MWRender bool hasRain() const; + bool getRainRipplesEnabled() const; + float getPrecipitationAlpha() const; void setRainSpeed(float speed); @@ -194,6 +196,7 @@ namespace MWRender float mRainMaxHeight; float mRainEntranceSpeed; int mRainMaxRaindrops; + bool mRipples; float mWindSpeed; float mBaseWindSpeed; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 1018724595..9e279fb5c2 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -77,6 +77,7 @@ namespace MWRender float mRainSpeed; float mRainEntranceSpeed; int mRainMaxRaindrops; + bool mRipples; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 85df70adfc..553bdeeaaa 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -205,21 +205,25 @@ namespace MWRender } }; - class RainIntensityUpdater : public SceneUtil::StateSetUpdater + class RainSettingsUpdater : public SceneUtil::StateSetUpdater { public: - RainIntensityUpdater() + RainSettingsUpdater() : mRainIntensity(0.f) + , mEnableRipples(false) { } void setRainIntensity(float rainIntensity) { mRainIntensity = rainIntensity; } + void setRipplesEnabled(bool enableRipples) { mEnableRipples = enableRipples; } protected: void setDefaults(osg::StateSet* stateset) override { osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); stateset->addUniform(rainIntensityUniform.get()); + osg::ref_ptr enableRainRipplesUniform = new osg::Uniform("enableRainRipples", false); + stateset->addUniform(enableRainRipplesUniform.get()); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override @@ -227,10 +231,14 @@ namespace MWRender osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); if (rainIntensityUniform != nullptr) rainIntensityUniform->set(mRainIntensity); + osg::ref_ptr enableRainRipplesUniform = stateset->getUniform("enableRainRipples"); + if (enableRainRipplesUniform != nullptr) + enableRainRipplesUniform->set(mEnableRipples); } private: float mRainIntensity; + bool mEnableRipples; }; class Refraction : public SceneUtil::RTTNode @@ -430,7 +438,7 @@ namespace MWRender Water::Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico) - : mRainIntensityUpdater(nullptr) + : mRainSettingsUpdater(nullptr) , mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) @@ -579,7 +587,7 @@ namespace MWRender node->setStateSet(stateset); node->setUpdateCallback(nullptr); - mRainIntensityUpdater = nullptr; + mRainSettingsUpdater = nullptr; // Add animated textures std::vector> textures; @@ -711,8 +719,8 @@ namespace MWRender normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRainIntensityUpdater = new RainIntensityUpdater(); - node->setUpdateCallback(mRainIntensityUpdater); + mRainSettingsUpdater = new RainSettingsUpdater(); + node->setUpdateCallback(mRainSettingsUpdater); mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, std::move(program), normalMap); @@ -801,8 +809,14 @@ namespace MWRender void Water::setRainIntensity(float rainIntensity) { - if (mRainIntensityUpdater) - mRainIntensityUpdater->setRainIntensity(rainIntensity); + if (mRainSettingsUpdater) + mRainSettingsUpdater->setRainIntensity(rainIntensity); + } + + void Water::setRainRipplesEnabled(bool enableRipples) + { + if (mRainSettingsUpdater) + mRainSettingsUpdater->setRipplesEnabled(enableRipples); } void Water::update(float dt, bool paused) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 0204cb4303..d3241bf3a7 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -46,13 +46,13 @@ namespace MWRender class Refraction; class Reflection; class RippleSimulation; - class RainIntensityUpdater; + class RainSettingsUpdater; class Ripples; /// Water rendering class Water { - osg::ref_ptr mRainIntensityUpdater; + osg::ref_ptr mRainSettingsUpdater; osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; @@ -113,6 +113,7 @@ namespace MWRender void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); void setRainIntensity(const float rainIntensity); + void setRainRipplesEnabled(bool enableRipples); void update(float dt, bool paused); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..5e2d7d3fec 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -175,6 +175,7 @@ namespace MWWorld , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) , mParticleEffect(particleEffect) , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mRipples(Fallback::Map::getBool("Weather_" + name + "_Ripples")) , mStormDirection(Weather::defaultDirection()) , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) @@ -1129,6 +1130,7 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + mResult.mRipples = current.mRipples; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; @@ -1241,6 +1243,7 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + mResult.mRipples = current.mRipples; } else { @@ -1257,6 +1260,7 @@ namespace MWWorld mResult.mRainMinHeight = other.mRainMinHeight; mResult.mRainMaxHeight = other.mRainMaxHeight; mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + mResult.mRipples = other.mRipples; } } } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 65f926c096..d40f7030c8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -191,6 +191,8 @@ namespace MWWorld std::string mRainEffect; + bool mRipples; + osg::Vec3f mStormDirection; float mCloudsMaximumPercent; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 300d2b5dd1..2a6d1817c9 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -11,7 +11,10 @@ static const std::set allowedKeysInt = { "LightAttenuation_Lin "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip" }; + "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", + "Weather_Clear_Ripples", "Weather_Cloudy_Ripples", "Weather_Foggy_Ripples", "Weather_Overcast_Ripples", + "Weather_Rain_Ripples", "Weather_Thunderstorm_Ripples", "Weather_Ashstorm_Ripples", "Weather_Blight_Ripples", + "Weather_Snow_Ripples", "Weather_Blizzard_Ripples" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", @@ -160,7 +163,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Rain_Ambient_Sunrise_Color", "Weather_Rain_Ambient_Sunset_Color", "Weather_Rain_Cloud_Texture", "Weather_Rain_Fog_Day_Color", "Weather_Rain_Fog_Night_Color", "Weather_Rain_Fog_Sunrise_Color", "Weather_Rain_Fog_Sunset_Color", "Weather_Rain_Rain_Loop_Sound_ID", "Weather_Rain_Ripple_Radius", - "Weather_Rain_Ripples", "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", "Weather_Rain_Ripples_Per_Drop", + "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", "Weather_Rain_Ripples_Per_Drop", "Weather_Rain_Sky_Day_Color", "Weather_Rain_Sky_Night_Color", "Weather_Rain_Sky_Sunrise_Color", "Weather_Rain_Sky_Sunset_Color", "Weather_Rain_Sun_Day_Color", "Weather_Rain_Sun_Disc_Sunset_Color", "Weather_Rain_Sun_Night_Color", "Weather_Rain_Sun_Sunrise_Color", "Weather_Rain_Sun_Sunset_Color", @@ -168,7 +171,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Snow_Ambient_Sunrise_Color", "Weather_Snow_Ambient_Sunset_Color", "Weather_Snow_Cloud_Texture", "Weather_Snow_Fog_Day_Color", "Weather_Snow_Fog_Night_Color", "Weather_Snow_Fog_Sunrise_Color", "Weather_Snow_Fog_Sunset_Color", "Weather_Snow_Gravity_Scale", "Weather_Snow_High_Kill", "Weather_Snow_Low_Kill", - "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", "Weather_Snow_Ripples", "Weather_Snow_Ripple_Scale", + "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", "Weather_Snow_Ripple_Scale", "Weather_Snow_Ripple_Speed", "Weather_Snow_Ripples_Per_Flake", "Weather_Snow_Sky_Day_Color", "Weather_Snow_Sky_Night_Color", "Weather_Snow_Sky_Sunrise_Color", "Weather_Snow_Sky_Sunset_Color", "Weather_Snow_Snow_Diameter", "Weather_Snow_Snow_Entrance_Speed", "Weather_Snow_Snow_Height_Max", diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 987dab10a6..7ae1720185 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -81,6 +81,7 @@ uniform float near; uniform float far; uniform float rainIntensity; +uniform bool enableRainRipples; uniform vec2 screenRes; @@ -113,7 +114,7 @@ void main(void) vec4 rainRipple; - if (rainIntensity > 0.01) + if (rainIntensity > 0.01 && enableRainRipples) rainRipple = rainCombined(position.xy/1000.0, waterTimer) * clamp(rainIntensity, 0.0, 1.0); else rainRipple = vec4(0.0); From 9d357e0b04d09803f3ac77c5bdb699df5a3dd8af Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 14:23:12 +0100 Subject: [PATCH 0638/2167] Fix script to install luadocumentor 78577b255d19a1f4f4f539662e00357936b73c33 is a part of https://gitlab.com/ptmikheev/openmw-luadocumentor.git repo. Before doing checkout need to change the directory. --- docs/source/install_luadocumentor_in_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install_luadocumentor_in_docker.sh b/docs/source/install_luadocumentor_in_docker.sh index 6d24f3e9f3..fd7fcdb0e6 100755 --- a/docs/source/install_luadocumentor_in_docker.sh +++ b/docs/source/install_luadocumentor_in_docker.sh @@ -30,8 +30,8 @@ PATH=$PATH:~/luarocks/bin echo "Install openmwluadocumentor" git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git -git checkout 78577b255d19a1f4f4f539662e00357936b73c33 cd openmw-luadocumentor +git checkout 78577b255d19a1f4f4f539662e00357936b73c33 luarocks make luarocks/openmwluadocumentor-0.2.0-1.rockspec cd ~ rm -r openmw-luadocumentor From af40d7ce8077d88a50109cb8f5258c04d9c74506 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Dec 2023 15:50:36 +0100 Subject: [PATCH 0639/2167] End pursue package if the target doens't have a bounty --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aipursue.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 12282a515d..e0baac0764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1152,7 +1152,7 @@ namespace MWMechanics if (npcStats.getCrimeId() != -1) { // if you've paid for your crimes and I haven't noticed - if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId() || playerStats.getBounty() <= 0) + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 2ae4ce5def..699e96cd32 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -12,6 +12,7 @@ #include "actorutil.hpp" #include "character.hpp" #include "creaturestats.hpp" +#include "npcstats.hpp" namespace MWMechanics { @@ -47,6 +48,9 @@ namespace MWMechanics if (target.getClass().getCreatureStats(target).isDead()) return true; + if (target.getClass().getNpcStats(target).getBounty() <= 0) + return true; + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); // Set the target destination From 5c10727380006d6d8c29e5e394d1d38fe794c3f4 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:06:16 -0600 Subject: [PATCH 0640/2167] Feat(CS): Add definition files for selection group record type --- components/CMakeLists.txt | 2 +- components/esm3/selectiongroup.cpp | 38 ++++++++++++++++++++++++++++++ components/esm3/selectiongroup.hpp | 34 ++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 components/esm3/selectiongroup.cpp create mode 100644 components/esm3/selectiongroup.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..ca6b644d15 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,7 +166,7 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp formatversion landrecorddata + infoorder timestamp formatversion landrecorddata selectiongroup ) add_component_dir (esmterrain diff --git a/components/esm3/selectiongroup.cpp b/components/esm3/selectiongroup.cpp new file mode 100644 index 0000000000..6b819a4bbc --- /dev/null +++ b/components/esm3/selectiongroup.cpp @@ -0,0 +1,38 @@ +#include "selectiongroup.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + void SelectionGroup::load(ESMReader& esm, bool& isDeleted) + { + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().toInt()) + { + case fourCC("SELC"): + mId = esm.getRefId(); + break; + case fourCC("SELI"): + selectedInstances.push_back(esm.getRefId().getRefIdString()); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } + + void SelectionGroup::save(ESMWriter& esm, bool isDeleted) const + { + esm.writeHNCRefId("SELC", mId); + for (std::string id : selectedInstances) + esm.writeHNCString("SELI", id); + } + + void SelectionGroup::blank() {} + +} diff --git a/components/esm3/selectiongroup.hpp b/components/esm3/selectiongroup.hpp new file mode 100644 index 0000000000..021f3c95d5 --- /dev/null +++ b/components/esm3/selectiongroup.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTS_ESM_SELECTIONGROUP_H +#define COMPONENTS_ESM_SELECTIONGROUP_H + +#include + +#include "components/esm/defs.hpp" +#include "components/esm/refid.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct SelectionGroup + { + constexpr static RecNameInts sRecordId = REC_SELG; + + static constexpr std::string_view getRecordType() { return "SelectionGroup"; } + + uint32_t mRecordFlags = 0; + + RefId mId; + + std::vector selectedInstances; + + void load(ESMReader& esm, bool& isDeleted); + void save(ESMWriter& esm, bool isDeleted = false) const; + + /// Set record to default state (does not touch the ID). + void blank(); + }; +} + +#endif From 24443e00bffdbdb16d98e5427c3ac6a668616658 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:08:34 -0600 Subject: [PATCH 0641/2167] Feat(CS): Implement selection groups into data model --- apps/opencs/model/doc/saving.cpp | 4 ++++ apps/opencs/model/world/columnimp.cpp | 31 +++++++++++++++++++++++++ apps/opencs/model/world/columnimp.hpp | 12 ++++++++++ apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 26 +++++++++++++++++++++ apps/opencs/model/world/data.hpp | 6 +++++ apps/opencs/model/world/universalid.cpp | 1 + apps/opencs/model/world/universalid.hpp | 1 + components/esm/defs.hpp | 2 ++ 9 files changed, 85 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index b2e4d4649a..ed785c38fe 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "../world/data.hpp" #include "../world/idcollection.hpp" @@ -52,6 +53,9 @@ CSMDoc::Saving::Saving(Document& document, const std::filesystem::path& projectP appendStage(new WriteCollectionStage>( mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getSelectionGroups(), mState, CSMWorld::Scope_Project)); + appendStage(new CloseSaveStage(mState)); // save content file diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 981ec5278d..89190023c6 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -333,6 +333,37 @@ namespace CSMWorld return true; } + SelectionGroupColumn::SelectionGroupColumn() + : Column(Columns::ColumnId_SelectionGroupObjects, ColumnBase::Display_None) + { + } + + QVariant SelectionGroupColumn::get(const Record& record) const + { + QVariant data; + QStringList selectionInfo; + const std::vector& instances = record.get().selectedInstances; + + for (std::string instance : instances) + selectionInfo << QString::fromStdString(instance); + data.setValue(selectionInfo); + + return data; + } + + void SelectionGroupColumn::set(Record& record, const QVariant& data) + { + ESM::SelectionGroup record2 = record.get(); + for (const auto& item : data.toStringList()) + record2.selectedInstances.push_back(item.toStdString()); + record.setModified(record2); + } + + bool SelectionGroupColumn::isEditable() const + { + return false; + } + std::optional getSkillIndex(std::string_view value) { int index = ESM::Skill::refIdToIndex(ESM::RefId::stringRefId(value)); diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index da805d5c6d..74b81cebc1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -2391,6 +2392,17 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; + + struct SelectionGroupColumn : public Column + { + SelectionGroupColumn(); + + QVariant get(const Record& record) const override; + + void set(Record& record, const QVariant& data) override; + + bool isEditable() const override; + }; } // This is required to access the type as a QVariant. diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 92f41a2f20..dff8b2d7dd 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -347,6 +347,8 @@ namespace CSMWorld ColumnId_LevelledCreatureId = 315, + ColumnId_SelectionGroupObjects = 316, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index e8fd138ab4..9b137a6602 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -620,6 +620,10 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mDebugProfiles.addColumn(new DescriptionColumn); mDebugProfiles.addColumn(new ScriptColumn(ScriptColumn::Type_Lines)); + mSelectionGroups.addColumn(new StringIdColumn); + mSelectionGroups.addColumn(new RecordStateColumn); + mSelectionGroups.addColumn(new SelectionGroupColumn); + mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); mMetaData.addColumn(new StringIdColumn(true)); @@ -664,6 +668,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture); addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video); addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData); + addModel(new IdTable(&mSelectionGroups), UniversalId::Type_SelectionGroup); mActorAdapter = std::make_unique(*this); @@ -908,6 +913,16 @@ CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() return mDebugProfiles; } +CSMWorld::IdCollection& CSMWorld::Data::getSelectionGroups() +{ + return mSelectionGroups; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getSelectionGroups() const +{ + return mSelectionGroups; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLand() const { return mLand; @@ -1369,6 +1384,17 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages) mDebugProfiles.load(*mReader, mBase); break; + case ESM::REC_SELG: + + if (!mProject) + { + unhandledRecord = true; + break; + } + + mSelectionGroups.load(*mReader, mBase); + break; + default: unhandledRecord = true; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 1b63986eac..8e01452f3b 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ namespace CSMWorld IdCollection mBodyParts; IdCollection mMagicEffects; IdCollection mDebugProfiles; + IdCollection mSelectionGroups; IdCollection mSoundGens; IdCollection mStartScripts; NestedInfoCollection mTopicInfos; @@ -251,6 +253,10 @@ namespace CSMWorld IdCollection& getDebugProfiles(); + const IdCollection& getSelectionGroups() const; + + IdCollection& getSelectionGroups(); + const IdCollection& getLand() const; IdCollection& getLand(); diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index dec533b015..0a6b9c8e7c 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -68,6 +68,7 @@ namespace ":./resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", ":./debug-profile.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", ":./sound-generator.png" }, diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 2d3385bcb4..6bee62cf93 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -133,6 +133,7 @@ namespace CSMWorld Type_LandTexture, Type_Pathgrids, Type_Pathgrid, + Type_SelectionGroup, Type_StartScripts, Type_StartScript, Type_Search, diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 6254830f63..cbc70582c0 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -170,6 +170,8 @@ namespace ESM // format 1 REC_FILT = esm3Recname("FILT"), REC_DBGP = esm3Recname("DBGP"), ///< only used in project files + REC_SELG = esm3Recname("SELG"), + REC_LUAL = esm3Recname("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon) // format 16 - Lua scripts in saved games From 0ec6dcbf1fd555efb23accbb80cca718f2ea9d5f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:09:08 -0600 Subject: [PATCH 0642/2167] Feat(Settings): Implement shortcuts for hiding refs & selection groups --- apps/opencs/model/prefs/state.cpp | 23 +++++++++++++++++++++++ apps/opencs/model/prefs/values.hpp | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d44c9de888..3386530429 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -413,6 +413,29 @@ void CSMPrefs::State::declare() declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); + declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space)); + declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H)); + declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H)); + declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1)); + declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1)); + declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2)); + declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2)); + declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3)); + declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3)); + declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4)); + declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4)); + declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5)); + declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5)); + declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6)); + declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6)); + declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7)); + declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7)); + declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8)); + declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8)); + declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9)); + declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9)); + declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0)); + declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0)); declareSubcategory("1st/Free Camera"); declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 4683258e57..80d2f8f182 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -477,6 +477,29 @@ namespace CSMPrefs Settings::SettingValue mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" }; Settings::SettingValue mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" }; Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; + Settings::SettingValue mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" }; + Settings::SettingValue mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" }; + Settings::SettingValue mSceneToggleHidden{ mIndex, sName, "scene-toggle-visibility", "H" }; + Settings::SettingValue mSceneSelectGroup1{ mIndex, sName, "scene-group-1", "1" }; + Settings::SettingValue mSceneSaveGroup1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; + Settings::SettingValue mSceneSelectGroup2{ mIndex, sName, "scene-group-2", "2" }; + Settings::SettingValue mSceneSaveGroup2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; + Settings::SettingValue mSceneSelectGroup3{ mIndex, sName, "scene-group-3", "3" }; + Settings::SettingValue mSceneSaveGroup3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; + Settings::SettingValue mSceneSelectGroup4{ mIndex, sName, "scene-group-4", "4" }; + Settings::SettingValue mSceneSaveGroup4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; + Settings::SettingValue mSceneSelectGroup5{ mIndex, sName, "scene-group-5", "5" }; + Settings::SettingValue mSceneSaveGroup5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; + Settings::SettingValue mSceneSelectGroup6{ mIndex, sName, "scene-group-6", "6" }; + Settings::SettingValue mSceneSaveGroup6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; + Settings::SettingValue mSceneSelectGroup7{ mIndex, sName, "scene-group-7", "7" }; + Settings::SettingValue mSceneSaveGroup7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; + Settings::SettingValue mSceneSelectGroup8{ mIndex, sName, "scene-group-8", "8" }; + Settings::SettingValue mSceneSaveGroup8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; + Settings::SettingValue mSceneSelectGroup9{ mIndex, sName, "scene-group-9", "9" }; + Settings::SettingValue mSceneSaveGroup9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; + Settings::SettingValue mSceneSelectGroup10{ mIndex, sName, "scene-group-0", "0" }; + Settings::SettingValue mSceneSaveGroup10{ mIndex, sName, "scene-save-0", "Ctrl+0" }; Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; From cf098033b9bdfe041df87c80f9b555846cdc684c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:09:32 -0600 Subject: [PATCH 0643/2167] Feat(Mask.hpp): Add mask for hidden objects --- apps/opencs/view/render/mask.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 818be8b228..7f767e19ac 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -11,6 +11,7 @@ namespace CSVRender enum Mask : unsigned int { // elements that are part of the actual scene + Mask_Hidden = 0x0, Mask_Reference = 0x2, Mask_Pathgrid = 0x4, Mask_Water = 0x8, From 9d155afc15508fcccaec055f52c208d1845651ad Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:10:55 -0600 Subject: [PATCH 0644/2167] Feat(worldspacewidget.hpp): Add virtual decs for selection functions --- apps/opencs/view/render/worldspacewidget.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 442f4922f0..b7391c1691 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -201,6 +201,12 @@ namespace CSVRender virtual std::vector> getSelection(unsigned int elementMask) const = 0; + virtual void selectGroup(const std::vector) const = 0; + + virtual void hideGroup(const std::vector) const = 0; + + virtual void unhideAll() const = 0; + virtual std::vector> getEdited(unsigned int elementMask) const = 0; virtual void setSubMode(int subMode, unsigned int elementMask) = 0; @@ -300,6 +306,8 @@ namespace CSVRender void speedMode(bool activate); + void toggleHiddenInstances(); + protected slots: void elementSelectionChanged(); From 94eadd436d7b9710f476b14d60bb8a9b1e2c532f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:11:23 -0600 Subject: [PATCH 0645/2167] Feat(worldspacewidget.cpp): Implement shortcut for visibility switching & unhiding all instances --- apps/opencs/view/render/worldspacewidget.cpp | 24 ++++++++++++++++++++ apps/opencs/view/render/worldspacewidget.hpp | 2 -- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 6911f5f043..3c7dc34efb 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -50,6 +50,7 @@ #include "cameracontroller.hpp" #include "instancemode.hpp" +#include "mask.hpp" #include "object.hpp" #include "pathgridmode.hpp" @@ -135,6 +136,12 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag); + connect(new CSMPrefs::Shortcut("scene-toggle-visibility", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::toggleHiddenInstances); + + connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::unhideAll); + mInConstructor = false; } @@ -740,6 +747,23 @@ void CSVRender::WorldspaceWidget::speedMode(bool activate) mSpeedMode = activate; } +void CSVRender::WorldspaceWidget::toggleHiddenInstances() +{ + const std::vector> selection = getSelection(Mask_Reference); + + if (selection.empty()) + return; + + const CSVRender::ObjectTag* firstSelection = dynamic_cast(selection.begin()->get()); + + const CSVRender::Mask firstMask + = firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden; + + for (const auto& object : selection) + if (const auto objectTag = dynamic_cast(object.get())) + objectTag->mObject->getRootNode()->setNodeMask(firstMask); +} + void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) { if (activate) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index b7391c1691..a6d87440f1 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -203,8 +203,6 @@ namespace CSVRender virtual void selectGroup(const std::vector) const = 0; - virtual void hideGroup(const std::vector) const = 0; - virtual void unhideAll() const = 0; virtual std::vector> getEdited(unsigned int elementMask) const = 0; From 8edc1484183d10e948bfe9577bc3e3b7571dd2d9 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:20:35 -0600 Subject: [PATCH 0646/2167] Feat(CS): Implement select/unhide functions into interior & exterior worldspace widgets --- apps/opencs/view/render/pagedworldspacewidget.cpp | 12 ++++++++++++ apps/opencs/view/render/pagedworldspacewidget.hpp | 4 ++++ apps/opencs/view/render/unpagedworldspacewidget.cpp | 10 ++++++++++ apps/opencs/view/render/unpagedworldspacewidget.hpp | 4 ++++ 4 files changed, 30 insertions(+) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 00d519ecc8..fab706e3ca 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -875,6 +875,18 @@ std::vector> CSVRender::PagedWorldspaceWidget:: return result; } +void CSVRender::PagedWorldspaceWidget::selectGroup(std::vector group) const +{ + for (const auto& [_, cell] : mCells) + cell->selectFromGroup(group); +} + +void CSVRender::PagedWorldspaceWidget::unhideAll() const +{ + for (const auto& [_, cell] : mCells) + cell->unhideAll(); +} + std::vector> CSVRender::PagedWorldspaceWidget::getEdited( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 9ba8911c7e..3d2ab97e89 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -163,6 +163,10 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; + void selectGroup(const std::vector group) const override; + + void unhideAll() const override; + std::vector> getEdited(unsigned int elementMask) const override; void setSubMode(int subMode, unsigned int elementMask) override; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index fee608b200..ea99294c28 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -199,6 +199,16 @@ std::vector> CSVRender::UnpagedWorldspaceWidget return mCell->getSelection(elementMask); } +void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector group) const +{ + mCell->selectFromGroup(group); +} + +void CSVRender::UnpagedWorldspaceWidget::unhideAll() const +{ + mCell->unhideAll(); +} + std::vector> CSVRender::UnpagedWorldspaceWidget::getEdited( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 10446354e9..22a0475fe2 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -93,6 +93,10 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; + void selectGroup(const std::vector group) const override; + + void unhideAll() const override; + std::vector> getEdited(unsigned int elementMask) const override; void setSubMode(int subMode, unsigned int elementMask) override; From f287914f1eb256a2f5e12aa24af62c559cdff13a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:22:36 -0600 Subject: [PATCH 0647/2167] Feat(cell.cpp): Add select/unhide functions in cell.cpp --- apps/opencs/view/render/cell.cpp | 19 +++++++++++++++++++ apps/opencs/view/render/cell.hpp | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index c74782f2d1..a1f3c0c41f 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -612,6 +612,25 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele return result; } +void CSVRender::Cell::selectFromGroup(const std::vector group) +{ + for (const auto& [_, object] : mObjects) + if (auto found = std::ranges::find_if(group.begin(), group.end(), + [&object](const std::string& id) { return object->getReferenceId() == id; }); + found != group.end()) + object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); +} + +void CSVRender::Cell::unhideAll() +{ + for (const auto& [_, object] : mObjects) + { + osg::ref_ptr rootNode = object->getRootNode(); + if (rootNode->getNodeMask() == Mask_Hidden) + rootNode->setNodeMask(Mask_Reference); + } +} + std::vector> CSVRender::Cell::getSelection(unsigned int elementMask) const { std::vector> result; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index cf50604c29..52e01c631a 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -148,6 +148,10 @@ namespace CSVRender // already selected void selectAllWithSameParentId(int elementMask); + void selectFromGroup(const std::vector group); + + void unhideAll(); + void handleSelectDrag(Object* object, DragMode dragMode); void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); From 23e75bed8f7bd327e095b2127c770f5b03d4fa88 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:22:56 -0600 Subject: [PATCH 0648/2167] Feat(object.cpp): Make object outline an optional argument when selecting it --- apps/opencs/view/render/object.cpp | 5 ++--- apps/opencs/view/render/object.hpp | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 7782ce36c9..0e114ac06e 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include @@ -485,7 +484,7 @@ CSVRender::Object::~Object() mParentNode->removeChild(mRootNode); } -void CSVRender::Object::setSelected(bool selected) +void CSVRender::Object::setSelected(bool selected, osg::Vec4f color) { mSelected = selected; @@ -499,7 +498,7 @@ void CSVRender::Object::setSelected(bool selected) mRootNode->removeChild(mBaseNode); if (selected) { - mOutline->setWireframeColor(osg::Vec4f(1, 1, 1, 1)); + mOutline->setWireframeColor(color); mOutline->addChild(mBaseNode); mRootNode->addChild(mOutline); } diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 436c410c84..9b18b99561 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -138,7 +139,7 @@ namespace CSVRender ~Object(); /// Mark the object as selected, selected objects show an outline effect - void setSelected(bool selected); + void setSelected(bool selected, osg::Vec4f color = osg::Vec4f(1, 1, 1, 1)); bool getSelected() const; From a7f8ee11064c833a52ac11ffedde44c3aed65546 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:23:57 -0600 Subject: [PATCH 0649/2167] Feat(instancemode.cpp): Implement save/load selection group functions --- apps/opencs/view/render/instancemode.cpp | 76 ++++++++++++++++++++++++ apps/opencs/view/render/instancemode.hpp | 5 ++ 2 files changed, 81 insertions(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 7332d9c84b..2d79c912f6 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -186,6 +186,71 @@ osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, con return mousePlanePoint; } +void CSVRender::InstanceMode::saveSelectionGroup(const int group) +{ + QStringList strings; + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QVariant selectionObjects; + CSMWorld::CommandMacro macro(undoStack, "Replace Selection Group"); + std::string groupName = "project::" + std::to_string(group); + + const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); + const int selectionObjectsIndex + = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); + + if (dynamic_cast(&getWorldspaceWidget())) + groupName += "-ext"; + else + groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); + + CSMWorld::CreateCommand* newGroup = new CSMWorld::CreateCommand(*mSelectionGroups, groupName); + + newGroup->setType(CSMWorld::UniversalId::Type_SelectionGroup); + + for (const auto& object : selection) + if (const CSVRender::ObjectTag* objectTag = dynamic_cast(object.get())) + strings << QString::fromStdString(objectTag->mObject->getReferenceId()); + + selectionObjects.setValue(strings); + + newGroup->addValue(selectionObjectsIndex, selectionObjects); + + if (mSelectionGroups->getModelIndex(groupName, 0).row() != -1) + macro.push(new CSMWorld::DeleteCommand(*mSelectionGroups, groupName)); + + macro.push(newGroup); + + getWorldspaceWidget().clearSelection(Mask_Reference); +} + +void CSVRender::InstanceMode::getSelectionGroup(const int group) +{ + std::string groupName = "project::" + std::to_string(group); + std::vector targets; + + const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); + const int selectionObjectsIndex + = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); + + if (dynamic_cast(&getWorldspaceWidget())) + groupName += "-ext"; + else + groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); + + const QModelIndex groupSearch = mSelectionGroups->getModelIndex(groupName, selectionObjectsIndex); + + if (groupSearch.row() == -1) + return; + + for (const QString& target : groupSearch.data().toStringList()) + targets.push_back(target.toStdString()); + + if (!selection.empty()) + getWorldspaceWidget().clearSelection(Mask_Reference); + + getWorldspaceWidget().selectGroup(targets); +} + CSVRender::InstanceMode::InstanceMode( WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, @@ -199,6 +264,9 @@ CSVRender::InstanceMode::InstanceMode( , mUnitScaleDist(1) , mParentNode(std::move(parentNode)) { + mSelectionGroups = dynamic_cast( + worldspaceWidget->getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_SelectionGroup)); + connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); @@ -229,6 +297,14 @@ CSVRender::InstanceMode::InstanceMode( = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToTerrainSeparately); + + for (short i = 0; i <= 9; i++) + { + connect(new CSMPrefs::Shortcut("scene-group-" + std::to_string(i), worldspaceWidget), + qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->getSelectionGroup(i); }); + connect(new CSMPrefs::Shortcut("scene-save-" + std::to_string(i), worldspaceWidget), + qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->saveSelectionGroup(i); }); + } } void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 917fde301a..4e0172759a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -14,6 +14,8 @@ #include "editmode.hpp" #include "instancedragmodes.hpp" +#include +#include class QDragEnterEvent; class QDropEvent; @@ -60,6 +62,7 @@ namespace CSVRender osg::ref_ptr mParentNode; osg::Vec3 mDragStart; std::vector mObjectsAtDragStart; + CSMWorld::IdTable* mSelectionGroups; int getSubModeFromId(const std::string& id) const; @@ -133,6 +136,8 @@ namespace CSVRender void subModeChanged(const std::string& id); void deleteSelectedInstances(); void cloneSelectedInstances(); + void getSelectionGroup(const int group); + void saveSelectionGroup(const int group); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToCollisionSeparately(); From 33ce7782e9d8cb3d353cca4812b9efa0f816c567 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:27:50 -0600 Subject: [PATCH 0650/2167] Feat(worldspacewidget.cpp): Add shortcut to clear selection --- apps/opencs/view/render/worldspacewidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 3c7dc34efb..da02c1e179 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -142,6 +142,9 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::unhideAll); + connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + [this] { this->clearSelection(Mask_Reference); }); + mInConstructor = false; } From 25f3e09da9a5551ceefe88d768fc16e88ad50080 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sat, 23 Dec 2023 13:29:06 -0600 Subject: [PATCH 0651/2167] Fix(CS): Correct build issues on some compilers --- apps/opencs/view/render/cell.cpp | 13 +++++++++---- apps/opencs/view/render/instancemode.hpp | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a1f3c0c41f..b0c8425ad3 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -615,10 +615,15 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele void CSVRender::Cell::selectFromGroup(const std::vector group) { for (const auto& [_, object] : mObjects) - if (auto found = std::ranges::find_if(group.begin(), group.end(), - [&object](const std::string& id) { return object->getReferenceId() == id; }); - found != group.end()) - object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); + { + for (const auto& objectName : group) + { + if (objectName == object->getReferenceId()) + { + object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); + } + } + } } void CSVRender::Cell::unhideAll() diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 4e0172759a..9267823e22 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -14,8 +14,8 @@ #include "editmode.hpp" #include "instancedragmodes.hpp" +#include #include -#include class QDragEnterEvent; class QDropEvent; From 0cf55d36172ad676d49ece4368277a73a32d74dc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 18:37:48 +0100 Subject: [PATCH 0652/2167] Use RecastGlobalAllocator for Detour --- .../detournavigator/recastglobalallocator.hpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/recastglobalallocator.hpp b/components/detournavigator/recastglobalallocator.hpp index 956f050a72..f69805eb97 100644 --- a/components/detournavigator/recastglobalallocator.hpp +++ b/components/detournavigator/recastglobalallocator.hpp @@ -3,6 +3,7 @@ #include "recasttempallocator.hpp" +#include #include #include @@ -14,10 +15,14 @@ namespace DetourNavigator public: static void init() { instance(); } - static void* alloc(size_t size, rcAllocHint hint) + static void* recastAlloc(size_t size, rcAllocHint hint) { return alloc(size, hint == RC_ALLOC_TEMP); } + + static void* detourAlloc(size_t size, dtAllocHint hint) { return alloc(size, hint == DT_ALLOC_TEMP); } + + static void* alloc(size_t size, bool temp) { void* result = nullptr; - if (rcLikely(hint == RC_ALLOC_TEMP)) + if (rcLikely(temp)) result = tempAllocator().alloc(size); if (rcUnlikely(!result)) result = allocPerm(size); @@ -38,7 +43,11 @@ namespace DetourNavigator } private: - RecastGlobalAllocator() { rcAllocSetCustom(&RecastGlobalAllocator::alloc, &RecastGlobalAllocator::free); } + RecastGlobalAllocator() + { + rcAllocSetCustom(&RecastGlobalAllocator::recastAlloc, &RecastGlobalAllocator::free); + dtAllocSetCustom(&RecastGlobalAllocator::detourAlloc, &RecastGlobalAllocator::free); + } static RecastGlobalAllocator& instance() { From 329500b0876ac1b31facbc267eebc46186bbd4bc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 18:53:38 +0100 Subject: [PATCH 0653/2167] Remove redundant return --- components/detournavigator/recasttempallocator.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/detournavigator/recasttempallocator.hpp b/components/detournavigator/recasttempallocator.hpp index bb23dc8494..08db5f9fca 100644 --- a/components/detournavigator/recasttempallocator.hpp +++ b/components/detournavigator/recasttempallocator.hpp @@ -52,7 +52,6 @@ namespace DetourNavigator mTop = mPrev; mPrev = getTempPtrPrev(mTop); } - return; } private: From edaac852d16bf03ff8e0a6154fb6a4e221f8af76 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 09:26:59 +0100 Subject: [PATCH 0654/2167] Use settings values to declare bool settings --- apps/opencs/model/prefs/boolsetting.cpp | 2 +- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 59 +++++++++++++------------ apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c7520c415d..44262e2012 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,7 +11,7 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index fd67019a78..edabf85058 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs public: explicit BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d44c9de888..1c922ee8d2 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -38,11 +38,11 @@ void CSMPrefs::State::declare() declareInt(mValues->mWindows.mDefaultHeight, "Default window height") .setTooltip("Newly opened top-level windows will open with this height.") .setMin(80); - declareBool("show-statusbar", "Show Status Bar", true) + declareBool(mValues->mWindows.mShowStatusbar, "Show Status Bar") .setTooltip( "If a newly open top level window is showing status bars or not. " " Note that this does not affect existing windows."); - declareBool("reuse", "Reuse Subviews", true) + declareBool(mValues->mWindows.mReuse, "Reuse Subviews") .setTooltip( "When a new subview is requested and a matching subview already " " exist, do not open a new subview and use the existing one instead."); @@ -51,7 +51,7 @@ void CSMPrefs::State::declare() "If the maximum number is reached and a new subview is opened " "it will be placed into a new top-level window.") .setRange(1, 256); - declareBool("hide-subview", "Hide single subview", false) + declareBool(mValues->mWindows.mHideSubview, "Hide single subview") .setTooltip( "When a view contains only a single subview, hide the subview title " "bar and if this subview is closed also close the view (unless it is the last " @@ -67,7 +67,7 @@ void CSMPrefs::State::declare() .addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.") .addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - declareBool("grow-limit", "Grow Limit Screen", false) + declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen") .setTooltip( "When \"Grow then Scroll\" option is selected, the window size grows to" " the width of the virtual desktop. \nIf this option is selected the the window growth" @@ -103,20 +103,21 @@ void CSMPrefs::State::declare() .addValue(jumpAndSelect) .addValue("Jump Only", "Scroll new record into view") .addValue("No Jump", "No special action"); - declareBool("extended-config", "Manually specify affected record types for an extended delete/revert", false) + declareBool( + mValues->mIdTables.mExtendedConfig, "Manually specify affected record types for an extended delete/revert") .setTooltip( "Delete and revert commands have an extended form that also affects " "associated records.\n\n" "If this option is enabled, types of affected records are selected " "manually before a command execution.\nOtherwise, all associated " "records are deleted/reverted immediately."); - declareBool("subview-new-window", "Open Record in new window", false) + declareBool(mValues->mIdTables.mSubviewNewWindow, "Open Record in new window") .setTooltip( "When editing a record, open the view in a new window," " rather than docked in the main view."); declareCategory("ID Dialogues"); - declareBool("toolbar", "Show toolbar", true); + declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); declareCategory("Reports"); EnumValue actionNone("None"); @@ -131,22 +132,23 @@ void CSMPrefs::State::declare() declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues); declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues); declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues); - declareBool("ignore-base-records", "Ignore base records in verifier", false); + declareBool(mValues->mReports.mIgnoreBaseRecords, "Ignore base records in verifier"); declareCategory("Search & Replace"); declareInt(mValues->mSearchAndReplace.mCharBefore, "Characters before search string") .setTooltip("Maximum number of character to display in search result before the searched text"); declareInt(mValues->mSearchAndReplace.mCharAfter, "Characters after search string") .setTooltip("Maximum number of character to display in search result after the searched text"); - declareBool("auto-delete", "Delete row from result table after a successful replace", true); + declareBool(mValues->mSearchAndReplace.mAutoDelete, "Delete row from result table after a successful replace"); declareCategory("Scripts"); - declareBool("show-linenum", "Show Line Numbers", true) + declareBool(mValues->mScripts.mShowLinenum, "Show Line Numbers") .setTooltip( "Show line numbers to the left of the script editor window." "The current row and column numbers of the text cursor are shown at the bottom."); - declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor."); - declareBool("mono-font", "Use monospace font", true); + declareBool(mValues->mScripts.mWrapLines, "Wrap Lines") + .setTooltip("Wrap lines longer than width of script editor."); + declareBool(mValues->mScripts.mMonoFont, "Use monospace font"); declareInt(mValues->mScripts.mTabWidth, "Tab Width") .setTooltip("Number of characters for tab width") .setRange(1, 10); @@ -155,12 +157,12 @@ void CSMPrefs::State::declare() .addValue("Ignore", "Do not report warning") .addValue(warningsNormal) .addValue("Strict", "Promote warning to an error"); - declareBool("toolbar", "Show toolbar", true); + declareBool(mValues->mScripts.mToolbar, "Show toolbar"); declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") .setRange(0, 10000); declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); - declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); + declareBool(mValues->mScripts.mHighlightOccurrences, "Highlight other occurrences of selected names"); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); @@ -171,7 +173,7 @@ void CSMPrefs::State::declare() declareColour("colour-id", "Highlight Colour: IDs", QColor("blue")); declareCategory("General Input"); - declareBool("cycle", "Cyclic next/previous", false) + declareBool(mValues->mGeneralInput.mCycle, "Cyclic next/previous") .setTooltip( "When using next/previous functions at the last/first item of a " "list go to the first/last item"); @@ -182,19 +184,19 @@ void CSMPrefs::State::declare() declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); - declareBool("p-navi-free-invert", "Invert Free Camera Mouse Input", false); + declareBool(mValues->mSceneInput.mPNaviFreeInvert, "Invert Free Camera Mouse Input"); declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); - declareBool("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); + declareBool(mValues->mSceneInput.mPNaviOrbitInvert, "Invert Orbit Camera Mouse Input"); declareDouble("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) .setRange(0.001, 1000.0); - declareBool("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); + declareBool(mValues->mSceneInput.mNaviOrbitConstRoll, "Keep camera roll constant for orbital camera"); - declareBool("context-select", "Context Sensitive Selection", false); + declareBool(mValues->mSceneInput.mContextSelect, "Context Sensitive Selection"); declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0); declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0) @@ -207,12 +209,12 @@ void CSMPrefs::State::declare() .setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".") .setRange(0, 10000); declareInt(mValues->mRendering.mCameraFov, "Camera FOV").setRange(10, 170); - declareBool("camera-ortho", "Orthographic projection for camera", false); + declareBool(mValues->mRendering.mCameraOrtho, "Orthographic projection for camera"); declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); - declareBool("scene-use-gradient", "Use Gradient Background", true); + declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) .setTooltip( @@ -228,11 +230,11 @@ void CSMPrefs::State::declare() .setTooltip( "Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); - declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true); + declareBool(mValues->mRendering.mSceneDayNightSwitchNodes, "Use Day/Night Switch Nodes"); declareCategory("Tooltips"); - declareBool("scene", "Show Tooltips in 3D scenes", true); - declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false); + declareBool(mValues->mTooltips.mScene, "Show Tooltips in 3D scenes"); + declareBool(mValues->mTooltips.mSceneHideBasic, "Hide basic 3D scenes tooltips"); declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); EnumValue createAndInsert("Create cell and insert"); @@ -282,7 +284,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") .setMin(1); - declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false) + declareBool(mValues->mSceneEditing.mLandeditPostSmoothpainting, "Smooth land after painting height") .setTooltip("Raise and lower tools will leave bumpy finish without this option"); declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25) .setTooltip( @@ -290,7 +292,7 @@ void CSMPrefs::State::declare() "Negative values may be used to roughen instead of smooth.") .setMin(-1) .setMax(1); - declareBool("open-list-view", "Open displays list view", false) + declareBool(mValues->mSceneEditing.mOpenListView, "Open displays list view") .setTooltip( "When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); @@ -487,12 +489,13 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, return *setting; } -CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const QString& label, bool default_) +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::BoolSetting* setting + = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 1d44f4b71a..a5fd5b0936 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -67,7 +67,7 @@ namespace CSMPrefs DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); - BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); + BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); From 81f7149f42d9452fd4af0f5e754ca4361073496c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2023 19:00:16 +0400 Subject: [PATCH 0655/2167] Use a multiplication sign for custom resolution --- apps/launcher/ui/graphicspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index 70ab1f0728..c0e2b0be06 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -107,7 +107,7 @@ - x + × From 1ca2a0ef66a89cae6627161761c0376cf69b775b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2023 14:10:27 +0400 Subject: [PATCH 0656/2167] Store generated UI by pointer to avoid redundant processing --- apps/launcher/CMakeLists.txt | 1 - apps/opencs/CMakeLists.txt | 1 - .../contentselector/view/contentselector.cpp | 94 ++++++++++++------- .../contentselector/view/contentselector.hpp | 27 ++++-- 4 files changed, 81 insertions(+), 42 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 8d2208c9df..aa3970efdb 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -41,7 +41,6 @@ set(LAUNCHER_UI ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/settingspage.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/directorypicker.ui - ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index cea2b66331..70efd06090 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -139,7 +139,6 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc ) set (OPENCS_UI - ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/filedialog.ui ) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 62d476b944..3f75b82487 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,5 +1,7 @@ #include "contentselector.hpp" +#include "ui_contentselector.h" + #include #include @@ -9,14 +11,15 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts) : QObject(parent) + , ui(std::make_unique()) { - ui.setupUi(parent); - ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); + ui->setupUi(parent); + ui->addonView->setDragDropMode(QAbstractItemView::InternalMove); if (!showOMWScripts) { - ui.languageComboBox->setHidden(true); - ui.refreshButton->setHidden(true); + ui->languageComboBox->setHidden(true); + ui->refreshButton->setHidden(true); } buildContentModel(showOMWScripts); @@ -24,21 +27,23 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool show buildAddonView(); } +ContentSelectorView::ContentSelector::~ContentSelector() = default; + void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { - QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); + QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->addItem(tr("")); - ui.gameFileView->setVisible(true); + ui->gameFileView->addItem(tr("")); + ui->gameFileView->setVisible(true); - connect(ui.gameFileView, qOverload(&ComboBox::currentIndexChanged), this, + connect(ui->gameFileView, qOverload(&ComboBox::currentIndexChanged), this, &ContentSelector::slotCurrentGameFileIndexChanged); - ui.gameFileView->setCurrentIndex(0); + ui->gameFileView->setCurrentIndex(0); } class AddOnProxyModel : public QSortFilterProxyModel @@ -60,9 +65,34 @@ public: } }; +bool ContentSelectorView::ContentSelector::isGamefileSelected() const +{ + return ui->gameFileView->currentIndex() > 0; +} + +QWidget* ContentSelectorView::ContentSelector::uiWidget() const +{ + return ui->contentGroupBox; +} + +QComboBox* ContentSelectorView::ContentSelector::languageBox() const +{ + return ui->languageComboBox; +} + +QToolButton* ContentSelectorView::ContentSelector::refreshButton() const +{ + return ui->refreshButton; +} + +QLineEdit* ContentSelectorView::ContentSelector::searchFilter() const +{ + return ui->searchFilter; +} + void ContentSelectorView::ContentSelector::buildAddonView() { - ui.addonView->setVisible(true); + ui->addonView->setVisible(true); mAddonProxyModel = new AddOnProxyModel(this); mAddonProxyModel->setFilterRegularExpression(searchFilter()->text()); @@ -70,12 +100,12 @@ void ContentSelectorView::ContentSelector::buildAddonView() mAddonProxyModel->setDynamicSortFilter(true); mAddonProxyModel->setSourceModel(mContentModel); - connect(ui.searchFilter, &QLineEdit::textEdited, mAddonProxyModel, &QSortFilterProxyModel::setFilterWildcard); - connect(ui.searchFilter, &QLineEdit::textEdited, this, &ContentSelector::slotSearchFilterTextChanged); + connect(ui->searchFilter, &QLineEdit::textEdited, mAddonProxyModel, &QSortFilterProxyModel::setFilterWildcard); + connect(ui->searchFilter, &QLineEdit::textEdited, this, &ContentSelector::slotSearchFilterTextChanged); - ui.addonView->setModel(mAddonProxyModel); + ui->addonView->setModel(mAddonProxyModel); - connect(ui.addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); + connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::signalAddonDataChanged); buildContextMenu(); @@ -83,10 +113,10 @@ void ContentSelectorView::ContentSelector::buildAddonView() void ContentSelectorView::ContentSelector::buildContextMenu() { - ui.addonView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui.addonView, &QTableView::customContextMenuRequested, this, &ContentSelector::slotShowContextMenu); + ui->addonView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->addonView, &QTableView::customContextMenuRequested, this, &ContentSelector::slotShowContextMenu); - mContextMenu = new QMenu(ui.addonView); + mContextMenu = new QMenu(ui->addonView); mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); @@ -116,7 +146,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename) if (!filename.isEmpty()) { const ContentSelectorModel::EsmFile* file = mContentModel->item(filename); - index = ui.gameFileView->findText(file->fileName()); + index = ui->gameFileView->findText(file->fileName()); // verify that the current index is also checked in the model if (!mContentModel->setCheckState(filename, true)) @@ -126,7 +156,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename) } } - ui.gameFileView->setCurrentIndex(index); + ui->gameFileView->setCurrentIndex(index); } void ContentSelectorView::ContentSelector::clearCheckStates() @@ -143,7 +173,7 @@ void ContentSelectorView::ContentSelector::setContentList(const QStringList& lis { if (list.isEmpty()) { - slotCurrentGameFileIndexChanged(ui.gameFileView->currentIndex()); + slotCurrentGameFileIndexChanged(ui->gameFileView->currentIndex()); } else mContentModel->setContentList(list); @@ -164,14 +194,14 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne // add any game files to the combo box for (const QString& gameFileName : mContentModel->gameFiles()) { - if (ui.gameFileView->findText(gameFileName) == -1) + if (ui->gameFileView->findText(gameFileName) == -1) { - ui.gameFileView->addItem(gameFileName); + ui->gameFileView->addItem(gameFileName); } } - if (ui.gameFileView->currentIndex() != 0) - ui.gameFileView->setCurrentIndex(0); + if (ui->gameFileView->currentIndex() != 0) + ui->gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); mContentModel->checkForLoadOrderErrors(); @@ -194,10 +224,10 @@ void ContentSelectorView::ContentSelector::clearFiles() QString ContentSelectorView::ContentSelector::currentFile() const { - QModelIndex currentIdx = ui.addonView->currentIndex(); + QModelIndex currentIdx = ui->addonView->currentIndex(); - if (!currentIdx.isValid() && ui.gameFileView->currentIndex() > 0) - return ui.gameFileView->currentText(); + if (!currentIdx.isValid() && ui->gameFileView->currentIndex() > 0) + return ui->gameFileView->currentText(); QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); return mContentModel->data(idx, Qt::DisplayRole).toString(); @@ -225,7 +255,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected) { - QString fileName = ui.gameFileView->itemText(index); + QString fileName = ui->gameFileView->itemText(index); const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName); if (file != nullptr) { @@ -253,14 +283,14 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QMo void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos) { - QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos); + QPoint globalPos = ui->addonView->viewport()->mapToGlobal(pos); mContextMenu->exec(globalPos); } void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) { Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; - for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes()) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState) @@ -284,7 +314,7 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() { QClipboard* clipboard = QApplication::clipboard(); QString filepaths; - for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes()) { int row = mAddonProxyModel->mapToSource(index).row(); const ContentSelectorModel::EsmFile* file = mContentModel->item(row); @@ -299,5 +329,5 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText) { - ui.addonView->setDragEnabled(newText.isEmpty()); + ui->addonView->setDragEnabled(newText.isEmpty()); } diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 5919d2e516..48377acb86 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -1,13 +1,22 @@ #ifndef CONTENTSELECTOR_HPP #define CONTENTSELECTOR_HPP -#include +#include + +#include +#include +#include +#include -#include "ui_contentselector.h" #include class QSortFilterProxyModel; +namespace Ui +{ + class ContentSelector; +} + namespace ContentSelectorView { class ContentSelector : public QObject @@ -23,6 +32,8 @@ namespace ContentSelectorView public: explicit ContentSelector(QWidget* parent = nullptr, bool showOMWScripts = false); + ~ContentSelector() override; + QString currentFile() const; void addFiles(const QString& path, bool newfiles = false); @@ -39,18 +50,18 @@ namespace ContentSelectorView void setGameFile(const QString& filename = QString("")); - bool isGamefileSelected() const { return ui.gameFileView->currentIndex() > 0; } + bool isGamefileSelected() const; - QWidget* uiWidget() const { return ui.contentGroupBox; } + QWidget* uiWidget() const; - QComboBox* languageBox() const { return ui.languageComboBox; } + QComboBox* languageBox() const; - QToolButton* refreshButton() const { return ui.refreshButton; } + QToolButton* refreshButton() const; - QLineEdit* searchFilter() const { return ui.searchFilter; } + QLineEdit* searchFilter() const; private: - Ui::ContentSelector ui; + std::unique_ptr ui; void buildContentModel(bool showOMWScripts); void buildGameFileView(); From 01e2e56f9777eb85f474b96a8139c78136047b59 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Dec 2023 17:55:49 +0000 Subject: [PATCH 0657/2167] Add game-independent VFS directory to CS' VFS This should have been like this all along - all the other applications that use the game's VFS do this. --- apps/opencs/editor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index c21fc12a05..05f90b96f3 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -200,6 +200,8 @@ std::pair> CS::Editor::readConfig dataDirs.insert(dataDirs.end(), dataLocal.begin(), dataLocal.end()); + dataDirs.insert(dataDirs.begin(), mResources / "vfs"); + // iterate the data directories and add them to the file dialog for loading mFileDialog.addFiles(dataDirs); From 03a7643301ad7f607233f597f89cf5bd844a8b66 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 00:58:29 +0100 Subject: [PATCH 0658/2167] Add option to show timeseries delta graph to osg_stats.py To see spikes in a single frame and correlate them with frame duration. --- scripts/osg_stats.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d8dab2ad1a..79f0a8a0b9 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -27,6 +27,8 @@ import termtables help='Show a graph for given metric over time.') @click.option('--commulative_timeseries', type=str, multiple=True, help='Show a graph for commulative sum of a given metric over time.') +@click.option('--timeseries_delta', type=str, multiple=True, + help='Show a graph for delta between neighbouring frames of a given metric over time.') @click.option('--hist', type=str, multiple=True, help='Show a histogram for all values of given metric.') @click.option('--hist_ratio', nargs=2, type=str, multiple=True, @@ -47,6 +49,8 @@ import termtables help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') @click.option('--commulative_timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') +@click.option('--timeseries_delta_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') @click.option('--stats_sum', is_flag=True, help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, @@ -69,7 +73,8 @@ import termtables def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, commulative_timeseries, commulative_timeseries_sum, frame_number_name, - hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by): + hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, + timeseries_delta, timeseries_delta_sum): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} if not show_common_path_prefix and len(sources) > 1: longest_common_prefix = os.path.commonprefix(list(sources.keys())) @@ -92,6 +97,9 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if commulative_timeseries: draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) + if timeseries_delta: + draw_timeseries_delta(sources=frames, keys=matching_keys(timeseries_delta), add_sum=timeseries_delta_sum, + begin_frame=begin_frame, end_frame=end_frame) if hist: draw_hists(sources=frames, keys=matching_keys(hist)) if hist_ratio: @@ -186,6 +194,20 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig.canvas.manager.set_window_title('commulative_timeseries') +def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): + fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(begin_frame + 1, end_frame)) + for name, frames in sources.items(): + for key in keys: + ax.plot(x, numpy.diff(frames[key]), label=f'{key}:{name}') + if add_sum: + ax.plot(x, numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', + linestyle='--') + ax.grid(True) + ax.legend() + fig.canvas.manager.set_window_title('timeseries_delta') + + def draw_hists(sources, keys): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( From 26ffde0c048ae7f0e529adaa232407e8fb945992 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Dec 2023 14:21:28 +0100 Subject: [PATCH 0659/2167] Reduce ccache size for gcc to 3G To avoid having jobs being unable to generate the cache like https://gitlab.com/OpenMW/openmw/-/jobs/5766643208: Creating cache Ubuntu_GCC.ubuntu_22.04.v1-3-non_protected... apt-cache/: found 1082 matching artifact files and directories ccache/: found 7443 matching artifact files and directories FATAL: write ../../../cache/OpenMW/openmw/Ubuntu_GCC.ubuntu_22.04.v1-3-non_protected/archive_991882895: no space left on device Failed to create cache --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 44655466ea..63f5bfb45e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,7 +142,7 @@ Ubuntu_GCC: variables: CC: gcc CXX: g++ - CCACHE_SIZE: 4G + CCACHE_SIZE: 3G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h @@ -193,7 +193,7 @@ Ubuntu_GCC_Debug: variables: CC: gcc CXX: g++ - CCACHE_SIZE: 4G + CCACHE_SIZE: 3G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. From c6b7dfc23a494f244114ddf64fb3e22bc3cb969b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 22:55:11 +0100 Subject: [PATCH 0660/2167] Convert to float to compute stdev --- scripts/osg_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 79f0a8a0b9..8c504dcad7 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -350,7 +350,7 @@ def make_stats(source, key, values, precision): sum=fixed_float(sum(values), precision), mean=fixed_float(statistics.mean(values), precision), median=fixed_float(statistics.median(values), precision), - stdev=fixed_float(statistics.stdev(values), precision), + stdev=fixed_float(statistics.stdev(float(v) for v in values), precision), q95=fixed_float(numpy.quantile(values, 0.95), precision), ) From 60940e7561696b3526f5e3db5447b75105a70efb Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 22:09:42 +0100 Subject: [PATCH 0661/2167] Fill absent values with previous present Due to OSG stats reporting usage and implementation for some attributes values are missing on loading screens. --- scripts/osg_stats.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 8c504dcad7..3cdd0febae 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -143,7 +143,7 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): end_frame = min(end_frame, max(v[-1][frame_number_name] for v in sources.values()) + 1) for name in sources.keys(): for key in keys: - result[name][key] = [0] * (end_frame - begin_frame) + result[name][key] = [None] * (end_frame - begin_frame) for name, frames in sources.items(): for frame in frames: number = frame[frame_number_name] @@ -154,7 +154,14 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): result[name][key][index] = frame[key] for name in result.keys(): for key in keys: - result[name][key] = numpy.array(result[name][key]) + prev = 0.0 + values = result[name][key] + for i in range(len(values)): + if values[i] is not None: + prev = values[i] + else: + values[i] = prev + result[name][key] = numpy.array(values) return result, begin_frame, end_frame From f2c284688bc6a2a375b0641bcb8cf79abc69656f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 25 Dec 2023 11:29:36 +0400 Subject: [PATCH 0662/2167] Make content selector tooltip localizable --- components/contentselector/model/esmfile.cpp | 7 ------- components/contentselector/model/esmfile.hpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 741ed173a2..75a0adb45e 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -4,13 +4,6 @@ #include int ContentSelectorModel::EsmFile::sPropertyCount = 7; -QString ContentSelectorModel::EsmFile::sToolTip = QString( - "Author: %1
\ - Version: %2
\ - Modified: %3
\ - Path:
%4
\ -
Description:
%5
\ -
Dependencies: %6
"); ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) : ModelItem(parent) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 1ffaa8fe72..5a04ec8b38 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -59,7 +59,7 @@ namespace ContentSelectorModel QString description() const { return mDescription; } QString toolTip() const { - return sToolTip.arg(mAuthor) + return mTooltipTemlate.arg(mAuthor) .arg(mVersion) .arg(mModified.toString(Qt::ISODate)) .arg(mPath) @@ -72,9 +72,16 @@ namespace ContentSelectorModel public: static int sPropertyCount; - static QString sToolTip; private: + QString mTooltipTemlate = tr( + "Author: %1
" + "Version: %2
" + "Modified: %3
" + "Path:
%4
" + "
Description:
%5
" + "
Dependencies: %6
"); + QString mFileName; QString mAuthor; QDateTime mModified; From e5af4322c7bc3bfc28b9f0e2ad9678687a9956bd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 21:25:07 +0100 Subject: [PATCH 0663/2167] Add flag to print all openmw output in integration tests --- scripts/integration_tests.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py index da2b9aa7fe..41c6c3a5a2 100755 --- a/scripts/integration_tests.py +++ b/scripts/integration_tests.py @@ -13,6 +13,7 @@ parser.add_argument("--omw", type=str, default="openmw", help="path to openmw bi parser.add_argument( "--workdir", type=str, default="integration_tests_output", help="directory for temporary files and logs" ) +parser.add_argument("--verbose", action='store_true', help="print all openmw output") args = parser.parse_args() example_suite_dir = Path(args.example_suite).resolve() @@ -78,7 +79,10 @@ def runTest(name): ) as process: quit_requested = False for line in process.stdout: - stdout_lines.append(line) + if args.verbose: + sys.stdout.write(line) + else: + stdout_lines.append(line) words = line.split(" ") if len(words) > 1 and words[1] == "E]": print(line, end="") @@ -102,7 +106,7 @@ def runTest(name): exit_ok = False if os.path.exists(config_dir / "openmw.log"): shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log") - if not exit_ok: + if not exit_ok and not args.verbose: sys.stdout.writelines(stdout_lines) if test_success and exit_ok: print(f"{name} succeeded") From 8fe0832b382ac2eac6be0301b8e44bedfa714c78 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 21:26:18 +0100 Subject: [PATCH 0664/2167] Replace check for normalized distance =~ value by distance > 0 We just need to make sure player moved, for how long is not important. To avoid failures like: https://gitlab.com/OpenMW/openmw/-/jobs/5815281969: TEST playerForwardRunning FAILED [string "test.lua"]:80: [string "player.lua"]:44: Normalized forward runned distance: 0.782032 ~= 1.000000 --- scripts/data/integration_tests/test_lua_api/player.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 41022828f9..544bf5adc0 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -40,8 +40,7 @@ testing.registerLocalTest('playerForwardRunning', coroutine.yield() end local direction, distance = (self.position - startPos):normalize() - local normalizedDistance = distance / types.Actor.runSpeed(self) - testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized forward runned distance') + testing.expectGreaterThan(distance, 0, 'Run forward, distance') testing.expectEqualWithDelta(direction.x, 0, 0.1, 'Run forward, X coord') testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord') end) @@ -59,8 +58,7 @@ testing.registerLocalTest('playerDiagonalWalking', coroutine.yield() end local direction, distance = (self.position - startPos):normalize() - local normalizedDistance = distance / types.Actor.walkSpeed(self) - testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized diagonally walked distance') + testing.expectGreaterThan(distance, 0, 'Walk diagonally, distance') testing.expectEqualWithDelta(direction.x, -0.707, 0.1, 'Walk diagonally, X coord') testing.expectEqualWithDelta(direction.y, -0.707, 0.1, 'Walk diagonally, Y coord') end) From 3363616f56dbf9a2c31467a6056183ee5169fd9d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Dec 2023 15:22:00 +0100 Subject: [PATCH 0665/2167] Remove redundant startsWith function --- components/vfs/manager.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index bfc001e4f2..ec8939f5dc 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -70,21 +70,13 @@ namespace VFS return found->second->getPath(); } - namespace - { - bool startsWith(std::string_view text, std::string_view start) - { - return text.rfind(start, 0) == 0; - } - } - Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const { if (path.empty()) return { mIndex.begin(), mIndex.end() }; std::string normalized = Path::normalizeFilename(path); const auto it = mIndex.lower_bound(normalized); - if (it == mIndex.end() || !startsWith(it->first, normalized)) + if (it == mIndex.end() || !it->first.starts_with(normalized)) return { it, it }; ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; From 0d8dc5aabc8a62bf6a6733dd87afac708c63eb24 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Dec 2023 15:20:48 +0100 Subject: [PATCH 0666/2167] Use string_view for VFS lookups --- apps/openmw_test_suite/testing_util.hpp | 4 ++-- components/vfs/archive.hpp | 7 +++++-- components/vfs/bsaarchive.hpp | 4 ++-- components/vfs/filesystemarchive.cpp | 10 ++++------ components/vfs/filesystemarchive.hpp | 8 +++----- components/vfs/manager.cpp | 6 +++--- components/vfs/manager.hpp | 8 ++++---- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 89fb7f9e73..0c941053a7 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -57,13 +57,13 @@ namespace TestingOpenMW { } - void listResources(std::map& out) override + void listResources(VFS::FileMap& out) override { for (const auto& [key, value] : mFiles) out.emplace(VFS::Path::normalizeFilename(key), value); } - bool contains(const std::string& file) const override { return mFiles.count(file) != 0; } + bool contains(std::string_view file) const override { return mFiles.contains(file); } std::string getDescription() const override { return "TestData"; } }; diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index e377e8c5b6..79c876b391 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -19,16 +20,18 @@ namespace VFS virtual std::filesystem::path getPath() = 0; }; + using FileMap = std::map>; + class Archive { public: virtual ~Archive() = default; /// List all resources contained in this archive. - virtual void listResources(std::map& out) = 0; + virtual void listResources(FileMap& out) = 0; /// True if this archive contains the provided normalized file. - virtual bool contains(const std::string& file) const = 0; + virtual bool contains(std::string_view file) const = 0; virtual std::string getDescription() const = 0; }; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 517c95e273..29098db45d 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -48,7 +48,7 @@ namespace VFS virtual ~BsaArchive() {} - void listResources(std::map& out) override + void listResources(FileMap& out) override { for (auto& resource : mResources) { @@ -59,7 +59,7 @@ namespace VFS } } - bool contains(const std::string& file) const override + bool contains(std::string_view file) const override { for (const auto& it : mResources) { diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 96f5e87f5e..7d88dd9cc0 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -17,7 +17,7 @@ namespace VFS { } - void FileSystemArchive::listResources(std::map& out) + void FileSystemArchive::listResources(FileMap& out) { if (!mBuiltIndex) { @@ -51,14 +51,12 @@ namespace VFS } else { - for (index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) - { - out[it->first] = &it->second; - } + for (auto& [k, v] : mIndex) + out[k] = &v; } } - bool FileSystemArchive::contains(const std::string& file) const + bool FileSystemArchive::contains(std::string_view file) const { return mIndex.find(file) != mIndex.end(); } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 39d711b327..e31ef9bd30 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -27,16 +27,14 @@ namespace VFS public: FileSystemArchive(const std::filesystem::path& path); - void listResources(std::map& out) override; + void listResources(FileMap& out) override; - bool contains(const std::string& file) const override; + bool contains(std::string_view file) const override; std::string getDescription() const override; private: - typedef std::map index; - index mIndex; - + std::map> mIndex; bool mBuiltIndex; std::filesystem::path mPath; }; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index ec8939f5dc..cc231847f5 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -35,11 +35,11 @@ namespace VFS return getNormalized(Path::normalizeFilename(name)); } - Files::IStreamPtr Manager::getNormalized(const std::string& normalizedName) const + Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { - std::map::const_iterator found = mIndex.find(normalizedName); + const auto found = mIndex.find(normalizedName); if (found == mIndex.end()) - throw std::runtime_error("Resource '" + normalizedName + "' not found"); + throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found"); return found->second->open(); } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index db38e4b240..76405aae2c 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -41,7 +41,7 @@ namespace VFS class RecursiveDirectoryIterator { public: - RecursiveDirectoryIterator(std::map::const_iterator it) + RecursiveDirectoryIterator(FileMap::const_iterator it) : mIt(it) { } @@ -55,7 +55,7 @@ namespace VFS } private: - std::map::const_iterator mIt; + FileMap::const_iterator mIt; }; using RecursiveDirectoryRange = IteratorPair; @@ -83,7 +83,7 @@ namespace VFS /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. - Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + Files::IStreamPtr getNormalized(std::string_view normalizedName) const; std::string getArchive(std::string_view name) const; @@ -101,7 +101,7 @@ namespace VFS private: std::vector> mArchives; - std::map mIndex; + FileMap mIndex; }; } From 71e33cf8b2441a6d008f8106e407852417431935 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Dec 2023 00:28:47 +0100 Subject: [PATCH 0667/2167] Add unit tests for GenericObjectCache --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../resource/testobjectcache.cpp | 308 ++++++++++++++++++ components/resource/objectcache.hpp | 2 +- 3 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/resource/testobjectcache.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 4f93319c96..203c4ac2db 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -94,6 +94,8 @@ file(GLOB UNITTEST_SRC_FILES nifosg/testnifloader.cpp esmterrain/testgridsampling.cpp + + resource/testobjectcache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp new file mode 100644 index 0000000000..5b7741025a --- /dev/null +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -0,0 +1,308 @@ +#include + +#include +#include + +#include + +namespace Resource +{ + namespace + { + using namespace ::testing; + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheShouldReturnNullptrByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCache(42), nullptr); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldReturnNulloptByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(42), std::nullopt); + } + + struct Object : osg::Object + { + Object() = default; + + Object(const Object& other, const osg::CopyOp& copyOp = osg::CopyOp()) + : osg::Object(other, copyOp) + { + } + + META_Object(ResourceTest, Object) + }; + + TEST(ResourceGenericObjectCacheTest, shouldStoreValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_EQ(cache->getRefFromObjectCache(key), value); + } + + TEST(ResourceGenericObjectCacheTest, shouldStoreNullptrValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(nullptr)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldExtendLifetimeForItemsWithZeroTimestamp) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value, 0); + value = nullptr; + + const double referenceTime = 1000; + const double expiryDelay = 1; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldReplaceExistingItemByKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key, value1); + ASSERT_EQ(cache->getRefFromObjectCache(key), value1); + cache->addEntryToObjectCache(key, value2); + EXPECT_EQ(cache->getRefFromObjectCache(key), value2); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldMarkLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr, referenceTime + expiryDelay); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + 2 * expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime + expiryDelay); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldRemoveExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(value)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredNullptrItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldNotExtendItemLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + cache->addEntryToObjectCache("a", nullptr); + cache->addEntryToObjectCache("c", nullptr); + EXPECT_THAT(cache->lowerBound(std::string_view("b")), Optional(Pair("c", _))); + } + + TEST(ResourceGenericObjectCacheTest, shouldSupportRemovingItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + ASSERT_EQ(cache->getRefFromObjectCache(key), value); + cache->removeFromObjectCache(key); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, clearShouldRemoveAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key1 = 42; + const int key2 = 13; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key1, value1); + cache->addEntryToObjectCache(key2, value2); + + ASSERT_EQ(cache->getRefFromObjectCache(key1), value1); + ASSERT_EQ(cache->getRefFromObjectCache(key2), value2); + + cache->clear(); + + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key1), std::nullopt); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key2), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, callShouldIterateOverAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + std::vector> actual; + cache->call([&](int key, osg::Object* value) { actual.emplace_back(key, value); }); + + EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); + } + + TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(13, value1); + cache->addEntryToObjectCache(42, value2); + + EXPECT_EQ(cache->getCacheSize(), 2); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(4, value3); + + EXPECT_THAT(cache->lowerBound(3), Optional(Pair(4, value3))); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnNulloptWhenKeyIsGreaterThanAnyOther) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + EXPECT_EQ(cache->lowerBound(4), std::nullopt); + } + } +} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 881729ffc4..f8a5843395 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -180,7 +180,7 @@ namespace Resource /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template - void call(Functor& f) + void call(Functor&& f) { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) From ffffb427f5102ad69355b901e38dc746c33bc13e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Sep 2023 21:58:10 +0400 Subject: [PATCH 0668/2167] Implement crime disposition modifier (bug 4683) --- CHANGELOG.md | 1 + apps/openmw/mwgui/dialogue.cpp | 11 ++ apps/openmw/mwmechanics/actors.cpp | 3 + .../mwmechanics/mechanicsmanagerimp.cpp | 124 ++++++++++++++---- apps/openmw/mwmechanics/npcstats.cpp | 18 +++ apps/openmw/mwmechanics/npcstats.hpp | 5 + apps/openmw/mwscript/miscextensions.cpp | 6 +- components/esm3/formatversion.hpp | 2 +- components/esm3/npcstats.cpp | 7 + components/esm3/npcstats.hpp | 1 + 10 files changed, 149 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..8d8d6f8ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bug #4382: Sound output device does not change when it should Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4683: Disposition decrease when player commits crime is not implemented properly Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 79673463ef..0e44b8c03e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -23,9 +23,11 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "bookpage.hpp" #include "textcolours.hpp" @@ -736,6 +738,15 @@ namespace MWGui bool dispositionVisible = false; if (!mPtr.isEmpty() && mPtr.getClass().isNpc()) { + // If actor was a witness to a crime which was payed off, + // restore original disposition immediately. + MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); + if (npcStats.getCrimeId() != -1 && npcStats.getCrimeDispositionModifier() != 0) + { + if (npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId()) + npcStats.setCrimeDispositionModifier(0); + } + dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition( diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..afb7ec3e8e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1164,6 +1164,9 @@ namespace MWMechanics creatureStats.setAlarmed(false); creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr)); + // Restore original disposition + npcStats.setCrimeDispositionModifier(0); + // Update witness crime id npcStats.setCrimeId(-1); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8a64af1cbd..3a253d9dc3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -473,8 +473,8 @@ namespace MWMechanics int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp) { - const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); - float x = static_cast(npcSkill.getBaseDisposition()); + const MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + float x = static_cast(npcStats.getBaseDisposition() + npcStats.getCrimeDispositionModifier()); MWWorld::LiveCellRef* npc = ptr.get(); MWWorld::Ptr playerPtr = getPlayer(); @@ -1287,62 +1287,134 @@ namespace MWMechanics if (!canReportCrime(actor, victim, playerFollowers)) continue; - if (reported && actor.getClass().isClass(actor, "guard")) + NpcStats& observerStats = actor.getClass().getNpcStats(actor); + + int alarm = observerStats.getAiSetting(AiSetting::Alarm).getBase(); + float alarmTerm = 0.01f * alarm; + + bool isActorVictim = actor == victim; + float dispTerm = isActorVictim ? dispVictim : disp; + + bool isActorGuard = actor.getClass().isClass(actor, "guard"); + + int currentDisposition = getDerivedDisposition(actor); + + bool isPermanent = false; + bool applyOnlyIfHostile = false; + int dispositionModifier = 0; + // Murdering and trespassing seem to do not affect disposition + if (type == OT_Theft) + { + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else if (type == OT_Pickpocket) + { + if (alarm >= 100 && isActorGuard) + dispositionModifier = static_cast(dispTerm); + else if (isActorVictim && isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else if (isActorVictim) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm); + } + } + else if (type == OT_Assault) + { + if (isActorVictim && !isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm); + } + else if (alarm >= 100) + dispositionModifier = static_cast(dispTerm); + else if (isActorVictim && isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else + { + applyOnlyIfHostile = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + } + + bool setCrimeId = false; + if (isPermanent && dispositionModifier != 0 && !applyOnlyIfHostile) + { + setCrimeId = true; + dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + int baseDisposition = observerStats.getBaseDisposition(); + observerStats.setBaseDisposition(baseDisposition + dispositionModifier); + } + else if (dispositionModifier != 0 && !applyOnlyIfHostile) + { + setCrimeId = true; + dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + observerStats.modCrimeDispositionModifier(dispositionModifier); + } + + if (isActorGuard && alarm >= 100) { // Mark as Alarmed for dialogue - actor.getClass().getCreatureStats(actor).setAlarmed(true); + observerStats.setAlarmed(true); - // Set the crime ID, which we will use to calm down participants - // once the bounty has been paid. - actor.getClass().getNpcStats(actor).setCrimeId(id); + setCrimeId = true; - if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) + if (!observerStats.getAiSequence().isInPursuit()) { - actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); + observerStats.getAiSequence().stack(AiPursue(player), actor); } } else { - float dispTerm = (actor == victim) ? dispVictim : disp; - - float alarmTerm - = 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase(); - if (type == OT_Pickpocket && alarmTerm <= 0) + // If Alarm is 0, treat it like 100 to calculate a Fight modifier for a victim of pickpocketing. + // Observers which do not try to arrest player do not care about pickpocketing at all. + if (type == OT_Pickpocket && isActorVictim && alarmTerm == 0.0) alarmTerm = 1.0; + else if (type == OT_Pickpocket && !isActorVictim) + alarmTerm = 0.0; - if (actor != victim) - dispTerm *= alarmTerm; - - float fightTerm = static_cast((actor == victim) ? fightVictim : fight); + float fightTerm = static_cast(isActorVictim ? fightVictim : fight); fightTerm += getFightDispositionBias(dispTerm); fightTerm += getFightDistanceBias(actor, player); fightTerm *= alarmTerm; - const int observerFightRating - = actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase(); + const int observerFightRating = observerStats.getAiSetting(AiSetting::Fight).getBase(); if (observerFightRating + fightTerm > 100) fightTerm = static_cast(100 - observerFightRating); fightTerm = std::max(0.f, fightTerm); if (observerFightRating + fightTerm >= 100) { + if (dispositionModifier != 0 && applyOnlyIfHostile) + { + dispositionModifier + = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + observerStats.modCrimeDispositionModifier(dispositionModifier); + } + startCombat(actor, player); - NpcStats& observerStats = actor.getClass().getNpcStats(actor); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast(fightTerm)); - observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast(dispTerm)); - - // Set the crime ID, which we will use to calm down participants - // once the bounty has been paid. - observerStats.setCrimeId(id); + setCrimeId = true; // Mark as Alarmed for dialogue observerStats.setAlarmed(true); } } + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid and restore their disposition to player character. + if (setCrimeId) + observerStats.setCrimeId(id); } if (reported) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index b9df650fc3..808059fccd 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -20,6 +20,7 @@ MWMechanics::NpcStats::NpcStats() : mDisposition(0) + , mCrimeDispositionModifier(0) , mReputation(0) , mCrimeId(-1) , mBounty(0) @@ -43,6 +44,21 @@ void MWMechanics::NpcStats::setBaseDisposition(int disposition) mDisposition = disposition; } +int MWMechanics::NpcStats::getCrimeDispositionModifier() const +{ + return mCrimeDispositionModifier; +} + +void MWMechanics::NpcStats::setCrimeDispositionModifier(int value) +{ + mCrimeDispositionModifier = value; +} + +void MWMechanics::NpcStats::modCrimeDispositionModifier(int value) +{ + mCrimeDispositionModifier += value; +} + const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const { auto it = mSkills.find(id); @@ -464,6 +480,7 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const state.mFactions[iter->first].mRank = iter->second; state.mDisposition = mDisposition; + state.mCrimeDispositionModifier = mCrimeDispositionModifier; for (const auto& [id, value] : mSkills) { @@ -528,6 +545,7 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state) } mDisposition = state.mDisposition; + mCrimeDispositionModifier = state.mCrimeDispositionModifier; for (size_t i = 0; i < state.mSkills.size(); ++i) { diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index b6a655e84f..7113ee6207 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -22,6 +22,7 @@ namespace MWMechanics class NpcStats : public CreatureStats { int mDisposition; + int mCrimeDispositionModifier; std::map mSkills; // SkillValue.mProgress used by the player only int mReputation; @@ -54,6 +55,10 @@ namespace MWMechanics int getBaseDisposition() const; void setBaseDisposition(int disposition); + int getCrimeDispositionModifier() const; + void setCrimeDispositionModifier(int value); + void modCrimeDispositionModifier(int value); + int getReputation() const; void setReputation(int reputation); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 687b512106..b8dc047737 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1346,8 +1346,10 @@ namespace MWScript { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); - MWBase::Environment::get().getWorld()->confiscateStolenItems(player); - MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->confiscateStolenItems(player); + world->getPlayer().recordCrimeId(); + world->getPlayer().setDrawState(MWMechanics::DrawState::Nothing); } }; diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 949cdadc38..b460c15247 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -24,7 +24,7 @@ namespace ESM inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index dc221a5b43..371f506fb4 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -37,6 +37,9 @@ namespace ESM mDisposition = 0; esm.getHNOT(mDisposition, "DISP"); + mCrimeDispositionModifier = 0; + esm.getHNOT(mCrimeDispositionModifier, "DISM"); + const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion; for (auto& skill : mSkills) skill.load(esm, intFallback); @@ -94,6 +97,9 @@ namespace ESM if (mDisposition) esm.writeHNT("DISP", mDisposition); + if (mCrimeDispositionModifier) + esm.writeHNT("DISM", mCrimeDispositionModifier); + for (const auto& skill : mSkills) skill.save(esm); @@ -141,6 +147,7 @@ namespace ESM { mIsWerewolf = false; mDisposition = 0; + mCrimeDispositionModifier = 0; mBounty = 0; mReputation = 0; mWerewolfKills = 0; diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index 425a62162b..b539602e06 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -33,6 +33,7 @@ namespace ESM std::map mFactions; int32_t mDisposition; + int32_t mCrimeDispositionModifier; std::array, ESM::Skill::Length> mSkills; int32_t mBounty; int32_t mReputation; From 145f7b5672235f2ce17e4953a9677b93a0c0e820 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 11:35:03 +0100 Subject: [PATCH 0669/2167] Avoid using osg::ref_ptr when reference is enough --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/effectmanager.cpp | 4 +- apps/openmw/mwrender/util.cpp | 61 +++++++++++------------ apps/openmw/mwrender/util.hpp | 9 ++-- apps/openmw/mwworld/projectilemanager.cpp | 2 +- 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b0e1f82bd..7fa8e43c37 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1575,7 +1575,7 @@ namespace MWRender // Notify that this animation has attached magic effects mHasMagicEffects = true; - overrideFirstRootTexture(texture, mResourceSystem, node); + overrideFirstRootTexture(texture, mResourceSystem, *node); } void Animation::removeEffect(int effectId) diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index e5b8431c84..83a4091402 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -52,9 +52,9 @@ namespace MWRender node->accept(assignVisitor); if (isMagicVFX) - overrideFirstRootTexture(textureOverride, mResourceSystem, node); + overrideFirstRootTexture(textureOverride, mResourceSystem, *node); else - overrideTexture(textureOverride, mResourceSystem, node); + overrideTexture(textureOverride, mResourceSystem, *node); mParentNode->addChild(trans); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index cd03784e8c..f1a1177f85 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -11,41 +11,40 @@ namespace MWRender { - - class TextureOverrideVisitor : public osg::NodeVisitor + namespace { - public: - TextureOverrideVisitor(std::string_view texture, Resource::ResourceSystem* resourcesystem) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mTexture(texture) - , mResourcesystem(resourcesystem) + class TextureOverrideVisitor : public osg::NodeVisitor { - } - - void apply(osg::Node& node) override - { - int index = 0; - osg::ref_ptr nodePtr(&node); - if (node.getUserValue("overrideFx", index)) + public: + TextureOverrideVisitor(std::string_view texture, Resource::ResourceSystem* resourcesystem) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mTexture(texture) + , mResourcesystem(resourcesystem) { - if (index == 1) - overrideTexture(mTexture, mResourcesystem, std::move(nodePtr)); } - traverse(node); - } - std::string_view mTexture; - Resource::ResourceSystem* mResourcesystem; - }; - void overrideFirstRootTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) - { - TextureOverrideVisitor overrideVisitor(texture, resourceSystem); - node->accept(overrideVisitor); + void apply(osg::Node& node) override + { + int index = 0; + if (node.getUserValue("overrideFx", index)) + { + if (index == 1) + overrideTexture(mTexture, mResourcesystem, node); + } + traverse(node); + } + std::string_view mTexture; + Resource::ResourceSystem* mResourcesystem; + }; } - void overrideTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) + void overrideFirstRootTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node) + { + TextureOverrideVisitor overrideVisitor(texture, resourceSystem); + node.accept(overrideVisitor); + } + + void overrideTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node) { if (texture.empty()) return; @@ -58,14 +57,14 @@ namespace MWRender tex->setName("diffuseMap"); osg::ref_ptr stateset; - if (node->getStateSet()) - stateset = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + if (const osg::StateSet* const src = node.getStateSet()) + stateset = new osg::StateSet(*src, osg::CopyOp::SHALLOW_COPY); else stateset = new osg::StateSet; stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); - node->setStateSet(stateset); + node.setStateSet(stateset); } bool shouldAddMSAAIntermediateTarget() diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index 64edaf8e18..fc43680d67 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -2,9 +2,8 @@ #define OPENMW_MWRENDER_UTIL_H #include -#include -#include +#include namespace osg { @@ -21,11 +20,9 @@ namespace MWRender // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty // of the .NIF file's root node, if it had a NiTexturingProperty. Used for applying "particle textures" to magic // effects. - void overrideFirstRootTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + void overrideFirstRootTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node); - void overrideTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + void overrideTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node); // Node callback to entirely skip the traversal. class NoTraverseCallback : public osg::NodeCallback diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ed7914b89c..fdb9d92741 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -255,7 +255,7 @@ namespace MWWorld SceneUtil::AssignControllerSourcesVisitor assignVisitor(state.mEffectAnimationTime); state.mNode->accept(assignVisitor); - MWRender::overrideFirstRootTexture(texture, mResourceSystem, std::move(projectile)); + MWRender::overrideFirstRootTexture(texture, mResourceSystem, *projectile); } void ProjectileManager::update(State& state, float duration) From 81a483fc7f194417c158e10bb14397cf5950bb49 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 13:00:30 +0100 Subject: [PATCH 0670/2167] Remove unused vfs argument from correctMeshPath --- apps/navmeshtool/worldspacedata.cpp | 2 +- apps/openmw/mwclass/activator.cpp | 3 +-- apps/openmw/mwclass/classmodel.hpp | 5 +--- apps/openmw/mwclass/creature.cpp | 4 +--- apps/openmw/mwclass/esm4base.hpp | 2 ++ apps/openmw/mwclass/esm4npc.cpp | 3 +-- apps/openmw/mwclass/npc.cpp | 12 ++++------ apps/openmw/mwlua/types/activator.cpp | 6 ++--- apps/openmw/mwlua/types/apparatus.cpp | 4 ++-- apps/openmw/mwlua/types/armor.cpp | 5 ++-- apps/openmw/mwlua/types/book.cpp | 5 ++-- apps/openmw/mwlua/types/clothing.cpp | 5 ++-- apps/openmw/mwlua/types/container.cpp | 9 ++----- apps/openmw/mwlua/types/creature.cpp | 7 ++---- apps/openmw/mwlua/types/door.cpp | 14 ++++------- apps/openmw/mwlua/types/ingredient.cpp | 4 ++-- apps/openmw/mwlua/types/light.cpp | 5 ++-- apps/openmw/mwlua/types/lockpick.cpp | 5 ++-- apps/openmw/mwlua/types/misc.cpp | 4 ++-- apps/openmw/mwlua/types/potion.cpp | 5 ++-- apps/openmw/mwlua/types/probe.cpp | 5 ++-- apps/openmw/mwlua/types/repair.cpp | 7 ++---- apps/openmw/mwlua/types/static.cpp | 7 ++---- apps/openmw/mwlua/types/terminal.cpp | 9 ++----- apps/openmw/mwlua/types/weapon.cpp | 5 ++-- apps/openmw/mwmechanics/activespells.cpp | 5 +--- apps/openmw/mwmechanics/actors.cpp | 11 ++------- apps/openmw/mwmechanics/character.cpp | 13 ++++------ apps/openmw/mwmechanics/spellcasting.cpp | 25 ++++++++----------- apps/openmw/mwmechanics/spelleffects.cpp | 12 +++------- apps/openmw/mwmechanics/summoning.cpp | 5 +--- apps/openmw/mwrender/actoranimation.cpp | 3 +-- apps/openmw/mwrender/esm4npcanimation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.cpp | 29 ++++++++--------------- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwworld/groundcoverstore.cpp | 6 ++--- apps/openmw/mwworld/projectilemanager.cpp | 7 ++---- apps/openmw/mwworld/worldimp.cpp | 5 ++-- components/misc/resourcehelpers.cpp | 2 +- components/misc/resourcehelpers.hpp | 2 +- 40 files changed, 93 insertions(+), 180 deletions(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 7f579f8fde..0b3a1202d0 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -131,7 +131,7 @@ namespace NavMeshTool osg::ref_ptr shape = [&] { try { - return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model, &vfs)); + return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model)); } catch (const std::exception& e) { diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index fc6cfadb55..a0ee260260 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -145,12 +145,11 @@ namespace MWClass = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const ESM::RefId* creatureId = nullptr; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature& iter : store.get()) { if (!iter.mModel.empty() - && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel, vfs))) + && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel))) { creatureId = !iter.mOriginal.empty() ? &iter.mOriginal : &iter.mId; break; diff --git a/apps/openmw/mwclass/classmodel.hpp b/apps/openmw/mwclass/classmodel.hpp index 5d1019ee1d..65c2f87a14 100644 --- a/apps/openmw/mwclass/classmodel.hpp +++ b/apps/openmw/mwclass/classmodel.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWCLASS_CLASSMODEL_H #define OPENMW_MWCLASS_CLASSMODEL_H -#include "../mwbase/environment.hpp" - #include "../mwworld/livecellref.hpp" #include "../mwworld/ptr.hpp" @@ -19,8 +17,7 @@ namespace MWClass const MWWorld::LiveCellRef* ref = ptr.get(); if (!ref->mBase->mModel.empty()) - return Misc::ResourceHelpers::correctMeshPath( - ref->mBase->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); + return Misc::ResourceHelpers::correctMeshPath(ref->mBase->mModel); return {}; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ed601a9255..c5ce954baa 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -644,12 +644,10 @@ namespace MWClass const std::string model = getModel(ptr); if (!model.empty()) { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature& creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() - && Misc::StringUtils::ciEqual( - model, Misc::ResourceHelpers::correctMeshPath(creature.mModel, vfs))) + && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(creature.mModel))) { const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 9c02af963d..7059ae07cb 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -6,6 +6,8 @@ #include #include +#include "../mwbase/environment.hpp" + #include "../mwgui/tooltips.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 638144eb66..71ac0e317c 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -175,8 +175,7 @@ namespace MWClass model = data.mTraits->mModel; else model = data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale; - const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - return Misc::ResourceHelpers::correctMeshPath(model, vfs); + return Misc::ResourceHelpers::correctMeshPath(model); } std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7c6f16b06f..cbb1f4b307 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -431,24 +431,22 @@ namespace MWClass models.push_back(Settings::models().mXbaseanimfemale); models.push_back(Settings::models().mXbaseanim); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (!npc->mBase->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel)); if (!npc->mBase->mHead.empty()) { const ESM::BodyPart* head = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHead); if (head) - models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); } if (!npc->mBase->mHair.empty()) { const ESM::BodyPart* hair = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHair); if (hair) - models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); @@ -487,7 +485,7 @@ namespace MWClass const ESM::BodyPart* part = MWBase::Environment::get().getESMStore()->get().search(partname); if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } } } @@ -501,7 +499,7 @@ namespace MWClass { const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } } } diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index a9edc1493f..5ffae0ccd7 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -43,8 +43,6 @@ namespace MWLua { void addActivatorBindings(sol::table activator, const Context& context) { - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - activator["createRecordDraft"] = tableToActivator; addRecordFunctionBinding(activator, context); @@ -54,8 +52,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Activator& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index 10bdbcdd29..05eae8b2aa 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -38,8 +38,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index 4808107ebf..b0df533327 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -95,9 +95,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 4fe2f9d071..dfdbcff1ca 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -104,9 +104,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 7f4d6e7002..74b03148cb 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -90,9 +90,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index 25a1c1adce..ac2e75dea4 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -5,10 +5,7 @@ #include #include -#include -#include #include -#include namespace sol { @@ -42,8 +39,6 @@ namespace MWLua }; container["capacity"] = container["getCapacity"]; // for compatibility; should be removed later - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(container, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Container"); @@ -53,8 +48,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Container& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 332a9b9b14..54a3c37750 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -29,8 +29,6 @@ namespace MWLua { "Humanoid", ESM::Creature::Humanoid }, })); - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(creature, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Creature"); @@ -39,9 +37,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Creature& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); record["baseCreature"] = sol::readonly_property( diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index 5a2cfc8aee..a4185434c3 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -55,8 +55,6 @@ namespace MWLua return sol::make_object(lua, LCell{ &cell }); }; - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(door, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); @@ -65,9 +63,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Door& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); record["openSound"] = sol::readonly_property( @@ -95,8 +92,6 @@ namespace MWLua return sol::make_object(lua, LCell{ &cell }); }; - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(door, context, "ESM4Door"); sol::usertype record = context.mLua->sol().new_usertype("ESM4_Door"); @@ -106,9 +101,8 @@ namespace MWLua record["id"] = sol::readonly_property( [](const ESM4::Door& rec) -> std::string { return ESM::RefId(rec.mId).serializeText(); }); record["name"] = sol::readonly_property([](const ESM4::Door& rec) -> std::string { return rec.mFullName; }); - record["model"] = sol::readonly_property([vfs](const ESM4::Door& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM4::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["isAutomatic"] = sol::readonly_property( [](const ESM4::Door& rec) -> bool { return rec.mDoorFlags & ESM4::Door::Flag_AutomaticDoor; }); } diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index 31791a19ea..72b9f27263 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -33,8 +33,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index 347bb61641..d2cf195219 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Light& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 786471461a..6de1f7f670 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index d359534638..9e6b2d6ae5 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -83,8 +83,8 @@ namespace MWLua record["id"] = sol::readonly_property( [](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 022af56b02..33302a3d34 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -73,9 +73,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index 668e58c98c..6a3784b41a 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index 75d0a17c49..5e97e8c787 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -6,8 +6,6 @@ #include #include -#include -#include namespace sol { @@ -31,9 +29,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp index 76dac4fa00..960218ca68 100644 --- a/apps/openmw/mwlua/types/static.cpp +++ b/apps/openmw/mwlua/types/static.cpp @@ -21,8 +21,6 @@ namespace MWLua { void addStaticBindings(sol::table stat, const Context& context) { - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(stat, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Static"); @@ -30,8 +28,7 @@ namespace MWLua = [](const ESM::Static& rec) -> std::string { return "ESM3_Static[" + rec.mId.toDebugString() + "]"; }; record["id"] = sol::readonly_property([](const ESM::Static& rec) -> std::string { return rec.mId.serializeText(); }); - record["model"] = sol::readonly_property([vfs](const ESM::Static& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Static& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); } } diff --git a/apps/openmw/mwlua/types/terminal.cpp b/apps/openmw/mwlua/types/terminal.cpp index b0f8e3be0b..02a9465b91 100644 --- a/apps/openmw/mwlua/types/terminal.cpp +++ b/apps/openmw/mwlua/types/terminal.cpp @@ -6,8 +6,6 @@ #include #include -#include "apps/openmw/mwworld/esmstore.hpp" - namespace sol { template <> @@ -21,9 +19,6 @@ namespace MWLua void addESM4TerminalBindings(sol::table term, const Context& context) { - - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(term, context, "ESM4Terminal"); sol::usertype record = context.mLua->sol().new_usertype("ESM4_Terminal"); @@ -38,8 +33,8 @@ namespace MWLua record["resultText"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { return rec.mResultText; }); record["name"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { return rec.mFullName; }); - record["model"] = sol::readonly_property([vfs](const ESM4::Terminal& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); } } diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 993f1ecc95..de9b2efb95 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -129,9 +129,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index decbae765b..727bf95ed0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -321,11 +321,8 @@ namespace MWMechanics ESM::RefId::stringRefId("VFX_Reflect")); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation && !reflectStatic->mModel.empty()) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel, vfs), + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel), ESM::MagicEffect::Reflect, false); - } caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); } if (removedSpell) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..76226df2ee 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -211,11 +211,8 @@ namespace const ESM::Static* const fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Soul_Trap")); if (fx != nullptr) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", creature.getRefData().getPosition().asVec3()); - } MWBase::Environment::get().getSoundManager()->playSound3D( creature.getRefData().getPosition().asVec3(), ESM::RefId::stringRefId("conjuration hit"), 1.f, 1.f); @@ -1825,12 +1822,8 @@ namespace MWMechanics const ESM::Static* fx = MWBase::Environment::get().getESMStore()->get().search( ESM::RefId::stringRefId("VFX_Summon_End")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", - ptr.getRefData().getPosition().asVec3()); - } + Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", ptr.getRefData().getPosition().asVec3()); // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2f39bb3eef..58a5dfbba4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1615,18 +1615,13 @@ namespace MWMechanics const ESM::Static* castStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); - const VFS::Manager* const vfs - = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 L Hand", effect->mParticle); + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + -1, false, "Bip01 L Hand", effect->mParticle); if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 R Hand", effect->mParticle); + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + -1, false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation const ESM::ENAMstruct& firstEffect = effects->front(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 52e371b6e9..e4e07b162f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -41,7 +41,6 @@ namespace MWMechanics const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const { const auto world = MWBase::Environment::get().getWorld(); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::map> toApply; int index = -1; for (const ESM::ENAMstruct& effectInfo : effects.mList) @@ -75,12 +74,12 @@ namespace MWMechanics { if (effectInfo.mRange == ESM::RT_Target) world->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, 1.0f); + Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); continue; } else - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, - mHitPosition, static_cast(effectInfo.mArea * 2)); + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, + static_cast(effectInfo.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -532,7 +531,6 @@ namespace MWMechanics { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::ENAMstruct& effectData : effects) { @@ -547,15 +545,15 @@ namespace MWMechanics // check if the effect was already added if (std::find(addedEffects.begin(), addedEffects.end(), - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)) + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)) != addedEffects.end()) continue; MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mIndex, - false, {}, effect->mParticle); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mIndex, false, + {}, effect->mParticle); } else { @@ -585,13 +583,13 @@ namespace MWMechanics } scale = std::max(scale, 1.f); MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mParticle, pos, scale); + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mParticle, pos, scale); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); - addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)); + addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); if (!effect->mCastSound.empty()) @@ -628,11 +626,8 @@ namespace MWMechanics { // Don't play particle VFX unless the effect is new or it should be looping. if (playNonLooping || loop) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), magicEffect.mIndex, - loop, {}, magicEffect.mParticle); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), magicEffect.mIndex, loop, + {}, magicEffect.mParticle); } } } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index db9ec3e588..334bdf8839 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -285,11 +285,8 @@ namespace const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation && !absorbStatic->mModel.empty()) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs), - ESM::MagicEffect::SpellAbsorption, false); - } + animation->addEffect( + Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::SpellAbsorption, false); const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; if (spell) @@ -461,10 +458,7 @@ namespace MWMechanics const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1); } } else if (caster == getPlayer()) diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 9b641e5e5c..85a8d971a9 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -105,10 +105,7 @@ namespace MWMechanics const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_Start")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1, false); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1, false); } } catch (std::exception& e) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 600ae6f0ed..fa316fe649 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -130,8 +130,7 @@ namespace MWRender if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) return std::string(); if (!bodypart->mModel.empty()) - return Misc::ResourceHelpers::correctMeshPath( - bodypart->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); + return Misc::ResourceHelpers::correctMeshPath(bodypart->mModel); } } } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 3ea8f829ce..550959787e 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -12,8 +12,8 @@ #include #include +#include "../mwbase/environment.hpp" #include "../mwclass/esm4npc.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender @@ -51,7 +51,7 @@ namespace MWRender if (model.empty()) return; mResourceSystem->getSceneManager()->getInstance( - Misc::ResourceHelpers::correctMeshPath(model, mResourceSystem->getVFS()), mObjectRoot.get()); + Misc::ResourceHelpers::correctMeshPath(model), mObjectRoot.get()); } template diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d1cd5fed60..01bdcec665 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -48,7 +48,7 @@ namespace { - std::string getVampireHead(const ESM::RefId& race, bool female, const VFS::Manager& vfs) + std::string getVampireHead(const ESM::RefId& race, bool female) { static std::map, const ESM::BodyPart*> sVampireMapping; @@ -78,7 +78,7 @@ namespace const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) return std::string(); - return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel, &vfs); + return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel); } } @@ -467,7 +467,7 @@ namespace MWRender { const ESM::BodyPart* bp = store.get().search(headName); if (bp) - mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel); else Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; } @@ -476,12 +476,12 @@ namespace MWRender { const ESM::BodyPart* bp = store.get().search(hairName); if (bp) - mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel); else Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; } - const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale, *mResourceSystem->getVFS()); + const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale); if (!isWerewolf && isVampire && !vampireHead.empty()) mHeadModel = vampireHead; @@ -494,8 +494,7 @@ namespace MWRender std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) smodel = Misc::ResourceHelpers::correctActorModelPath( - Misc::ResourceHelpers::correctMeshPath(mNpc->mModel, mResourceSystem->getVFS()), - mResourceSystem->getVFS()); + Misc::ResourceHelpers::correctMeshPath(mNpc->mModel), mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); @@ -646,9 +645,8 @@ namespace MWRender if (store != inv.end() && (part = *store).getType() == ESM::Light::sRecordId) { const ESM::Light* light = part.get()->mBase; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - Misc::ResourceHelpers::correctMeshPath(light->mModel, vfs), false, nullptr, true); + Misc::ResourceHelpers::correctMeshPath(light->mModel), false, nullptr, true); if (mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), SceneUtil::LightCommon(*light)); } @@ -666,13 +664,9 @@ namespace MWRender { if (mPartPriorities[part] < 1) { - const ESM::BodyPart* bodypart = parts[part]; - if (bodypart) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + if (const ESM::BodyPart* bodypart = parts[part]) addOrReplaceIndividualPart(static_cast(part), -1, 1, - Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs)); - } + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel)); } } @@ -901,11 +895,8 @@ namespace MWRender } if (bodypart) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(static_cast(part.mPart), group, priority, - Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs), enchantedGlow, glowColor); - } + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel), enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 99ebb94647..f1bccc13c9 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -623,7 +623,7 @@ namespace MWRender std::string model = getModel(type, ref.mRefID, store); if (model.empty()) continue; - model = Misc::ResourceHelpers::correctMeshPath(model, mSceneManager->getVFS()); + model = Misc::ResourceHelpers::correctMeshPath(model); if (activeGrid && type != ESM::REC_STAT) { diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp index 85b9376f0d..17bf72b5d3 100644 --- a/apps/openmw/mwworld/groundcoverstore.cpp +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -26,8 +26,6 @@ namespace MWWorld const ::EsmLoader::EsmData content = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder, listener); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - static constexpr std::string_view prefix = "grass\\"; for (const ESM::Static& stat : statics) { @@ -35,7 +33,7 @@ namespace MWWorld std::replace(model.begin(), model.end(), '/', '\\'); if (!model.starts_with(prefix)) continue; - mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model); } for (const ESM::Static& stat : content.mStatics) @@ -44,7 +42,7 @@ namespace MWWorld std::replace(model.begin(), model.end(), '/', '\\'); if (!model.starts_with(prefix)) continue; - mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model); } for (const ESM::Cell& cell : content.mCells) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ed7914b89c..58048cdccf 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -210,8 +210,6 @@ namespace MWWorld if (state.mIdMagic.size() > 1) { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { std::ostringstream nodeName; @@ -223,7 +221,7 @@ namespace MWWorld attachTo->accept(findVisitor); if (findVisitor.mFoundNode) mResourceSystem->getSceneManager()->getInstance( - Misc::ResourceHelpers::correctMeshPath(weapon->mModel, vfs), findVisitor.mFoundNode); + Misc::ResourceHelpers::correctMeshPath(weapon->mModel), findVisitor.mFoundNode); } } @@ -331,8 +329,7 @@ namespace MWWorld if (state.mIdMagic.size() > 1) { model = Misc::ResourceHelpers::correctMeshPath( - MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic[1])->mModel, - MWBase::Environment::get().getResourceSystem()->getVFS()); + MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic[1])->mModel); } state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b6b7de24b4..e42f75eb97 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3632,9 +3632,8 @@ namespace MWWorld if (texture.empty()) texture = Fallback::Map::getString("Blood_Texture_0"); - std::string model = Misc::ResourceHelpers::correctMeshPath( - std::string{ Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))) }, // [0, 2] - mResourceSystem->getVFS()); + std::string model = Misc::ResourceHelpers::correctMeshPath(std::string{ + Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))) } /*[0, 2]*/); mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ce552df4f7..aa0e0dec7d 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -159,7 +159,7 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } -std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath, const VFS::Manager* vfs) +std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) { std::string res = "meshes\\"; res.append(resPath); diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 478569ed14..f2b576813b 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -33,7 +33,7 @@ namespace Misc std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); // Adds "meshes\\". - std::string correctMeshPath(std::string_view resPath, const VFS::Manager* vfs); + std::string correctMeshPath(std::string_view resPath); // Adds "sound\\". std::string correctSoundPath(const std::string& resPath); From f80ba4e28c1926b3be3177fba39dc9bd71b2d3de Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 26 Dec 2023 13:54:23 +0000 Subject: [PATCH 0671/2167] Apply 1 suggestion(s) to 1 file(s) --- apps/opencs/model/world/columnimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 89190023c6..215e4c3dfc 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -344,7 +344,7 @@ namespace CSMWorld QStringList selectionInfo; const std::vector& instances = record.get().selectedInstances; - for (std::string instance : instances) + for (const std::string& instance : instances) selectionInfo << QString::fromStdString(instance); data.setValue(selectionInfo); From 8f85c9194dd92c5f49dcd11fff16be2c88d5d2c5 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 26 Dec 2023 09:15:50 -0800 Subject: [PATCH 0672/2167] lua - add bindings to get frame duration --- apps/openmw/mwlua/luabindings.cpp | 1 + files/lua_api/openmw/core.lua | 5 +++++ files/lua_api/openmw/world.lua | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index a50459502b..6331bc5466 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -86,6 +86,7 @@ namespace MWLua api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; + api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); }; if (!global) return; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 18898b5002..f42f51b9b3 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -55,6 +55,11 @@ -- @function [parent=#core] getRealTime -- @return #number +--- +-- Frame duration in seconds +-- @function [parent=#core] getRealFrameDuration +-- @return #number + --- -- Get a GMST setting from content files. -- @function [parent=#core] getGMST diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 5baa624c5d..404b744eb8 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -111,6 +111,11 @@ -- @function [parent=#world] setGameTimeScale -- @param #number ratio +--- +-- Frame duration in seconds +-- @function [parent=#world] getRealFrameDuration +-- @return #number + --- -- Whether the world is paused (onUpdate doesn't work when the world is paused). -- @function [parent=#world] isWorldPaused From 1b594d874e769460fb59ab28b6822d453b6af386 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 26 Dec 2023 21:53:49 +0300 Subject: [PATCH 0673/2167] Use modified value for governing attribute training limit (#7742) --- CHANGELOG.md | 1 + apps/openmw/mwgui/trainingwindow.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..3cd31a2856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves + Bug #7742: Governing attribute training limit should use the modified attribute Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index c915619a1a..5395f6db1c 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -167,7 +167,7 @@ namespace MWGui // You can not train a skill above its governing attribute if (pcStats.getSkill(skill->mId).getBase() - >= pcStats.getAttribute(ESM::Attribute::indexToRefId(skill->mData.mAttribute)).getBase()) + >= pcStats.getAttribute(ESM::Attribute::indexToRefId(skill->mData.mAttribute)).getModified()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}"); return; From 18345973615c89955332d93adb0b605b25b2d752 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 00:49:43 +0300 Subject: [PATCH 0674/2167] Move friendly fire logic to onHit --- apps/openmw/mwclass/creature.cpp | 19 ++++++++++----- apps/openmw/mwclass/npc.cpp | 6 +++-- apps/openmw/mwmechanics/combat.cpp | 23 +++++++++++++++++++ apps/openmw/mwmechanics/combat.hpp | 2 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 18 +-------------- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ed601a9255..8b80b9336b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -353,16 +353,23 @@ namespace MWClass { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + // Self defense + bool setOnPcHitMe = true; + // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) + { stats.setAttacked(true); - // Self defense - bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. - - // No retaliation for totally static creatures (they have no movement or attacks anyway) - if (isMobile(ptr) && !attacker.isEmpty()) - setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + // No retaliation for totally static creatures (they have no movement or attacks anyway) + if (isMobile(ptr)) + { + if (MWMechanics::friendlyHit(attacker, ptr, true)) + setOnPcHitMe = false; + else + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + } + } // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7c6f16b06f..0295d3600f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -682,14 +682,16 @@ namespace MWClass MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); - // Note OnPcHitMe is not set for friendly hits. bool setOnPcHitMe = true; // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); - setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + if (MWMechanics::friendlyHit(attacker, ptr, true)) + setOnPcHitMe = false; + else + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } // Attacker and target store each other as hitattemptactor if they have no one stored yet diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 02279859b5..3ce55f8f6c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -10,6 +10,7 @@ #include #include +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -649,4 +650,26 @@ namespace MWMechanics return std::make_pair(result, hitPos); } + + bool friendlyHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& target, bool complain) + { + const MWWorld::Ptr& player = getPlayer(); + if (attacker != player) + return false; + + std::set followersAttacker; + MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(attacker, followersAttacker); + if (followersAttacker.find(target) == followersAttacker.end()) + return false; + + MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); + statsTarget.friendlyHit(); + if (statsTarget.getFriendlyHits() >= 4) + return false; + + if (complain) + MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit")); + return true; + } + } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 515d2e406c..92033c7e77 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -66,6 +66,8 @@ namespace MWMechanics // Similarly cursed hit target selection std::pair getHitContact(const MWWorld::Ptr& actor, float reach); + + bool friendlyHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& target, bool complain); } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8a64af1cbd..49f176c6a6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1394,26 +1394,10 @@ namespace MWMechanics if (target == player || !attacker.getClass().isActor()) return false; - MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); - if (attacker == player) - { - std::set followersAttacker; - getActorsSidingWith(attacker, followersAttacker); - if (followersAttacker.find(target) != followersAttacker.end()) - { - statsTarget.friendlyHit(); - - if (statsTarget.getFriendlyHits() < 4) - { - MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit")); - return false; - } - } - } - if (canCommitCrimeAgainst(target, attacker)) commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault); + MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); AiSequence& seq = statsTarget.getAiSequence(); if (!attacker.isEmpty() From 6a16686107f6bb5b60aed6f1ac43eae0c0e078d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 09:32:18 +0100 Subject: [PATCH 0675/2167] Use settings values to declare string settings --- apps/opencs/model/prefs/state.cpp | 13 ++++++------- apps/opencs/model/prefs/state.hpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.hpp | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 1c922ee8d2..9b7475dea8 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -440,13 +440,12 @@ void CSMPrefs::State::declare() declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence()); declareCategory("Models"); - declareString("baseanim", "base animations", "meshes/base_anim.nif") - .setTooltip("3rd person base model with textkeys-data"); - declareString("baseanimkna", "base animations, kna", "meshes/base_animkna.nif") + declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); + declareString(mValues->mModels.mBaseanimkna, "base animations, kna") .setTooltip("3rd person beast race base model with textkeys-data"); - declareString("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif") + declareString(mValues->mModels.mBaseanimfemale, "base animations, female") .setTooltip("3rd person female base model with textkeys-data"); - declareString("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif").setTooltip("3rd person werewolf skin"); + declareString(mValues->mModels.mWolfskin, "base animations, wolf").setTooltip("3rd person werewolf skin"); } void CSMPrefs::State::declareCategory(const std::string& key) @@ -547,13 +546,13 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const QString& label, const std::string& default_) + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::StringSetting* setting - = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index a5fd5b0936..62db966fcd 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -75,7 +75,7 @@ namespace CSMPrefs ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); + StringSetting& declareString(Settings::SettingValue& value, const QString& label); ModifierSetting& declareModifier(Settings::SettingValue& value, const QString& label); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 56c46bb6af..10bd8cb558 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,7 +12,7 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 25e1a362ff..0a7d2a4935 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -24,7 +24,7 @@ namespace CSMPrefs public: explicit StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); StringSetting& setTooltip(const std::string& tooltip); From 13c8e04b272472e8f57743c1596e77421c573c6c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 17:20:57 +0100 Subject: [PATCH 0676/2167] Make traits and base data optional for ESM4 NPC Fallout 3 is not fully supported and it causes failures to load NPCs. Log errors and make sure there is no nullptr dereference. --- apps/openmw/mwclass/esm4npc.cpp | 47 +++++++++++++++-------- apps/openmw/mwrender/esm4npcanimation.cpp | 33 ++++++++-------- apps/openmw/mwrender/esm4npcanimation.hpp | 9 ++++- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 638144eb66..eca33d0701 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -88,26 +88,34 @@ namespace MWClass auto data = std::make_unique(); const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - auto npcRecs = withBaseTemplates(ptr.get()->mBase); + const ESM4::Npc* const base = ptr.get()->mBase; + auto npcRecs = withBaseTemplates(base); data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + + if (data->mTraits == nullptr) + Log(Debug::Warning) << "Traits are not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); - if (!data->mTraits) - throw std::runtime_error("ESM4 NPC traits not found"); - if (!data->mBaseData) - throw std::runtime_error("ESM4 NPC base data not found"); + if (data->mBaseData == nullptr) + Log(Debug::Warning) << "Base data is not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; - data->mRace = store->get().find(data->mTraits->mRace); - if (data->mTraits->mIsTES4) - data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; - else if (data->mTraits->mIsFONV) - data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; - else if (data->mTraits->mIsFO4) - data->mIsFemale - = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 - else - data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + if (data->mTraits != nullptr) + { + data->mRace = store->get().find(data->mTraits->mRace); + if (data->mTraits->mIsTES4) + data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; + else if (data->mTraits->mIsFONV) + data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 + else + data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + } if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { @@ -116,7 +124,7 @@ namespace MWClass if (auto* armor = ESM4Impl::resolveLevelled(ESM::FormId::fromUint32(item.item))) data->mEquippedArmor.push_back(armor); - else if (data->mTraits->mIsTES4) + else if (data->mTraits != nullptr && data->mTraits->mIsTES4) { const auto* clothing = ESM4Impl::resolveLevelled( ESM::FormId::fromUint32(item.item)); @@ -170,6 +178,8 @@ namespace MWClass std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const { const ESM4NpcCustomData& data = getCustomData(ptr); + if (data.mTraits == nullptr) + return {}; std::string_view model; if (data.mTraits->mIsTES4) model = data.mTraits->mModel; @@ -181,6 +191,9 @@ namespace MWClass std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const { - return getCustomData(ptr).mBaseData->mFullName; + const ESM4::Npc* const baseData = getCustomData(ptr).mBaseData; + if (baseData == nullptr) + return {}; + return baseData->mFullName; } } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 3ea8f829ce..3a3ed37344 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -13,7 +13,6 @@ #include #include "../mwclass/esm4npc.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender @@ -28,11 +27,13 @@ namespace MWRender void ESM4NpcAnimation::updateParts() { - if (!mObjectRoot.get()) + if (mObjectRoot == nullptr) return; const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + if (traits == nullptr) + return; if (traits->mIsTES4) - updatePartsTES4(); + updatePartsTES4(*traits); else if (traits->mIsFONV) { // Not implemented yet @@ -42,7 +43,7 @@ namespace MWRender // There is no easy way to distinguish TES5 and FO3. // In case of FO3 the function shouldn't crash the game and will // only lead to the NPC not being rendered. - updatePartsTES5(); + updatePartsTES5(*traits); } } @@ -65,9 +66,8 @@ namespace MWRender return rec->mModel; } - void ESM4NpcAnimation::updatePartsTES4() + void ESM4NpcAnimation::updatePartsTES4(const ESM4::Npc& traits) { - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -77,13 +77,13 @@ namespace MWRender insertPart(bodyPart.mesh); for (const ESM4::Race::BodyPart& bodyPart : race->mHeadParts) insertPart(bodyPart.mesh); - if (!traits->mHair.isZeroOrUnset()) + if (!traits.mHair.isZeroOrUnset()) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - if (const ESM4::Hair* hair = store->get().search(traits->mHair)) + if (const ESM4::Hair* hair = store->get().search(traits.mHair)) insertPart(hair->mModel); else - Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits->mHair); + Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits.mHair); } for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) @@ -111,11 +111,10 @@ namespace MWRender } } - void ESM4NpcAnimation::updatePartsTES5() + void ESM4NpcAnimation::updatePartsTES5(const ESM4::Npc& traits) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -132,9 +131,9 @@ namespace MWRender Log(Debug::Error) << "ArmorAddon not found: " << ESM::RefId(armaId); continue; } - bool compatibleRace = arma->mRacePrimary == traits->mRace; + bool compatibleRace = arma->mRacePrimary == traits.mRace; for (auto r : arma->mRaces) - if (r == traits->mRace) + if (r == traits.mRace) compatibleRace = true; if (compatibleRace) armorAddons.push_back(arma); @@ -143,12 +142,12 @@ namespace MWRender for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) findArmorAddons(armor); - if (!traits->mWornArmor.isZeroOrUnset()) + if (!traits.mWornArmor.isZeroOrUnset()) { - if (const ESM4::Armor* armor = store->get().search(traits->mWornArmor)) + if (const ESM4::Armor* armor = store->get().search(traits.mWornArmor)) findArmorAddons(armor); else - Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits->mWornArmor); + Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits.mWornArmor); } if (!race->mSkin.isZeroOrUnset()) { @@ -183,7 +182,7 @@ namespace MWRender std::set usedHeadPartTypes; if (usedParts & ESM4::Armor::TES5_Hair) usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair); - insertHeadParts(traits->mHeadParts, usedHeadPartTypes); + insertHeadParts(traits.mHeadParts, usedHeadPartTypes); insertHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale, usedHeadPartTypes); } } diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp index 7bb3fe1103..274c060b06 100644 --- a/apps/openmw/mwrender/esm4npcanimation.hpp +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -3,6 +3,11 @@ #include "animation.hpp" +namespace ESM4 +{ + struct Npc; +} + namespace MWRender { class ESM4NpcAnimation : public Animation @@ -18,8 +23,8 @@ namespace MWRender void insertHeadParts(const std::vector& partIds, std::set& usedHeadPartTypes); void updateParts(); - void updatePartsTES4(); - void updatePartsTES5(); + void updatePartsTES4(const ESM4::Npc& traits); + void updatePartsTES5(const ESM4::Npc& traits); }; } From 94c052dfefee9eaca0aa2f46adc9de8b6b383c9c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 01:50:30 +0300 Subject: [PATCH 0677/2167] Classify the damage passed to Class::onHit --- apps/openmw/mwbase/luamanager.hpp | 3 ++- apps/openmw/mwclass/creature.cpp | 9 ++++++--- apps/openmw/mwclass/creature.hpp | 3 ++- apps/openmw/mwclass/npc.cpp | 8 +++++--- apps/openmw/mwclass/npc.hpp | 3 ++- apps/openmw/mwmechanics/combat.cpp | 6 ++++-- apps/openmw/mwmechanics/damagesourcetype.hpp | 15 +++++++++++++++ apps/openmw/mwmechanics/spelleffects.cpp | 3 ++- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 9 ++++++--- 10 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 apps/openmw/mwmechanics/damagesourcetype.hpp diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e4b16ff725..6db85d77ca 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -61,7 +61,8 @@ namespace MWBase // TODO: notify LuaManager about other events // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, - // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; + // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, + // DamageSourceType sourceType) = 0; struct InputEvent { diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8b80b9336b..cbf5a3d63d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -284,7 +284,8 @@ namespace MWClass if (!success) { - victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); + victim.getClass().onHit( + victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -345,11 +346,13 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + victim.getClass().onHit( + victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee); } void Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index bd7101e93d..b407852242 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -67,7 +67,8 @@ namespace MWClass const osg::Vec3f& hitPosition, bool success) const override; void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const override; std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0295d3600f..df074ec8bf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -599,7 +599,8 @@ namespace MWClass float damage = 0.0f; if (!success) { - othercls.onHit(victim, damage, false, weapon, ptr, osg::Vec3f(), false); + othercls.onHit( + victim, damage, false, weapon, ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee); MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); return; @@ -672,11 +673,12 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee); } void Npc::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const { MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index eb8cafc9d1..ca0d0ac95d 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -82,7 +82,8 @@ namespace MWClass const osg::Vec3f& hitPosition, bool success) const override; void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const override; void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3ce55f8f6c..3f17df96fd 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -231,7 +231,8 @@ namespace MWMechanics if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue)) { - victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); + victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false, + MWMechanics::DamageSourceType::Ranged); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); return; } @@ -287,7 +288,8 @@ namespace MWMechanics victim.getClass().getContainerStore(victim).add(projectile, 1); } - victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); + victim.getClass().onHit( + victim, damage, true, projectile, attacker, hitPosition, true, MWMechanics::DamageSourceType::Ranged); } } diff --git a/apps/openmw/mwmechanics/damagesourcetype.hpp b/apps/openmw/mwmechanics/damagesourcetype.hpp new file mode 100644 index 0000000000..e140a8106f --- /dev/null +++ b/apps/openmw/mwmechanics/damagesourcetype.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_MWMECHANICS_DAMAGESOURCETYPE_H +#define OPENMW_MWMECHANICS_DAMAGESOURCETYPE_H + +namespace MWMechanics +{ + enum class DamageSourceType + { + Unspecified, + Melee, + Ranged, + Magical, + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index db9ec3e588..88d978733c 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -356,7 +356,8 @@ namespace // Notify the target actor they've been hit bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) - target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); + target.getClass().onHit( + target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true, MWMechanics::DamageSourceType::Magical); // Apply resistances if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index e6080ce447..5fbda6d570 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -124,7 +124,7 @@ namespace MWWorld } void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, - const osg::Vec3f& hitPosition, bool successful) const + const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const { throw std::runtime_error("class cannot be hit"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 7b7e9135ba..87e70b3198 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -13,6 +13,8 @@ #include "ptr.hpp" #include "../mwmechanics/aisetting.hpp" +#include "../mwmechanics/damagesourcetype.hpp" + #include #include @@ -142,11 +144,12 @@ namespace MWWorld /// (default implementation: throw an exception) virtual void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the - /// actor responsible for the attack, and \a successful specifies if the hit is - /// successful or not. + /// actor responsible for the attack. \a successful specifies if the hit is + /// successful or not. \a sourceType classifies the damage source. virtual void block(const Ptr& ptr) const; ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield From d5428b23d8b7e03d5416b0e2897deaf147256554 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 01:51:57 +0300 Subject: [PATCH 0678/2167] Disable voiced responses to magical friendly hits (bug #7646) Disable ranged friendly fire --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 4 +++- apps/openmw/mwclass/npc.cpp | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..e86dc8d354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7643: Can't enchant items with constant effect on self magic effects for non-player character + Bug #7646: Follower voices pain sounds when attacked with magic Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index cbf5a3d63d..4e8cdabba0 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -367,7 +367,9 @@ namespace MWClass // No retaliation for totally static creatures (they have no movement or attacks anyway) if (isMobile(ptr)) { - if (MWMechanics::friendlyHit(attacker, ptr, true)) + bool complain = sourceType == MWMechanics::DamageSourceType::Melee; + bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; + if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) setOnPcHitMe = false; else setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index df074ec8bf..f3cd1534d1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -690,7 +690,9 @@ namespace MWClass if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); - if (MWMechanics::friendlyHit(attacker, ptr, true)) + bool complain = sourceType == MWMechanics::DamageSourceType::Melee; + bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; + if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) setOnPcHitMe = false; else setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); From b2f972df3d6f4b25f0ace3ac5c46472ac29efc20 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Wed, 27 Dec 2023 17:14:33 +0000 Subject: [PATCH 0679/2167] Update custom-shader-effects.rst to reflect new change --- docs/source/reference/modding/custom-shader-effects.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 0bd1fbec85..60a306a97a 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -6,10 +6,9 @@ This node must have the prefix `omw:data` and have a valid JSON object that foll .. note:: - This is a new feature to inject OpenMW-specific shader effects. Only a single - effect is currently supported. By default, the shader effects will propogate - to all a node's children. Other propogation modes and effects will come with - future releases. + This is a new feature to inject OpenMW-specific shader effects. By default, + the shader effects will propagate to all of a node's children. + Other propagation modes and effects will come with future releases. Soft Effect From 9d3ede7575e8e6c2fb890c32e12be031bccaa00d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 27 Dec 2023 19:11:49 +0000 Subject: [PATCH 0680/2167] Revert "Merge branch 'skating-olympics' into 'master'" This reverts merge request !3631 --- CHANGELOG.md | 1 - apps/openmw/mwbase/world.hpp | 6 +- apps/openmw/mwmechanics/character.cpp | 6 +- apps/openmw/mwphysics/movementsolver.cpp | 11 +-- apps/openmw/mwphysics/mtphysics.cpp | 88 +++-------------------- apps/openmw/mwphysics/mtphysics.hpp | 8 +-- apps/openmw/mwphysics/physicssystem.cpp | 17 ++--- apps/openmw/mwphysics/physicssystem.hpp | 3 +- apps/openmw/mwphysics/ptrholder.hpp | 19 +---- apps/openmw/mwworld/projectilemanager.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 4 +- 12 files changed, 32 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8d6f8ccf..f0181da99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ------ Bug #2623: Snowy Granius doesn't prioritize conjuration spells - Bug #3330: Backward displacement of the character when attacking in 3rd person Bug #3438: NPCs can't hit bull netch with melee weapons Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 23961554ca..14e3b2b3b7 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,13 +295,9 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) - = 0; + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - /// \param duration The duration this speed shall be held, starting at current simulation time - /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead virtual void updateAnimatedCollisionShape(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 58a5dfbba4..20c7fd0a92 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2416,9 +2416,7 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - { - world->queueMovement(mPtr, vec, duration, mInJump && mJumpState == JumpState_None); - } + world->queueMovement(mPtr, vec); } movement = vec; @@ -2491,7 +2489,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement, duration, mInJump && mJumpState == JumpState_None); + world->queueMovement(mPtr, movement); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 63ffb055dd..c0b5014b31 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -170,8 +170,6 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it - // TODO: This will cause instability in idle animations and other in-place animations. Should include a flag for - // this when queueing up movement if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; @@ -202,8 +200,7 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { - osg::Vec3f diff = velocity * remainingTime; - osg::Vec3f nextpos = newPosition + diff; + osg::Vec3f nextpos = newPosition + velocity * remainingTime; bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air @@ -215,11 +212,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely - // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle - // animations. Note that, although both these comparisons to 1e-6 are logically the same, they test separate - // floating point accuracy cases. - if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) + if ((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 238d00deac..52b96d9d13 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -25,7 +25,6 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -36,7 +35,6 @@ #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" -#include "ptrholder.hpp" namespace MWPhysics { @@ -197,67 +195,6 @@ namespace void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} }; - struct InitMovement - { - int mSteps = 0; - float mDelta = 0.f; - float mSimulationTime = 0.f; - - // Returns how the actor or projectile wants to move between startTime and endTime - osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const - { - osg::Vec3f movement = osg::Vec3f(); - actor.eraseMovementIf([&](MWPhysics::Movement& v) { - if (v.mJump) - return false; - float start = std::max(v.mSimulationTimeStart, startTime); - float stop = std::min(v.mSimulationTimeStop, endTime); - movement += v.mVelocity * (stop - start); - if (std::abs(stop - v.mSimulationTimeStop) < 0.0001f) - return true; - return false; - }); - - return movement; - } - - std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const - { - std::optional inertia = std::nullopt; - actor.eraseMovementIf([&](MWPhysics::Movement& v) { - if (v.mJump && v.mSimulationTimeStart >= startTime) - { - inertia = v.mVelocity; - return true; - } - return false; - }); - return inertia; - } - - void operator()(auto& sim) const - { - if (mSteps <= 0 || mDelta < 0.00001f) - return; - - auto locked = sim.lock(); - if (!locked.has_value()) - return; - - auto& [ptrHolder, frameDataRef] = *locked; - - // Because takeMovement() returns movement instead of velocity, convert it back to velocity for the - // movement solver - osg::Vec3f velocity - = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); - // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a - // jump - auto inertia = takeInertia(*ptrHolder, mSimulationTime); - - frameDataRef.get().mMovement += inertia.value_or(velocity); - } - }; - struct PreStep { btCollisionWorld* mCollisionWorld; @@ -564,18 +501,18 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime, - std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { assert(mSimulations != &simulations); waitForWorkers(); - prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats); + prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } - void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, + void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. @@ -585,9 +522,6 @@ namespace MWPhysics double timeStart = mTimer->tick(); - // The simulation time when the movement solving begins. - float simulationTimeStart = simulationTime - timeAccum; - // start by finishing previous background computation if (mNumThreads != 0) { @@ -602,15 +536,10 @@ namespace MWPhysics timeAccum -= numSteps * newDelta; // init - const Visitors::InitPosition initPositionVisitor{ mCollisionWorld }; + const Visitors::InitPosition vis{ mCollisionWorld }; for (auto& sim : simulations) { - std::visit(initPositionVisitor, sim); - } - const Visitors::InitMovement initMovementVisitor{ numSteps, newDelta, simulationTimeStart }; - for (auto& sim : simulations) - { - std::visit(initMovementVisitor, sim); + std::visit(vis, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -623,10 +552,10 @@ namespace MWPhysics mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) - { mWorldFrameData = std::make_unique(); + + if (mAdvanceSimulation) mBudgetCursor += 1; - } if (mNumThreads == 0) { @@ -935,7 +864,6 @@ namespace MWPhysics std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } - mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 986f2be973..57f3711096 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,8 +46,8 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,8 +87,8 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector* mSimulations = nullptr; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9a85ee009f..2196834a50 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -43,7 +43,6 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "actor.hpp" #include "collisiontype.hpp" @@ -624,20 +623,18 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) { - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->queueMovement(velocity, start, start + duration, jump); + found->second->setVelocity(velocity); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { - actor->clearMovement(); + actor->setVelocity(osg::Vec3f()); actor->setInertialForce(osg::Vec3f()); } } @@ -725,10 +722,8 @@ namespace MWPhysics { std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); - float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt; // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements( - mTimeAccum, simulationTime, simulations, frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); } } @@ -912,7 +907,7 @@ namespace MWPhysics ->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() - , mMovement() + , mMovement(actor.velocity()) , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) @@ -927,7 +922,7 @@ namespace MWPhysics ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) - , mMovement() + , mMovement(projectile.velocity()) , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 7758c6dfd7..ad56581eb3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,8 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 16c3db0691..fc8fd94c30 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H -#include #include #include #include @@ -14,14 +13,6 @@ namespace MWPhysics { - struct Movement - { - osg::Vec3f mVelocity = osg::Vec3f(); - float mSimulationTimeStart = 0.f; // The time at which this movement begun - float mSimulationTimeStop = 0.f; // The time at which this movement finished - bool mJump = false; - }; - class PtrHolder { public: @@ -41,13 +32,9 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void clearMovement() { mMovement = {}; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop, bool jump = false) - { - mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); - } + void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } - void eraseMovementIf(const auto& predicate) { std::erase_if(mMovement, predicate); } + osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -66,7 +53,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - std::list mMovement; + osg::Vec3f mVelocity; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 1cb593f208..3cb08be5fa 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -30,7 +30,6 @@ #include #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" @@ -455,8 +454,7 @@ namespace MWWorld } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - projectile->queueMovement(direction * speed, start, start + duration); + projectile->setVelocity(direction * speed); update(magicBoltState, duration); @@ -484,8 +482,7 @@ namespace MWWorld projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - projectile->queueMovement(projectileState.mVelocity, start, start + duration); + projectile->setVelocity(projectileState.mVelocity); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting // direction. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e42f75eb97..07334396b7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) { - mPhysics->queueObjectMovement(ptr, velocity, duration, jump); + mPhysics->queueObjectMovement(ptr, velocity); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index aa5f9d56f0..4b9a0ccb98 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,11 +383,9 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - /// \param duration The duration this speed shall be held, starting at current simulation time - /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead void updateAnimatedCollisionShape(const Ptr& ptr) override; From 30cff6f6eeeb84b45dba38aec810df0abb201567 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Dec 2023 01:05:45 +0300 Subject: [PATCH 0681/2167] Avoid crashes upon Weapon::canBeEquipped attack check for the inventory doll --- apps/openmw/mwclass/weapon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 1cc2c86761..7e4c47993c 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -276,8 +276,8 @@ namespace MWClass return { 0, "#{sInventoryMessage1}" }; // Do not allow equip weapons from inventory during attack - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) - && MWBase::Environment::get().getWindowManager()->isGuiMode()) + if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) return { 0, "#{sCantEquipWeapWarning}" }; std::pair, bool> slots_ = getEquipmentSlots(ptr); From 310b8206dd341d2992e70882f1bfd1f9a57be2b9 Mon Sep 17 00:00:00 2001 From: alekulyn Date: Wed, 29 Nov 2023 08:43:33 -0600 Subject: [PATCH 0682/2167] Fix #7696 --- components/terrain/compositemaprenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 5319820c83..34655a03d6 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -49,7 +49,8 @@ namespace Terrain double timeLeft = availableTime; - while (!mCompileSet.empty() && timeLeft > 0) + const auto deadline = std::chrono::steady_clock::now() + std::chrono::duration(availableTime); + while (!mCompileSet.empty() && std::chrono::steady_clock::now() < deadline) { osg::ref_ptr node = *mCompileSet.begin(); mCompileSet.erase(node); From f71862fb7659c959a804b42efb8c007745be5850 Mon Sep 17 00:00:00 2001 From: alekulyn Date: Wed, 27 Dec 2023 22:30:04 -0600 Subject: [PATCH 0683/2167] Remove unnecessary code --- components/terrain/compositemaprenderer.cpp | 17 +++-------------- components/terrain/compositemaprenderer.hpp | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 34655a03d6..adf58162e8 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -43,12 +43,10 @@ namespace Terrain mImmediateCompileSet.erase(node); mMutex.unlock(); - compile(*node, renderInfo, nullptr); + compile(*node, renderInfo); mMutex.lock(); } - double timeLeft = availableTime; - const auto deadline = std::chrono::steady_clock::now() + std::chrono::duration(availableTime); while (!mCompileSet.empty() && std::chrono::steady_clock::now() < deadline) { @@ -56,7 +54,7 @@ namespace Terrain mCompileSet.erase(node); mMutex.unlock(); - compile(*node, renderInfo, &timeLeft); + compile(*node, renderInfo); mMutex.lock(); if (node->mCompiled < node->mDrawables.size()) @@ -69,7 +67,7 @@ namespace Terrain mTimer.setStartTick(); } - void CompositeMapRenderer::compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const + void CompositeMapRenderer::compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo) const { // if there are no more external references we can assume the texture is no longer required if (compositeMap.mTexture->referenceCount() <= 1) @@ -125,15 +123,6 @@ namespace Terrain ++compositeMap.mCompiled; compositeMap.mDrawables[i] = nullptr; - - if (timeLeft) - { - *timeLeft -= timer.time_s(); - timer.setStartTick(); - - if (*timeLeft <= 0) - break; - } } if (compositeMap.mCompiled == compositeMap.mDrawables.size()) compositeMap.mDrawables = std::vector>(); diff --git a/components/terrain/compositemaprenderer.hpp b/components/terrain/compositemaprenderer.hpp index eeecec75dc..1e33c717ec 100644 --- a/components/terrain/compositemaprenderer.hpp +++ b/components/terrain/compositemaprenderer.hpp @@ -38,7 +38,7 @@ namespace Terrain void drawImplementation(osg::RenderInfo& renderInfo) const override; - void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const; + void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo) const; /// Set the available time in seconds for compiling (non-immediate) composite maps each frame void setMinimumTimeAvailableForCompile(double time); From 02775c490bbd3a601436815807492664574a7517 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Dec 2023 21:49:25 +0100 Subject: [PATCH 0684/2167] Discard additional tokens in non-expression contexts --- .../mwscript/test_scripts.cpp | 26 ++++++++++++++++++- components/compiler/lineparser.cpp | 13 +++++----- components/compiler/skipparser.cpp | 20 ++++++++++++-- components/compiler/skipparser.hpp | 6 ++++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index b623084fb9..dbed262235 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -19,7 +19,16 @@ namespace mErrorHandler.reset(); std::istringstream input(scriptBody); Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); - scanner.scan(mParser); + try + { + scanner.scan(mParser); + } + catch (...) + { + if (!shouldFail) + logErrors(); + throw; + } if (mErrorHandler.isGood()) return CompiledScript(mParser.getProgram(), mParser.getLocals()); else if (!shouldFail) @@ -385,6 +394,12 @@ if (player->GameHour == 10) set player->GameHour to 20 endif +End)mwscript"; + + const std::string sIssue4996 = R"mwscript(---Begin issue4996 + +player-> SetPos, Z, myZ + 50 + End)mwscript"; const std::string sIssue5087 = R"mwscript(Begin Begin @@ -457,6 +472,9 @@ set a to 1 -+'\/.,><$@---!=\/?--------(){}------ show a +( GetDisabled == 1 ) +GetDisabled == 1 + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -810,6 +828,12 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue4888)); } + TEST_F(MWScriptTest, mwscript_test_4996) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue4996)); + } + TEST_F(MWScriptTest, mwscript_test_5087) { registerExtensions(); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 460dcfb2f9..90bdac1610 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -271,18 +271,14 @@ namespace Compiler mCode.insert(mCode.end(), code.begin(), code.end()); extensions->generateInstructionCode(keyword, mCode, mLiterals, mExplicit, optionals); + SkipParser skip(getErrorHandler(), getContext(), true); + scanner.scan(skip); + mState = EndState; return true; } - } - if (const Extensions* extensions = getContext().getExtensions()) - { char returnType; - std::string argumentType; - - bool hasExplicit = mState == ExplicitState; - if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit)) { if (!hasExplicit && mState == ExplicitState) @@ -302,6 +298,9 @@ namespace Compiler int optionals = mExprParser.parseArguments(argumentType, scanner, code, keyword); mCode.insert(mCode.end(), code.begin(), code.end()); extensions->generateFunctionCode(keyword, mCode, mLiterals, mExplicit, optionals); + + SkipParser skip(getErrorHandler(), getContext(), true); + scanner.scan(skip); } mState = EndState; return true; diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index 0036e93dda..39d834c8ca 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,39 +1,55 @@ #include "skipparser.hpp" +#include "errorhandler.hpp" #include "scanner.hpp" namespace Compiler { - SkipParser::SkipParser(ErrorHandler& errorHandler, const Context& context) + SkipParser::SkipParser(ErrorHandler& errorHandler, const Context& context, bool reportStrayArguments) : Parser(errorHandler, context) + , mReportStrayArguments(reportStrayArguments) { } + void SkipParser::reportStrayArgument(const TokenLoc& loc) + { + if (mReportStrayArguments) + getErrorHandler().warning("Extra argument", loc); + } + bool SkipParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseFloat(float value, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseName(const std::string& name, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseKeyword(int keyword, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseSpecial(int code, const TokenLoc& loc, Scanner& scanner) { if (code == Scanner::S_newline) + { + if (mReportStrayArguments) + scanner.putbackSpecial(code, loc); return false; - + } + reportStrayArgument(loc); return true; } } diff --git a/components/compiler/skipparser.hpp b/components/compiler/skipparser.hpp index fdc5effc13..304ed40330 100644 --- a/components/compiler/skipparser.hpp +++ b/components/compiler/skipparser.hpp @@ -11,8 +11,12 @@ namespace Compiler class SkipParser : public Parser { + bool mReportStrayArguments; + + void reportStrayArgument(const TokenLoc& loc); + public: - SkipParser(ErrorHandler& errorHandler, const Context& context); + SkipParser(ErrorHandler& errorHandler, const Context& context, bool reportStrayArguments = false); bool parseInt(int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. From 56401a90a14b467e780283009849ddd1da1552f7 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Dec 2023 23:14:44 +0100 Subject: [PATCH 0685/2167] Merge GenericObjectCache update and remove functions They are always called together. Single iteration over the items is more efficient along with locking the mutex only once. --- .../resource/testobjectcache.cpp | 46 ++++++---------- components/resource/objectcache.hpp | 54 ++++++------------- components/resource/resourcemanager.hpp | 6 +-- 3 files changed, 32 insertions(+), 74 deletions(-) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 5b7741025a..25b6d87e52 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -63,9 +63,7 @@ namespace Resource const double referenceTime = 1000; const double expiryDelay = 1; - - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -92,16 +90,13 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr, referenceTime + expiryDelay); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + 2 * expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime + expiryDelay); + cache->update(referenceTime + 2 * expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } @@ -117,12 +112,10 @@ namespace Resource cache->addEntryToObjectCache(key, value); value = nullptr; - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } @@ -137,12 +130,10 @@ namespace Resource osg::ref_ptr value(new Object); cache->addEntryToObjectCache(key, value); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(value)); } @@ -158,12 +149,10 @@ namespace Resource cache->addEntryToObjectCache(key, value); value = nullptr; - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -177,12 +166,10 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -196,16 +183,13 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index f8a5843395..08ef0dc060 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -48,48 +49,25 @@ namespace Resource { } - /** For each object in the cache which has an reference count greater than 1 - * (and therefore referenced by elsewhere in the application) set the time stamp - * for that object in the cache to specified time. - * This would typically be called once per frame by applications which are doing database paging, - * and need to prune objects that are no longer required. - * The time used should be taken from the FrameStamp::getReferenceTime().*/ - void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) - { - // look for objects with external references and update their time stamp. - std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - // If ref count is greater than 1, the object has an external reference. - // If the timestamp is yet to be initialized, it needs to be updated too. - if ((itr->second.mValue != nullptr && itr->second.mValue->referenceCount() > 1) - || itr->second.mLastUsage == 0.0) - itr->second.mLastUsage = referenceTime; - } - } - - /** Removed object in the cache which have a time stamp at or before the specified expiry time. - * This would typically be called once per frame by applications which are doing database paging, - * and need to prune objects that are no longer required, and called after the a called - * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ - void removeExpiredObjectsInCache(double expiryTime) + // Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced + // from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other + // places so nullptr or not references elsewhere items are not always removed. + void update(double referenceTime, double expiryDelay) { std::vector> objectsToRemove; { + const double expiryTime = referenceTime - expiryDelay; std::lock_guard lock(_objectCacheMutex); - // Remove expired entries from object cache - typename ObjectCacheMap::iterator oitr = _objectCache.begin(); - while (oitr != _objectCache.end()) - { - if (oitr->second.mLastUsage <= expiryTime) - { - if (oitr->second.mValue != nullptr) - objectsToRemove.push_back(std::move(oitr->second.mValue)); - _objectCache.erase(oitr++); - } - else - ++oitr; - } + std::erase_if(_objectCache, [&](auto& v) { + Item& item = v.second; + if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0) + item.mLastUsage = referenceTime; + if (item.mLastUsage > expiryTime) + return false; + if (item.mValue != nullptr) + objectsToRemove.push_back(std::move(item.mValue)); + return true; + }); } // note, actual unref happens outside of the lock objectsToRemove.clear(); diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 55e4e142b5..b2427c308a 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -49,11 +49,7 @@ namespace Resource virtual ~GenericResourceManager() = default; /// Clear cache entries that have not been referenced for longer than expiryDelay. - void updateCache(double referenceTime) override - { - mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); - } + void updateCache(double referenceTime) override { mCache->update(referenceTime, mExpiryDelay); } /// Clear all cache entries. void clearCache() override { mCache->clear(); } From fd2fc63dd3c1c7d7788c4e16b979d82c90eafba2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 13:50:58 +0100 Subject: [PATCH 0686/2167] Support heterogeneous lookup in GenericObjectCache --- .../resource/testobjectcache.cpp | 57 +++++++++++++++++++ components/resource/objectcache.hpp | 17 ++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 25b6d87e52..1c8cff6af0 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -288,5 +288,62 @@ namespace Resource EXPECT_EQ(cache->lowerBound(4), std::nullopt); } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(std::string_view("key"), value); + EXPECT_EQ(cache->getRefFromObjectCache(key), value); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldKeyMoving) + { + osg::ref_ptr> cache(new GenericObjectCache); + std::string key(128, 'a'); + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(std::move(key), value); + EXPECT_EQ(key, ""); + EXPECT_EQ(cache->getRefFromObjectCache(std::string(128, 'a')), value); + } + + TEST(ResourceGenericObjectCacheTest, removeFromObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + ASSERT_EQ(cache->getRefFromObjectCache(key), value); + cache->removeFromObjectCache(std::string_view("key")); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_EQ(cache->getRefFromObjectCache(std::string_view("key")), value); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(std::string_view("key")), Optional(value)); + } + + TEST(ResourceGenericObjectCacheTest, checkInObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_TRUE(cache->checkInObjectCache(std::string_view("key"), 0)); + } } } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 08ef0dc060..066f5b1d3e 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -81,14 +81,19 @@ namespace Resource } /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ - void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) + template + void addEntryToObjectCache(K&& key, osg::Object* object, double timestamp = 0.0) { std::lock_guard lock(_objectCacheMutex); - _objectCache[key] = Item{ object, timestamp }; + const auto it = _objectCache.find(key); + if (it == _objectCache.end()) + _objectCache.emplace_hint(it, std::forward(key), Item{ object, timestamp }); + else + it->second = Item{ object, timestamp }; } /** Remove Object from cache.*/ - void removeFromObjectCache(const KeyType& key) + void removeFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); @@ -97,7 +102,7 @@ namespace Resource } /** Get an ref_ptr from the object cache*/ - osg::ref_ptr getRefFromObjectCache(const KeyType& key) + osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); @@ -107,7 +112,7 @@ namespace Resource return nullptr; } - std::optional> getRefFromObjectCacheOrNone(const KeyType& key) + std::optional> getRefFromObjectCacheOrNone(const auto& key) { const std::lock_guard lock(_objectCacheMutex); const auto it = _objectCache.find(key); @@ -117,7 +122,7 @@ namespace Resource } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ - bool checkInObjectCache(const KeyType& key, double timeStamp) + bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); From 2f0613c8d453f72a22f71076384e45c07a9b235b Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:18:29 +0100 Subject: [PATCH 0687/2167] Remove user defined destructor for GenericObjectCache --- components/resource/objectcache.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 066f5b1d3e..9cb74c762c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -194,8 +194,6 @@ namespace Resource double mLastUsage; }; - virtual ~GenericObjectCache() {} - using ObjectCacheMap = std::map>; ObjectCacheMap _objectCache; From 7b1ee2780b16652406cbfae21e5936f425489aa5 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:17:12 +0100 Subject: [PATCH 0688/2167] Use ranged for loops in GenericObjectCache --- components/resource/objectcache.hpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 9cb74c762c..8f99e6dfda 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -139,26 +139,18 @@ namespace Resource void releaseGLObjects(osg::State* state) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - osg::Object* object = itr->second.mValue.get(); - object->releaseGLObjects(state); - } + for (const auto& [k, v] : _objectCache) + v.mValue->releaseGLObjects(state); } /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - if (osg::Object* object = itr->second.mValue.get()) - { - osg::Node* node = dynamic_cast(object); - if (node) + for (const auto& [k, v] : _objectCache) + if (osg::Object* const object = v.mValue.get()) + if (osg::Node* const node = dynamic_cast(object)) node->accept(nv); - } - } } /** call operator()(KeyType, osg::Object*) for each object in the cache. */ @@ -166,8 +158,8 @@ namespace Resource void call(Functor&& f) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->first, it->second.mValue.get()); + for (const auto& [k, v] : _objectCache) + f(k, v.mValue.get()); } /** Get the number of objects in the cache. */ From 45b1b4f1e0344caf3c8dd503ea3d9e167000badd Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:19:13 +0100 Subject: [PATCH 0689/2167] Remove redundant ObjectCacheMap alias --- components/resource/objectcache.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 8f99e6dfda..5374cef460 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -96,7 +96,7 @@ namespace Resource void removeFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) _objectCache.erase(itr); } @@ -105,7 +105,7 @@ namespace Resource osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) return itr->second.mValue; else @@ -125,7 +125,7 @@ namespace Resource bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) { itr->second.mLastUsage = timeStamp; @@ -186,9 +186,7 @@ namespace Resource double mLastUsage; }; - using ObjectCacheMap = std::map>; - - ObjectCacheMap _objectCache; + std::map> _objectCache; mutable std::mutex _objectCacheMutex; }; From 7a817d31470d1804e7944e6d1f116c59662dfcb9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:23:36 +0100 Subject: [PATCH 0690/2167] Apply project naming styleguide to GenericObjectCache --- components/resource/objectcache.hpp | 68 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 5374cef460..dffa0e9fdb 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -57,8 +57,8 @@ namespace Resource std::vector> objectsToRemove; { const double expiryTime = referenceTime - expiryDelay; - std::lock_guard lock(_objectCacheMutex); - std::erase_if(_objectCache, [&](auto& v) { + std::lock_guard lock(mMutex); + std::erase_if(mItems, [&](auto& v) { Item& item = v.second; if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0) item.mLastUsage = referenceTime; @@ -76,18 +76,18 @@ namespace Resource /** Remove all objects in the cache regardless of having external references or expiry times.*/ void clear() { - std::lock_guard lock(_objectCacheMutex); - _objectCache.clear(); + std::lock_guard lock(mMutex); + mItems.clear(); } /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ template void addEntryToObjectCache(K&& key, osg::Object* object, double timestamp = 0.0) { - std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.find(key); - if (it == _objectCache.end()) - _objectCache.emplace_hint(it, std::forward(key), Item{ object, timestamp }); + std::lock_guard lock(mMutex); + const auto it = mItems.find(key); + if (it == mItems.end()) + mItems.emplace_hint(it, std::forward(key), Item{ object, timestamp }); else it->second = Item{ object, timestamp }; } @@ -95,18 +95,18 @@ namespace Resource /** Remove Object from cache.*/ void removeFromObjectCache(const auto& key) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) - _objectCache.erase(itr); + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) + mItems.erase(itr); } /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const auto& key) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) return itr->second.mValue; else return nullptr; @@ -114,9 +114,9 @@ namespace Resource std::optional> getRefFromObjectCacheOrNone(const auto& key) { - const std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.find(key); - if (it == _objectCache.end()) + const std::lock_guard lock(mMutex); + const auto it = mItems.find(key); + if (it == mItems.end()) return std::nullopt; return it->second.mValue; } @@ -124,9 +124,9 @@ namespace Resource /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const auto& key, double timeStamp) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) { itr->second.mLastUsage = timeStamp; return true; @@ -138,16 +138,16 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) v.mValue->releaseGLObjects(state); } /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) if (osg::Object* const object = v.mValue.get()) if (osg::Node* const node = dynamic_cast(object)) node->accept(nv); @@ -157,24 +157,24 @@ namespace Resource template void call(Functor&& f) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) f(k, v.mValue.get()); } /** Get the number of objects in the cache. */ unsigned int getCacheSize() const { - std::lock_guard lock(_objectCacheMutex); - return _objectCache.size(); + std::lock_guard lock(mMutex); + return mItems.size(); } template std::optional>> lowerBound(K&& key) { - const std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.lower_bound(std::forward(key)); - if (it == _objectCache.end()) + const std::lock_guard lock(mMutex); + const auto it = mItems.lower_bound(std::forward(key)); + if (it == mItems.end()) return std::nullopt; return std::pair(it->first, it->second.mValue); } @@ -186,8 +186,8 @@ namespace Resource double mLastUsage; }; - std::map> _objectCache; - mutable std::mutex _objectCacheMutex; + std::map> mItems; + mutable std::mutex mMutex; }; class ObjectCache : public GenericObjectCache From 088d4ee5ceae7f27465615f3563969954b0c8b27 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 23:45:56 +0100 Subject: [PATCH 0691/2167] Use settings values to declare double settings --- apps/opencs/model/prefs/doublesetting.cpp | 2 +- apps/opencs/model/prefs/doublesetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 47 +++++++++++++---------- apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 679c8d6a92..bbe573f800 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mPrecision(2) , mMin(0) diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index add85cb9b3..856cebcb46 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs public: explicit DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 9b7475dea8..45ec001bd4 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -180,29 +180,36 @@ void CSMPrefs::State::declare() declareCategory("3D Scene Input"); - declareDouble("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); - declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareDouble(mValues->mSceneInput.mNaviWheelFactor, "Camera Zoom Sensitivity").setRange(-100.0, 100.0); + declareDouble(mValues->mSceneInput.mSNaviSensitivity, "Secondary Camera Movement Sensitivity") + .setRange(-1000.0, 1000.0); - declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); + declareDouble(mValues->mSceneInput.mPNaviFreeSensitivity, "Free Camera Sensitivity") + .setPrecision(5) + .setRange(0.0, 1.0); declareBool(mValues->mSceneInput.mPNaviFreeInvert, "Invert Free Camera Mouse Input"); - declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); - declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareDouble(mValues->mSceneInput.mNaviFreeLinSpeed, "Free Camera Linear Speed").setRange(1.0, 10000.0); + declareDouble(mValues->mSceneInput.mNaviFreeRotSpeed, "Free Camera Rotational Speed").setRange(0.001, 6.28); + declareDouble(mValues->mSceneInput.mNaviFreeSpeedMult, "Free Camera Speed Multiplier (from Modifier)") + .setRange(0.001, 1000.0); - declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); + declareDouble(mValues->mSceneInput.mPNaviOrbitSensitivity, "Orbit Camera Sensitivity") + .setPrecision(5) + .setRange(0.0, 1.0); declareBool(mValues->mSceneInput.mPNaviOrbitInvert, "Invert Orbit Camera Mouse Input"); - declareDouble("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); - declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) + declareDouble(mValues->mSceneInput.mNaviOrbitRotSpeed, "Orbital Camera Rotational Speed").setRange(0.001, 6.28); + declareDouble(mValues->mSceneInput.mNaviOrbitSpeedMult, "Orbital Camera Speed Multiplier (from Modifier)") .setRange(0.001, 1000.0); declareBool(mValues->mSceneInput.mNaviOrbitConstRoll, "Keep camera roll constant for orbital camera"); declareBool(mValues->mSceneInput.mContextSelect, "Context Sensitive Selection"); - declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); - declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0); - declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0) + declareDouble(mValues->mSceneInput.mDragFactor, "Mouse sensitivity during drag operations").setRange(0.001, 100.0); + declareDouble(mValues->mSceneInput.mDragWheelFactor, "Mouse wheel sensitivity during drag operations") + .setRange(0.001, 100.0); + declareDouble(mValues->mSceneInput.mDragShiftFactor, "Shift-acceleration factor during drag operations") .setTooltip("Acceleration factor during drag operations while holding down shift") .setRange(0.001, 100.0); - declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareDouble(mValues->mSceneInput.mRotateFactor, "Free rotation factor").setPrecision(4).setRange(0.0001, 0.1); declareCategory("Rendering"); declareInt(mValues->mRendering.mFramerateLimit, "FPS limit") @@ -213,7 +220,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); - declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); + declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1); declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) @@ -264,9 +271,9 @@ void CSMPrefs::State::declare() secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); declareCategory("3D Scene Editing"); - declareDouble("gridsnap-movement", "Grid snap size", 16); - declareDouble("gridsnap-rotation", "Angle snap size", 15); - declareDouble("gridsnap-scale", "Scale snap size", 0.25); + declareDouble(mValues->mSceneEditing.mGridsnapMovement, "Grid snap size"); + declareDouble(mValues->mSceneEditing.mGridsnapRotation, "Angle snap size"); + declareDouble(mValues->mSceneEditing.mGridsnapScale, "Scale snap size"); declareInt(mValues->mSceneEditing.mDistance, "Drop Distance") .setTooltip( "If an instance drop can not be placed against another object at the " @@ -286,7 +293,7 @@ void CSMPrefs::State::declare() .setMin(1); declareBool(mValues->mSceneEditing.mLandeditPostSmoothpainting, "Smooth land after painting height") .setTooltip("Raise and lower tools will leave bumpy finish without this option"); - declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25) + declareDouble(mValues->mSceneEditing.mLandeditPostSmoothstrength, "Smoothing strength (post-edit)") .setTooltip( "If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " "Negative values may be used to roughen instead of smooth.") @@ -475,13 +482,13 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(Settings::SettingValue& v return *setting; } -CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, const QString& label, double default_) +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::DoubleSetting* setting - = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 62db966fcd..eaf829de7e 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -65,7 +65,7 @@ namespace CSMPrefs IntSetting& declareInt(Settings::SettingValue& value, const QString& label); - DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); + DoubleSetting& declareDouble(Settings::SettingValue& value, const QString& label); BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); From abbbd8cf698a209adc516cec2aadde8daef02921 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Dec 2023 19:23:03 +0300 Subject: [PATCH 0692/2167] Fix interior sun direction (bug #7585) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3380a3537d..44cbca1001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default + Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2cfcd2fd37..fa92fa1420 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -693,8 +693,9 @@ namespace MWRender osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell.getMood().mDirectionalColor); setSunColour(diffuse, diffuse, 1.f); - - const osg::Vec4f interiorSunPos = osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f); + // This is total nonsense but it's what Morrowind uses + static const osg::Vec4f interiorSunPos + = osg::Vec4f(-1.f, osg::DegreesToRadians(45.f), osg::DegreesToRadians(45.f), 0.f); mPostProcessor->getStateUpdater()->setSunPos(interiorSunPos, false); mSunLight->setPosition(interiorSunPos); } From 0fc78aa1732279d5a62cc1723e4552287a0ebcb8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 13:10:13 +0100 Subject: [PATCH 0693/2167] Make ESM::StringRefId compatible with std::string UniversalId --- apps/opencs/model/world/universalid.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index dec533b015..d883cccea5 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -360,6 +360,10 @@ const std::string& CSMWorld::UniversalId::getId() const if (const std::string* result = std::get_if(&mValue)) return *result; + if (const ESM::RefId* refId = std::get_if(&mValue)) + if (const ESM::StringRefId* result = refId->getIf()) + return result->getValue(); + throw std::logic_error("invalid access to ID of " + ::toString(getArgumentType()) + " UniversalId"); } From 7b5310b5694b4128f6c9b9addc04701ea582b3db Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 14:04:02 +0100 Subject: [PATCH 0694/2167] Write StringRefId as is when converting UniversalId to string To avoid adding quotes which on conversion ESM::StringRefId -> UniversalId -> std::string -> UniversalId changes the string value adding quotes. --- apps/opencs/model/world/universalid.cpp | 2 ++ apps/opencs_tests/model/world/testuniversalid.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index d883cccea5..3d40d5f30f 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -187,6 +187,8 @@ namespace { mStream << ": " << value; } + + void operator()(const ESM::RefId& value) const { mStream << ": " << value.toString(); } }; struct GetTypeData diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp index 2e610b0dd0..54538a591d 100644 --- a/apps/opencs_tests/model/world/testuniversalid.cpp +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -177,11 +177,11 @@ namespace CSMWorld UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: \"g\"", + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", ":./instance.png" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", - "Instance: Index:SKIL:0x2a", ":./instance.png" }, + "Instance: SKIL:0x2a", ":./instance.png" }, }; INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); From 39dd73263dc37c6ac116e794d14f0bba3c5301e4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 14:15:28 +0100 Subject: [PATCH 0695/2167] Avoid converting UniversalId to a different type via string --- apps/opencs/model/world/universalid.cpp | 6 ++++++ apps/opencs/model/world/universalid.hpp | 2 ++ apps/opencs/view/doc/view.cpp | 2 +- apps/opencs/view/world/table.cpp | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 3d40d5f30f..2c2948e9e2 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -328,6 +328,12 @@ CSMWorld::UniversalId::UniversalId(Type type, ESM::RefId id) throw std::logic_error("invalid RefId argument UniversalId type: " + std::to_string(type)); } +CSMWorld::UniversalId::UniversalId(Type type, const UniversalId& id) + : mType(type) + , mValue(id.mValue) +{ +} + CSMWorld::UniversalId::UniversalId(Type type, int index) : mType(type) , mValue(index) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 2d3385bcb4..8c188e8da5 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -158,6 +158,8 @@ namespace CSMWorld UniversalId(Type type, int index); ///< Using a type for a non-index-argument UniversalId will throw an exception. + UniversalId(Type type, const UniversalId& id); + Class getClass() const; ArgumentType getArgumentType() const; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4f4e687d8f..e1bf7e6ac6 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -629,7 +629,7 @@ void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string if (isReferenceable) { view = mSubViewFactory.makeSubView( - CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); + CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id), *mDocument); } else { diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 1e80805630..4212e952e8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -694,10 +694,10 @@ void CSVWorld::Table::previewRecord() if (selectedRows.size() == 1) { - std::string id = getUniversalId(selectedRows.begin()->row()).getId(); + CSMWorld::UniversalId id = getUniversalId(selectedRows.begin()->row()); QModelIndex index - = mModel->getModelIndex(id, mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); + = mModel->getModelIndex(id.getId(), mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); if (mModel->data(index) != CSMWorld::RecordBase::State_Deleted) emit editRequest(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, id), ""); From 0e2e386dc99f08f42361c8581f63081c147f458f Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Dec 2023 18:56:59 +0000 Subject: [PATCH 0696/2167] Lua actions take 3 --- CI/file_name_exceptions.txt | 1 + apps/openmw/mwbase/luamanager.hpp | 8 + apps/openmw/mwlua/inputbindings.cpp | 124 +++++++ apps/openmw/mwlua/luamanagerimp.cpp | 12 +- apps/openmw/mwlua/luamanagerimp.hpp | 7 + apps/openmw_test_suite/CMakeLists.txt | 1 + .../lua/test_inputactions.cpp | 65 ++++ components/CMakeLists.txt | 2 +- components/lua/inputactions.cpp | 288 +++++++++++++++ components/lua/inputactions.hpp | 153 ++++++++ components/misc/strings/algorithm.hpp | 7 + .../lua-scripting/engine_handlers.rst | 3 +- .../lua-scripting/setting_renderers.rst | 24 ++ files/data/CMakeLists.txt | 2 + files/data/builtin.omwscripts | 2 + files/data/l10n/OMWControls/en.yaml | 73 +++- files/data/l10n/OMWControls/fr.yaml | 3 + files/data/l10n/OMWControls/ru.yaml | 2 + files/data/l10n/OMWControls/sv.yaml | 3 + files/data/scripts/omw/camera/camera.lua | 64 ++-- files/data/scripts/omw/camera/move360.lua | 41 ++- .../data/scripts/omw/input/actionbindings.lua | 255 +++++++++++++ .../data/scripts/omw/input/smoothmovement.lua | 92 +++++ files/data/scripts/omw/playercontrols.lua | 344 +++++++++++------- files/lua_api/openmw/input.lua | 115 +++++- 25 files changed, 1490 insertions(+), 201 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_inputactions.cpp create mode 100644 components/lua/inputactions.cpp create mode 100644 components/lua/inputactions.hpp create mode 100644 files/data/scripts/omw/input/actionbindings.lua create mode 100644 files/data/scripts/omw/input/smoothmovement.lua diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt index 5035d73f27..c3bcee8661 100644 --- a/CI/file_name_exceptions.txt +++ b/CI/file_name_exceptions.txt @@ -19,6 +19,7 @@ apps/openmw_test_suite/lua/test_serialization.cpp apps/openmw_test_suite/lua/test_storage.cpp apps/openmw_test_suite/lua/test_ui_content.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp +apps/openmw_test_suite/lua/test_inputactions.cpp apps/openmw_test_suite/misc/test_endianness.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_stringops.cpp diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e4b16ff725..6e611aa88f 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -29,6 +29,14 @@ namespace ESM struct LuaScripts; } +namespace LuaUtil +{ + namespace InputAction + { + class Registry; + } +} + namespace MWBase { // \brief LuaManager is the central interface through which the engine invokes lua scripts. diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 02babf0399..8763dce28d 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -4,12 +4,14 @@ #include #include +#include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" +#include "luamanagerimp.hpp" namespace sol { @@ -17,6 +19,16 @@ namespace sol struct is_automagical : std::false_type { }; + + template <> + struct is_automagical : std::false_type + { + }; + + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -46,9 +58,121 @@ namespace MWLua touchpadEvent["pressure"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); + auto inputActions = context.mLua->sol().new_usertype("InputActions"); + inputActions[sol::meta_function::index] + = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputAction::Registry& registry) { + auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key) + -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); + }; + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputActions[sol::meta_function::pairs] = pairs; + } + + auto actionInfo = context.mLua->sol().new_usertype("ActionInfo", "key", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; })); + + auto inputTriggers = context.mLua->sol().new_usertype("InputTriggers"); + inputTriggers[sol::meta_function::index] + = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { + auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) + -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); + }; + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputTriggers[sol::meta_function::pairs] = pairs; + } + + auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo", "key", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; })); + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); + api["ACTION_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Boolean", LuaUtil::InputAction::Type::Boolean }, + { "Number", LuaUtil::InputAction::Type::Number }, + { "Range", LuaUtil::InputAction::Type::Range }, + })); + + api["actions"] = std::ref(context.mLuaManager->inputActions()); + api["registerAction"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputAction::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mType = options["type"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + parsedOptions.mDefaultValue = options["defaultValue"].get(); + manager->inputActions().insert(parsedOptions); + }; + api["bindAction"] = [manager = context.mLuaManager]( + std::string_view key, const sol::table& callback, sol::table dependencies) { + std::vector parsedDependencies; + parsedDependencies.reserve(dependencies.size()); + for (size_t i = 1; i <= dependencies.size(); ++i) + { + sol::object dependency = dependencies[i]; + if (!dependency.is()) + throw std::domain_error("The dependencies argument must be a list of Action keys"); + parsedDependencies.push_back(dependency.as()); + } + if (!manager->inputActions().bind(key, LuaUtil::Callback::fromLua(callback), parsedDependencies)) + throw std::domain_error("Cyclic action binding"); + }; + api["registerActionHandler"] + = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { + manager->inputActions().registerHandler(key, LuaUtil::Callback::fromLua(callback)); + }; + api["getBooleanActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Boolean); + }; + api["getNumberActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Number); + }; + api["getRangeActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Range); + }; + + api["triggers"] = std::ref(context.mLuaManager->inputTriggers()); + api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputTrigger::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + manager->inputTriggers().insert(parsedOptions); + }; + api["registerTriggerHandler"] + = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { + manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback)); + }; + api["activateTrigger"] + = [manager = context.mLuaManager](std::string_view key) { manager->inputTriggers().activate(key); }; + api["isIdle"] = [input]() { return input->isIdle(); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; api["isKeyPressed"] = [](SDL_Scancode code) -> bool { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c324360287..baf13edac4 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -225,10 +225,12 @@ namespace MWLua playerScripts->processInputEvent(event); } mInputEvents.clear(); + double frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() + ? 0.0 + : MWBase::Environment::get().getFrameDuration(); + mInputActions.update(frameDuration); if (playerScripts) - playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() - ? 0.0 - : MWBase::Environment::get().getFrameDuration()); + playerScripts->onFrame(frameDuration); mProcessingInputEvents = false; for (const std::string& message : mUIMessages) @@ -291,6 +293,8 @@ namespace MWLua } mGlobalStorage.clearTemporaryAndRemoveCallbacks(); mPlayerStorage.clearTemporaryAndRemoveCallbacks(); + mInputActions.clear(); + mInputTriggers.clear(); for (int i = 0; i < 5; ++i) lua_gc(mLua.sol(), LUA_GCCOLLECT, 0); } @@ -520,6 +524,8 @@ namespace MWLua MWBase::Environment::get().getL10nManager()->dropCache(); mUiResourceManager.clear(); mLua.dropScriptCache(); + mInputActions.clear(); + mInputTriggers.clear(); initConfiguration(); { // Reload global scripts diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index ae16689562..8bd189d8e9 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -144,6 +145,9 @@ namespace MWLua void reportStats(unsigned int frameNumber, osg::Stats& stats) const; std::string formatResourceUsageStats() const override; + LuaUtil::InputAction::Registry& inputActions() { return mInputActions; } + LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, @@ -206,6 +210,9 @@ namespace MWLua LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; + + LuaUtil::InputAction::Registry mInputActions; + LuaUtil::InputTrigger::Registry mInputTriggers; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 4f93319c96..c8679ab7f4 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -28,6 +28,7 @@ file(GLOB UNITTEST_SRC_FILES lua/test_l10n.cpp lua/test_storage.cpp lua/test_async.cpp + lua/test_inputactions.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_inputactions.cpp b/apps/openmw_test_suite/lua/test_inputactions.cpp new file mode 100644 index 0000000000..5bdd39ada1 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_inputactions.cpp @@ -0,0 +1,65 @@ +#include "gmock/gmock.h" +#include + +#include +#include + +#include "../testing_util.hpp" + +namespace +{ + using namespace testing; + using namespace TestingOpenMW; + + TEST(LuaInputActionsTest, MultiTree) + { + { + LuaUtil::InputAction::MultiTree tree; + auto a = tree.insert(); + auto b = tree.insert(); + auto c = tree.insert(); + auto d = tree.insert(); + EXPECT_TRUE(tree.multiEdge(c, { a, b })); + EXPECT_TRUE(tree.multiEdge(a, { d })); + EXPECT_FALSE(tree.multiEdge(d, { c })); + } + + { + LuaUtil::InputAction::MultiTree tree; + auto a = tree.insert(); + auto b = tree.insert(); + auto c = tree.insert(); + EXPECT_TRUE(tree.multiEdge(b, { a })); + EXPECT_TRUE(tree.multiEdge(c, { a, b })); + } + } + + TEST(LuaInputActionsTest, Registry) + { + sol::state lua; + LuaUtil::InputAction::Registry registry; + LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description", + sol::make_object(lua, false) }); + registry.insert(a); + LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description", + sol::make_object(lua, false) }); + registry.insert(b); + LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) }); + LuaUtil::Callback bindBToA( + { lua.load("return function(_, _, aValue) return aValue end")(), sol::table(lua, sol::create) }); + EXPECT_TRUE(registry.bind("a", bindA, {})); + EXPECT_TRUE(registry.bind("b", bindBToA, { "a" })); + registry.update(1.0); + sol::object bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(bValue.is()); + LuaUtil::Callback badA( + { lua.load("return function() return 'not_a_bool' end")(), sol::table(lua, sol::create) }); + EXPECT_TRUE(registry.bind("a", badA, {})); + testing::internal::CaptureStderr(); + registry.update(1.0); + sol::object aValue = registry.valueOfType("a", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(aValue.is()); + bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(bValue.is() && bValue.as() == aValue.as()); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..95e523d07c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,7 +44,7 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box + shapes/box inputactions ) add_component_dir (l10n diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp new file mode 100644 index 0000000000..c21fbcf112 --- /dev/null +++ b/components/lua/inputactions.cpp @@ -0,0 +1,288 @@ +#include "inputactions.hpp" + +#include +#include + +#include +#include + +#include "luastate.hpp" + +namespace LuaUtil +{ + namespace InputAction + { + namespace + { + std::string_view typeName(Type actionType) + { + switch (actionType) + { + case Type::Boolean: + return "Boolean"; + case Type::Number: + return "Number"; + case Type::Range: + return "Range"; + default: + throw std::logic_error("Unknown input action type"); + } + } + } + + MultiTree::Node MultiTree::insert() + { + size_t nextId = size(); + mChildren.push_back({}); + mParents.push_back({}); + return nextId; + } + + bool MultiTree::validateTree() const + { + std::vector complete(size(), false); + traverse([&complete](Node node) { complete[node] = true; }); + return std::find(complete.begin(), complete.end(), false) == complete.end(); + } + + template + void MultiTree::traverse(Function callback) const + { + std::queue nodeQueue; + std::vector complete(size(), false); + for (Node root = 0; root < size(); ++root) + { + if (!complete[root]) + nodeQueue.push(root); + while (!nodeQueue.empty()) + { + Node node = nodeQueue.back(); + nodeQueue.pop(); + + bool isComplete = true; + for (Node parent : mParents[node]) + isComplete = isComplete && complete[parent]; + complete[node] = isComplete; + if (isComplete) + { + callback(node); + for (Node child : mChildren[node]) + nodeQueue.push(child); + } + } + } + } + + bool MultiTree::multiEdge(Node target, const std::vector& source) + { + mParents[target].reserve(mParents[target].size() + source.size()); + for (Node s : source) + { + mParents[target].push_back(s); + mChildren[s].push_back(target); + } + bool validTree = validateTree(); + if (!validTree) + { + for (Node s : source) + { + mParents[target].pop_back(); + mChildren[s].pop_back(); + } + } + return validTree; + } + + namespace + { + bool validateActionValue(sol::object value, Type type) + { + switch (type) + { + case Type::Boolean: + return value.get_type() == sol::type::boolean; + case Type::Number: + return value.get_type() == sol::type::number; + case Type::Range: + if (value.get_type() != sol::type::number) + return false; + double d = value.as(); + return 0.0 <= d && d <= 1.0; + } + throw std::invalid_argument("Unknown action type"); + } + } + + void Registry::insert(Info info) + { + if (mIds.find(info.mKey) != mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey)); + if (info.mKey.empty()) + throw std::domain_error("Action key can't be an empty string"); + if (info.mL10n.empty()) + throw std::domain_error("Localization context can't be empty"); + if (!validateActionValue(info.mDefaultValue, info.mType)) + throw std::logic_error(Misc::StringUtils::format( + "Invalid value: \"%s\" for action \"%s\"", LuaUtil::toString(info.mDefaultValue), info.mKey)); + Id id = mBindingTree.insert(); + mKeys.push_back(info.mKey); + mIds[std::string(info.mKey)] = id; + mInfo.push_back(info); + mHandlers.push_back({}); + mBindings.push_back({}); + mValues.push_back(info.mDefaultValue); + } + + std::optional Registry::nextKey(std::string_view key) const + { + auto it = mIds.find(key); + if (it == mIds.end()) + return std::nullopt; + auto nextId = it->second + 1; + if (nextId >= mKeys.size()) + return std::nullopt; + return mKeys.at(nextId); + } + + std::optional Registry::operator[](std::string_view actionKey) + { + auto iter = mIds.find(actionKey); + if (iter == mIds.end()) + return std::nullopt; + return mInfo[iter->second]; + } + + Registry::Id Registry::safeIdByKey(std::string_view key) + { + auto iter = mIds.find(key); + if (iter == mIds.end()) + throw std::logic_error(Misc::StringUtils::format("Unknown action key: \"%s\"", key)); + return iter->second; + } + + bool Registry::bind( + std::string_view key, const LuaUtil::Callback& callback, const std::vector& dependencies) + { + Id id = safeIdByKey(key); + std::vector dependencyIds; + dependencyIds.reserve(dependencies.size()); + for (std::string_view s : dependencies) + dependencyIds.push_back(safeIdByKey(s)); + bool validEdge = mBindingTree.multiEdge(id, dependencyIds); + if (validEdge) + mBindings[id].push_back(Binding{ + callback, + std::move(dependencyIds), + }); + return validEdge; + } + + sol::object Registry::valueOfType(std::string_view key, Type type) + { + Id id = safeIdByKey(key); + Info info = mInfo[id]; + if (info.mType != type) + throw std::logic_error( + Misc::StringUtils::format("Attempt to get value of type \"%s\" from action \"%s\" with type \"%s\"", + typeName(type), key, typeName(info.mType))); + return mValues[id]; + } + + void Registry::update(double dt) + { + std::vector dependencyValues; + mBindingTree.traverse([this, &dependencyValues, dt](Id node) { + sol::main_object newValue = mValues[node]; + std::vector& bindings = mBindings[node]; + bindings.erase(std::remove_if(bindings.begin(), bindings.end(), + [&](const Binding& binding) { + if (!binding.mCallback.isValid()) + return true; + + dependencyValues.clear(); + for (Id parent : binding.mDependencies) + dependencyValues.push_back(mValues[parent]); + try + { + newValue = sol::main_object( + binding.mCallback.call(dt, newValue, sol::as_args(dependencyValues))); + } + catch (std::exception& e) + { + if (!validateActionValue(newValue, mInfo[node].mType)) + Log(Debug::Error) << Misc::StringUtils::format( + "Error due to invalid value of action \"%s\"(\"%s\"): ", mKeys[node], + LuaUtil::toString(newValue)) + << e.what(); + else + Log(Debug::Error) << "Error in callback: " << e.what(); + } + return false; + }), + bindings.end()); + + if (!validateActionValue(newValue, mInfo[node].mType)) + Log(Debug::Error) << Misc::StringUtils::format( + "Invalid value of action \"%s\": %s", mKeys[node], LuaUtil::toString(newValue)); + if (mValues[node] != newValue) + { + mValues[node] = sol::object(newValue); + std::vector& handlers = mHandlers[node]; + handlers.erase(std::remove_if(handlers.begin(), handlers.end(), + [&](const LuaUtil::Callback& handler) { + if (!handler.isValid()) + return true; + handler.tryCall(newValue); + return false; + }), + handlers.end()); + } + }); + } + } + + namespace InputTrigger + { + Registry::Id Registry::safeIdByKey(std::string_view key) + { + auto it = mIds.find(key); + if (it == mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Unknown trigger key \"%s\"", key)); + return it->second; + } + + void Registry::insert(Info info) + { + if (mIds.find(info.mKey) != mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey)); + if (info.mKey.empty()) + throw std::domain_error("Trigger key can't be an empty string"); + if (info.mL10n.empty()) + throw std::domain_error("Localization context can't be empty"); + Id id = mIds.size(); + mIds[info.mKey] = id; + mInfo.push_back(info); + mHandlers.push_back({}); + } + + void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback) + { + Id id = safeIdByKey(key); + mHandlers[id].push_back(callback); + } + + void Registry::activate(std::string_view key) + { + Id id = safeIdByKey(key); + std::vector& handlers = mHandlers[id]; + handlers.erase(std::remove_if(handlers.begin(), handlers.end(), + [&](const LuaUtil::Callback& handler) { + if (!handler.isValid()) + return true; + handler.tryCall(); + return false; + }), + handlers.end()); + } + } +} diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp new file mode 100644 index 0000000000..ac3907b55d --- /dev/null +++ b/components/lua/inputactions.hpp @@ -0,0 +1,153 @@ +#ifndef COMPONENTS_LUA_INPUTACTIONS +#define COMPONENTS_LUA_INPUTACTIONS + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace LuaUtil::InputAction +{ + enum class Type + { + Boolean, + Number, + Range, + }; + + struct Info + { + std::string mKey; + Type mType; + std::string mL10n; + std::string mName; + std::string mDescription; + sol::main_object mDefaultValue; + }; + + class MultiTree + { + public: + using Node = size_t; + + Node insert(); + bool multiEdge(Node target, const std::vector& source); + size_t size() const { return mParents.size(); } + + template // Function = void(Node) + void traverse(Function callback) const; + + void clear() + { + mParents.clear(); + mChildren.clear(); + } + + private: + std::vector> mParents; + std::vector> mChildren; + + bool validateTree() const; + }; + + class Registry + { + public: + using ConstIterator = std::vector::const_iterator; + void insert(Info info); + size_t size() const { return mKeys.size(); } + std::optional firstKey() const { return mKeys.empty() ? std::nullopt : std::optional(mKeys[0]); } + std::optional nextKey(std::string_view key) const; + std::optional operator[](std::string_view actionKey); + bool bind( + std::string_view key, const LuaUtil::Callback& callback, const std::vector& dependencies); + sol::object valueOfType(std::string_view key, Type type); + void update(double dt); + void registerHandler(std::string_view key, const LuaUtil::Callback& handler) + { + mHandlers[safeIdByKey(key)].push_back(handler); + } + void clear() + { + mKeys.clear(); + mIds.clear(); + mInfo.clear(); + mHandlers.clear(); + mBindings.clear(); + mValues.clear(); + mBindingTree.clear(); + } + + private: + using Id = MultiTree::Node; + Id safeIdByKey(std::string_view key); + struct Binding + { + LuaUtil::Callback mCallback; + std::vector mDependencies; + }; + std::vector mKeys; + std::unordered_map> mIds; + std::vector mInfo; + std::vector> mHandlers; + std::vector> mBindings; + std::vector mValues; + MultiTree mBindingTree; + }; +} + +namespace LuaUtil::InputTrigger +{ + struct Info + { + std::string mKey; + std::string mL10n; + std::string mName; + std::string mDescription; + }; + + class Registry + { + public: + std::optional firstKey() const + { + return mIds.empty() ? std::nullopt : std::optional(mIds.begin()->first); + } + std::optional nextKey(std::string_view key) const + { + auto it = mIds.find(key); + if (it == mIds.end() || ++it == mIds.end()) + return std::nullopt; + return it->first; + } + std::optional operator[](std::string_view key) + { + Id id = safeIdByKey(key); + return mInfo[id]; + } + void insert(Info info); + void registerHandler(std::string_view key, const LuaUtil::Callback& callback); + void activate(std::string_view key); + void clear() + { + mInfo.clear(); + mHandlers.clear(); + mIds.clear(); + } + + private: + using Id = size_t; + Id safeIdByKey(std::string_view key); + std::unordered_map> mIds; + std::vector mInfo; + std::vector> mHandlers; + }; +} + +#endif // COMPONENTS_LUA_INPUTACTIONS diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index f34801b8d3..28bc696cd3 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -99,6 +99,13 @@ namespace Misc::StringUtils bool operator()(std::string_view left, std::string_view right) const { return ciLess(left, right); } }; + struct StringHash + { + using is_transparent = void; + [[nodiscard]] size_t operator()(std::string_view sv) const { return std::hash{}(sv); } + [[nodiscard]] size_t operator()(const std::string& s) const { return std::hash{}(s); } + }; + /** @brief Replaces all occurrences of a string in another string. * * @param str The string to operate on. diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 1ffa1820f3..10ed3ee555 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -109,7 +109,8 @@ Engine handler is a function defined by a script, that can be called by the engi | Usage example: | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` * - onInputAction(id) - - | `Game control `_ is pressed. + - | (DEPRECATED, use `registerActionHandler `_) + | `Game control `_ is pressed. | Usage example: | ``if id == input.ACTION.ToggleWeapon then ...`` * - onTouchPress(touchEvent) diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 966246d503..7f40eb08bd 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -126,3 +126,27 @@ Table with the following optional fields: * - disabled - bool (false) - Disables changing the setting from the UI + +inputBinding +----- + +Allows the user to bind inputs to an action or trigger + +**Argument** + +Table with the following fields: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default) + - description + * - type + - 'keyboardPress', 'keyboardHold' + - The type of input that's allowed to be bound + * - key + - #string + - Key of the action or trigger to which the input is bound + diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..cec613d128 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -92,6 +92,8 @@ set(BUILTIN_DATA_FILES scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua + scripts/omw/input/actionbindings.lua + scripts/omw/input/smoothmovement.lua shaders/adjustments.omwfx shaders/bloomlinear.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index ec08c5299d..e4338df533 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -13,6 +13,8 @@ GLOBAL: scripts/omw/worldeventhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua +PLAYER: scripts/omw/input/actionbindings.lua +PLAYER: scripts/omw/input/smoothmovement.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index de6edde19a..9c45c1d1e5 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -10,7 +10,76 @@ alwaysRunDescription: | toggleSneak: "Toggle sneak" toggleSneakDescription: | - This setting causes the behavior of the sneak key (bound to Ctrl by default) - to toggle sneaking on and off rather than requiring the key to be held down while sneaking. + This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off + rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. +smoothControllerMovement: "Smooth controller movement" +smoothControllerMovementDescription: | + Enables smooth movement with controller stick, with no abrupt switch from walking to running. + +TogglePOV_name: "Toggle POV" +TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode." + +Zoom3rdPerson_name: "Zoom In/Out" +Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view." + +MoveForward_name: "Move Forward" +MoveForward_description: "Can cancel out with Move Backward" + +MoveBackward_name: "Move Backward" +MoveBackward_description: "Can cancel out with Move Forward" + +MoveLeft_name: "Move Left" +MoveLeft_description: "Can cancel out with Move Right" + +MoveRight_name: "Move Right" +MoveRight_description: "Can cancel out with Move Left" + +Use_name: "Use" +Use_description: "Attack with a weapon or cast a spell depending on current stance" + +Run_name: "Run" +Run_description: "Hold to run/walk, depending on the Always Run setting" + +AlwaysRun_name: "Always Run" +AlwaysRun_description: "Toggle the Always Run setting" + +Jump_name: "Jump" +Jump_description: "Jump whenever you are on the ground" + +AutoMove_name: "Auto Run" +AutoMove_description: "Toggle continous forward movement" + +Sneak_name: "Sneak" +Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off" + +ToggleSneak_name: "Toggle Sneak" +ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on" + +ToggleWeapon_name: "Ready Weapon" +ToggleWeapon_description: "Enter or leave the weapon stance" + +ToggleSpell_name: "Ready Magic" +ToggleSpell_description: "Enter or leave the magic stance" + +Inventory_name: "Inventory" +Inventory_description: "Open the inventory" + +Journal_name: "Journal" +Journal_description: "Open the journal" + +QuickKeysMenu_name: "QuickKeysMenu" +QuickKeysMenu_description: "Open the quick keys menu" + +SmoothMoveForward_name: "Smooth Move Forward" +SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions" + +SmoothMoveBackward_name: "Smooth Move Backward" +SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions" + +SmoothMoveLeft_name: "Smooth Move Left" +SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions" + +SkmoothMoveRight_name: "SmoothMove Right" +SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions" diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index 6d58ee1794..dab9ddb8fc 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -14,3 +14,6 @@ toggleSneakDescription: | Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. + +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 30bb15ee57..0ce3609e16 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -14,3 +14,5 @@ toggleSneakDescription: | чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/l10n/OMWControls/sv.yaml b/files/data/l10n/OMWControls/sv.yaml index 43565053c5..73fc5e18dc 100644 --- a/files/data/l10n/OMWControls/sv.yaml +++ b/files/data/l10n/OMWControls/sv.yaml @@ -13,3 +13,6 @@ toggleSneakDescription: | Denna inställningen gör att smygknappen (förinställt till ctrl) slår smygning på eller av vid ett knapptryck istället för att att kräva att knappen hålls nedtryckt för att smyga. Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad. + +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 38441f9e59..6c162f3a25 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -10,6 +10,24 @@ local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor local Player = require('openmw.types').Player +input.registerAction { + key = 'TogglePOV', + l10n = 'OMWControls', + name = 'TogglePOV_name', + description = 'TogglePOV_description', + type = input.ACTION_TYPE.Boolean, + defaultValue = false, +} + +input.registerAction { + key = 'Zoom3rdPerson', + l10n = 'OMWControls', + name = 'Zoom3rdPerson_name', + description = 'Zoom3rdPerson_description', + type = input.ACTION_TYPE.Number, + defaultValue = 0, +} + local settings = require('scripts.omw.camera.settings').thirdPerson local head_bobbing = require('scripts.omw.camera.head_bobbing') local third_person = require('scripts.omw.camera.third_person') @@ -63,7 +81,7 @@ local previewTimer = 0 local function updatePOV(dt) local switchLimit = 0.25 - if input.isActionPressed(input.ACTION.TogglePOV) and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then + if input.getBooleanActionValue('TogglePOV') and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then previewTimer = previewTimer + dt if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then third_person.standingPreview = false @@ -117,18 +135,19 @@ local maxDistance = 800 local function zoom(delta) if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or - camera.getMode() == MODE.Static or next(noZoom) then + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or + camera.getMode() == MODE.Static or next(noZoom) then return end if camera.getMode() ~= MODE.FirstPerson then local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance() if delta > 0 and third_person.baseDistance == minDistance and - (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then + (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then primaryMode = MODE.FirstPerson camera.setMode(primaryMode) elseif delta > 0 or obstacleDelta < -delta then - third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance) + third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, + maxDistance) end elseif delta < 0 and not next(noModeControl) then primaryMode = MODE.ThirdPerson @@ -137,21 +156,10 @@ local function zoom(delta) end end -local function applyControllerZoom(dt) - if input.isActionPressed(input.ACTION.TogglePOV) then - local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft) - local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) - local controllerZoom = (triggerRight - triggerLeft) * 100 * dt - if controllerZoom ~= 0 then - zoom(controllerZoom) - end - end -end - local function updateStandingPreview() local mode = camera.getMode() if not previewIfStandStill or next(noStandingPreview) - or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then + or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then third_person.standingPreview = false return end @@ -184,7 +192,7 @@ local function updateIdleTimer(dt) if not input.isIdle() then idleTimer = 0 elseif self.controls.movement ~= 0 or self.controls.sideMovement ~= 0 or self.controls.jump or self.controls.use ~= 0 then - idleTimer = 0 -- also reset the timer in case of a scripted movement + idleTimer = 0 -- also reset the timer in case of a scripted movement else idleTimer = idleTimer + dt end @@ -205,7 +213,14 @@ local function onFrame(dt) updateStandingPreview() updateCrosshair() end - applyControllerZoom(dt) + + do + local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson') + if Zoom3rdPerson ~= 0 then + zoom(Zoom3rdPerson) + end + end + third_person.update(dt, smoothedSpeed) if not next(noHeadBobbing) then head_bobbing.update(dt, smoothedSpeed) end if slowViewChange then @@ -312,15 +327,6 @@ return { engineHandlers = { onUpdate = onUpdate, onFrame = onFrame, - onInputAction = function(action) - if core.isWorldPaused() or I.UI.getMode() then return end - if action == input.ACTION.ZoomIn then - zoom(10) - elseif action == input.ACTION.ZoomOut then - zoom(-10) - end - move360.onInputAction(action) - end, onTeleported = function() camera.instantTransition() end, @@ -329,7 +335,7 @@ return { if data and data.distance then third_person.baseDistance = data.distance end end, onSave = function() - return {version = 0, distance = third_person.baseDistance} + return { version = 0, distance = third_person.baseDistance } end, }, } diff --git a/files/data/scripts/omw/camera/move360.lua b/files/data/scripts/omw/camera/move360.lua index 18c30ae77b..a80019f3a0 100644 --- a/files/data/scripts/omw/camera/move360.lua +++ b/files/data/scripts/omw/camera/move360.lua @@ -30,6 +30,26 @@ local function turnOff() end end +local function processZoom3rdPerson() + if + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or + input.getBooleanActionValue('TogglePOV') or + not I.Camera.isModeControlEnabled() + then + return + end + local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson') + if Zoom3rdPerson > 0 and camera.getMode() == MODE.Preview + and I.Camera.getBaseThirdPersonDistance() == 30 then + self.controls.yawChange = camera.getYaw() - self.rotation:getYaw() + camera.setMode(MODE.FirstPerson) + elseif Zoom3rdPerson < 0 and camera.getMode() == MODE.FirstPerson then + camera.setMode(MODE.Preview) + I.Camera.setBaseThirdPersonDistance(30) + end +end + function M.onFrame(dt) if core.isWorldPaused() then return end local newActive = M.enabled and Actor.getStance(self) == Actor.STANCE.Nothing @@ -39,9 +59,10 @@ function M.onFrame(dt) turnOff() end if not active then return end + processZoom3rdPerson() if camera.getMode() == MODE.Static then return end if camera.getMode() == MODE.ThirdPerson then camera.setMode(MODE.Preview) end - if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then + if camera.getMode() == MODE.Preview and not input.getBooleanActionValue('TogglePOV') then camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5) local move = util.vector2(self.controls.sideMovement, self.controls.movement) local yawDelta = camera.getYaw() - self.rotation:getYaw() @@ -59,22 +80,4 @@ function M.onFrame(dt) end end -function M.onInputAction(action) - if not active or core.isWorldPaused() or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or - input.isActionPressed(input.ACTION.TogglePOV) or - not I.Camera.isModeControlEnabled() then - return - end - if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview - and I.Camera.getBaseThirdPersonDistance() == 30 then - self.controls.yawChange = camera.getYaw() - self.rotation:getYaw() - camera.setMode(MODE.FirstPerson) - elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then - camera.setMode(MODE.Preview) - I.Camera.setBaseThirdPersonDistance(30) - end -end - return M diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua new file mode 100644 index 0000000000..06ded80793 --- /dev/null +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -0,0 +1,255 @@ +local core = require('openmw.core') +local input = require('openmw.input') +local util = require('openmw.util') +local async = require('openmw.async') +local storage = require('openmw.storage') +local ui = require('openmw.ui') + +local I = require('openmw.interfaces') + +local actionPressHandlers = {} +local function onActionPress(id, handler) + actionPressHandlers[id] = actionPressHandlers[id] or {} + table.insert(actionPressHandlers[id], handler) +end + +local function bindHold(key, actionId) + input.bindAction(key, async:callback(function() + return input.isActionPressed(actionId) + end), {}) +end + +local function bindMovement(key, actionId, axisId, direction) + input.bindAction(key, async:callback(function() + local actionActive = input.isActionPressed(actionId) + local axisActive = input.getAxisValue(axisId) * direction > 0 + return (actionActive or axisActive) and 1 or 0 + end), {}) +end + +local function bindTrigger(key, actionid) + onActionPress(actionid, function() + input.activateTrigger(key) + end) +end + +bindTrigger('AlwaysRun', input.ACTION.AlwaysRun) +bindTrigger('ToggleSneak', input.ACTION.Sneak) +bindTrigger('ToggleWeapon', input.ACTION.ToggleWeapon) +bindTrigger('ToggleSpell', input.ACTION.ToggleSpell) +bindTrigger('Jump', input.ACTION.Jump) +bindTrigger('AutoMove', input.ACTION.AutoMove) +bindTrigger('Inventory', input.ACTION.Inventory) +bindTrigger('Journal', input.ACTION.Journal) +bindTrigger('QuickKeysMenu', input.ACTION.QuickKeysMenu) + +bindHold('TogglePOV', input.ACTION.TogglePOV) +bindHold('Sneak', input.ACTION.Sneak) + +bindHold('Run', input.ACTION.Run) +input.bindAction('Run', async:callback(function(_, value) + local controllerInput = util.vector2( + input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward), + input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) + ):length2() + return value or controllerInput > 0.25 +end), {}) + +input.bindAction('Use', async:callback(function() + -- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged. + -- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable. + return input.isActionPressed(input.ACTION.Use) or input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) >= 0.6 +end), {}) + +bindMovement('MoveBackward', input.ACTION.MoveBackward, input.CONTROLLER_AXIS.MoveForwardBackward, 1) +bindMovement('MoveForward', input.ACTION.MoveForward, input.CONTROLLER_AXIS.MoveForwardBackward, -1) +bindMovement('MoveRight', input.ACTION.MoveRight, input.CONTROLLER_AXIS.MoveLeftRight, 1) +bindMovement('MoveLeft', input.ACTION.MoveLeft, input.CONTROLLER_AXIS.MoveLeftRight, -1) + +do + local zoomInOut = 0 + onActionPress(input.ACTION.ZoomIn, function() + zoomInOut = zoomInOut + 1 + end) + onActionPress(input.ACTION.ZoomOut, function() + zoomInOut = zoomInOut - 1 + end) + input.bindAction('Zoom3rdPerson', async:callback(function(dt, _, togglePOV) + local Zoom3rdPerson = zoomInOut * 10 + if togglePOV then + local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft) + local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) + local controllerZoom = (triggerRight - triggerLeft) * 100 * dt + Zoom3rdPerson = Zoom3rdPerson + controllerZoom + end + zoomInOut = 0 + return Zoom3rdPerson + end), { 'TogglePOV' }) +end + +local bindingSection = storage.playerSection('OMWInputBindings') + +local keyboardPresses = {} +local keybordHolds = {} +local boundActions = {} + +local function bindAction(action) + if boundActions[action] then return end + boundActions[action] = true + input.bindAction(action, async:callback(function() + if keybordHolds[action] then + for _, binding in pairs(keybordHolds[action]) do + if input.isKeyPressed(binding.code) then return true end + end + end + return false + end), {}) +end + +local function registerBinding(binding, id) + if not input.actions[binding.key] and not input.triggers[binding.key] then + print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) + return + end + if binding.type == 'keyboardPress' then + local bindings = keyboardPresses[binding.code] or {} + bindings[id] = binding + keyboardPresses[binding.code] = bindings + elseif binding.type == 'keyboardHold' then + local bindings = keybordHolds[binding.key] or {} + bindings[id] = binding + keybordHolds[binding.key] = bindings + bindAction(binding.key) + else + error('Unknown binding type "' .. binding.type .. '"') + end +end + +function clearBinding(id) + for _, boundTriggers in pairs(keyboardPresses) do + boundTriggers[id] = nil + end + for _, boundKeys in pairs(keybordHolds) do + boundKeys[id] = nil + end +end + +local function updateBinding(id, binding) + bindingSection:set(id, binding) + clearBinding(id) + if binding ~= nil then + registerBinding(binding, id) + end + return id +end + +local interfaceL10n = core.l10n('interface') + +I.Settings.registerRenderer('inputBinding', function(id, set, arg) + if type(id) ~= 'string' then error('inputBinding: must have a string default value') end + if not arg.type then error('inputBinding: type argument is required') end + if not arg.key then error('inputBinding: key argument is required') end + local info = input.actions[arg.key] or input.triggers[arg.key] + if not info then return {} end + + local l10n = core.l10n(info.key) + + local name = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.name), + }, + } + + local description = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.description), + }, + } + + local binding = bindingSection:get(id) + local label = binding and input.getKeyName(binding.code) or interfaceL10n('None') + + local recorder = { + template = I.MWUI.templates.textEditLine, + props = { + readOnly = true, + text = label, + }, + events = { + focusGain = async:callback(function() + if binding == nil then return end + updateBinding(id, nil) + set(id) + end), + keyPress = async:callback(function(key) + if binding ~= nil or key.code == input.KEY.Escape then return end + + local newBinding = { + code = key.code, + type = arg.type, + key = arg.key, + } + updateBinding(id, newBinding) + set(id) + end), + }, + } + + local row = { + type = ui.TYPE.Flex, + props = { + horizontal = true, + }, + content = ui.content { + name, + { props = { size = util.vector2(10, 0) } }, + recorder, + }, + } + local column = { + type = ui.TYPE.Flex, + content = ui.content { + row, + description, + }, + } + + return column +end) + +local initiated = false + +local function init() + for id, binding in pairs(bindingSection:asTable()) do + registerBinding(binding, id) + end +end + +return { + engineHandlers = { + onFrame = function() + if not initiated then + initiated = true + init() + end + end, + onInputAction = function(id) + if not actionPressHandlers[id] then + return + end + for _, handler in ipairs(actionPressHandlers[id]) do + handler() + end + end, + onKeyPress = function(e) + local bindings = keyboardPresses[e.code] + if bindings then + for _, binding in pairs(bindings) do + input.activateTrigger(binding.key) + end + end + end, + } +} diff --git a/files/data/scripts/omw/input/smoothmovement.lua b/files/data/scripts/omw/input/smoothmovement.lua new file mode 100644 index 0000000000..ebd322f25d --- /dev/null +++ b/files/data/scripts/omw/input/smoothmovement.lua @@ -0,0 +1,92 @@ +local input = require('openmw.input') +local util = require('openmw.util') +local async = require('openmw.async') +local storage = require('openmw.storage') +local types = require('openmw.types') +local self = require('openmw.self') + +local NPC = types.NPC + +local moveActions = { + 'MoveForward', + 'MoveBackward', + 'MoveLeft', + 'MoveRight' +} +for _, key in ipairs(moveActions) do + local smoothKey = 'Smooth' .. key + input.registerAction { + key = smoothKey, + l10n = 'OMWControls', + name = smoothKey .. '_name', + description = smoothKey .. '_description', + type = input.ACTION_TYPE.Range, + defaultValue = 0, + } +end + +local settings = storage.playerSection('SettingsOMWControls') + +local function shouldAlwaysRun(actor) + return actor.controls.sneak or not NPC.isOnGround(actor) or NPC.isSwimming(actor) +end + +local function remapToWalkRun(actor, inputMovement) + if shouldAlwaysRun(actor) then + return true, inputMovement + end + local normalizedInput, inputSpeed = inputMovement:normalize() + local switchPoint = 0.5 + if inputSpeed < switchPoint then + return false, inputMovement * 2 + else + local matchWalkingSpeed = NPC.getWalkSpeed(actor) / NPC.getRunSpeed(actor) + local runSpeedRatio = 2 * (inputSpeed - switchPoint) * (1 - matchWalkingSpeed) + matchWalkingSpeed + return true, normalizedInput * math.min(1, runSpeedRatio) + end +end + +local function computeSmoothMovement() + local controllerInput = util.vector2( + input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward), + input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) + ) + return remapToWalkRun(self, controllerInput) +end + +local function bindSmoothMove(key, axis, direction) + local smoothKey = 'Smooth' .. key + input.bindAction(smoothKey, async:callback(function() + local _, movement = computeSmoothMovement() + return math.max(direction * movement[axis], 0) + end), {}) + input.bindAction(key, async:callback(function(_, standardMovement, smoothMovement) + if not settings:get('smoothControllerMovement') then + return standardMovement + end + + if smoothMovement > 0 then + return smoothMovement + else + return standardMovement + end + end), { smoothKey }) +end + +bindSmoothMove('MoveForward', 'x', -1) +bindSmoothMove('MoveBackward', 'x', 1) +bindSmoothMove('MoveRight', 'y', 1) +bindSmoothMove('MoveLeft', 'y', -1) + +input.bindAction('Run', async:callback(function(_, run) + if not settings:get('smoothControllerMovement') then + return run + end + local smoothRun, movement = computeSmoothMovement() + if movement:length2() > 0 then + -- ignore always run + return smoothRun ~= settings:get('alwaysRun') + else + return run + end +end), {}) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 7b405180e8..d3f9ecea5f 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -1,12 +1,12 @@ local core = require('openmw.core') local input = require('openmw.input') local self = require('openmw.self') -local util = require('openmw.util') +local storage = require('openmw.storage') local ui = require('openmw.ui') +local async = require('openmw.async') local Actor = require('openmw.types').Actor local Player = require('openmw.types').Player -local storage = require('openmw.storage') local I = require('openmw.interfaces') local settingsGroup = 'SettingsOMWControls' @@ -16,16 +16,16 @@ local function boolSetting(key, default) key = key, renderer = 'checkbox', name = key, - description = key..'Description', + description = key .. 'Description', default = default, } end I.Settings.registerPage({ - key = 'OMWControls', - l10n = 'OMWControls', - name = 'ControlsPage', - description = 'ControlsPageDescription', + key = 'OMWControls', + l10n = 'OMWControls', + name = 'ControlsPage', + description = 'ControlsPageDescription', }) I.Settings.registerGroup({ @@ -36,95 +36,68 @@ I.Settings.registerGroup({ permanentStorage = true, settings = { boolSetting('alwaysRun', false), - boolSetting('toggleSneak', false), + boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI + boolSetting('smoothControllerMovement', true), }, }) -local settings = storage.playerSection(settingsGroup) +local settings = storage.playerSection('SettingsOMWControls') -local attemptJump = false -local startAttack = false -local autoMove = false -local movementControlsOverridden = false -local combatControlsOverridden = false -local uiControlsOverridden = false +do + local rangeActions = { + 'MoveForward', + 'MoveBackward', + 'MoveLeft', + 'MoveRight' + } + for _, key in ipairs(rangeActions) do + input.registerAction { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + type = input.ACTION_TYPE.Range, + defaultValue = 0, + } + end -local function processMovement() - local controllerMovement = -input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward) - local controllerSideMovement = input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) - if controllerMovement ~= 0 or controllerSideMovement ~= 0 then - -- controller movement - if util.vector2(controllerMovement, controllerSideMovement):length2() < 0.25 - and not self.controls.sneak and Actor.isOnGround(self) and not Actor.isSwimming(self) then - self.controls.run = false - self.controls.movement = controllerMovement * 2 - self.controls.sideMovement = controllerSideMovement * 2 - else - self.controls.run = true - self.controls.movement = controllerMovement - self.controls.sideMovement = controllerSideMovement - end - else - -- keyboard movement - self.controls.movement = 0 - self.controls.sideMovement = 0 - if input.isActionPressed(input.ACTION.MoveLeft) then - self.controls.sideMovement = self.controls.sideMovement - 1 - end - if input.isActionPressed(input.ACTION.MoveRight) then - self.controls.sideMovement = self.controls.sideMovement + 1 - end - if input.isActionPressed(input.ACTION.MoveBackward) then - self.controls.movement = self.controls.movement - 1 - end - if input.isActionPressed(input.ACTION.MoveForward) then - self.controls.movement = self.controls.movement + 1 - end - self.controls.run = input.isActionPressed(input.ACTION.Run) ~= settings:get('alwaysRun') + local booleanActions = { + 'Use', + 'Run', + 'Sneak', + } + for _, key in ipairs(booleanActions) do + input.registerAction { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + type = input.ACTION_TYPE.Boolean, + defaultValue = false, + } end - if self.controls.movement ~= 0 or not Actor.canMove(self) then - autoMove = false - elseif autoMove then - self.controls.movement = 1 - end - self.controls.jump = attemptJump and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) - if not settings:get('toggleSneak') then - self.controls.sneak = input.isActionPressed(input.ACTION.Sneak) - end -end -local function processAttacking() - if startAttack then - self.controls.use = 1 - elseif Actor.stance(self) == Actor.STANCE.Spell then - self.controls.use = 0 - elseif input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) < 0.6 - and not input.isActionPressed(input.ACTION.Use) then - -- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged. - -- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable. - self.controls.use = 0 + local triggers = { + 'Jump', + 'AutoMove', + 'ToggleWeapon', + 'ToggleSpell', + 'AlwaysRun', + 'ToggleSneak', + 'Inventory', + 'Journal', + 'QuickKeysMenu', + } + for _, key in ipairs(triggers) do + input.registerTrigger { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + } end end -local function onFrame(dt) - local controlsAllowed = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) - and not core.isWorldPaused() and not I.UI.getMode() - if not movementControlsOverridden then - if controlsAllowed then - processMovement() - else - self.controls.movement = 0 - self.controls.sideMovement = 0 - self.controls.jump = false - end - end - if controlsAllowed and not combatControlsOverridden then - processAttacking() - end - attemptJump = false - startAttack = false -end - local function checkNotWerewolf() if Player.isWerewolf(self) then ui.showMessage(core.getGMST('sWerewolfRefusal')) @@ -139,68 +112,163 @@ local function isJournalAllowed() return I.UI.getWindowsForMode(I.UI.MODE.Interface)[I.UI.WINDOW.Magic] end -local function onInputAction(action) - if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) then - return +local movementControlsOverridden = false + +local autoMove = false +local function processMovement() + local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward') + local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') + local run = input.getBooleanActionValue('Run') ~= settings:get('alwaysRun') + + if movement ~= 0 or not Actor.canMove(self) then + autoMove = false + elseif autoMove then + movement = 1 end - if not uiControlsOverridden then - if action == input.ACTION.Inventory then - if I.UI.getMode() == nil then - I.UI.setMode(I.UI.MODE.Interface) - elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then - I.UI.removeMode(I.UI.getMode()) - end - elseif action == input.ACTION.Journal then - if I.UI.getMode() == I.UI.MODE.Journal then - I.UI.removeMode(I.UI.MODE.Journal) - elseif isJournalAllowed() then - I.UI.addMode(I.UI.MODE.Journal) - end - elseif action == input.ACTION.QuickKeysMenu then - if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then - I.UI.removeMode(I.UI.MODE.QuickKeysMenu) - elseif checkNotWerewolf() and Player.isCharGenFinished(self) then - I.UI.addMode(I.UI.MODE.QuickKeysMenu) - end + self.controls.movement = movement + self.controls.sideMovement = sideMovement + self.controls.run = run + + if not settings:get('toggleSneak') then + self.controls.sneak = input.getBooleanActionValue('Sneak') + end +end + +local function controlsAllowed() + return not core.isWorldPaused() + and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) + and not I.UI.getMode() +end + +local function movementAllowed() + return controlsAllowed() and not movementControlsOverridden +end + +input.registerTriggerHandler('Jump', async:callback(function() + if not movementAllowed() then return end + self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) +end)) + +input.registerTriggerHandler('ToggleSneak', async:callback(function() + if not movementAllowed() then return end + if settings:get('toggleSneak') then + self.controls.sneak = not self.controls.sneak + end +end)) + +input.registerTriggerHandler('AlwaysRun', async:callback(function() + if not movementAllowed() then return end + settings:set('alwaysRun', not settings:get('alwaysRun')) +end)) + +input.registerTriggerHandler('AutoMove', async:callback(function() + if not movementAllowed() then return end + autoMove = not autoMove +end)) + +local combatControlsOverridden = false + +local function combatAllowed() + return controlsAllowed() and not combatControlsOverridden +end + +input.registerTriggerHandler('ToggleSpell', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Spell then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then + if checkNotWerewolf() then + Actor.setStance(self, Actor.STANCE.Spell) end end +end)) - if core.isWorldPaused() or I.UI.getMode() then - return +input.registerTriggerHandler('ToggleWeapon', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Weapon then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then + Actor.setStance(self, Actor.STANCE.Weapon) end +end)) - if action == input.ACTION.Jump then - attemptJump = true - elseif action == input.ACTION.Use then - startAttack = Actor.stance(self) ~= Actor.STANCE.Nothing - elseif action == input.ACTION.AutoMove and not movementControlsOverridden then - autoMove = not autoMove - elseif action == input.ACTION.AlwaysRun and not movementControlsOverridden then - settings:set('alwaysRun', not settings:get('alwaysRun')) - elseif action == input.ACTION.Sneak and not movementControlsOverridden then - if settings:get('toggleSneak') then - self.controls.sneak = not self.controls.sneak - end - elseif action == input.ACTION.ToggleSpell and not combatControlsOverridden then - if Actor.stance(self) == Actor.STANCE.Spell then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then - if checkNotWerewolf() then - Actor.setStance(self, Actor.STANCE.Spell) - end - end - elseif action == input.ACTION.ToggleWeapon and not combatControlsOverridden then - if Actor.stance(self) == Actor.STANCE.Weapon then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then - Actor.setStance(self, Actor.STANCE.Weapon) - end +local startUse = false +input.registerActionHandler('Use', async:callback(function(value) + if value then startUse = true end +end)) +local function processAttacking() + if Actor.stance(self) == Actor.STANCE.Spell then + self.controls.use = startUse and 1 or 0 + else + self.controls.use = input.getBooleanActionValue('Use') and 1 or 0 + end + startUse = false +end + +local uiControlsOverridden = false + +input.registerTriggerHandler('ToggleWeapon', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Weapon then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then + Actor.setStance(self, Actor.STANCE.Weapon) + end +end)) + + +local function uiAllowed() + return Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not uiControlsOverridden +end + +input.registerTriggerHandler('Inventory', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == nil then + I.UI.setMode(I.UI.MODE.Interface) + elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then + I.UI.removeMode(I.UI.getMode()) + end +end)) + +input.registerTriggerHandler('Journal', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == I.UI.MODE.Journal then + I.UI.removeMode(I.UI.MODE.Journal) + elseif isJournalAllowed() then + I.UI.addMode(I.UI.MODE.Journal) + end +end)) + +input.registerTriggerHandler('QuickKeysMenu', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then + I.UI.removeMode(I.UI.MODE.QuickKeysMenu) + elseif checkNotWerewolf() and Player.isCharGenFinished(self) then + I.UI.addMode(I.UI.MODE.QuickKeysMenu) + end +end)) + +local function onFrame(_) + if movementAllowed() then + processMovement() + elseif not movementControlsOverridden then + self.controls.movement = 0 + self.controls.sideMovement = 0 + self.controls.jump = false + end + if combatAllowed() then + processAttacking() end end local function onSave() - return {sneaking = self.controls.sneak} + return { + sneaking = self.controls.sneak + } end local function onLoad(data) @@ -211,7 +279,6 @@ end return { engineHandlers = { onFrame = onFrame, - onInputAction = onInputAction, onSave = onSave, onLoad = onLoad, }, @@ -242,4 +309,3 @@ return { overrideUiControls = function(v) uiControlsOverridden = v end, } } - diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 4ca4e5af4e..563a4ab1f5 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -11,8 +11,7 @@ -- @return #boolean --- --- Is a specific control currently pressed. --- Input bindings can be changed ingame using Options/Controls menu. +-- (DEPRECATED, use getBooleanActionValue) Input bindings can be changed ingame using Options/Controls menu. -- @function [parent=#input] isActionPressed -- @param #number actionId One of @{openmw.input#ACTION} -- @return #boolean @@ -108,6 +107,7 @@ -- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH --- +-- (DEPRECATED, use actions with matching keys) -- @type ACTION -- @field [parent=#ACTION] #number GameMenu -- @field [parent=#ACTION] #number Screenshot @@ -153,7 +153,7 @@ -- @field [parent=#ACTION] #number TogglePostProcessorHUD --- --- Values that can be used with isActionPressed. +-- (DEPRECATED, use getBooleanActionValue) Values that can be used with isActionPressed. -- @field [parent=#input] #ACTION ACTION --- @@ -187,10 +187,10 @@ -- @field [parent=#CONTROLLER_AXIS] #number RightY Right stick vertical axis (from -1 to 1) -- @field [parent=#CONTROLLER_AXIS] #number TriggerLeft Left trigger (from 0 to 1) -- @field [parent=#CONTROLLER_AXIS] #number TriggerRight Right trigger (from 0 to 1) --- @field [parent=#CONTROLLER_AXIS] #number LookUpDown View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number LookUpDown (DEPRECATED, use the LookUpDown action) View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight (DEPRECATED, use the LookLeftRight action) View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward (DEPRECATED, use the MoveForwardBackward action) Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight (DEPRECATED, use the MoveLeftRight action) Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) --- -- Values that can be used with getAxisValue. @@ -327,4 +327,105 @@ -- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner), -- @field [parent=#TouchEvent] #number pressure Pressure of the finger. +--- +-- @type ActionType + +--- +-- @type ACTION_TYPE +-- @field #ActionType Boolean Input action with value of true or false +-- @field #ActionType Number Input action with a numeric value +-- @field #ActionType Range Input action with a numeric value between 0 and 1 (inclusive) + +--- +-- Values that can be used in registerAction +-- @field [parent=#input] #ACTION_TYPE ACTION_TYPE + +--- +-- @type ActionInfo +-- @field [parent=#Actioninfo] #string key +-- @field [parent=#Actioninfo] #ActionType type +-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#Actioninfo] #string name Localization key of the action's name +-- @field [parent=#Actioninfo] #string description Localization key of the action's description +-- @field [parent=#Actioninfo] defaultValue initial value of the action + +--- +-- Map of all currently registered actions +-- @field [parent=#input] #map<#string,#ActionInfo> actions + +--- +-- Registers a new input action. The key must be unique +-- @function [parent=#input] registerAction +-- @param #ActionInfo info + +--- +-- Provides a function computing the value of given input action. +-- The callback is called once a frame, after the values of dependency actions are resolved. +-- Throws an error if a cyclic action dependency is detected. +-- @function [parent=#input] bindAction +-- @param #string key +-- @param openmw.async#Callback callback returning the new value of the action, and taking as arguments: +-- frame time in seconds, +-- value of the function, +-- value of the first dependency action, +-- ... +-- @param #list<#string> dependencies +-- @usage +-- input.bindAction('Activate', async:callback(function(dt, use, sneak, run) +-- -- while sneaking, only activate things while holding the run binding +-- return use and (run or not sneak) +-- end), { 'Sneak', 'Run' }) + +--- +-- Registers a function to be called whenever the action's value changes +-- @function [parent=#input] registerActionHandler +-- @param #string key +-- @param openmw.async#Callback callback takes the new action value as the only argument + +--- +-- Returns the value of a Boolean action +-- @function [parent=#input] getBooleanActionValue +-- @param #string key +-- @return #boolean + +--- +-- Returns the value of a Number action +-- @function [parent=#input] getNumberActionValue +-- @param #string key +-- @return #number + +--- +-- Returns the value of a Range action +-- @function [parent=#input] getRangeActionValue +-- @param #string key +-- @return #number + +--- +-- @type TriggerInfo +-- @field [parent=#Actioninfo] #string key +-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#Actioninfo] #string name Localization key of the trigger's name +-- @field [parent=#Actioninfo] #string description Localization key of the trigger's description + +--- +-- Map of all currently registered triggers +-- @field [parent=#input] #map<#string,#TriggerInfo> triggers + +--- +-- Registers a new input trigger. The key must be unique +-- @function [parent=#input] registerTrigger +-- @param #TriggerInfo info + +--- +-- Registers a function to be called whenever the trigger activates +-- @function [parent=#input] registerTriggerHandler +-- @param #string key +-- @param openmw.async#Callback callback takes the new action value as the only argument + +--- +-- Activates the trigger with the given key +-- @function [parent=#input] activateTrigger +-- @param #string key + + return nil From f83a7b711a87cc01e0ad035b73129dce20b70948 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 02:43:38 +0300 Subject: [PATCH 0697/2167] Don't handle per-vertex normals as tangent space normals --- files/shaders/compatibility/bs/default.frag | 2 +- files/shaders/compatibility/bs/default.vert | 2 +- files/shaders/compatibility/bs/nolighting.vert | 2 +- files/shaders/compatibility/groundcover.vert | 2 +- files/shaders/compatibility/objects.frag | 2 +- files/shaders/compatibility/objects.vert | 2 +- files/shaders/compatibility/terrain.frag | 2 +- files/shaders/compatibility/terrain.vert | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index ec5f5c8978..77131c6a52 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -80,7 +80,7 @@ void main() vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); specularColor *= normalTex.a; #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif float shadowing = unshadowedLightRatio(linearDepth); diff --git a/files/shaders/compatibility/bs/default.vert b/files/shaders/compatibility/bs/default.vert index d9d47843c0..21942ec91e 100644 --- a/files/shaders/compatibility/bs/default.vert +++ b/files/shaders/compatibility/bs/default.vert @@ -69,7 +69,7 @@ void main(void) #if @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/bs/nolighting.vert b/files/shaders/compatibility/bs/nolighting.vert index 3b0fa7b626..57cedc6e94 100644 --- a/files/shaders/compatibility/bs/nolighting.vert +++ b/files/shaders/compatibility/bs/nolighting.vert @@ -63,7 +63,7 @@ void main(void) } #if @shadows_enabled - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index 95a17b5084..8cf53a19e0 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -156,7 +156,7 @@ void main(void) #endif #if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if @diffuseMap diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 043aa266d8..56c7abf27c 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -169,7 +169,7 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; #if @normalMap vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif vec3 viewVec = normalize(passViewPos); diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 2bebcd60bf..081ff909cf 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -101,7 +101,7 @@ void main(void) #endif #if @envMap || !PER_PIXEL_LIGHTING || @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if @envMap diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 38b985223e..abc7425eb0 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -65,7 +65,7 @@ void main() #if @normalMap vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif float shadowing = unshadowedLightRatio(linearDepth); diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index 3b2cb16db4..cbfb7769ba 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -52,7 +52,7 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING || @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if !PER_PIXEL_LIGHTING From 91a01ae1e2be1034d04a0b103b5b81d5bb60fa0b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 03:23:27 +0300 Subject: [PATCH 0698/2167] Remove duplicate ToggleWeapon trigger handler registration --- files/data/scripts/omw/playercontrols.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index d3f9ecea5f..f14724f0a2 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -208,16 +208,6 @@ end local uiControlsOverridden = false -input.registerTriggerHandler('ToggleWeapon', async:callback(function() - if not combatAllowed() then return end - if Actor.stance(self) == Actor.STANCE.Weapon then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then - Actor.setStance(self, Actor.STANCE.Weapon) - end -end)) - - local function uiAllowed() return Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not uiControlsOverridden end From 640fa53bb82ce4ef079d91daf22e7b037b785bfd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 04:00:20 +0300 Subject: [PATCH 0699/2167] Make rain and snow ripple settings global rather than per-weather (#7748) --- apps/openmw/mwrender/sky.cpp | 15 ++++++++++++--- apps/openmw/mwrender/sky.hpp | 3 ++- apps/openmw/mwrender/skyutil.hpp | 1 - apps/openmw/mwworld/weather.cpp | 4 ---- apps/openmw/mwworld/weather.hpp | 2 -- components/fallback/validate.cpp | 6 ++---- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 87c6d1f2e7..6df3734252 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -257,7 +257,8 @@ namespace MWRender , mRainMaxHeight(0.f) , mRainEntranceSpeed(1.f) , mRainMaxRaindrops(0) - , mRipples(false) + , mRainRipplesEnabled(Fallback::Map::getBool("Weather_Rain_Ripples")) + , mSnowRipplesEnabled(Fallback::Map::getBool("Weather_Snow_Ripples")) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) @@ -519,7 +520,16 @@ namespace MWRender bool SkyManager::getRainRipplesEnabled() const { - return mRipples; + if (!mEnabled || mIsStorm) + return false; + + if (hasRain()) + return mRainRipplesEnabled; + + if (mParticleNode && mCurrentParticleEffect == "meshes\\snow.nif") + return mSnowRipplesEnabled; + + return false; } float SkyManager::getPrecipitationAlpha() const @@ -636,7 +646,6 @@ namespace MWRender mRainMinHeight = weather.mRainMinHeight; mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; - mRipples = weather.mRipples; mWindSpeed = weather.mWindSpeed; mBaseWindSpeed = weather.mBaseWindSpeed; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 9e4801ad7d..96af2e6ec9 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -196,7 +196,8 @@ namespace MWRender float mRainMaxHeight; float mRainEntranceSpeed; int mRainMaxRaindrops; - bool mRipples; + bool mRainRipplesEnabled; + bool mSnowRipplesEnabled; float mWindSpeed; float mBaseWindSpeed; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 9e279fb5c2..1018724595 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -77,7 +77,6 @@ namespace MWRender float mRainSpeed; float mRainEntranceSpeed; int mRainMaxRaindrops; - bool mRipples; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5e2d7d3fec..aa75730b40 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -175,7 +175,6 @@ namespace MWWorld , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) , mParticleEffect(particleEffect) , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") - , mRipples(Fallback::Map::getBool("Weather_" + name + "_Ripples")) , mStormDirection(Weather::defaultDirection()) , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) @@ -1130,7 +1129,6 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - mResult.mRipples = current.mRipples; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; @@ -1243,7 +1241,6 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - mResult.mRipples = current.mRipples; } else { @@ -1260,7 +1257,6 @@ namespace MWWorld mResult.mRainMinHeight = other.mRainMinHeight; mResult.mRainMaxHeight = other.mRainMaxHeight; mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; - mResult.mRipples = other.mRipples; } } } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index d40f7030c8..65f926c096 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -191,8 +191,6 @@ namespace MWWorld std::string mRainEffect; - bool mRipples; - osg::Vec3f mStormDirection; float mCloudsMaximumPercent; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 2a6d1817c9..8dfcd97570 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -11,10 +11,8 @@ static const std::set allowedKeysInt = { "LightAttenuation_Lin "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", - "Weather_Clear_Ripples", "Weather_Cloudy_Ripples", "Weather_Foggy_Ripples", "Weather_Overcast_Ripples", - "Weather_Rain_Ripples", "Weather_Thunderstorm_Ripples", "Weather_Ashstorm_Ripples", "Weather_Blight_Ripples", - "Weather_Snow_Ripples", "Weather_Blizzard_Ripples" }; + "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", + "Weather_Snow_Ripples" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", From e63933efa6da7d7ac23952e1cca62eb944323784 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Dec 2023 17:12:46 +0000 Subject: [PATCH 0700/2167] Use NAM9 for stack count --- CHANGELOG.md | 1 + apps/esmtool/esmtool.cpp | 2 +- apps/essimporter/converter.cpp | 2 +- apps/essimporter/convertinventory.cpp | 2 +- apps/opencs/model/tools/referencecheck.cpp | 5 +- apps/opencs/model/world/columnimp.hpp | 10 ++-- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/columns.hpp | 2 +- apps/opencs/model/world/data.cpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 10 ++-- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 12 ++-- apps/openmw/mwclass/npc.cpp | 10 ++-- apps/openmw/mwgui/alchemywindow.cpp | 6 +- apps/openmw/mwgui/containeritemmodel.cpp | 14 ++--- apps/openmw/mwgui/draganddrop.cpp | 2 +- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/inventoryitemmodel.cpp | 2 +- apps/openmw/mwgui/inventorywindow.cpp | 9 ++- apps/openmw/mwgui/itemmodel.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +- apps/openmw/mwgui/recharge.cpp | 2 +- apps/openmw/mwgui/referenceinterface.cpp | 6 +- apps/openmw/mwgui/repair.cpp | 4 +- apps/openmw/mwgui/spellmodel.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 8 +-- apps/openmw/mwlua/cellbindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 5 +- apps/openmw/mwlua/objectbindings.cpp | 32 +++++------ apps/openmw/mwlua/types/actor.cpp | 4 +- apps/openmw/mwlua/types/item.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 5 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aipursue.cpp | 4 +- apps/openmw/mwmechanics/alchemy.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 16 ++++-- apps/openmw/mwmechanics/recharge.cpp | 4 +- apps/openmw/mwrender/actoranimation.cpp | 6 +- apps/openmw/mwscript/containerextensions.cpp | 8 +-- apps/openmw/mwscript/miscextensions.cpp | 6 +- apps/openmw/mwworld/actionharvest.cpp | 2 +- apps/openmw/mwworld/actiontake.cpp | 10 ++-- apps/openmw/mwworld/cellref.cpp | 12 ++-- apps/openmw/mwworld/cellref.hpp | 16 +++--- apps/openmw/mwworld/cellstore.cpp | 55 +++++++++---------- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 50 +++++++++-------- apps/openmw/mwworld/inventorystore.cpp | 28 +++++----- apps/openmw/mwworld/livecellref.cpp | 5 ++ apps/openmw/mwworld/livecellref.hpp | 3 + apps/openmw/mwworld/localscripts.cpp | 6 +- apps/openmw/mwworld/localscripts.hpp | 2 +- apps/openmw/mwworld/manualref.cpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/ptr.cpp | 2 +- apps/openmw/mwworld/refdata.cpp | 29 ---------- apps/openmw/mwworld/refdata.hpp | 14 ----- apps/openmw/mwworld/scene.cpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 24 ++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- apps/openmw_test_suite/esm3/testsaveload.cpp | 4 +- components/esm3/cellref.cpp | 8 +-- components/esm3/cellref.hpp | 3 +- components/esm3/formatversion.hpp | 3 +- components/esm3/inventorystate.cpp | 8 +-- components/esm3/objectstate.cpp | 11 ++-- components/esm3/objectstate.hpp | 2 - components/esm4/loadachr.cpp | 6 +- components/esm4/loadachr.hpp | 2 + components/esm4/loadrefr.cpp | 6 +- components/esm4/loadrefr.hpp | 2 +- 76 files changed, 269 insertions(+), 293 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d09f00b9e..26d6c46ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute + Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 88342a2a5b..a36996ff4f 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -265,7 +265,7 @@ namespace std::cout << " Faction rank: " << ref.mFactionRank << '\n'; std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n'; std::cout << " Uses/health: " << ref.mChargeInt << '\n'; - std::cout << " Gold value: " << ref.mGoldValue << '\n'; + std::cout << " Count: " << ref.mCount << '\n'; std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << '\n'; std::cout << " Deleted: " << deleted << '\n'; if (!ref.mKey.empty()) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index b44d376842..07146fc388 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -34,7 +34,7 @@ namespace objstate.mPosition = cellref.mPos; objstate.mRef.mRefNum = cellref.mRefNum; if (cellref.mDeleted) - objstate.mCount = 0; + objstate.mRef.mCount = 0; convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 69a2ea4120..7025b0ae43 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -16,7 +16,7 @@ namespace ESSImport objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); - objstate.mCount = item.mCount; + objstate.mRef.mCount = item.mCount; state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 87d133a59e..511458b946 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -98,9 +98,8 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message if (cellRef.mEnchantmentCharge < -1) messages.add(id, "Negative number of enchantment points", "", CSMDoc::Message::Severity_Error); - // Check if gold value isn't negative - if (cellRef.mGoldValue < 0) - messages.add(id, "Negative gold value", "", CSMDoc::Message::Severity_Error); + if (cellRef.mCount < 1) + messages.add(id, "Reference without count", {}, CSMDoc::Message::Severity_Error); } int CSMTools::ReferenceCheckStage::setup() diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 74b81cebc1..eba09bd8b1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1111,19 +1111,19 @@ namespace CSMWorld }; template - struct GoldValueColumn : public Column + struct StackSizeColumn : public Column { - GoldValueColumn() - : Column(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) + StackSizeColumn() + : Column(Columns::ColumnId_StackCount, ColumnBase::Display_Integer) { } - QVariant get(const Record& record) const override { return record.get().mGoldValue; } + QVariant get(const Record& record) const override { return record.get().mCount; } void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mGoldValue = data.toInt(); + record2.mCount = data.toInt(); record.setModified(record2); } diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 4a476b52f3..45759cd234 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -56,7 +56,7 @@ namespace CSMWorld { ColumnId_FactionIndex, "Faction Index" }, { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, - { ColumnId_CoinValue, "Coin Value" }, + { ColumnId_StackCount, "Count" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_LockLevel, "Lock Level" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index dff8b2d7dd..74e5bdd006 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -45,7 +45,7 @@ namespace CSMWorld ColumnId_FactionIndex = 31, ColumnId_Charges = 32, ColumnId_Enchantment = 33, - ColumnId_CoinValue = 34, + ColumnId_StackCount = 34, ColumnId_Teleport = 35, ColumnId_TeleportCell = 36, ColumnId_LockLevel = 37, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9b137a6602..ea4c8966d8 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -587,7 +587,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new FactionIndexColumn); mRefs.addColumn(new ChargesColumn); mRefs.addColumn(new EnchantmentChargesColumn); - mRefs.addColumn(new GoldValueColumn); + mRefs.addColumn(new StackSizeColumn); mRefs.addColumn(new TeleportColumn); mRefs.addColumn(new TeleportCellColumn); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index b8c3974d75..694f67e445 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection() inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns(inventoryColumns); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 14e3b2b3b7..4247ef2e3e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -185,7 +185,7 @@ namespace MWBase virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - virtual void removeRefScript(MWWorld::RefData* ref) = 0; + virtual void removeRefScript(const MWWorld::CellRef* ref) = 0; //< Remove the script attached to ref from mLocalScripts virtual MWWorld::Ptr getPtr(const ESM::RefId& name, bool activeOnly) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 052315ce54..810a7840d4 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -828,7 +828,7 @@ namespace MWClass } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - if (ptr.getRefData().getCount() <= 0 + if (ptr.getCellRef().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; @@ -848,7 +848,7 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); - if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + if (ptr.getCellRef().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) @@ -860,16 +860,16 @@ namespace MWClass static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay - = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + = ptr.getCellRef().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (isFlagBitSet(ptr, ESM::Creature::Respawn) && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { - if (ptr.getRefData().getCount() == 0) + if (ptr.getCellRef().getCount() == 0) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 461cf07276..fbae54737c 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -81,7 +81,7 @@ namespace MWClass if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); - if (creature.getRefData().getCount() == 0) + if (creature.getCellRef().getCount() == 0) customData.mSpawn = true; else if (creatureStats.isDead()) { diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index ae78773fa1..fa91b607f2 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -79,8 +79,8 @@ namespace MWClass const MWWorld::LiveCellRef* ref = ptr.get(); int value = ref->mBase->mData.mValue; - if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) - value = ptr.getCellRef().getGoldValue(); + if (isGold(ptr) && ptr.getCellRef().getCount() != 1) + value = 1; if (!ptr.getCellRef().getSoul().empty()) { @@ -189,8 +189,7 @@ namespace MWClass const MWWorld::LiveCellRef* ref = newRef.getPtr().get(); MWWorld::Ptr ptr(cell.insert(ref), &cell); - ptr.getCellRef().setGoldValue(goldAmount); - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(goldAmount); return ptr; } @@ -203,7 +202,7 @@ namespace MWClass { const MWWorld::LiveCellRef* ref = ptr.get(); newPtr = MWWorld::Ptr(cell.insert(ref), &cell); - newPtr.getRefData().setCount(count); + newPtr.getCellRef().setCount(count); } newPtr.getCellRef().unsetRefNum(); newPtr.getRefData().setLuaScripts(nullptr); @@ -216,10 +215,9 @@ namespace MWClass MWWorld::Ptr newPtr; if (isGold(ptr)) { - newPtr = createGold(cell, getValue(ptr) * ptr.getRefData().getCount()); + newPtr = createGold(cell, getValue(ptr) * ptr.getCellRef().getCount()); newPtr.getRefData() = ptr.getRefData(); newPtr.getCellRef().setRefNum(ptr.getCellRef().getRefNum()); - newPtr.getRefData().setCount(1); } else { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8e2bf1b351..330a48daeb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1358,7 +1358,7 @@ namespace MWClass } const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - if (ptr.getRefData().getCount() <= 0 + if (ptr.getCellRef().getCount() <= 0 && (!(ptr.get()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead())) { state.mHasCustomState = false; @@ -1395,7 +1395,7 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); - if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + if (ptr.getCellRef().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) @@ -1407,16 +1407,16 @@ namespace MWClass static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay - = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + = ptr.getCellRef().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { - if (ptr.getRefData().getCount() == 0) + if (ptr.getCellRef().getCount() == 0) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index de3e0c19e0..4215782e2f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -151,7 +151,7 @@ namespace MWGui if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); - if (ingred.getRefData().getCount() == 0) + if (ingred.getCellRef().getCount() == 0) mAlchemy->removeIngredient(i); } @@ -413,7 +413,7 @@ namespace MWGui } if (!item.isEmpty()) - mSortModel->addDragItem(item, item.getRefData().getCount()); + mSortModel->addDragItem(item, item.getCellRef().getCount()); if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); @@ -428,7 +428,7 @@ namespace MWGui ingredient->setUserString("ToolTipType", "ItemPtr"); ingredient->setUserData(MWWorld::Ptr(item)); - ingredient->setCount(item.getRefData().getCount()); + ingredient->setCount(item.getCellRef().getCount()); } mItemView->update(); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index ba4f7156c9..af1b585cff 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -129,7 +129,7 @@ namespace MWGui { if (stacks(*it, item.mBase)) { - int quantity = it->mRef->mData.getCount(false); + int quantity = it->mRef->mRef.getCount(false); // If this is a restocking quantity, just don't remove it if (quantity < 0 && mTrading) toRemove += quantity; @@ -144,11 +144,11 @@ namespace MWGui { if (stacks(source, item.mBase)) { - int refCount = source.getRefData().getCount(); + int refCount = source.getCellRef().getCount(); if (refCount - toRemove <= 0) MWBase::Environment::get().getWorld()->deleteObject(source); else - source.getRefData().setCount(std::max(0, refCount - toRemove)); + source.getCellRef().setCount(std::max(0, refCount - toRemove)); toRemove -= refCount; if (toRemove <= 0) return; @@ -176,7 +176,7 @@ namespace MWGui if (stacks(*it, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack.mCount += it->getRefData().getCount(); + itemStack.mCount += it->getCellRef().getCount(); found = true; break; } @@ -185,7 +185,7 @@ namespace MWGui if (!found) { // no stack yet, create one - ItemStack newItem(*it, this, it->getRefData().getCount()); + ItemStack newItem(*it, this, it->getCellRef().getCount()); mItems.push_back(newItem); } } @@ -198,7 +198,7 @@ namespace MWGui if (stacks(source, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack.mCount += source.getRefData().getCount(); + itemStack.mCount += source.getCellRef().getCount(); found = true; break; } @@ -207,7 +207,7 @@ namespace MWGui if (!found) { // no stack yet, create one - ItemStack newItem(source, this, source.getRefData().getCount()); + ItemStack newItem(source, this, source.getCellRef().getCount()); mItems.push_back(newItem); } } diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index c99e97e37d..0fa2cc4e21 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -126,7 +126,7 @@ namespace MWGui void DragAndDrop::onFrame() { - if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) + if (mIsOnDragAndDrop && mItem.mBase.getCellRef().getCount() == 0) finish(); } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index f5bcb1fb5f..8264dd60b6 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -373,7 +373,7 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("enchant fail")); MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage34}"); - if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getRefData().getCount()) + if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getCellRef().getCount()) { setSoulGem(MWWorld::Ptr()); mEnchanting.nextCastStyle(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index e20637d58c..b52d49b7c2 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -115,7 +115,7 @@ namespace MWGui if (!item.getClass().showsInInventory(item)) continue; - ItemStack newItem(item, this, item.getRefData().getCount()); + ItemStack newItem(item, this, item.getCellRef().getCount()); if (mActor.getClass().hasInventoryStore(mActor)) { diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 932723c3fd..0fbd15dda2 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -560,7 +560,7 @@ namespace MWGui if (mEquippedStackableCount.has_value()) { // the count to unequip - int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); + int count = ptr.getCellRef().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); if (count > 0) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); @@ -604,7 +604,7 @@ namespace MWGui // Save the currently equipped count before useItem() if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) - mEquippedStackableCount = slotIt->getRefData().getCount(); + mEquippedStackableCount = slotIt->getCellRef().getCount(); else mEquippedStackableCount = 0; } @@ -735,7 +735,7 @@ namespace MWGui if (!object.getClass().hasToolTip(object)) return; - int count = object.getRefData().getCount(); + int count = object.getCellRef().getCount(); if (object.getClass().isGold(object)) count *= object.getClass().getValue(object); @@ -755,8 +755,7 @@ namespace MWGui // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object - MWWorld::Ptr newObject - = *player.getClass().getContainerStore(player).add(object, object.getRefData().getCount()); + MWWorld::Ptr newObject = *player.getClass().getContainerStore(player).add(object, count); // remove from world MWBase::Environment::get().getWorld()->deleteObject(object); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 4bdd2399fe..a4cf48fcbe 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -58,7 +58,7 @@ namespace MWGui MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip) { MWWorld::Ptr ret = MWWorld::Ptr(); - if (static_cast(item.mBase.getRefData().getCount()) <= count) + if (static_cast(item.mBase.getCellRef().getCount()) <= count) { // We are moving the full stack ret = otherModel->addItem(item, count, allowAutoEquip); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index fb03ab99c9..204fa00492 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -85,7 +85,7 @@ namespace MWGui { MWWorld::Ptr item = *mKey[index].button->getUserData(); // Make sure the item is available and is not broken - if (item.isEmpty() || item.getRefData().getCount() < 1 + if (item.isEmpty() || item.getCellRef().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement @@ -383,12 +383,12 @@ namespace MWGui item = nullptr; // check the item is available and not broken - if (item.isEmpty() || item.getRefData().getCount() < 1 + if (item.isEmpty() || item.getCellRef().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { item = store.findReplacement(key->id); - if (item.isEmpty() || item.getRefData().getCount() < 1) + if (item.isEmpty() || item.getCellRef().getCount() < 1) { MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index c2acea93ce..7d57988d97 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -75,7 +75,7 @@ namespace MWGui mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); - bool toolBoxVisible = (gem.getRefData().getCount() != 0); + bool toolBoxVisible = gem.getCellRef().getCount() != 0; mGemBox->setVisible(toolBoxVisible); mGemBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index de7c93d862..6dad6b8543 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -2,14 +2,14 @@ namespace MWGui { - ReferenceInterface::ReferenceInterface() {} + ReferenceInterface::ReferenceInterface() = default; - ReferenceInterface::~ReferenceInterface() {} + ReferenceInterface::~ReferenceInterface() = default; void ReferenceInterface::checkReferenceAvailable() { // check if count of the reference has become 0 - if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) + if (!mPtr.isEmpty() && mPtr.getCellRef().getCount() == 0) { mPtr = MWWorld::Ptr(); onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 63b51d7d2c..c1602b8407 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -86,7 +86,7 @@ namespace MWGui mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); - bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); + bool toolBoxVisible = (mRepair.getTool().getCellRef().getCount() != 0); mToolBox->setVisible(toolBoxVisible); mToolBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); @@ -142,7 +142,7 @@ namespace MWGui void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) { - if (!mRepair.getTool().getRefData().getCount()) + if (!mRepair.getTool().getCellRef().getCount()) return; mRepair.repair(ptr); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index f340d072e0..385464da24 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -137,7 +137,7 @@ namespace MWGui newSpell.mItem = item; newSpell.mId = item.getCellRef().getRefId(); newSpell.mName = item.getClass().getName(item); - newSpell.mCount = item.getRefData().getCount(); + newSpell.mCount = item.getCellRef().getCount(); newSpell.mType = Spell::Type_EnchantedItem; newSpell.mSelected = invStore.getSelectedEnchantItem() == it; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index a13cfe4d77..bdc19a260d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -120,7 +120,7 @@ namespace MWGui tooltipSize = createToolTip(info, checkOwned()); } else - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), true); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); @@ -187,7 +187,7 @@ namespace MWGui if (mFocusObject.isEmpty()) return; - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") { @@ -211,7 +211,7 @@ namespace MWGui mFocusObject = item; if (!mFocusObject.isEmpty()) - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), false); } else if (type == "Spell") { @@ -306,7 +306,7 @@ namespace MWGui { if (!mFocusObject.isEmpty()) { - MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); + MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), true, checkOwned()); setCoord(viewSize.width / 2 - tooltipSize.width / 2, std::max(0, int(mFocusToolTipY * viewSize.height - tooltipSize.height)), tooltipSize.width, diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index e43354bfaf..62dcc69330 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -121,7 +121,7 @@ namespace MWLua cell.mStore->load(); ObjectIdList res = std::make_shared>(); auto visitor = [&](const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; MWBase::Environment::get().getWorldModel()->registerPtr(ptr); if (getLiveCellRefType(ptr.mRef) == ptr.getType()) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index baf13edac4..43092af3dc 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -140,9 +140,8 @@ namespace MWLua mObjectLists.update(); - std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) { - return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().getRefData().isDeleted(); - }); + std::erase_if(mActiveLocalScripts, + [](const LocalScripts* l) { return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().mRef->isDeleted(); }); mGlobalScripts.statsNextFrame(); for (LocalScripts* scripts : mActiveLocalScripts) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index e938d90e5e..f45decea3c 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -124,7 +124,7 @@ namespace MWLua newPtr = cls.moveToCell(ptr, *destCell, toPos(pos, rot)); ptr.getCellRef().unsetRefNum(); ptr.getRefData().setLuaScripts(nullptr); - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); ESM::RefId script = cls.getScript(newPtr); if (!script.empty()) world->getLocalScripts().add(script, newPtr); @@ -256,7 +256,7 @@ namespace MWLua [types = getTypeToPackageTable(context.mLua->sol())]( const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); - objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); + objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getCellRef().getCount(); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { @@ -340,10 +340,10 @@ namespace MWLua auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); }; auto setEnabled = [context](const GObject& object, bool enable) { - if (enable && object.ptr().getRefData().isDeleted()) + if (enable && object.ptr().mRef->isDeleted()) throw std::runtime_error("Object is removed"); context.mLuaManager->addAction([object, enable] { - if (object.ptr().getRefData().isDeleted()) + if (object.ptr().mRef->isDeleted()) return; if (object.ptr().isInCell()) { @@ -417,20 +417,20 @@ namespace MWLua using DelayedRemovalFn = std::function; auto removeFn = [](const MWWorld::Ptr ptr, int countToRemove) -> std::optional { - int rawCount = ptr.getRefData().getCount(false); + int rawCount = ptr.getCellRef().getCount(false); int currentCount = std::abs(rawCount); int signedCountToRemove = (rawCount < 0 ? -1 : 1) * countToRemove; if (countToRemove <= 0 || countToRemove > currentCount) throw std::runtime_error("Can't remove " + std::to_string(countToRemove) + " of " + std::to_string(currentCount) + " items"); - ptr.getRefData().setCount(rawCount - signedCountToRemove); // Immediately change count + ptr.getCellRef().setCount(rawCount - signedCountToRemove); // Immediately change count if (!ptr.getContainerStore() && currentCount > countToRemove) return std::nullopt; // Delayed action to trigger side effects return [signedCountToRemove](MWWorld::Ptr ptr) { // Restore the original count - ptr.getRefData().setCount(ptr.getRefData().getCount(false) + signedCountToRemove); + ptr.getCellRef().setCount(ptr.getCellRef().getCount(false) + signedCountToRemove); // And now remove properly if (ptr.getContainerStore()) ptr.getContainerStore()->remove(ptr, std::abs(signedCountToRemove), false); @@ -443,7 +443,7 @@ namespace MWLua }; objectT["remove"] = [removeFn, context](const GObject& object, sol::optional count) { std::optional delayed - = removeFn(object.ptr(), count.value_or(object.ptr().getRefData().getCount())); + = removeFn(object.ptr(), count.value_or(object.ptr().getCellRef().getCount())); if (delayed.has_value()) context.mLuaManager->addAction([fn = *delayed, object] { fn(object.ptr()); }); }; @@ -463,7 +463,7 @@ namespace MWLua }; objectT["moveInto"] = [removeFn, context](const GObject& object, const sol::object& dest) { const MWWorld::Ptr& ptr = object.ptr(); - int count = ptr.getRefData().getCount(); + int count = ptr.getCellRef().getCount(); MWWorld::Ptr destPtr; if (dest.is()) destPtr = dest.as().ptr(); @@ -474,9 +474,9 @@ namespace MWLua std::optional delayedRemovalFn = removeFn(ptr, count); context.mLuaManager->addAction([item = object, count, cont = GObject(destPtr), delayedRemovalFn] { const MWWorld::Ptr& oldPtr = item.ptr(); - auto& refData = oldPtr.getRefData(); + auto& refData = oldPtr.getCellRef(); refData.setCount(count); // temporarily undo removal to run ContainerStore::add - refData.enable(); + oldPtr.getRefData().enable(); cont.ptr().getClass().getContainerStore(cont.ptr()).add(oldPtr, count, false); refData.setCount(0); if (delayedRemovalFn.has_value()) @@ -487,7 +487,7 @@ namespace MWLua const osg::Vec3f& pos, const sol::object& options) { MWWorld::CellStore* cell = findCell(cellOrName, pos); MWWorld::Ptr ptr = object.ptr(); - int count = ptr.getRefData().getCount(); + int count = ptr.getCellRef().getCount(); if (count == 0) throw std::runtime_error("Object is either removed or already in the process of teleporting"); osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3(); @@ -508,9 +508,9 @@ namespace MWLua context.mLuaManager->addAction( [object, cell, pos, rot, count, delayedRemovalFn, placeOnGround] { MWWorld::Ptr oldPtr = object.ptr(); - oldPtr.getRefData().setCount(count); + oldPtr.getCellRef().setCount(count); MWWorld::Ptr newPtr = oldPtr.getClass().moveToCell(oldPtr, *cell); - oldPtr.getRefData().setCount(0); + oldPtr.getCellRef().setCount(0); newPtr.getRefData().disable(); teleportNotPlayer(newPtr, cell, pos, rot, placeOnGround); delayedRemovalFn(oldPtr); @@ -522,10 +522,10 @@ namespace MWLua [cell, pos, rot, placeOnGround] { teleportPlayer(cell, pos, rot, placeOnGround); }); else { - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); context.mLuaManager->addAction( [object, cell, pos, rot, count, placeOnGround] { - object.ptr().getRefData().setCount(count); + object.ptr().getCellRef().setCount(count); teleportNotPlayer(object.ptr(), cell, pos, rot, placeOnGround); }, "TeleportAction"); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 370e9e7f69..a4449f6fb0 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -37,7 +37,7 @@ namespace MWLua itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get(item)); if (old_it != store.end() && *old_it == itemPtr) return { old_it, true }; // already equipped - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 + if (itemPtr.isEmpty() || itemPtr.getCellRef().getCount() == 0 || itemPtr.getContainerStore() != static_cast(&store)) { Log(Debug::Warning) << "Object" << std::get(item).toString() << " is not in inventory"; @@ -51,7 +51,7 @@ namespace MWLua if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) return { old_it, true }; // already equipped itemPtr = store.search(recordId); - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + if (itemPtr.isEmpty() || itemPtr.getCellRef().getCount() == 0) { Log(Debug::Warning) << "There is no object with recordId='" << stringId << "' in inventory"; return { store.end(), false }; diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index b616075496..648229a5e5 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -15,7 +15,7 @@ namespace MWLua item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; item["isRestocking"] - = [](const Object& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; + = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; addItemDataBindings(item, context); } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 31abda44c2..be4fe5e674 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -30,7 +30,7 @@ namespace MWMechanics // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return true; // Turn to target and move to it directly, without pathfinding. diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5285fb31dd..674a660ff6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -115,7 +115,7 @@ namespace MWMechanics if (target.isEmpty()) return true; - if (!target.getRefData().getCount() + if (!target.getCellRef().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently // registered with the MechanicsManager || target.getClass().getCreatureStats(target).isDead()) @@ -478,8 +478,7 @@ namespace MWMechanics MWWorld::Ptr AiCombat::getTarget() const { - if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted() - || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.isEmpty() || mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) { mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5c07c18166..b4779dc900 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -99,7 +99,7 @@ namespace MWMechanics // Target is not here right now, wait for it to return // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return false; actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 183c30bfb7..bdf96983ae 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -63,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { if (!mCachedTarget.isEmpty()) { - if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 699e96cd32..461db45133 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -38,7 +38,7 @@ namespace MWMechanics // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return true; if (isTargetMagicallyHidden(target) @@ -83,7 +83,7 @@ namespace MWMechanics { if (!mCachedTarget.isEmpty()) { - if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index f8e0046e19..aea3e36632 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -289,7 +289,7 @@ void MWMechanics::Alchemy::removeIngredients() { iter->getContainerStore()->remove(*iter, 1); - if (iter->getRefData().getCount() < 1) + if (iter->getCellRef().getCount() < 1) *iter = MWWorld::Ptr(); } @@ -369,7 +369,7 @@ int MWMechanics::Alchemy::countPotionsToBrew() const for (TIngredientsIterator iter(beginIngredients()); iter != endIngredients(); ++iter) if (!iter->isEmpty()) { - int count = iter->getRefData().getCount(); + int count = iter->getCellRef().getCount(); if ((count > 0 && count < toBrew) || toBrew < 0) toBrew = count; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d4fd63309f..59b1392dc9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1022,7 +1022,7 @@ namespace MWMechanics if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; - int itemCount = it->getRefData().getCount(); + int itemCount = it->getCellRef().getCount(); for (OwnerMap::iterator ownerIt = owners.begin(); ownerIt != owners.end();) { int toRemove = std::min(itemCount, ownerIt->second); @@ -1034,7 +1034,7 @@ namespace MWMechanics ++ownerIt; } - int toMove = it->getRefData().getCount() - itemCount; + int toMove = it->getCellRef().getCount() - itemCount; containerStore.add(*it, toMove); store.remove(*it, toMove); @@ -1084,15 +1084,21 @@ namespace MWMechanics } } - if (!(item.getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId)) + const bool isGold = item.getClass().isGold(item); + if (!isGold) { if (victim.isEmpty() - || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 + || (victim.getClass().isActor() && victim.getCellRef().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead())) mStolenItems[item.getCellRef().getRefId()][owner] += count; } if (alarm) - commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), item.getClass().getValue(item) * count); + { + int value = count; + if (!isGold) + value *= item.getClass().getValue(item); + commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), value); + } } bool MechanicsManager::commitCrime(const MWWorld::Ptr& player, const MWWorld::Ptr& victim, OffenseType type, diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 9c42e4088c..6e16436bcc 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -38,7 +38,7 @@ namespace MWMechanics bool rechargeItem(const MWWorld::Ptr& item, const MWWorld::Ptr& gem) { - if (!gem.getRefData().getCount()) + if (!gem.getCellRef().getCount()) return false; MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -87,7 +87,7 @@ namespace MWMechanics player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); gem.getContainerStore()->remove(gem, 1); - if (gem.getRefData().getCount() == 0) + if (gem.getCellRef().getCount() == 0) { std::string message = MWBase::Environment::get() .getESMStore() diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index a4541ab5a3..e31a1eb711 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -426,7 +426,7 @@ namespace MWRender const auto& weaponType = MWMechanics::getWeaponType(type); if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { - ammoCount = ammo->getRefData().getCount(); + ammoCount = ammo->getCellRef().getCount(); osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; @@ -439,7 +439,7 @@ namespace MWRender if (ammo == inv.end()) return; - ammoCount = ammo->getRefData().getCount(); + ammoCount = ammo->getCellRef().getCount(); bool arrowAttached = isArrowAttached(); if (arrowAttached) ammoCount--; @@ -514,7 +514,7 @@ namespace MWRender ItemLightMap::iterator iter = mItemLights.find(item); if (iter != mItemLights.end()) { - if (!item.getRefData().getCount()) + if (!item.getCellRef().getCount()) { removeHiddenItemLight(item); } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 4a0d302cd8..3822a247bd 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -109,7 +109,7 @@ namespace MWScript return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; // Check if "item" can be placed in a container MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), item, 1); @@ -195,7 +195,7 @@ namespace MWScript runtime.pop(); if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); @@ -231,7 +231,7 @@ namespace MWScript return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; // Explicit calls to non-unique actors affect the base record if (!R::implicit && ptr.getClass().isActor() @@ -460,7 +460,7 @@ namespace MWScript it != invStore.cend(); ++it) { if (it->getCellRef().getSoul() == name) - count += it->getRefData().getCount(); + count += it->getCellRef().getCount(); } runtime.push(count); } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b8dc047737..9d75334f00 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -715,7 +715,7 @@ namespace MWScript MWWorld::ConstContainerStoreIterator it = store.getSlot(slot); if (it != store.end() && it->getCellRef().getRefId() == item) { - numNotEquipped -= it->getRefData().getCount(); + numNotEquipped -= it->getCellRef().getCount(); } } @@ -724,7 +724,7 @@ namespace MWScript MWWorld::ContainerStoreIterator it = store.getSlot(slot); if (it != store.end() && it->getCellRef().getRefId() == item) { - int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); + int numToRemove = std::min(amount - numNotEquipped, it->getCellRef().getCount()); store.unequipItemQuantity(*it, numToRemove); numNotEquipped += numToRemove; } @@ -1418,7 +1418,7 @@ namespace MWScript if (ptr.getRefData().isDeletedByContentFile()) msg << "[Deleted by content file]" << std::endl; - if (!ptr.getRefData().getCount()) + if (!ptr.getCellRef().getCount()) msg << "[Deleted]" << std::endl; msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index 708260d395..30f316c2db 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -37,7 +37,7 @@ namespace MWWorld if (!it->getClass().showsInInventory(*it)) continue; - int itemCount = it->getRefData().getCount(); + int itemCount = it->getCellRef().getCount(); // Note: it is important to check for crime before move an item from container. Otherwise owner check will // not work for a last item in the container - empty harvested containers are considered as "allowed to // use". diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 83d6e729c9..1f37499e8f 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -30,10 +30,12 @@ namespace MWWorld } } - MWBase::Environment::get().getMechanicsManager()->itemTaken( - actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); - MWWorld::Ptr newitem - = *actor.getClass().getContainerStore(actor).add(getTarget(), getTarget().getRefData().getCount()); + int count = getTarget().getCellRef().getCount(); + if (getTarget().getClass().isGold(getTarget())) + count *= getTarget().getClass().getValue(getTarget()); + + MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, getTarget(), MWWorld::Ptr(), count); + MWWorld::Ptr newitem = *actor.getClass().getContainerStore(actor).add(getTarget(), count); MWBase::Environment::get().getWorld()->deleteObject(getTarget()); setTarget(newitem); } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index b7a3713eba..fda7157010 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -377,17 +377,19 @@ namespace MWWorld } } - void CellRef::setGoldValue(int value) + void CellRef::setCount(int value) { - if (value != getGoldValue()) + if (value != getCount(false)) { mChanged = true; std::visit(ESM::VisitOverload{ - [&](ESM4::Reference& /*ref*/) {}, - [&](ESM4::ActorCharacter&) {}, - [&](ESM::CellRef& ref) { ref.mGoldValue = value; }, + [&](ESM4::Reference& ref) { ref.mCount = value; }, + [&](ESM4::ActorCharacter& ref) { ref.mCount = value; }, + [&](ESM::CellRef& ref) { ref.mCount = value; }, }, mCellRef.mVariant); + if (value == 0) + MWBase::Environment::get().getWorld()->removeRefScript(this); } } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 92bc355db4..4dcac4def5 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -231,18 +231,20 @@ namespace MWWorld } void setTrap(const ESM::RefId& trap); - // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int getGoldValue() const + int getCount(bool absolute = true) const { struct Visitor { - int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; } - int operator()(const ESM4::Reference& /*ref*/) { return 0; } - int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); } + int operator()(const ESM::CellRef& ref) { return ref.mCount; } + int operator()(const ESM4::Reference& ref) { return ref.mCount; } + int operator()(const ESM4::ActorCharacter& ref) { return ref.mCount; } }; - return std::visit(Visitor(), mCellRef.mVariant); + int count = std::visit(Visitor(), mCellRef.mVariant); + if (absolute) + return std::abs(count); + return count; } - void setGoldValue(int value); + void setCount(int value); // Write the content of this CellRef into the given ObjectState void writeState(ESM::ObjectState& state) const; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 930f26c1cc..fd24bb7271 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -152,7 +152,7 @@ namespace if (toIgnore.find(&*iter) != toIgnore.end()) continue; - if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getCellRef().getCount() > 0) return actor; } @@ -175,7 +175,7 @@ namespace // Reference that came from a content file and has not been changed -> ignore continue; } - if (liveCellRef.mData.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) + if (liveCellRef.mRef.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; @@ -201,8 +201,8 @@ namespace { for (auto& item : state.mInventory.mItems) { - if (item.mCount > 0 && baseItem.mItem == item.mRef.mRefID) - item.mCount = -item.mCount; + if (item.mRef.mCount > 0 && baseItem.mItem == item.mRef.mRefID) + item.mRef.mCount = -item.mRef.mCount; } } } @@ -298,9 +298,9 @@ namespace // instance is invalid. But non-storable item are always stored in saves together with their original cell. // If a non-storable item references a content file, but is not found in this content file, // we should drop it. Likewise if this stack is empty. - if (!MWWorld::ContainerStore::isStorableType() || !state.mCount) + if (!MWWorld::ContainerStore::isStorableType() || !state.mRef.mCount) { - if (state.mCount) + if (state.mRef.mCount) Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; return; @@ -676,7 +676,7 @@ namespace MWWorld MWWorld::Ptr actor(base, this); if (!actor.getClass().isActor()) continue; - if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getCellRef().getCount() > 0) return actor; } @@ -1042,7 +1042,7 @@ namespace MWWorld for (const auto& [base, store] : mMovedToAnotherCell) { ESM::RefNum refNum = base->mRef.getRefNum(); - if (base->mData.isDeleted() && !refNum.hasContentFile()) + if (base->isDeleted() && !refNum.hasContentFile()) continue; // filtered out in writeReferenceCollection ESM::RefId movedTo = store->getCell()->getId(); @@ -1168,20 +1168,18 @@ namespace MWWorld { if (mState == State_Loaded) { - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& creature : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&creature); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& npc : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&npc); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } @@ -1196,29 +1194,26 @@ namespace MWWorld if (mState == State_Loaded) { - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& creature : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&creature); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& npc : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&npc); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& container : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 + Ptr ptr = getCurrentPtr(&container); + if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getCellRef().getCount() > 0 && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); @@ -1262,7 +1257,7 @@ namespace MWWorld } forEachType([](Ptr ptr) { // no need to clearCorpse, handled as part of get() - if (!ptr.getRefData().isDeleted()) + if (!ptr.mRef->isDeleted()) ptr.getClass().respawn(ptr); return true; }); @@ -1290,7 +1285,7 @@ namespace MWWorld for (auto& item : list) { Ptr ptr = getCurrentPtr(&item); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { checkItem(ptr); } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index c80cf56d6a..0c6527ce22 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -118,7 +118,7 @@ namespace MWWorld /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) { - return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); + return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || cref.getCount() > 0); } /// Moves object from this cell to the given cell. diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 5fbda6d570..d5062d6add 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -373,7 +373,7 @@ namespace MWWorld { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference - newPtr.getRefData().setCount(count); + newPtr.getCellRef().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); return newPtr; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b55a524a48..d30ea21494 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -51,7 +51,7 @@ namespace for (const MWWorld::LiveCellRef& liveCellRef : cellRefList.mList) { - if (const int count = liveCellRef.mData.getCount(); count > 0) + if (const int count = liveCellRef.mRef.getCount(); count > 0) sum += count * liveCellRef.mBase->mData.mWeight; } @@ -65,7 +65,7 @@ namespace for (MWWorld::LiveCellRef& liveCellRef : list.mList) { - if ((liveCellRef.mBase->mId == id) && liveCellRef.mData.getCount()) + if ((liveCellRef.mBase->mId == id) && liveCellRef.mRef.getCount()) { MWWorld::Ptr ptr(&liveCellRef, nullptr); ptr.setContainerStore(store); @@ -132,7 +132,7 @@ void MWWorld::ContainerStore::storeStates( { for (const LiveCellRef& liveCellRef : collection.mList) { - if (liveCellRef.mData.getCount() == 0) + if (liveCellRef.mRef.getCount() == 0) continue; ESM::ObjectState state; storeState(liveCellRef, state); @@ -192,7 +192,7 @@ int MWWorld::ContainerStore::count(const ESM::RefId& id) const int total = 0; for (const auto&& iter : *this) if (iter.getCellRef().getRefId() == id) - total += iter.getRefData().getCount(); + total += iter.getCellRef().getCount(); return total; } @@ -219,9 +219,9 @@ void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* l MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, int count) { resolve(); - if (ptr.getRefData().getCount() <= count) + if (ptr.getCellRef().getCount() <= count) return end(); - MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getCellRef().getCount(false), count)); MWWorld::Ptr newPtr = *it; newPtr.getCellRef().unsetRefNum(); @@ -232,7 +232,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); - remove(ptr, ptr.getRefData().getCount() - count); + remove(ptr, ptr.getCellRef().getCount() - count); return it; } @@ -257,9 +257,9 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: { if (stacks(*iter, item)) { - iter->getRefData().setCount( - addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); - item.getRefData().setCount(0); + iter->getCellRef().setCount( + addItems(iter->getCellRef().getCount(false), item.getCellRef().getCount(false))); + item.getCellRef().setCount(0); retval = iter; break; } @@ -385,22 +385,24 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for // detecting player gold) + // Note that adding 1 gold_100 is equivalent to adding 1 gold_001. Morrowind.exe resolves gold in leveled lists to + // gold_001 and TESCS disallows adding gold other than gold_001 to inventories. If a content file defines a + // container containing gold_100 anyway, the item is not turned to gold_001 until the player puts it down in the + // world and picks it up again. We just turn it into gold_001 here and ignore that oddity. if (ptr.getClass().isGold(ptr)) { - int realCount = count * ptr.getClass().getValue(ptr); - for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { if (iter->getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId) { - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); flagAsModified(); return iter; } } - MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); - return addNewStack(ref.getPtr(), realCount); + MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, count); + return addNewStack(ref.getPtr(), count); } // determine whether to stack or not @@ -414,7 +416,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, if (stacks(*iter, ptr)) { // stack - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); flagAsModified(); return iter; @@ -480,7 +482,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack(const Const break; } - it->getRefData().setCount(count); + it->getCellRef().setCount(count); flagAsModified(); return it; @@ -562,7 +564,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplac resolve(); int toRemove = count; - RefData& itemRef = item.getRefData(); + CellRef& itemRef = item.getCellRef(); if (itemRef.getCount() <= toRemove) { @@ -667,7 +669,7 @@ void MWWorld::ContainerStore::addInitialItemImp( void MWWorld::ContainerStore::clear() { for (auto&& iter : *this) - iter.getRefData().setCount(0); + iter.getCellRef().setCount(0); flagAsModified(); mModified = true; @@ -690,7 +692,7 @@ void MWWorld::ContainerStore::resolve() if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(container.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, container.mCell); @@ -712,7 +714,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(container.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, container.mCell); @@ -729,7 +731,7 @@ void MWWorld::ContainerStore::unresolve() if (mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); fillNonRandom(container.get()->mBase->mInventory, ESM::RefId(), mSeed); addScripts(*this, container.mCell); mResolved = false; @@ -1332,7 +1334,7 @@ MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBas { if (incIterator()) nextType(); - } while (mType != -1 && !(**this).getRefData().getCount()); + } while (mType != -1 && !(**this).getCellRef().getCount()); return *this; } @@ -1384,7 +1386,7 @@ MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(int mas { nextType(); - if (mType == -1 || (**this).getRefData().getCount()) + if (mType == -1 || (**this).getCellRef().getCount()) return; ++*this; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 095f5d3cc1..f48f4e6e31 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -79,11 +79,11 @@ void MWWorld::InventoryStore::readEquipmentState( slot = allowedSlots.first.front(); // unstack if required - if (!allowedSlots.second && iter->getRefData().getCount() > 1) + if (!allowedSlots.second && iter->getCellRef().getCount() > 1) { - int count = iter->getRefData().getCount(false); + int count = iter->getCellRef().getCount(false); MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1); - iter->getRefData().setCount(subtractItems(count, 1)); + iter->getCellRef().setCount(subtractItems(count, 1)); mSlots[slot] = newIter; } else @@ -171,7 +171,7 @@ void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iter // unstack item pointed to by iterator if required if (iterator != end() && !slots_.second - && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + && iterator->getCellRef().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { unstack(*iterator); } @@ -355,7 +355,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) { if (!itemsSlots.second) { - if (weapon->getRefData().getCount() > 1) + if (weapon->getCellRef().getCount() > 1) { unstack(*weapon); } @@ -478,7 +478,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped { // unstack item pointed to by iterator if required - if (iter->getRefData().getCount() > 1) + if (iter->getCellRef().getCount() > 1) { unstack(*iter); } @@ -590,7 +590,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac int retCount = ContainerStore::remove(item, count, equipReplacement, resolve); bool wasEquipped = false; - if (!item.getRefData().getCount()) + if (!item.getCellRef().getCount()) { for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { @@ -618,7 +618,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac autoEquip(); } - if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) + if (item.getCellRef().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) { mSelectedEnchantItem = end(); } @@ -643,7 +643,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, b // empty this slot mSlots[slot] = end(); - if (it->getRefData().getCount()) + if (it->getCellRef().getCount()) { retval = restack(*it); @@ -690,10 +690,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con throw std::runtime_error("attempt to unequip an item that is not currently equipped"); if (count <= 0) throw std::runtime_error("attempt to unequip nothing (count <= 0)"); - if (count > item.getRefData().getCount()) + if (count > item.getCellRef().getCount()) throw std::runtime_error("attempt to unequip more items than equipped"); - if (count == item.getRefData().getCount()) + if (count == item.getCellRef().getCount()) return unequipItem(item); // Move items to an existing stack if possible, otherwise split count items out into a new stack. @@ -702,13 +702,13 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con { if (stacks(*iter, item) && !isEquipped(*iter)) { - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); - item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); + item.getCellRef().setCount(subtractItems(item.getCellRef().getCount(false), count)); return iter; } } - return unstack(item, item.getRefData().getCount() - count); + return unstack(item, item.getCellRef().getCount() - count); } MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index d4e2ac40c0..61b838bbf0 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -106,6 +106,11 @@ unsigned int MWWorld::LiveCellRefBase::getType() const return mClass->getType(); } +bool MWWorld::LiveCellRefBase::isDeleted() const +{ + return mData.isDeletedByContentFile() || mRef.getCount(false) == 0; +} + namespace MWWorld { std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 1e4d1441f5..c95dd589b2 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -59,6 +59,9 @@ namespace MWWorld template static LiveCellRef* dynamicCast(LiveCellRefBase* value); + /// Returns true if the object was either deleted by the content file or by gameplay. + bool isDeleted() const; + protected: void loadImp(const ESM::ObjectState& state); ///< Load state into a LiveCellRef, that has already been initialised with base and diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 8d9a282791..955e1a91f8 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -24,7 +24,7 @@ namespace bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; const ESM::RefId& script = ptr.getClass().getScript(ptr); @@ -152,10 +152,10 @@ void MWWorld::LocalScripts::clearCell(CellStore* cell) } } -void MWWorld::LocalScripts::remove(RefData* ref) +void MWWorld::LocalScripts::remove(const MWWorld::CellRef* ref) { for (auto iter = mScripts.begin(); iter != mScripts.end(); ++iter) - if (&(iter->second.getRefData()) == ref) + if (&(iter->second.getCellRef()) == ref) { if (iter == mIter) ++mIter; diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 4bd2abeb84..09a913e655 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -41,7 +41,7 @@ namespace MWWorld void clearCell(CellStore* cell); ///< Remove all scripts belonging to \a cell. - void remove(RefData* ref); + void remove(const MWWorld::CellRef* ref); void remove(const Ptr& ptr); ///< Remove script for given reference (ignored if reference does not have a script listed). diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index e9fd02e6f5..c6c0444754 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -101,5 +101,5 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const ESM::RefId& throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown type)"); } - mPtr.getRefData().setCount(count); + mPtr.getCellRef().setCount(count); } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3cb08be5fa..0584a9fe94 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -434,7 +434,7 @@ namespace MWWorld MWWorld::Ptr caster = magicBoltState.getCaster(); if (!caster.isEmpty() && caster.getClass().isActor()) { - if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) + if (caster.getCellRef().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) { cleanupMagicBolt(magicBoltState); continue; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1421b51a2a..25715a26f1 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -10,7 +10,7 @@ namespace MWWorld std::string Ptr::toString() const { std::string res = "object"; - if (getRefData().isDeleted()) + if (mRef->isDeleted()) res = "deleted object"; res.append(getCellRef().getRefNum().toString()); res.append(" ("); diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 87c085ebd4..f7ba76da21 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -37,7 +37,6 @@ namespace MWWorld mBaseNode = refData.mBaseNode; mLocals = refData.mLocals; mEnabled = refData.mEnabled; - mCount = refData.mCount; mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; @@ -62,7 +61,6 @@ namespace MWWorld , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCount(1) , mCustomData(nullptr) , mChanged(false) , mFlags(0) @@ -79,7 +77,6 @@ namespace MWWorld , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCount(1) , mPosition(cellRef.mPos) , mCustomData(nullptr) , mChanged(false) @@ -92,7 +89,6 @@ namespace MWWorld , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mCount(ref.mCount) , mPosition(ref.mPos) , mCustomData(nullptr) , mChanged(false) @@ -105,7 +101,6 @@ namespace MWWorld , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mCount(mDeletedByContentFile ? 0 : 1) , mPosition(ref.mPos) , mCustomData(nullptr) , mChanged(false) @@ -118,7 +113,6 @@ namespace MWWorld , mDeletedByContentFile(deletedByContentFile) , mEnabled(objectState.mEnabled != 0) , mPhysicsPostponed(false) - , mCount(objectState.mCount) , mPosition(objectState.mPosition) , mAnimationState(objectState.mAnimationState) , mCustomData(nullptr) @@ -153,7 +147,6 @@ namespace MWWorld objectState.mHasLocals = mLocals.write(objectState.mLocals, scriptId); objectState.mEnabled = mEnabled; - objectState.mCount = mCount; objectState.mPosition = mPosition; objectState.mFlags = mFlags; @@ -205,39 +198,17 @@ namespace MWWorld return mBaseNode; } - int RefData::getCount(bool absolute) const - { - if (absolute) - return std::abs(mCount); - return mCount; - } - void RefData::setLocals(const ESM::Script& script) { if (mLocals.configure(script) && !mLocals.isEmpty()) mChanged = true; } - void RefData::setCount(int count) - { - if (count == 0) - MWBase::Environment::get().getWorld()->removeRefScript(this); - - mChanged = true; - - mCount = count; - } - void RefData::setDeletedByContentFile(bool deleted) { mDeletedByContentFile = deleted; } - bool RefData::isDeleted() const - { - return mDeletedByContentFile || mCount == 0; - } - bool RefData::isDeletedByContentFile() const { return mDeletedByContentFile; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index a6a45623f5..ae80a0d64e 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -58,9 +58,6 @@ namespace MWWorld bool mPhysicsPostponed : 1; private: - /// 0: deleted - int mCount; - ESM::Position mPosition; ESM::AnimationState mAnimationState; @@ -110,26 +107,15 @@ namespace MWWorld /// Set base node (can be a null pointer). void setBaseNode(osg::ref_ptr base); - int getCount(bool absolute = true) const; - void setLocals(const ESM::Script& script); MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); } void setLuaScripts(std::shared_ptr&&); - void setCount(int count); - ///< Set object count (an object pile is a simple object with a count >1). - /// - /// \warning Do not call setCount() to add or remove objects from a - /// container or an actor's inventory. Call ContainerStore::add() or - /// ContainerStore::remove() instead. - /// This flag is only used for content stack loading and will not be stored in the savegame. /// If the object was deleted by gameplay, then use setCount(0) instead. void setDeletedByContentFile(bool deleted); - /// Returns true if the object was either deleted by the content file or by gameplay. - bool isDeleted() const; /// Returns true if the object was deleted by a content file. bool isDeletedByContentFile() const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 036287d3a9..d8875b513e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -226,7 +226,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + if (!ptr.mRef->isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -648,7 +648,7 @@ namespace MWWorld if (ptr.mRef->mData.mPhysicsPostponed) { ptr.mRef->mData.mPhysicsPostponed = false; - if (ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) + if (ptr.mRef->mData.isEnabled() && ptr.mRef->mRef.getCount() > 0) { std::string model = getModel(ptr); if (!model.empty()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 07334396b7..1b6af6038e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -685,7 +685,7 @@ namespace MWWorld return mStore.get().find("sDefaultCellname")->mValue.getString(); } - void World::removeRefScript(MWWorld::RefData* ref) + void World::removeRefScript(const MWWorld::CellRef* ref) { mLocalScripts.remove(ref); } @@ -827,7 +827,7 @@ namespace MWWorld reference.getRefData().enable(); if (mWorldScene->getActiveCells().find(reference.getCell()) != mWorldScene->getActiveCells().end() - && reference.getRefData().getCount()) + && reference.getCellRef().getCount()) mWorldScene->addObjectToScene(reference); if (reference.getCellRef().getRefNum().hasContentFile()) @@ -879,7 +879,7 @@ namespace MWWorld } if (mWorldScene->getActiveCells().find(reference.getCell()) != mWorldScene->getActiveCells().end() - && reference.getRefData().getCount()) + && reference.getCellRef().getCount()) { mWorldScene->removeObjectFromScene(reference); mWorldScene->addPostponedPhysicsObjects(); @@ -1039,12 +1039,12 @@ namespace MWWorld void World::deleteObject(const Ptr& ptr) { - if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr) + if (!ptr.mRef->isDeleted() && ptr.getContainerStore() == nullptr) { if (ptr == getPlayerPtr()) throw std::runtime_error("can not delete player object"); - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); if (ptr.isInCell() && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() @@ -1061,9 +1061,9 @@ namespace MWWorld { if (!ptr.getCellRef().hasContentFile()) return; - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() && ptr.getRefData().isEnabled()) { @@ -1392,7 +1392,7 @@ namespace MWWorld MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) { - return copyObjectToCell(ptr, cell, pos, ptr.getRefData().getCount(), false); + return copyObjectToCell(ptr, cell, pos, ptr.getCellRef().getCount(), false); } MWWorld::Ptr World::safePlaceObject(const ConstPtr& ptr, const ConstPtr& referenceObject, @@ -1443,7 +1443,7 @@ namespace MWWorld ipos.rot[1] = 0; } - MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); + MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getCellRef().getCount(), false); adjustPosition(placed, true); // snap to ground return placed; } @@ -1893,7 +1893,7 @@ namespace MWWorld { MWWorld::LiveCellRef& ref = *static_cast*>(ptr.getBase()); - if (!ref.mData.isEnabled() || ref.mData.isDeleted()) + if (!ref.mData.isEnabled() || ref.isDeleted()) return true; if (ref.mRef.getTeleport()) @@ -2541,7 +2541,7 @@ namespace MWWorld bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; // vanilla Morrowind does not allow to sell items from containers with zero capacity @@ -3337,7 +3337,7 @@ namespace MWWorld >= mSquaredDist) return true; - if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted()) + if (!ptr.getRefData().isEnabled() || ptr.mRef->isDeleted()) return true; // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b9a0ccb98..b5d56753b0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -275,7 +275,7 @@ namespace MWWorld std::string_view getCellName(const MWWorld::Cell& cell) const override; std::string_view getCellName(const ESM::Cell* cell) const override; - void removeRefScript(MWWorld::RefData* ref) override; + void removeRefScript(const MWWorld::CellRef* ref) override; //< Remove the script attached to ref from mLocalScripts Ptr getPtr(const ESM::RefId& name, bool activeOnly) override; diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 96796defd4..ff68d0d4f1 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -292,7 +292,7 @@ namespace ESM record.mFactionRank = std::numeric_limits::max(); record.mChargeInt = std::numeric_limits::max(); record.mEnchantmentCharge = std::numeric_limits::max(); - record.mGoldValue = std::numeric_limits::max(); + record.mCount = std::numeric_limits::max(); record.mTeleport = true; generateArray(record.mDoorDest.pos); generateArray(record.mDoorDest.rot); @@ -317,7 +317,7 @@ namespace ESM EXPECT_EQ(record.mFactionRank, result.mFactionRank); EXPECT_EQ(record.mChargeInt, result.mChargeInt); EXPECT_EQ(record.mEnchantmentCharge, result.mEnchantmentCharge); - EXPECT_EQ(record.mGoldValue, result.mGoldValue); + EXPECT_EQ(record.mCount, result.mCount); EXPECT_EQ(record.mTeleport, result.mTeleport); EXPECT_EQ(record.mDoorDest, result.mDoorDest); EXPECT_EQ(record.mDestCell, result.mDestCell); diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 42edec8f1f..93a2ece669 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -107,7 +107,7 @@ namespace ESM getHTOrSkip(cellRef.mChargeInt); break; case fourCC("NAM9"): - getHTOrSkip(cellRef.mGoldValue); + getHTOrSkip(cellRef.mCount); break; case fourCC("DODT"): if constexpr (load) @@ -219,8 +219,8 @@ namespace ESM if (mChargeInt != -1) esm.writeHNT("INTV", mChargeInt); - if (mGoldValue > 1) - esm.writeHNT("NAM9", mGoldValue); + if (mCount != 1) + esm.writeHNT("NAM9", mCount); if (!inInventory && mTeleport) { @@ -259,7 +259,7 @@ namespace ESM mChargeInt = -1; mChargeIntRemainder = 0.0f; mEnchantmentCharge = -1; - mGoldValue = 1; + mCount = 1; mDestCell.clear(); mLockLevel = 0; mIsLocked = false; diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 55e5afcbf5..84b6ae1d18 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -65,8 +65,7 @@ namespace ESM // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; - // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int32_t mGoldValue; + int32_t mCount; // For doors - true if this door teleports to somewhere else, false // if it should open through animation. diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index b460c15247..1489926fbd 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -24,7 +24,8 @@ namespace ESM inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30; + inline constexpr FormatVersion MaxOldCountFormatVersion = 30; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 4175159ad5..1947be23e9 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -24,7 +24,7 @@ namespace ESM state.mRef.loadId(esm, true); state.load(esm); - if (state.mCount == 0) + if (state.mRef.mCount == 0) continue; mItems.push_back(state); @@ -43,7 +43,7 @@ namespace ESM if (!esm.applyContentFileMapping(state.mRef.mRefNum)) state.mRef.mRefNum = FormId(); // content file removed; unset refnum, but keep object. - if (state.mCount == 0) + if (state.mRef.mCount == 0) continue; mItems.push_back(state); @@ -117,8 +117,8 @@ namespace ESM const int count = entry.second; for (auto& item : mItems) { - if (item.mCount == count && id == item.mRef.mRefID) - item.mCount = -count; + if (item.mRef.mCount == count && id == item.mRef.mRefID) + item.mRef.mCount = -count; } } } diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a46200944a..7d26f431d6 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -29,8 +29,11 @@ namespace ESM mEnabled = 1; esm.getHNOT(mEnabled, "ENAB"); - mCount = 1; - esm.getHNOT(mCount, "COUN"); + if (mVersion <= MaxOldCountFormatVersion) + { + mRef.mCount = 1; + esm.getHNOT(mRef.mCount, "COUN"); + } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); @@ -60,9 +63,6 @@ namespace ESM if (!mEnabled && !inInventory) esm.writeHNT("ENAB", mEnabled); - if (mCount != 1) - esm.writeHNT("COUN", mCount); - if (!inInventory && mPosition != mRef.mPos) { std::array pos; @@ -85,7 +85,6 @@ namespace ESM mRef.blank(); mHasLocals = 0; mEnabled = false; - mCount = 1; for (int i = 0; i < 3; ++i) { mPosition.pos[i] = 0; diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index 4c09d16d18..b3f7bd3d45 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -32,7 +32,6 @@ namespace ESM Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; - int32_t mCount; Position mPosition; uint32_t mFlags; @@ -46,7 +45,6 @@ namespace ESM ObjectState() : mHasLocals(0) , mEnabled(0) - , mCount(0) , mFlags(0) , mHasCustomState(true) { diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index 1a6d47497e..dc181dda4b 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -82,6 +82,11 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) reader.getFormId(mEsp.parent); reader.get(mEsp.flags); break; + case ESM4::SUB_XCNT: + { + reader.get(mCount); + break; + } case ESM4::SUB_XRGD: // ragdoll case ESM4::SUB_XRGB: // ragdoll biped case ESM4::SUB_XHRS: // horse formId @@ -113,7 +118,6 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) case ESM4::SUB_XATO: // FONV case ESM4::SUB_MNAM: // FO4 case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XCNT: // FO4 case ESM4::SUB_XEMI: // FO4 case ESM4::SUB_XFVC: // FO4 case ESM4::SUB_XHLT: // FO4 diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index dd867bbafd..8abb47c8bc 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -57,6 +57,8 @@ namespace ESM4 EnableParent mEsp; + std::int32_t mCount = 1; + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index a193907ac4..fb26e39546 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -268,6 +268,11 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } + case ESM4::SUB_XCNT: + { + reader.get(mCount); + break; + } // lighting case ESM4::SUB_LNAM: // lighting template formId case ESM4::SUB_XLIG: // struct, FOV, fade, etc @@ -279,7 +284,6 @@ void ESM4::Reference::load(ESM4::Reader& reader) // case ESM4::SUB_XPCI: // formId case ESM4::SUB_XLCM: - case ESM4::SUB_XCNT: case ESM4::SUB_ONAM: case ESM4::SUB_VMAD: case ESM4::SUB_XPRM: diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index af04a791c8..ec76928827 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -97,7 +97,7 @@ namespace ESM4 EnableParent mEsp; - std::uint32_t mCount = 1; // only if > 1 + std::int32_t mCount = 1; // only if > 1 ESM::FormId mAudioLocation; From 94409ce1722d33dfc2656fa8e5dbfbcb11a58a37 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Dec 2023 14:56:59 +0100 Subject: [PATCH 0701/2167] Add missing UniversalId::mClass initialization --- apps/opencs/model/world/universalid.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 10648a1ecc..9daf87e20a 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -221,6 +221,23 @@ namespace return std::to_string(value); } + + CSMWorld::UniversalId::Class getClassByType(CSMWorld::UniversalId::Type type) + { + if (const auto it + = std::find_if(std::begin(sIdArg), std::end(sIdArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sIdArg)) + return it->mClass; + if (const auto it = std::find_if( + std::begin(sIndexArg), std::end(sIndexArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sIndexArg)) + return it->mClass; + if (const auto it + = std::find_if(std::begin(sNoArg), std::end(sNoArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sNoArg)) + return it->mClass; + throw std::logic_error("invalid UniversalId type: " + std::to_string(type)); + } } CSMWorld::UniversalId::UniversalId(const std::string& universalId) @@ -330,7 +347,8 @@ CSMWorld::UniversalId::UniversalId(Type type, ESM::RefId id) } CSMWorld::UniversalId::UniversalId(Type type, const UniversalId& id) - : mType(type) + : mClass(getClassByType(type)) + , mType(type) , mValue(id.mValue) { } From b835114ce214f40ae0279db818ee667ff5d45915 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 12:48:12 +0100 Subject: [PATCH 0702/2167] Prevent input type assertion --- components/compiler/scanner.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index d5cea61e7f..34b122413e 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -70,7 +70,11 @@ namespace Compiler && mData[3] == 0; } - bool isDigit() const { return std::isdigit(mData[0]) && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } + bool isDigit() const + { + return std::isdigit(static_cast(mData[0])) && mData[1] == 0 && mData[2] == 0 + && mData[3] == 0; + } bool isMinusSign() const { @@ -85,7 +89,8 @@ namespace Compiler if (isMinusSign()) return false; - return std::isalpha(mData[0]) || mData[1] != 0 || mData[2] != 0 || mData[3] != 0; + return std::isalpha(static_cast(mData[0])) || mData[1] != 0 || mData[2] != 0 + || mData[3] != 0; } void appendTo(std::string& str) const From 5bd5c8401801eaeb4972ef7def503cb57500f7dd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 16:55:32 +0100 Subject: [PATCH 0703/2167] Replace missing NPC races and default animations --- apps/openmw/mwworld/esmstore.cpp | 62 +++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 0b661b4442..137c9cf026 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -17,8 +17,10 @@ #include #include #include +#include #include "../mwmechanics/spelllist.hpp" +#include "../mwrender/actorutil.hpp" namespace { @@ -83,12 +85,22 @@ namespace throw std::runtime_error("List of NPC classes is empty!"); } + const ESM::RefId& getDefaultRace(const MWWorld::Store& races) + { + auto it = races.begin(); + if (it != races.end()) + return it->mId; + throw std::runtime_error("List of NPC races is empty!"); + } + std::vector getNPCsToReplace(const MWWorld::Store& factions, - const MWWorld::Store& classes, const MWWorld::Store& scripts, - const std::unordered_map& npcs) + const MWWorld::Store& classes, const MWWorld::Store& races, + const MWWorld::Store& scripts, const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found const ESM::RefId& defaultCls = getDefaultClass(classes); + // Same for races + const ESM::RefId& defaultRace = getDefaultRace(races); // Validate NPCs for non-existing class and faction. // We will replace invalid entries by fixed ones @@ -113,8 +125,7 @@ namespace } } - const ESM::RefId& npcClass = npc.mClass; - const ESM::Class* cls = classes.search(npcClass); + const ESM::Class* cls = classes.search(npc.mClass); if (!cls) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent class " @@ -123,6 +134,41 @@ namespace changed = true; } + const ESM::Race* race = races.search(npc.mRace); + if (race) + { + // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it + // will match the NPC's race) until the NPC's race is changed. If the player record contains a default + // non-beast race animation and the player selects a beast race in chargen, animations aren't applied + // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default. + if (!npc.mModel.empty() && npc.mId == "player") + { + const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); + std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); + if (model.size() == defaultModel.size()) + { + std::replace(model.begin(), model.end(), '/', '\\'); + std::string normalizedDefault = defaultModel; + std::replace(normalizedDefault.begin(), normalizedDefault.end(), '/', '\\'); + if (Misc::StringUtils::ciEqual(normalizedDefault, model)) + { + npc.mModel.clear(); + changed = true; + } + } + } + } + else + { + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace + << ", using " << defaultRace << " race as replacement."; + npc.mRace = defaultRace; + // Remove animations that might be race specific + npc.mModel.clear(); + changed = true; + } + if (!npc.mScript.empty() && !scripts.search(npc.mScript)) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script " @@ -580,8 +626,8 @@ namespace MWWorld void ESMStore::validate() { auto& npcs = getWritable(); - std::vector npcsToReplace = getNPCsToReplace( - getWritable(), getWritable(), getWritable(), npcs.mStatic); + std::vector npcsToReplace = getNPCsToReplace(getWritable(), getWritable(), + getWritable(), getWritable(), npcs.mStatic); for (const ESM::NPC& npc : npcsToReplace) { @@ -623,8 +669,8 @@ namespace MWWorld auto& npcs = getWritable(); auto& scripts = getWritable(); - std::vector npcsToReplace = getNPCsToReplace( - getWritable(), getWritable(), getWritable(), npcs.mDynamic); + std::vector npcsToReplace = getNPCsToReplace(getWritable(), getWritable(), + getWritable(), getWritable(), npcs.mDynamic); for (const ESM::NPC& npc : npcsToReplace) npcs.insert(npc); From 4636ab3f3ede63940551c5f010ab2c560cd50c1c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 17:14:34 +0100 Subject: [PATCH 0704/2167] Update cmakelists --- apps/openmw_test_suite/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 967511953d..421e9e82f1 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -7,6 +7,7 @@ file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp ../openmw/mwworld/timestamp.cpp + ../openmw/mwrender/actorutil.cpp mwworld/test_store.cpp mwworld/testduration.cpp From 84c15344ee2103c2f445799ea58c20177f16d52b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 21:55:22 +0100 Subject: [PATCH 0705/2167] Address feedback --- apps/openmw/mwworld/esmstore.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 137c9cf026..d5d2c03b8b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "../mwmechanics/spelllist.hpp" #include "../mwrender/actorutil.hpp" @@ -140,22 +141,17 @@ namespace // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it // will match the NPC's race) until the NPC's race is changed. If the player record contains a default // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default. + // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default + // values to be used. if (!npc.mModel.empty() && npc.mId == "player") { const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); - if (model.size() == defaultModel.size()) + if (VFS::Path::pathEqual(defaultModel, model)) { - std::replace(model.begin(), model.end(), '/', '\\'); - std::string normalizedDefault = defaultModel; - std::replace(normalizedDefault.begin(), normalizedDefault.end(), '/', '\\'); - if (Misc::StringUtils::ciEqual(normalizedDefault, model)) - { - npc.mModel.clear(); - changed = true; - } + npc.mModel.clear(); + changed = true; } } } From 03c791e61ac832a280d3326760f918835b6ca75c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 13:58:55 +0100 Subject: [PATCH 0706/2167] Move animation handling to NpcAnimation --- CHANGELOG.md | 1 + apps/openmw/mwrender/actorutil.cpp | 8 ++++++++ apps/openmw/mwrender/actorutil.hpp | 2 ++ apps/openmw/mwrender/npcanimation.cpp | 14 +++++++++++--- apps/openmw/mwworld/esmstore.cpp | 26 +------------------------- apps/openmw_test_suite/CMakeLists.txt | 1 - 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f16ac81b..eff35bfa53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 6cef42d60f..8da921e532 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -1,6 +1,7 @@ #include "actorutil.hpp" #include +#include namespace MWRender { @@ -29,4 +30,11 @@ namespace MWRender return Settings::models().mXbaseanim1st; } } + + bool isDefaultActorSkeleton(std::string_view model) + { + return VFS::Path::pathEqual(Settings::models().mBaseanimkna.get(), model) + || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) + || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index bbffc4ad24..3107bf0183 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -2,10 +2,12 @@ #define OPENMW_APPS_OPENMW_MWRENDER_ACTORUTIL_H #include +#include namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); + bool isDefaultActorSkeleton(std::string_view model); } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 900d0d9ae1..e17a1f34a3 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -493,8 +493,16 @@ namespace MWRender std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) - smodel = Misc::ResourceHelpers::correctActorModelPath( - Misc::ResourceHelpers::correctMeshPath(mNpc->mModel), mResourceSystem->getVFS()); + { + // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it + // will match the NPC's race) until the NPC's race is changed. If the player record contains a default + // non-beast race animation and the player selects a beast race in chargen, animations aren't applied + // properly. Morrowind.exe appears to handle an NPC using any of the base animations as not having custom + // animations. + std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); + if (!isDefaultActorSkeleton(model)) + smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); + } setObjectRoot(smodel, true, true, false); @@ -511,7 +519,7 @@ namespace MWRender addAnimSource(smodel, smodel); - if (!isWerewolf && mNpc->mRace.contains("argonian")) + if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index d5d2c03b8b..7ecaaa217d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -17,11 +17,8 @@ #include #include #include -#include -#include #include "../mwmechanics/spelllist.hpp" -#include "../mwrender/actorutil.hpp" namespace { @@ -136,32 +133,11 @@ namespace } const ESM::Race* race = races.search(npc.mRace); - if (race) - { - // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it - // will match the NPC's race) until the NPC's race is changed. If the player record contains a default - // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default - // values to be used. - if (!npc.mModel.empty() && npc.mId == "player") - { - const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); - std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); - if (VFS::Path::pathEqual(defaultModel, model)) - { - npc.mModel.clear(); - changed = true; - } - } - } - else + if (!race) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace << ", using " << defaultRace << " race as replacement."; npc.mRace = defaultRace; - // Remove animations that might be race specific - npc.mModel.clear(); changed = true; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 421e9e82f1..967511953d 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -7,7 +7,6 @@ file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp ../openmw/mwworld/timestamp.cpp - ../openmw/mwrender/actorutil.cpp mwworld/test_store.cpp mwworld/testduration.cpp From ef4e5b45e3ad4f77774f7f1817ad4fc4f8cef8ef Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 15:06:32 +0100 Subject: [PATCH 0707/2167] Don't add custom anim source if it's a default animation but keep the skeleton --- apps/openmw/mwrender/npcanimation.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e17a1f34a3..1559ebdd5d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -492,16 +492,12 @@ namespace MWRender getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; + bool isBase = !isWerewolf; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) { - // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it - // will match the NPC's race) until the NPC's race is changed. If the player record contains a default - // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe appears to handle an NPC using any of the base animations as not having custom - // animations. std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); - if (!isDefaultActorSkeleton(model)) - smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); + isBase = isDefaultActorSkeleton(model); + smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); } setObjectRoot(smodel, true, true, false); @@ -511,13 +507,14 @@ namespace MWRender if (!is1stPerson) { const std::string& base = Settings::models().mXbaseanim; - if (smodel != base && !isWerewolf) + if (!isWerewolf) addAnimSource(base, smodel); if (smodel != defaultSkeleton && base != defaultSkeleton) addAnimSource(defaultSkeleton, smodel); - addAnimSource(smodel, smodel); + if (!isBase) + addAnimSource(smodel, smodel); if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); @@ -525,10 +522,11 @@ namespace MWRender else { const std::string& base = Settings::models().mXbaseanim1st; - if (smodel != base && !isWerewolf) + if (!isWerewolf) addAnimSource(base, smodel); - addAnimSource(smodel, smodel); + if (!isBase) + addAnimSource(smodel, smodel); mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); From 93e50cc7aaa85df29071ab32bb2c8d14fb620489 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 1 Jan 2024 14:56:48 +0400 Subject: [PATCH 0708/2167] Improve format version in content selector --- apps/esmtool/esmtool.cpp | 4 ++-- components/contentselector/model/contentmodel.cpp | 12 ++++++------ components/contentselector/model/esmfile.cpp | 5 ++--- components/contentselector/model/esmfile.hpp | 8 ++++---- components/esm/esmcommon.hpp | 6 ------ components/esm3/esmreader.hpp | 4 ++-- components/esm3/esmwriter.cpp | 4 ++-- components/esm3/loadregn.cpp | 3 ++- components/esm3/loadtes3.cpp | 4 ++-- components/esm3/loadtes3.hpp | 7 ++----- 10 files changed, 24 insertions(+), 33 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a36996ff4f..13f222ed72 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -341,7 +341,7 @@ namespace { std::cout << "Author: " << esm.getAuthor() << '\n' << "Description: " << esm.getDesc() << '\n' - << "File format version: " << esm.getFVer() << '\n'; + << "File format version: " << esm.esmVersionF() << '\n'; std::vector masterData = esm.getGameFiles(); if (!masterData.empty()) { @@ -508,7 +508,7 @@ namespace ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); esm.setHeader(data.mHeader); - esm.setVersion(ESM::VER_13); + esm.setVersion(ESM::VER_130); esm.setRecordCount(recordCount); std::fstream save(info.outname, std::fstream::out | std::fstream::binary); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f8ecc67998..9b7bb11f09 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -489,7 +489,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf fileReader.setEncoder(&encoder); fileReader.open(std::move(stream), filepath); file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); - file->setFormat(fileReader.getFormatVersion()); + file->setFormat(QString::number(fileReader.esmVersionF())); file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); for (const auto& master : fileReader.getGameFiles()) file->addGameFile(QString::fromUtf8(master.name.c_str())); @@ -505,11 +505,11 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf case ESM::Format::Tes4: { ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); - ESM4::Reader reader(std::move(stream), filepath, nullptr, &encoder, true); - file->setAuthor(QString::fromUtf8(reader.getAuthor().c_str())); - file->setFormat(reader.esmVersion()); - file->setDescription(QString::fromUtf8(reader.getDesc().c_str())); - for (const auto& master : reader.getGameFiles()) + ESM4::Reader fileReader(std::move(stream), filepath, nullptr, &encoder, true); + file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); + file->setFormat(QString::number(fileReader.esmVersionF())); + file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); + for (const auto& master : fileReader.getGameFiles()) file->addGameFile(QString::fromUtf8(master.name.c_str())); break; } diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 75a0adb45e..7c62299048 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -26,7 +26,7 @@ void ContentSelectorModel::EsmFile::setDate(const QDateTime& modified) mModified = modified; } -void ContentSelectorModel::EsmFile::setFormat(int format) +void ContentSelectorModel::EsmFile::setFormat(const QString& format) { mVersion = format; } @@ -51,8 +51,7 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - stream << mFileName << mAuthor << QString::number(mVersion) << mModified.toString() << mPath << mDescription - << mGameFiles; + stream << mFileName << mAuthor << mVersion << mModified.toString() << mPath << mDescription << mGameFiles; return encodedData; } diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 5a04ec8b38..a65c778294 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -40,7 +40,7 @@ namespace ContentSelectorModel void setAuthor(const QString& author); void setSize(const int size); void setDate(const QDateTime& modified); - void setFormat(const int format); + void setFormat(const QString& format); void setFilePath(const QString& path); void setGameFiles(const QStringList& gameFiles); void setDescription(const QString& description); @@ -51,7 +51,7 @@ namespace ContentSelectorModel QString fileName() const { return mFileName; } QString author() const { return mAuthor; } QDateTime modified() const { return mModified; } - ESM::FormatVersion formatVersion() const { return mVersion; } + QString formatVersion() const { return mVersion; } QString filePath() const { return mPath; } /// @note Contains file names, not paths. @@ -76,7 +76,7 @@ namespace ContentSelectorModel private: QString mTooltipTemlate = tr( "Author: %1
" - "Version: %2
" + "Format version: %2
" "Modified: %3
" "Path:
%4
" "
Description:
%5
" @@ -85,7 +85,7 @@ namespace ContentSelectorModel QString mFileName; QString mAuthor; QDateTime mModified; - ESM::FormatVersion mVersion = ESM::DefaultFormatVersion; + QString mVersion = QString::number(ESM::DefaultFormatVersion); QString mPath; QStringList mGameFiles; QString mDescription; diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index e92ae06806..69b877c9c9 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -13,12 +13,6 @@ namespace ESM { - enum Version - { - VER_12 = 0x3f99999a, - VER_13 = 0x3fa66666 - }; - enum RecordFlag { // This flag exists, but is not used to determine if a record has been deleted while loading diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index bafc89a74c..461f154001 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -53,9 +53,9 @@ namespace ESM * *************************************************************************/ - int getVer() const { return mHeader.mData.version; } + int getVer() const { return mHeader.mData.version.ui; } int getRecordCount() const { return mHeader.mData.records; } - float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } + float esmVersionF() const { return (mHeader.mData.version.f); } const std::string& getAuthor() const { return mHeader.mData.author; } const std::string& getDesc() const { return mHeader.mData.desc; } const std::vector& getGameFiles() const { return mHeader.mMaster; } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index 66788aa924..ad64ced0a4 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -84,12 +84,12 @@ namespace ESM unsigned int ESMWriter::getVersion() const { - return mHeader.mData.version; + return mHeader.mData.version.ui; } void ESMWriter::setVersion(unsigned int ver) { - mHeader.mData.version = ver; + mHeader.mData.version.ui = ver; } void ESMWriter::setType(int type) diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 5148a446c2..63e785882e 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include namespace ESM { @@ -83,7 +84,7 @@ namespace ESM esm.writeHNOCString("FNAM", mName); - if (esm.getVersion() == VER_12) + if (esm.getVersion() == VER_120) esm.writeHNT("WEAT", mData.mProbabilities, mData.mProbabilities.size() - 2); else esm.writeHNT("WEAT", mData.mProbabilities); diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index b6fbe76553..eeb2d38761 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -9,7 +9,7 @@ namespace ESM void Header::blank() { - mData.version = VER_13; + mData.version.ui = VER_130; mData.type = 0; mData.author.clear(); mData.desc.clear(); @@ -26,7 +26,7 @@ namespace ESM if (esm.isNextSub("HEDR")) { esm.getSubHeader(); - esm.getT(mData.version); + esm.getT(mData.version.ui); esm.getT(mData.type); mData.author = esm.getMaybeFixedStringSize(32); mData.desc = esm.getMaybeFixedStringSize(256); diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 2f7493e15f..54fd303485 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -3,6 +3,7 @@ #include +#include "components/esm/common.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm3/formatversion.hpp" @@ -13,11 +14,7 @@ namespace ESM struct Data { - /* File format version. This is actually a float, the supported - versions are 1.2 and 1.3. These correspond to: - 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 - */ - uint32_t version; + ESM::ESMVersion version; int32_t type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description From 04b714198a56256b266b21cd250031767c2b7198 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 20:59:33 +0300 Subject: [PATCH 0709/2167] Manually clamp controller time (#7523) --- components/nifosg/controller.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 54e9e2bb16..f480152a73 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -57,7 +57,13 @@ namespace NifOsg } case Nif::NiTimeController::ExtrapolationMode::Constant: default: - return std::clamp(time, mStartTime, mStopTime); + { + if (time < mStartTime) + return mStartTime; + if (time > mStopTime) + return mStopTime; + return time; + } } } From 2fbdde34c619af2b9b1c4f88fdfb6f466d9d35c4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 21:55:55 +0300 Subject: [PATCH 0710/2167] Set paged refs' base node to null (#6335) --- apps/openmw/mwphysics/actor.cpp | 6 +++++- apps/openmw/mwphysics/object.cpp | 3 +++ apps/openmw/mwworld/scene.cpp | 4 +--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index dec055d68f..e1efe6d242 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -102,7 +102,11 @@ namespace MWPhysics updateScaleUnsafe(); if (!mRotationallyInvariant) - mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + { + const SceneUtil::PositionAttitudeTransform* baseNode = mPtr.getRefData().getBaseNode(); + if (baseNode) + mRotation = baseNode->getAttitude(); + } addCollisionMask(getCollisionMask()); updateCollisionObjectPositionUnsafe(); diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 53529ec729..9c97ac7c32 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -110,6 +110,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (!mPtr.getRefData().getBaseNode()) + return false; + assert(mShapeInstance->mCollisionShape->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d8875b513e..72b2dc3022 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,9 +115,7 @@ namespace if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else - ptr.getRefData().setBaseNode( - new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend - // on basenode + ptr.getRefData().setBaseNode(nullptr); setNodeRotation(ptr, rendering, rotation); if (ptr.getClass().useAnim()) From 0705175b9b71ca067e7cdf36e52c8d58ee47af3e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 31 Dec 2023 19:55:32 +0400 Subject: [PATCH 0711/2167] Remove qmake leftover --- CI/before_script.msvc.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index cdac794a7e..e11ceb499d 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -902,7 +902,6 @@ printf "Qt ${QT_VER}... " fi cd $QT_SDK - add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then DLLSUFFIX="d" From 5f2cbf810dda4962f0297d0bb60127a4dfe8fb9f Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:38:55 +0100 Subject: [PATCH 0712/2167] Check if controls are on for Use action --- files/data/scripts/omw/playercontrols.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index f14724f0a2..71a6094aaa 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -195,7 +195,7 @@ end)) local startUse = false input.registerActionHandler('Use', async:callback(function(value) - if value then startUse = true end + if value and controlsAllowed() then startUse = true end end)) local function processAttacking() if Actor.stance(self) == Actor.STANCE.Spell then From 1d8ee7984f1fef4e8cd2a4b377271bb1a50f7859 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:45:45 +0100 Subject: [PATCH 0713/2167] Make input.triggers[] consistent with input.actions[] --- components/lua/inputactions.cpp | 8 ++++++++ components/lua/inputactions.hpp | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp index c21fbcf112..69c91921eb 100644 --- a/components/lua/inputactions.cpp +++ b/components/lua/inputactions.cpp @@ -265,6 +265,14 @@ namespace LuaUtil mHandlers.push_back({}); } + std::optional Registry::operator[](std::string_view key) + { + auto iter = mIds.find(key); + if (iter == mIds.end()) + return std::nullopt; + return mInfo[iter->second]; + } + void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback) { Id id = safeIdByKey(key); diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp index ac3907b55d..abc6cf73fa 100644 --- a/components/lua/inputactions.hpp +++ b/components/lua/inputactions.hpp @@ -126,11 +126,7 @@ namespace LuaUtil::InputTrigger return std::nullopt; return it->first; } - std::optional operator[](std::string_view key) - { - Id id = safeIdByKey(key); - return mInfo[id]; - } + std::optional operator[](std::string_view key); void insert(Info info); void registerHandler(std::string_view key, const LuaUtil::Callback& callback); void activate(std::string_view key); From c1e8e88914572dd0e9b5258ed2cd339157f8fdd5 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:47:04 +0100 Subject: [PATCH 0714/2167] Fix input trigger docs --- files/lua_api/openmw/input.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 563a4ab1f5..0a85602bcc 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -402,10 +402,10 @@ --- -- @type TriggerInfo --- @field [parent=#Actioninfo] #string key --- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys --- @field [parent=#Actioninfo] #string name Localization key of the trigger's name --- @field [parent=#Actioninfo] #string description Localization key of the trigger's description +-- @field [parent=#TriggerInfo] #string key +-- @field [parent=#TriggerInfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#TriggerInfo] #string name Localization key of the trigger's name +-- @field [parent=#TriggerInfo] #string description Localization key of the trigger's description --- -- Map of all currently registered triggers From e9b48e35c0b36c0a9ba50bd0b844aee37deeb2f9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:54:02 +0100 Subject: [PATCH 0715/2167] Coverity defects --- apps/openmw/mwlua/inputbindings.cpp | 8 ++++---- components/lua/inputactions.cpp | 4 ++-- components/lua/inputactions.hpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 8763dce28d..33b19f3b4d 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -76,7 +76,7 @@ namespace MWLua inputActions[sol::meta_function::pairs] = pairs; } - auto actionInfo = context.mLua->sol().new_usertype("ActionInfo", "key", + context.mLua->sol().new_usertype("ActionInfo", "key", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", @@ -102,7 +102,7 @@ namespace MWLua inputTriggers[sol::meta_function::pairs] = pairs; } - auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo", "key", + context.mLua->sol().new_usertype("TriggerInfo", "key", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", @@ -127,7 +127,7 @@ namespace MWLua parsedOptions.mName = options["name"].get(); parsedOptions.mDescription = options["description"].get(); parsedOptions.mDefaultValue = options["defaultValue"].get(); - manager->inputActions().insert(parsedOptions); + manager->inputActions().insert(std::move(parsedOptions)); }; api["bindAction"] = [manager = context.mLuaManager]( std::string_view key, const sol::table& callback, sol::table dependencies) { @@ -164,7 +164,7 @@ namespace MWLua parsedOptions.mL10n = options["l10n"].get(); parsedOptions.mName = options["name"].get(); parsedOptions.mDescription = options["description"].get(); - manager->inputTriggers().insert(parsedOptions); + manager->inputTriggers().insert(std::move(parsedOptions)); }; api["registerTriggerHandler"] = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp index 69c91921eb..7c7551ba60 100644 --- a/components/lua/inputactions.cpp +++ b/components/lua/inputactions.cpp @@ -113,7 +113,7 @@ namespace LuaUtil } } - void Registry::insert(Info info) + void Registry::insert(const Info& info) { if (mIds.find(info.mKey) != mIds.end()) throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey)); @@ -251,7 +251,7 @@ namespace LuaUtil return it->second; } - void Registry::insert(Info info) + void Registry::insert(const Info& info) { if (mIds.find(info.mKey) != mIds.end()) throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey)); diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp index abc6cf73fa..d05bb71f2c 100644 --- a/components/lua/inputactions.hpp +++ b/components/lua/inputactions.hpp @@ -60,7 +60,7 @@ namespace LuaUtil::InputAction { public: using ConstIterator = std::vector::const_iterator; - void insert(Info info); + void insert(const Info& info); size_t size() const { return mKeys.size(); } std::optional firstKey() const { return mKeys.empty() ? std::nullopt : std::optional(mKeys[0]); } std::optional nextKey(std::string_view key) const; @@ -127,7 +127,7 @@ namespace LuaUtil::InputTrigger return it->first; } std::optional operator[](std::string_view key); - void insert(Info info); + void insert(const Info& info); void registerHandler(std::string_view key, const LuaUtil::Callback& callback); void activate(std::string_view key); void clear() From 9d1a3f243d0c3a514195467d323e0812ebf571f0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 14:31:13 +0100 Subject: [PATCH 0716/2167] Use the same controls check for startUse and processAttacking --- files/data/scripts/omw/playercontrols.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 71a6094aaa..289d3144dc 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -195,7 +195,7 @@ end)) local startUse = false input.registerActionHandler('Use', async:callback(function(value) - if value and controlsAllowed() then startUse = true end + if value and combatAllowed() then startUse = true end end)) local function processAttacking() if Actor.stance(self) == Actor.STANCE.Spell then From e734191b56db15677f1cb9413487a5345e3f6db6 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 14:40:03 +0100 Subject: [PATCH 0717/2167] Add clarifying comment --- files/data/scripts/omw/playercontrols.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 289d3144dc..ec7d0d238e 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -198,6 +198,8 @@ input.registerActionHandler('Use', async:callback(function(value) if value and combatAllowed() then startUse = true end end)) local function processAttacking() + -- for spell-casting, set controls.use to true for exactly one frame + -- otherwise spell casting is attempted every frame while Use is true if Actor.stance(self) == Actor.STANCE.Spell then self.controls.use = startUse and 1 or 0 else From fb16871c805a0a0ce7e34fae8d8d0f79843da72a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 2 Jan 2024 18:30:35 +0400 Subject: [PATCH 0718/2167] Avoid redundant string copies --- apps/openmw/mwclass/activator.cpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index a0ee260260..01437b2abd 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -110,7 +110,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 10687171a0..1ff7ef5bd6 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -105,7 +105,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 4006f21ce7..28bb1ff35c 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -265,7 +265,7 @@ namespace MWClass if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 55de7a64ab..d731f56394 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -127,7 +127,7 @@ namespace MWClass info.enchant = ref->mBase->mEnchant; - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 0614c92eb9..32a0b62729 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -172,7 +172,7 @@ namespace MWClass if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a985fe6d71..28779f971f 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -271,7 +271,7 @@ namespace MWClass text += "\nYou can not use evidence chests"; } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 810a7840d4..bb9c1bc277 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -599,7 +599,7 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 695bea5f10..99acfcf4df 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -293,7 +293,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 3e07a24610..5225170be7 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -145,7 +145,7 @@ namespace MWClass } info.effects = list; - info.text = text; + info.text = std::move(text); info.isIngredient = true; return info; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 92ba8e1512..6e34e3c2bd 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -177,7 +177,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 6c46f2e66f..d3c3d479e4 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -122,7 +122,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index fa91b607f2..0f26dfd2df 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -167,7 +167,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 5811ec10db..7cf0c54f5c 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -118,7 +118,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 7a6c00824d..96c94339bb 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -121,7 +121,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 0d38271aab..cf4a42be70 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -123,7 +123,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 7e4c47993c..2c9a9b5c7a 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -243,7 +243,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } From 14942d7541732e5a77d2b834389b482ea99ccbde Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Jan 2024 18:50:32 +0300 Subject: [PATCH 0719/2167] Shorten global map marker notes like vanilla (bug #7619) --- CHANGELOG.md | 1 + apps/openmw/mwgui/tooltips.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f16ac81b..14f2d3d5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place + Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdc19a260d..9ee7d08f31 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -481,10 +481,13 @@ namespace MWGui MyGUI::IntCoord(padding.left + 8 + 4, totalSize.height + padding.top, 300 - padding.left - 8 - 4, 300 - totalSize.height), MyGUI::Align::Default); - edit->setEditMultiLine(true); - edit->setEditWordWrap(true); - edit->setCaption(note); - edit->setSize(edit->getWidth(), edit->getTextSize().height); + constexpr size_t maxLength = 60; + std::string shortenedNote = note.substr(0, std::min(maxLength, note.find('\n'))); + if (shortenedNote.size() < note.size()) + shortenedNote += " ..."; + edit->setCaption(shortenedNote); + MyGUI::IntSize noteTextSize = edit->getTextSize(); + edit->setSize(std::max(edit->getWidth(), noteTextSize.width), noteTextSize.height); icon->setPosition(icon->getLeft(), (edit->getTop() + edit->getBottom()) / 2 - icon->getHeight() / 2); totalSize.height += std::max(edit->getHeight(), icon->getHeight()); totalSize.width = std::max(totalSize.width, edit->getWidth() + 8 + 4); From 73104189843cbfccff5f71bf49b8ed03294baf53 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Jan 2024 21:53:00 +0300 Subject: [PATCH 0720/2167] Downgrade Settings GUI mode to a modal (bug #6758) --- CHANGELOG.md | 1 + apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/mainmenu.cpp | 5 ++++- apps/openmw/mwgui/mode.hpp | 1 - apps/openmw/mwgui/settingswindow.cpp | 6 ++++-- apps/openmw/mwgui/settingswindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 5 ++++- apps/openmw/mwgui/windowmanagerimp.hpp | 1 + apps/openmw/mwinput/mousemanager.cpp | 4 +++- apps/openmw/mwlua/uibindings.cpp | 1 - 10 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff35bfa53..1c5c021a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW + Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 0c60fe9778..4711994b55 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -77,6 +77,7 @@ namespace MWGui class JailScreen; class MessageBox; class PostProcessorHud; + class SettingsWindow; enum ShowInDialogueMode { @@ -156,6 +157,7 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; + virtual MWGui::SettingsWindow* getSettingsWindow() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 37e835f1a4..d0c55f432e 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -18,6 +18,7 @@ #include "backgroundimage.hpp" #include "confirmationdialog.hpp" #include "savegamedialog.hpp" +#include "settingswindow.hpp" #include "videowidget.hpp" namespace MWGui @@ -97,7 +98,9 @@ namespace MWGui winMgr->removeGuiMode(GM_MainMenu); } else if (name == "options") - winMgr->pushGuiMode(GM_Settings); + { + winMgr->getSettingsWindow()->setVisible(true); + } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); else if (name == "exitgame") diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 63f81e8b47..44f9743743 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -6,7 +6,6 @@ namespace MWGui enum GuiMode { GM_None, - GM_Settings, // Settings window GM_Inventory, // Inventory mode GM_Container, GM_Companion, diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1060b3a20f..fbd54586df 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -240,7 +240,7 @@ namespace MWGui } SettingsWindow::SettingsWindow() - : WindowBase("openmw_settings_window.layout") + : WindowModal("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { @@ -450,7 +450,7 @@ namespace MWGui void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); + setVisible(false); } void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) @@ -1041,6 +1041,8 @@ namespace MWGui void SettingsWindow::onOpen() { + WindowModal::onOpen(); + highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1f96f7de54..47951ef121 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -7,7 +7,7 @@ namespace MWGui { - class SettingsWindow : public WindowBase + class SettingsWindow : public WindowModal { public: SettingsWindow(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3bd779a186..63d974ff35 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -408,7 +408,6 @@ namespace MWGui mSettingsWindow = settingsWindow.get(); mWindows.push_back(std::move(settingsWindow)); trackWindow(mSettingsWindow, makeSettingsWindowSettingValues()); - mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); auto confirmationDialog = std::make_unique(); mConfirmationDialog = confirmationDialog.get(); @@ -1475,6 +1474,10 @@ namespace MWGui { return mPostProcessorHud; } + MWGui::SettingsWindow* WindowManager::getSettingsWindow() + { + return mSettingsWindow; + } void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) { diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 7ee0554a26..b378a76ff8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -182,6 +182,7 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; MWGui::PostProcessorHud* getPostProcessorHud() override; + MWGui::SettingsWindow* getSettingsWindow() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 363a78fadd..c91dfaedac 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -13,6 +13,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwgui/settingswindow.hpp" + #include "../mwworld/player.hpp" #include "actions.hpp" @@ -156,7 +158,7 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings + if (!MWBase::Environment::get().getWindowManager()->getSettingsWindow()->isVisible() && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 98a653949d..843917275c 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -47,7 +47,6 @@ namespace MWLua } const std::unordered_map modeToName{ - { MWGui::GM_Settings, "SettingsMenu" }, { MWGui::GM_Inventory, "Interface" }, { MWGui::GM_Container, "Container" }, { MWGui::GM_Companion, "Companion" }, From 8d3efd27ba094a651b4d61221f151b88e839d9f9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 3 Jan 2024 09:10:03 +0400 Subject: [PATCH 0721/2167] Add missing initialization --- apps/openmw/mwrender/pingpongcanvas.hpp | 2 +- components/nifosg/controller.cpp | 2 +- components/nifosg/controller.hpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index f7212a3f18..5a37b7fbc9 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -61,7 +61,7 @@ namespace MWRender bool mPostprocessing = false; fx::DispatchArray mPasses; - fx::FlagsType mMask; + fx::FlagsType mMask = 0; osg::ref_ptr mFallbackProgram; osg::ref_ptr mMultiviewResolveProgram; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 54e9e2bb16..7a93865aca 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -442,7 +442,7 @@ namespace NifOsg } } - MaterialColorController::MaterialColorController() {} + MaterialColorController::MaterialColorController() = default; MaterialColorController::MaterialColorController( const Nif::NiMaterialColorController* ctrl, const osg::Material* baseMaterial) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index df6fdb2a24..51cf1cd428 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -347,7 +347,9 @@ namespace NifOsg private: Vec3Interpolator mData; - Nif::NiMaterialColorController::TargetColor mTargetColor; + Nif::NiMaterialColorController::TargetColor mTargetColor{ + Nif::NiMaterialColorController::TargetColor::Ambient + }; osg::ref_ptr mBaseMaterial; }; From 7ffb2bc3c40c3a2798cc6b649d9d81e666d039d4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 3 Jan 2024 20:34:44 +0400 Subject: [PATCH 0722/2167] Use error messages instead of unhandled exceptions --- apps/launcher/maindialog.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 177d4fe88c..5d558ef38f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -118,13 +118,14 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() const auto& userConfigDir = mCfgMgr.getUserConfigPath(); if (!exists(userConfigDir)) { - if (!create_directories(userConfigDir)) + std::error_code ec; + if (!create_directories(userConfigDir, ec)) { - cfgError(tr("Error opening OpenMW configuration file"), + cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()), tr("
Could not create directory %0

" - "Please make sure you have the right permissions " - "and try again.
") - .arg(Files::pathToQString(canonical(userConfigDir)))); + "%1
") + .arg(Files::pathToQString(userConfigDir)) + .arg(QString(ec.message().c_str()))); return FirstRunDialogResultFailure; } } @@ -457,13 +458,14 @@ bool Launcher::MainDialog::writeSettings() if (!exists(userPath)) { - if (!create_directories(userPath)) + std::error_code ec; + if (!create_directories(userPath, ec)) { - cfgError(tr("Error creating OpenMW configuration directory"), - tr("
Could not create %0

" - "Please make sure you have the right permissions " - "and try again.
") - .arg(Files::pathToQString(userPath))); + cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()), + tr("
Could not create directory %0

" + "%1
") + .arg(Files::pathToQString(userPath)) + .arg(QString(ec.message().c_str()))); return false; } } From 72fa4924dc9afa6974ae36808a482bad3d85a624 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 3 Jan 2024 22:55:00 +0100 Subject: [PATCH 0723/2167] Use settings values to declare enum settings --- apps/opencs/model/prefs/enumsetting.cpp | 78 ++++---------- apps/opencs/model/prefs/enumsetting.hpp | 36 ++----- apps/opencs/model/prefs/enumvalueview.hpp | 15 +++ apps/opencs/model/prefs/state.cpp | 120 +++++----------------- apps/opencs/model/prefs/state.hpp | 3 +- apps/opencs/model/prefs/values.hpp | 98 ++++++++---------- 6 files changed, 112 insertions(+), 238 deletions(-) create mode 100644 apps/opencs/model/prefs/enumvalueview.hpp diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 938da874e1..aaa4c28c61 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -15,38 +15,10 @@ #include "category.hpp" #include "state.hpp" -CSMPrefs::EnumValue::EnumValue(const std::string& value, const std::string& tooltip) - : mValue(value) - , mTooltip(tooltip) -{ -} - -CSMPrefs::EnumValue::EnumValue(const char* value) - : mValue(value) -{ -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValues& values) -{ - mValues.insert(mValues.end(), values.mValues.begin(), values.mValues.end()); - return *this; -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValue& value) -{ - mValues.push_back(value); - return *this; -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const std::string& tooltip) -{ - mValues.emplace_back(value, tooltip); - return *this; -} - -CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) +CSMPrefs::EnumSetting::EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label, + std::span values, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) + , mValues(values) , mWidget(nullptr) { } @@ -57,45 +29,28 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip(const std::string& tool return *this; } -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues(const EnumValues& values) -{ - mValues.add(values); - return *this; -} - -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const EnumValue& value) -{ - mValues.add(value); - return *this; -} - -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, const std::string& tooltip) -{ - mValues.add(value, tooltip); - return *this; -} - CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); mWidget = new QComboBox(parent); - size_t index = 0; - const std::string value = getValue(); - - for (size_t i = 0; i < mValues.mValues.size(); ++i) + for (std::size_t i = 0; i < mValues.size(); ++i) { - if (value == mValues.mValues[i].mValue) - index = i; + const EnumValueView& v = mValues[i]; - mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str())); + mWidget->addItem(QString::fromUtf8(v.mValue.data(), static_cast(v.mValue.size()))); - if (!mValues.mValues[i].mTooltip.empty()) - mWidget->setItemData( - static_cast(i), QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); + if (!v.mTooltip.empty()) + mWidget->setItemData(static_cast(i), + QString::fromUtf8(v.mTooltip.data(), static_cast(v.mTooltip.size())), Qt::ToolTipRole); } + const std::string value = getValue(); + const std::size_t index = std::find_if(mValues.begin(), mValues.end(), [&](const EnumValueView& v) { + return v.mValue == value; + }) - mValues.begin(); + mWidget->setCurrentIndex(static_cast(index)); if (!mTooltip.empty()) @@ -117,6 +72,9 @@ void CSMPrefs::EnumSetting::updateWidget() void CSMPrefs::EnumSetting::valueChanged(int value) { - setValue(mValues.mValues.at(value).mValue); + if (value < 0 || static_cast(value) >= mValues.size()) + throw std::logic_error("Invalid enum setting \"" + getKey() + "\" value index: " + std::to_string(value)); + + setValue(std::string(mValues[value].mValue)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 51241d593f..00953f914e 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -1,10 +1,13 @@ #ifndef CSM_PREFS_ENUMSETTING_H #define CSM_PREFS_ENUMSETTING_H +#include #include +#include #include #include +#include "enumvalueview.hpp" #include "setting.hpp" class QComboBox; @@ -13,47 +16,20 @@ namespace CSMPrefs { class Category; - struct EnumValue - { - std::string mValue; - std::string mTooltip; - - EnumValue(const std::string& value, const std::string& tooltip = ""); - - EnumValue(const char* value); - }; - - struct EnumValues - { - std::vector mValues; - - EnumValues& add(const EnumValues& values); - - EnumValues& add(const EnumValue& value); - - EnumValues& add(const std::string& value, const std::string& tooltip); - }; - class EnumSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - EnumValues mValues; + std::span mValues; QComboBox* mWidget; public: - explicit EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + explicit EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label, + std::span values, Settings::Index& index); EnumSetting& setTooltip(const std::string& tooltip); - EnumSetting& addValues(const EnumValues& values); - - EnumSetting& addValue(const EnumValue& value); - - EnumSetting& addValue(const std::string& value, const std::string& tooltip); - /// Return label, input widget. SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/enumvalueview.hpp b/apps/opencs/model/prefs/enumvalueview.hpp new file mode 100644 index 0000000000..f46d250a81 --- /dev/null +++ b/apps/opencs/model/prefs/enumvalueview.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H + +#include + +namespace CSMPrefs +{ + struct EnumValueView + { + std::string_view mValue; + std::string_view mTooltip; + }; +} + +#endif diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c78a70b63e..b05a4b7473 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -59,13 +59,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mWindows.mMinimumWidth, "Minimum subview width") .setTooltip("Minimum width of subviews.") .setRange(50, 10000); - EnumValue scrollbarOnly("Scrollbar Only", - "Simple addition of scrollbars, the view window " - "does not grow automatically."); - declareEnum("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly) - .addValue(scrollbarOnly) - .addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.") - .addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); + declareEnum(mValues->mWindows.mMainwindowScrollbar, "Horizontal scrollbar mode for main window."); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen") .setTooltip( @@ -75,34 +69,15 @@ void CSMPrefs::State::declare() #endif declareCategory("Records"); - EnumValue iconAndText("Icon and Text"); - EnumValues recordValues; - recordValues.add(iconAndText).add("Icon Only").add("Text Only"); - declareEnum("status-format", "Modification status display format", iconAndText).addValues(recordValues); - declareEnum("type-format", "ID type display format", iconAndText).addValues(recordValues); + declareEnum(mValues->mRecords.mStatusFormat, "Modification status display format"); + declareEnum(mValues->mRecords.mTypeFormat, "ID type display format"); declareCategory("ID Tables"); - EnumValue inPlaceEdit("Edit in Place", "Edit the clicked cell"); - EnumValue editRecord("Edit Record", "Open a dialogue subview for the clicked record"); - EnumValue view("View", "Open a scene subview for the clicked record (not available everywhere)"); - EnumValue editRecordAndClose("Edit Record and Close"); - EnumValues doubleClickValues; - doubleClickValues.add(inPlaceEdit) - .add(editRecord) - .add(view) - .add("Revert") - .add("Delete") - .add(editRecordAndClose) - .add("View and Close", "Open a scene subview for the clicked record and close the table subview"); - declareEnum("double", "Double Click", inPlaceEdit).addValues(doubleClickValues); - declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues); - declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues); - declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues); - EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection"); - declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect) - .addValue(jumpAndSelect) - .addValue("Jump Only", "Scroll new record into view") - .addValue("No Jump", "No special action"); + declareEnum(mValues->mIdTables.mDouble, "Double Click"); + declareEnum(mValues->mIdTables.mDoubleS, "Shift Double Click"); + declareEnum(mValues->mIdTables.mDoubleC, "Control Double Click"); + declareEnum(mValues->mIdTables.mDoubleSc, "Shift Control Double Click"); + declareEnum(mValues->mIdTables.mJumpToAdded, "Action on adding or cloning a record"); declareBool( mValues->mIdTables.mExtendedConfig, "Manually specify affected record types for an extended delete/revert") .setTooltip( @@ -120,18 +95,10 @@ void CSMPrefs::State::declare() declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); declareCategory("Reports"); - EnumValue actionNone("None"); - EnumValue actionEdit("Edit", "Open a table or dialogue suitable for addressing the listed report"); - EnumValue actionRemove("Remove", "Remove the report from the report table"); - EnumValue actionEditAndRemove("Edit And Remove", - "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report " - "table"); - EnumValues reportValues; - reportValues.add(actionNone).add(actionEdit).add(actionRemove).add(actionEditAndRemove); - declareEnum("double", "Double Click", actionEdit).addValues(reportValues); - declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues); - declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues); - declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues); + declareEnum(mValues->mReports.mDouble, "Double Click"); + declareEnum(mValues->mReports.mDoubleS, "Shift Double Click"); + declareEnum(mValues->mReports.mDoubleC, "Control Double Click"); + declareEnum(mValues->mReports.mDoubleSc, "Shift Control Double Click"); declareBool(mValues->mReports.mIgnoreBaseRecords, "Ignore base records in verifier"); declareCategory("Search & Replace"); @@ -152,11 +119,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mScripts.mTabWidth, "Tab Width") .setTooltip("Number of characters for tab width") .setRange(1, 10); - EnumValue warningsNormal("Normal", "Report warnings as warning"); - declareEnum("warnings", "Warning Mode", warningsNormal) - .addValue("Ignore", "Do not report warning") - .addValue(warningsNormal) - .addValue("Strict", "Promote warning to an error"); + declareEnum(mValues->mScripts.mWarnings, "Warning Mode"); declareBool(mValues->mScripts.mToolbar, "Show toolbar"); declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") @@ -244,32 +207,6 @@ void CSMPrefs::State::declare() declareBool(mValues->mTooltips.mSceneHideBasic, "Hide basic 3D scenes tooltips"); declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); - EnumValue createAndInsert("Create cell and insert"); - EnumValue showAndInsert("Show cell and insert"); - EnumValue dontInsert("Discard"); - EnumValue insertAnyway("Insert anyway"); - EnumValues insertOutsideCell; - insertOutsideCell.add(createAndInsert).add(dontInsert).add(insertAnyway); - EnumValues insertOutsideVisibleCell; - insertOutsideVisibleCell.add(showAndInsert).add(dontInsert).add(insertAnyway); - - EnumValue createAndLandEdit("Create cell and land, then edit"); - EnumValue showAndLandEdit("Show cell and edit"); - EnumValue dontLandEdit("Discard"); - EnumValues landeditOutsideCell; - landeditOutsideCell.add(createAndLandEdit).add(dontLandEdit); - EnumValues landeditOutsideVisibleCell; - landeditOutsideVisibleCell.add(showAndLandEdit).add(dontLandEdit); - - EnumValue SelectOnly("Select only"); - EnumValue SelectAdd("Add to selection"); - EnumValue SelectRemove("Remove from selection"); - EnumValue selectInvert("Invert selection"); - EnumValues primarySelectAction; - primarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); - EnumValues secondarySelectAction; - secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); - declareCategory("3D Scene Editing"); declareDouble(mValues->mSceneEditing.mGridsnapMovement, "Grid snap size"); declareDouble(mValues->mSceneEditing.mGridsnapRotation, "Angle snap size"); @@ -278,15 +215,13 @@ void CSMPrefs::State::declare() .setTooltip( "If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); - declareEnum("outside-drop", "Handling drops outside of cells", createAndInsert).addValues(insertOutsideCell); - declareEnum("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert) - .addValues(insertOutsideVisibleCell); - declareEnum("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit) - .setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.") - .addValues(landeditOutsideCell); - declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit) - .setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.") - .addValues(landeditOutsideVisibleCell); + declareEnum(mValues->mSceneEditing.mOutsideDrop, "Handling drops outside of cells"); + declareEnum(mValues->mSceneEditing.mOutsideVisibleDrop, "Handling drops outside of visible cells"); + declareEnum(mValues->mSceneEditing.mOutsideLandedit, "Handling terrain edit outside of cells") + .setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."); + declareEnum(mValues->mSceneEditing.mOutsideVisibleLandedit, "Handling terrain edit outside of visible cells") + .setTooltip( + "Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."); declareInt(mValues->mSceneEditing.mTexturebrushMaximumsize, "Maximum texture brush size").setMin(1); declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") @@ -303,16 +238,14 @@ void CSMPrefs::State::declare() .setTooltip( "When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); - declareEnum("primary-select-action", "Action for primary select", SelectOnly) + declareEnum(mValues->mSceneEditing.mPrimarySelectAction, "Action for primary select") .setTooltip( "Selection can be chosen between select only, add to selection, remove from selection and invert " - "selection.") - .addValues(primarySelectAction); - declareEnum("secondary-select-action", "Action for secondary select", SelectAdd) + "selection."); + declareEnum(mValues->mSceneEditing.mSecondarySelectAction, "Action for secondary select") .setTooltip( "Selection can be chosen between select only, add to selection, remove from selection and invert " - "selection.") - .addValues(secondarySelectAction); + "selection."); declareCategory("Key Bindings"); @@ -531,12 +464,13 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(Settings::SettingValue return *setting; } -CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, const QString& label, EnumValue default_) +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(EnumSettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting( + &mCurrentCategory->second, &mMutex, value.getValue().mName, label, value.getEnumValues(), *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index eaf829de7e..f3e580ea5a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -32,6 +32,7 @@ namespace CSMPrefs class ModifierSetting; class Setting; class StringSetting; + class EnumSettingValue; struct Values; /// \brief User settings state @@ -69,7 +70,7 @@ namespace CSMPrefs BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); - EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); + EnumSetting& declareEnum(EnumSettingValue& value, const QString& label); ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 80d2f8f182..474d0e1fb9 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H #define OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H +#include "enumvalueview.hpp" + #include #include @@ -15,12 +17,6 @@ namespace CSMPrefs { - struct EnumValueView - { - std::string_view mValue; - std::string_view mTooltip; - }; - class EnumSanitizer final : public Settings::Sanitizer { public: @@ -51,6 +47,26 @@ namespace CSMPrefs return std::make_unique(values); } + class EnumSettingValue + { + public: + explicit EnumSettingValue(Settings::Index& index, std::string_view category, std::string_view name, + std::span values, std::size_t defaultValueIndex) + : mValue( + index, category, name, std::string(values[defaultValueIndex].mValue), makeEnumSanitizerString(values)) + , mEnumValues(values) + { + } + + Settings::SettingValue& getValue() { return mValue; } + + std::span getEnumValues() const { return mEnumValues; } + + private: + Settings::SettingValue mValue; + std::span mEnumValues; + }; + struct WindowsCategory : Settings::WithIndex { using Settings::WithIndex::WithIndex; @@ -72,8 +88,7 @@ namespace CSMPrefs Settings::SettingValue mMaxSubviews{ mIndex, sName, "max-subviews", 256 }; Settings::SettingValue mHideSubview{ mIndex, sName, "hide-subview", false }; Settings::SettingValue mMinimumWidth{ mIndex, sName, "minimum-width", 325 }; - Settings::SettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", - std::string(sMainwindowScrollbarValues[0].mValue), makeEnumSanitizerString(sMainwindowScrollbarValues) }; + EnumSettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", sMainwindowScrollbarValues, 0 }; Settings::SettingValue mGrowLimit{ mIndex, sName, "grow-limit", false }; }; @@ -89,10 +104,8 @@ namespace CSMPrefs EnumValueView{ "Text Only", "" }, }; - Settings::SettingValue mStatusFormat{ mIndex, sName, "status-format", - std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; - Settings::SettingValue mTypeFormat{ mIndex, sName, "type-format", - std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + EnumSettingValue mStatusFormat{ mIndex, sName, "status-format", sRecordValues, 0 }; + EnumSettingValue mTypeFormat{ mIndex, sName, "type-format", sRecordValues, 0 }; }; struct IdTablesCategory : Settings::WithIndex @@ -118,16 +131,11 @@ namespace CSMPrefs EnumValueView{ "No Jump", "No special action" }, }; - Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sDoubleClickValues[0].mValue), - makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", - std::string(sDoubleClickValues[1].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", - std::string(sDoubleClickValues[2].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", - std::string(sDoubleClickValues[5].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", - std::string(sJumpAndSelectValues[0].mValue), makeEnumSanitizerString(sJumpAndSelectValues) }; + EnumSettingValue mDouble{ mIndex, sName, "double", sDoubleClickValues, 0 }; + EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sDoubleClickValues, 1 }; + EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sDoubleClickValues, 2 }; + EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sDoubleClickValues, 5 }; + EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; }; @@ -156,14 +164,10 @@ namespace CSMPrefs "report table" }, }; - Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sReportValues[1].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", std::string(sReportValues[2].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", std::string(sReportValues[3].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", std::string(sReportValues[0].mValue), - makeEnumSanitizerString(sReportValues) }; + EnumSettingValue mDouble{ mIndex, sName, "double", sReportValues, 1 }; + EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sReportValues, 2 }; + EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sReportValues, 3 }; + EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sReportValues, 0 }; Settings::SettingValue mIgnoreBaseRecords{ mIndex, sName, "ignore-base-records", false }; }; @@ -194,8 +198,7 @@ namespace CSMPrefs Settings::SettingValue mWrapLines{ mIndex, sName, "wrap-lines", false }; Settings::SettingValue mMonoFont{ mIndex, sName, "mono-font", true }; Settings::SettingValue mTabWidth{ mIndex, sName, "tab-width", 4 }; - Settings::SettingValue mWarnings{ mIndex, sName, "warnings", std::string(sWarningValues[1].mValue), - makeEnumSanitizerString(sWarningValues) }; + EnumSettingValue mWarnings{ mIndex, sName, "warnings", sWarningValues, 1 }; Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; Settings::SettingValue mCompileDelay{ mIndex, sName, "compile-delay", 100 }; Settings::SettingValue mErrorHeight{ mIndex, sName, "error-height", 100 }; @@ -310,14 +313,7 @@ namespace CSMPrefs EnumValueView{ "Discard", "" }, }; - static constexpr std::array sPrimarySelectAction{ - EnumValueView{ "Select only", "" }, - EnumValueView{ "Add to selection", "" }, - EnumValueView{ "Remove from selection", "" }, - EnumValueView{ "Invert selection", "" }, - }; - - static constexpr std::array sSecondarySelectAction{ + static constexpr std::array sSelectAction{ EnumValueView{ "Select only", "" }, EnumValueView{ "Add to selection", "" }, EnumValueView{ "Remove from selection", "" }, @@ -328,16 +324,12 @@ namespace CSMPrefs Settings::SettingValue mGridsnapRotation{ mIndex, sName, "gridsnap-rotation", 15 }; Settings::SettingValue mGridsnapScale{ mIndex, sName, "gridsnap-scale", 0.25 }; Settings::SettingValue mDistance{ mIndex, sName, "distance", 50 }; - Settings::SettingValue mOutsideDrop{ mIndex, sName, "outside-drop", - std::string(sInsertOutsideCellValues[0].mValue), makeEnumSanitizerString(sInsertOutsideCellValues) }; - Settings::SettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", - std::string(sInsertOutsideVisibleCellValues[0].mValue), - makeEnumSanitizerString(sInsertOutsideVisibleCellValues) }; - Settings::SettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", - std::string(sLandEditOutsideCellValues[0].mValue), makeEnumSanitizerString(sLandEditOutsideCellValues) }; - Settings::SettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", - std::string(sLandEditOutsideVisibleCellValues[0].mValue), - makeEnumSanitizerString(sLandEditOutsideVisibleCellValues) }; + EnumSettingValue mOutsideDrop{ mIndex, sName, "outside-drop", sInsertOutsideCellValues, 0 }; + EnumSettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", sInsertOutsideVisibleCellValues, + 0 }; + EnumSettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", sLandEditOutsideCellValues, 0 }; + EnumSettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", + sLandEditOutsideVisibleCellValues, 0 }; Settings::SettingValue mTexturebrushMaximumsize{ mIndex, sName, "texturebrush-maximumsize", 50 }; Settings::SettingValue mShapebrushMaximumsize{ mIndex, sName, "shapebrush-maximumsize", 100 }; Settings::SettingValue mLandeditPostSmoothpainting{ mIndex, sName, "landedit-post-smoothpainting", @@ -345,10 +337,8 @@ namespace CSMPrefs Settings::SettingValue mLandeditPostSmoothstrength{ mIndex, sName, "landedit-post-smoothstrength", 0.25 }; Settings::SettingValue mOpenListView{ mIndex, sName, "open-list-view", false }; - Settings::SettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", - std::string(sPrimarySelectAction[0].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; - Settings::SettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", - std::string(sPrimarySelectAction[1].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + EnumSettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", sSelectAction, 0 }; + EnumSettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", sSelectAction, 1 }; }; struct KeyBindingsCategory : Settings::WithIndex From 3ba03782c0c4241241feb2bed6c0cabd6439b56a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 4 Jan 2024 00:50:35 +0300 Subject: [PATCH 0724/2167] Silence OSG shininess limit warnings --- components/nifosg/nifloader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5234e700f2..2f7574d68b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2599,7 +2599,10 @@ namespace NifOsg emissiveMult = matprop->mEmissiveMult; mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mSpecular, 1.f)); - mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->mGlossiness); + // NIFs may provide specular exponents way above OpenGL's limit. + // They can't be used properly, but we don't need OSG to constantly harass us about it. + float glossiness = std::clamp(matprop->mGlossiness, 0.f, 128.f); + mat->setShininess(osg::Material::FRONT_AND_BACK, glossiness); if (!matprop->mController.empty()) { @@ -2714,7 +2717,8 @@ namespace NifOsg mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); - mat->setShininess(osg::Material::FRONT_AND_BACK, shaderprop->mGlossiness); + float glossiness = std::clamp(shaderprop->mGlossiness, 0.f, 128.f); + mat->setShininess(osg::Material::FRONT_AND_BACK, glossiness); emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); From 2c45d316fb05a2ad74ef6da94ac028cc50e8b33d Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 5 Jan 2024 11:21:12 +0000 Subject: [PATCH 0725/2167] Missing newlines in MWUI documentation --- files/data/scripts/omw/mwui/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/data/scripts/omw/mwui/init.lua b/files/data/scripts/omw/mwui/init.lua index 560d8af704..5ab2d3cb23 100644 --- a/files/data/scripts/omw/mwui/init.lua +++ b/files/data/scripts/omw/mwui/init.lua @@ -77,6 +77,7 @@ require('scripts.omw.mwui.space')(templates) --- -- Same as box, but with a semi-transparent background -- @field [parent=#Templates] openmw.ui#Template boxTransparent + --- -- Same as box, but with a solid background -- @field [parent=#Templates] openmw.ui#Template boxSolid @@ -100,6 +101,7 @@ require('scripts.omw.mwui.space')(templates) --- -- Same as box, but with a semi-transparent background -- @field [parent=#Templates] openmw.ui#Template boxTransparentThick + --- -- Same as box, but with a solid background -- @field [parent=#Templates] openmw.ui#Template boxSolidThick From 8e17aff6a67b6422205568fbee3ec3703d2fc6d5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 5 Jan 2024 22:17:46 +0100 Subject: [PATCH 0726/2167] Fix MagicSchoolData documentation --- files/lua_api/openmw/core.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index f42f51b9b3..890532fc13 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -871,6 +871,7 @@ -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute +--- -- @type MagicSchoolData -- @field #string name Human-readable name -- @field #string areaSound VFS path to the area sound From 594bd6e136a9d04ce6c395e00aa4f6ef119d86e3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Jan 2024 02:30:10 +0100 Subject: [PATCH 0727/2167] Use walking speed for swimming actor with water walking for pathfinding This will make them find shorter paths nearby shores. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aipackage.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0743fab..0dbd4e463f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,7 @@ Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute + Bug #7758: Water walking is not taken into account to compute path cost on the water Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index bdf96983ae..0eec5f195a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -492,8 +492,6 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 ? 0.0f : actorClass.getSwimSpeed(actor); - const float walkSpeed = [&] { if ((flags & DetourNavigator::Flag_walk) == 0) return 0.0f; @@ -502,6 +500,14 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P return actorClass.getRunSpeed(actor); }(); + const float swimSpeed = [&] { + if ((flags & DetourNavigator::Flag_swim) == 0) + return 0.0f; + if (hasWaterWalking(actor)) + return walkSpeed; + return actorClass.getSwimSpeed(actor); + }(); + const float maxSpeed = std::max(swimSpeed, walkSpeed); if (maxSpeed == 0) From 903299ce50ed52222b45212c131e3010bcac2b37 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Jan 2024 02:33:57 +0100 Subject: [PATCH 0728/2167] Avoid recomputing navigator flags when getting area costs --- apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- apps/openmw/mwmechanics/aipackage.cpp | 8 +++++--- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 8 +++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 674a660ff6..12072df2ac 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -283,8 +283,8 @@ namespace MWMechanics const MWBase::World* world = MWBase::Environment::get().getWorld(); // Try to build path to the target. const auto agentBounds = world->getPathfindingAgentBounds(actor); - const auto navigatorFlags = getNavigatorFlags(actor); - const auto areaCosts = getAreaCosts(actor); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); const auto& pathGridGraph = getPathGridGraph(pathgrid); mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds, diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 0eec5f195a..a265c70cf4 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -157,8 +157,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid), - agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, pathType); + agentBounds, navigatorFlags, areaCosts, endTolerance, pathType); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -486,10 +488,10 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: return result; } -DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::Ptr& actor) const +DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts( + const MWWorld::Ptr& actor, DetourNavigator::Flags flags) const { DetourNavigator::AreaCosts costs; - const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); const float walkSpeed = [&] { diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index fa018609e4..9e13ee9cd5 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -150,7 +150,7 @@ namespace MWMechanics DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; - DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; + DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor, DetourNavigator::Flags flags) const; const AiPackageTypeId mTypeId; const Options mOptions; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 30aad2e89a..be2601dc37 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -224,8 +224,10 @@ namespace MWMechanics { const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); constexpr float endTolerance = 0; + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid), - agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, PathType::Full); + agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full); } if (mPathFinder.isPathConstructed()) @@ -367,8 +369,8 @@ namespace MWMechanics const auto world = MWBase::Environment::get().getWorld(); const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigator = world->getNavigator(); - const auto navigatorFlags = getNavigatorFlags(actor); - const auto areaCosts = getAreaCosts(actor); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); do From c93b6dca0ae6ee607a9c1fae5106a0ac24cef9ea Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:02 -0600 Subject: [PATCH 0729/2167] Fix(CS): Add record type to selection groups to fix #7759 --- apps/opencs/model/world/data.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ea4c8966d8..6322a77e66 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -622,6 +622,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mSelectionGroups.addColumn(new StringIdColumn); mSelectionGroups.addColumn(new RecordStateColumn); + mSelectionGroups.addColumn(new FixedRecordTypeColumn(UniversalId::Type_SelectionGroup)); mSelectionGroups.addColumn(new SelectionGroupColumn); mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); From 3ff1bae372a67a9b18131e670e9c3ce0410b8f12 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:25 -0600 Subject: [PATCH 0730/2167] Cleanup(CS): More consistent names for selection group configs --- apps/opencs/model/prefs/values.hpp | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 80d2f8f182..7abe00d023 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -479,27 +479,27 @@ namespace CSMPrefs Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; Settings::SettingValue mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" }; Settings::SettingValue mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" }; - Settings::SettingValue mSceneToggleHidden{ mIndex, sName, "scene-toggle-visibility", "H" }; - Settings::SettingValue mSceneSelectGroup1{ mIndex, sName, "scene-group-1", "1" }; - Settings::SettingValue mSceneSaveGroup1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; - Settings::SettingValue mSceneSelectGroup2{ mIndex, sName, "scene-group-2", "2" }; - Settings::SettingValue mSceneSaveGroup2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; - Settings::SettingValue mSceneSelectGroup3{ mIndex, sName, "scene-group-3", "3" }; - Settings::SettingValue mSceneSaveGroup3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; - Settings::SettingValue mSceneSelectGroup4{ mIndex, sName, "scene-group-4", "4" }; - Settings::SettingValue mSceneSaveGroup4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; - Settings::SettingValue mSceneSelectGroup5{ mIndex, sName, "scene-group-5", "5" }; - Settings::SettingValue mSceneSaveGroup5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; - Settings::SettingValue mSceneSelectGroup6{ mIndex, sName, "scene-group-6", "6" }; - Settings::SettingValue mSceneSaveGroup6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; - Settings::SettingValue mSceneSelectGroup7{ mIndex, sName, "scene-group-7", "7" }; - Settings::SettingValue mSceneSaveGroup7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; - Settings::SettingValue mSceneSelectGroup8{ mIndex, sName, "scene-group-8", "8" }; - Settings::SettingValue mSceneSaveGroup8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; - Settings::SettingValue mSceneSelectGroup9{ mIndex, sName, "scene-group-9", "9" }; - Settings::SettingValue mSceneSaveGroup9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; - Settings::SettingValue mSceneSelectGroup10{ mIndex, sName, "scene-group-0", "0" }; - Settings::SettingValue mSceneSaveGroup10{ mIndex, sName, "scene-save-0", "Ctrl+0" }; + Settings::SettingValue mSceneToggleVisibility{ mIndex, sName, "scene-toggle-visibility", "H" }; + Settings::SettingValue mSceneGroup0{ mIndex, sName, "scene-group-0", "0" }; + Settings::SettingValue mSceneSave0{ mIndex, sName, "scene-save-0", "Ctrl+0" }; + Settings::SettingValue mSceneGroup1{ mIndex, sName, "scene-group-1", "1" }; + Settings::SettingValue mSceneSave1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; + Settings::SettingValue mSceneGroup2{ mIndex, sName, "scene-group-2", "2" }; + Settings::SettingValue mSceneSave2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; + Settings::SettingValue mSceneGroup3{ mIndex, sName, "scene-group-3", "3" }; + Settings::SettingValue mSceneSave3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; + Settings::SettingValue mSceneGroup4{ mIndex, sName, "scene-group-4", "4" }; + Settings::SettingValue mSceneSave4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; + Settings::SettingValue mSceneGroup5{ mIndex, sName, "scene-group-5", "5" }; + Settings::SettingValue mSceneSave5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; + Settings::SettingValue mSceneGroup6{ mIndex, sName, "scene-group-6", "6" }; + Settings::SettingValue mSceneSave6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; + Settings::SettingValue mSceneGroup7{ mIndex, sName, "scene-group-7", "7" }; + Settings::SettingValue mSceneSave7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; + Settings::SettingValue mSceneGroup8{ mIndex, sName, "scene-group-8", "8" }; + Settings::SettingValue mSceneSave8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; + Settings::SettingValue mSceneGroup9{ mIndex, sName, "scene-group-9", "9" }; + Settings::SettingValue mSceneSave9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; From c563219b6187a6661ff00a4fa08d119e4496b6cc Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:51 -0600 Subject: [PATCH 0731/2167] Cleanup(CS): Pass const ref when applicable for selection groups --- apps/opencs/view/render/cell.cpp | 2 +- apps/opencs/view/render/cell.hpp | 2 +- apps/opencs/view/render/object.cpp | 2 +- apps/opencs/view/render/object.hpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.hpp | 2 +- apps/opencs/view/render/unpagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/unpagedworldspacewidget.hpp | 2 +- apps/opencs/view/render/worldspacewidget.hpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index b0c8425ad3..f2c851cc72 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -612,7 +612,7 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele return result; } -void CSVRender::Cell::selectFromGroup(const std::vector group) +void CSVRender::Cell::selectFromGroup(const std::vector& group) { for (const auto& [_, object] : mObjects) { diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 52e01c631a..5bfce47904 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -148,7 +148,7 @@ namespace CSVRender // already selected void selectAllWithSameParentId(int elementMask); - void selectFromGroup(const std::vector group); + void selectFromGroup(const std::vector& group); void unhideAll(); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 0e114ac06e..4ad1aaca15 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -484,7 +484,7 @@ CSVRender::Object::~Object() mParentNode->removeChild(mRootNode); } -void CSVRender::Object::setSelected(bool selected, osg::Vec4f color) +void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color) { mSelected = selected; diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 9b18b99561..5c73b12211 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -139,7 +139,7 @@ namespace CSVRender ~Object(); /// Mark the object as selected, selected objects show an outline effect - void setSelected(bool selected, osg::Vec4f color = osg::Vec4f(1, 1, 1, 1)); + void setSelected(bool selected, const osg::Vec4f& color = osg::Vec4f(1, 1, 1, 1)); bool getSelected() const; diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index fab706e3ca..3d5c6fe565 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -875,7 +875,7 @@ std::vector> CSVRender::PagedWorldspaceWidget:: return result; } -void CSVRender::PagedWorldspaceWidget::selectGroup(std::vector group) const +void CSVRender::PagedWorldspaceWidget::selectGroup(const std::vector& group) const { for (const auto& [_, cell] : mCells) cell->selectFromGroup(group); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 3d2ab97e89..744cc7ccb9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -163,7 +163,7 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; - void selectGroup(const std::vector group) const override; + void selectGroup(const std::vector& group) const override; void unhideAll() const override; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index ea99294c28..899918c3b9 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -199,7 +199,7 @@ std::vector> CSVRender::UnpagedWorldspaceWidget return mCell->getSelection(elementMask); } -void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector group) const +void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector& group) const { mCell->selectFromGroup(group); } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 22a0475fe2..89c916415d 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -93,7 +93,7 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; - void selectGroup(const std::vector group) const override; + void selectGroup(const std::vector& group) const override; void unhideAll() const override; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index a6d87440f1..505d985ffa 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -201,7 +201,7 @@ namespace CSVRender virtual std::vector> getSelection(unsigned int elementMask) const = 0; - virtual void selectGroup(const std::vector) const = 0; + virtual void selectGroup(const std::vector&) const = 0; virtual void unhideAll() const = 0; From 74a6c81d53beca47794969bef862f4c510fbaad4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:14:29 +0100 Subject: [PATCH 0732/2167] Make ActorActiveEffects:getEffect return an empty value and strip expired effects from __pairs --- apps/openmw/mwlua/magicbindings.cpp | 14 ++++++++------ files/lua_api/openmw/types.lua | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 0a34c008a7..3d57ab24fc 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -769,8 +769,13 @@ namespace MWLua sol::state_view lua(ts); self.reset(); return sol::as_function([lua, self]() mutable -> std::pair { - if (!self.isEnd()) + while (!self.isEnd()) { + if (self.mIterator->second.getBase() == 0 && self.mIterator->second.getModifier() == 0.f) + { + self.advance(); + continue; + } ActiveEffect effect = ActiveEffect{ self.mIterator->first, self.mIterator->second }; auto result = sol::make_object(lua, effect); @@ -778,10 +783,7 @@ namespace MWLua self.advance(); return { key, result }; } - else - { - return { sol::lua_nil, sol::lua_nil }; - } + return { sol::lua_nil, sol::lua_nil }; }); }; @@ -823,7 +825,7 @@ namespace MWLua if (auto* store = effects.getStore()) if (auto effect = store->get(key)) return ActiveEffect{ key, effect.value() }; - return sol::nullopt; + return ActiveEffect{ key, MWMechanics::EffectParam() }; }; // types.Actor.activeEffects(o):removeEffect(id, ?arg) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a350d4dbea..ad30994fe8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -210,14 +210,14 @@ -- end -- @usage -- Check for a specific effect -- local effect = Actor.activeEffects(self):getEffect(core.magic.EFFECT_TYPE.Telekinesis) --- if effect then +-- if effect.magnitude ~= 0 then -- print(effect.id..', attribute='..tostring(effect.affectedAttribute)..', skill='..tostring(effect.affectedSkill)..', magnitude='..tostring(effect.magnitude)) -- else -- print('No Telekinesis effect') -- end -- @usage -- Check for a specific effect targeting a specific attribute. -- local effect = Actor.activeEffects(self):getEffect(core.magic.EFFECT_TYPE.FortifyAttribute, core.ATTRIBUTE.Luck) --- if effect then +-- if effect.magnitude ~= 0 then -- print(effect.id..', attribute='..tostring(effect.affectedAttribute)..', skill='..tostring(effect.affectedSkill)..', magnitude='..tostring(effect.magnitude)) -- else -- print('No Fortify Luck effect') @@ -229,7 +229,7 @@ -- @param self -- @param #string effectId effect ID -- @param #string extraParam Optional skill or attribute ID --- @return openmw.core#ActiveEffect if such an effect is active, nil otherwise +-- @return openmw.core#ActiveEffect --- -- Completely removes the active effect from the actor. From 72c382aca610a1f52d390aed260f6b93f1d0f4c9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:23:08 +0100 Subject: [PATCH 0733/2167] Don't crash when clicking the logo video --- apps/openmw/mwgui/windowbase.cpp | 2 +- apps/openmw/mwgui/windowbase.hpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 4c191eaeb8..a680e38cf8 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -58,7 +58,7 @@ void WindowBase::setVisible(bool visible) onClose(); } -bool WindowBase::isVisible() +bool WindowBase::isVisible() const { return mMainWidget->getVisible(); } diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 88b46b0bd2..54fb269305 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -37,7 +37,7 @@ namespace MWGui /// Sets the visibility of the window void setVisible(bool visible) override; /// Returns the visibility state of the window - bool isVisible(); + bool isVisible() const; void center(); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index c91dfaedac..55b50b91ae 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -158,8 +158,9 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - if (!MWBase::Environment::get().getWindowManager()->getSettingsWindow()->isVisible() - && !input->controlsDisabled()) + const MWGui::SettingsWindow* settingsWindow + = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); + if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } From a11ff46e82f9b161b041dae504a6b0a9690363cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:59:22 +0100 Subject: [PATCH 0734/2167] Drop support for save game format 4 --- CHANGELOG.md | 2 +- components/esm3/aisequence.cpp | 4 ++-- components/esm3/formatversion.hpp | 2 +- components/esm3/player.cpp | 18 +++++++----------- components/esm3/player.hpp | 3 +-- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0743fab..1438abb513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,7 +131,6 @@ Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds - Feature #6624: Drop support for old saves Feature #6726: Lua API for creating new objects Feature #6864: Lua file access API Feature #6922: Improve launcher appearance @@ -163,6 +162,7 @@ Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Task #5896: Do not use deprecated MyGUI properties + Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector Task #7151: Do not use std::strerror to get errno error message diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index bbd42c400e..99c85db1bb 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -29,7 +29,7 @@ namespace ESM void AiTravel::load(ESMReader& esm) { esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ); - esm.getHNOT(mHidden, "HIDD"); + esm.getHNT(mHidden, "HIDD"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); } @@ -258,7 +258,7 @@ namespace ESM } } - esm.getHNOT(mLastAiPackage, "LAST"); + esm.getHNT(mLastAiPackage, "LAST"); if (count > 1 && esm.getFormatVersion() <= MaxOldAiPackageFormatVersion) { diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 1489926fbd..9f499a7231 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -27,7 +27,7 @@ namespace ESM inline constexpr FormatVersion MaxOldCountFormatVersion = 30; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index aa6b59abb9..fd280bf12e 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -30,16 +30,12 @@ namespace ESM mPaidCrimeId = -1; esm.getHNOT(mPaidCrimeId, "PAYD"); - bool checkPrevItems = true; - while (checkPrevItems) + while (esm.peekNextSub("BOUN")) { - ESM::RefId boundItemId = esm.getHNORefId("BOUN"); - ESM::RefId prevItemId = esm.getHNORefId("PREV"); + ESM::RefId boundItemId = esm.getHNRefId("BOUN"); + ESM::RefId prevItemId = esm.getHNRefId("PREV"); - if (!boundItemId.empty()) - mPreviousItems[boundItemId] = prevItemId; - else - checkPrevItems = false; + mPreviousItems[boundItemId] = prevItemId; } if (esm.getFormatVersion() <= MaxOldSkillsAndAttributesFormatVersion) @@ -103,10 +99,10 @@ namespace ESM esm.writeHNT("CURD", mCurrentCrimeId); esm.writeHNT("PAYD", mPaidCrimeId); - for (PreviousItems::const_iterator it = mPreviousItems.begin(); it != mPreviousItems.end(); ++it) + for (const auto& [bound, prev] : mPreviousItems) { - esm.writeHNRefId("BOUN", it->first); - esm.writeHNRefId("PREV", it->second); + esm.writeHNRefId("BOUN", bound); + esm.writeHNRefId("PREV", prev); } esm.writeHNT("WWAT", mSaveAttributes); diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index 7f9309765c..0f76a3b5eb 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -33,8 +33,7 @@ namespace ESM float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; - typedef std::map PreviousItems; // previous equipped items, needed for bound spells - PreviousItems mPreviousItems; + std::map mPreviousItems; // previous equipped items, needed for bound spells void load(ESMReader& esm); void save(ESMWriter& esm) const; From 6d37618301efa572017bd2b1ebf7b019b6b483f0 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 6 Jan 2024 16:56:52 +0000 Subject: [PATCH 0735/2167] Add OpenMW-CS RC file to app rather than static lib Static libraries on Windows can't have embedded resources, so this mean the icon for the CS wasn't used. This could have also been resolved by explicitly requesting the library type as OBJECT rather than letting it default to STATIC (as object libraries aren't a thing on-disk and are just an abstraction in CMake so you can use the same object files in different targets), but this seemed less invasive. I also made it Win32-only as a Windows .rc file is meaningless on Unix, but it shouldn't be MSVC-only as MinGW can consume them. --- apps/opencs/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 70efd06090..610c5157aa 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -1,5 +1,4 @@ set (OPENCS_SRC - ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc ) opencs_units (. editor) @@ -146,6 +145,9 @@ source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) if(WIN32) set(QT_USE_QTMAIN TRUE) + set(OPENCS_RC_FILE ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc) +else(WIN32) + set(OPENCS_RC_FILE "") endif(WIN32) if (QT_VERSION_MAJOR VERSION_EQUAL 5) @@ -186,6 +188,7 @@ if(BUILD_OPENCS) ${OPENCS_CFG} ${OPENCS_DEFAULT_FILTERS_FILE} ${OPENCS_OPENMW_CFG} + ${OPENCS_RC_FILE} main.cpp ) From 6dc09b1cda50d0f31587d2cc3e6d0a5fe37c3ef1 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 7 Jan 2024 01:12:50 +0000 Subject: [PATCH 0736/2167] Update file before_install.osx.sh --- CI/before_install.osx.sh | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 40210428d0..e340d501ed 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,22 +1,18 @@ #!/bin/sh -ex export HOMEBREW_NO_EMOJI=1 +export HOMEBREW_NO_INSTALL_CLEANUP=1 +export HOMEBREW_AUTOREMOVE=1 -brew uninstall --ignore-dependencies python@3.8 || true -brew uninstall --ignore-dependencies python@3.9 || true -brew uninstall --ignore-dependencies qt@6 || true -brew uninstall --ignore-dependencies jpeg || true +# workaround for gitlab's pre-installed brew +# purge large and unnecessary packages that get in our way and have caused issues +brew uninstall ruby php openjdk node postgresql maven curl || true brew tap --repair brew update --quiet # Some of these tools can come from places other than brew, so check before installing -brew reinstall xquartz fontconfig freetype harfbuzz brotli - -# Fix: can't open file: @loader_path/libbrotlicommon.1.dylib (No such file or directory) -BREW_LIB_PATH="$(brew --prefix)/lib" -install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlidec.1.dylib -install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlienc.1.dylib +brew install curl xquartz gd fontconfig freetype harfbuzz brotli command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake From c1c774e11df123032d94ee5306e49b5dedaf980c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 02:05:07 +0300 Subject: [PATCH 0737/2167] Update the spells window when constant effects are added/removed (bug #7475) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/activespells.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..83baf27872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles + Bug #7475: Equipping a constant effect item doesn't update the magic menu Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 727bf95ed0..d8e409d9e2 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -228,6 +228,7 @@ namespace MWMechanics mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); } + bool updateSpellWindow = false; if (ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { @@ -264,6 +265,7 @@ namespace MWMechanics for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); + updateSpellWindow = true; } } } @@ -365,6 +367,7 @@ namespace MWMechanics for (const auto& effect : params.mEffects) onMagicEffectRemoved(ptr, params, effect); applyPurges(ptr, &spellIt); + updateSpellWindow = true; continue; } ++spellIt; @@ -377,6 +380,14 @@ namespace MWMechanics if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f) creatureStats.getAiSequence().stopCombat(); } + + if (ptr == player && updateSpellWindow) + { + // Something happened with the spell list -- possibly while the game is paused, + // so we want to make the spell window get the memo. + // We don't normally want to do this, so this targets constant enchantments. + MWBase::Environment::get().getWindowManager()->updateSpellWindow(); + } } void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) From 18982ea4a0a5aaff2b52e09089d488d947748f19 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 04:22:31 +0300 Subject: [PATCH 0738/2167] Read FO76 plugin header --- components/esm4/loadtes4.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index a2fbe8b139..0cbf91c52e 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -100,6 +100,7 @@ void ESM4::Header::load(ESM4::Reader& reader) case ESM4::SUB_OFST: // Oblivion only? case ESM4::SUB_DELE: // Oblivion only? case ESM4::SUB_TNAM: // Fallout 4 (CK only) + case ESM::fourCC("MMSB"): // Fallout 76 reader.skipSubRecordData(); break; default: From f9825328d2e9e80978735b81209bfa5afa3b5cf2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 17:22:39 +0300 Subject: [PATCH 0739/2167] Bring ESM4 texture set reading up-to-date with FO76 --- components/esm4/loadtxst.cpp | 25 ++++++++++++++++++++++--- components/esm4/loadtxst.hpp | 7 ++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 3f1aebafb4..3b5f04f265 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -44,6 +44,9 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("FLTR"): // FO76 + reader.getZString(mFilter); + break; case ESM4::SUB_TX00: reader.getZString(mDiffuse); break; @@ -51,29 +54,45 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) reader.getZString(mNormalMap); break; case ESM4::SUB_TX02: + // This is a "wrinkle map" in FO4/76 reader.getZString(mEnvMask); break; case ESM4::SUB_TX03: + // This is a glow map in FO4/76 reader.getZString(mToneMap); break; case ESM4::SUB_TX04: + // This is a height map in FO4/76 reader.getZString(mDetailMap); break; case ESM4::SUB_TX05: reader.getZString(mEnvMap); break; case ESM4::SUB_TX06: - reader.getZString(mUnknown); + reader.getZString(mMultiLayer); break; case ESM4::SUB_TX07: + // This is a "smooth specular" map in FO4/76 reader.getZString(mSpecular); break; + case ESM::fourCC("TX08"): // FO76 + reader.getZString(mSpecular); + break; + case ESM::fourCC("TX09"): // FO76 + reader.getZString(mLighting); + break; + case ESM::fourCC("TX10"): // FO76 + reader.getZString(mFlow); + break; + case ESM4::SUB_DNAM: + reader.get(mDataFlags); + break; case ESM4::SUB_MNAM: reader.getZString(mMaterial); break; - case ESM4::SUB_DNAM: - case ESM4::SUB_DODT: + case ESM4::SUB_DODT: // Decal data case ESM4::SUB_OBND: // object bounds + case ESM::fourCC("OPDS"): // Object placement defaults, FO76 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp index 0b55f37f8c..8e628df841 100644 --- a/components/esm4/loadtxst.hpp +++ b/components/esm4/loadtxst.hpp @@ -44,6 +44,7 @@ namespace ESM4 std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; + std::string mFilter; std::string mDiffuse; // includes alpha info std::string mNormalMap; // includes specular info (alpha channel) @@ -51,8 +52,12 @@ namespace ESM4 std::string mToneMap; std::string mDetailMap; std::string mEnvMap; - std::string mUnknown; + std::string mMultiLayer; std::string mSpecular; + std::string mSmoothSpecular; + std::string mLighting; + std::string mFlow; + std::uint16_t mDataFlags; std::string mMaterial; void load(ESM4::Reader& reader); From 0b63fafc6dc77b24d7eacf30813da3a94ae8c91f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 18:21:59 +0300 Subject: [PATCH 0740/2167] Bring ESM4 global variable reading up-to-date with FO76 --- components/esm4/loadglob.cpp | 11 ++++------- components/esm4/loadglob.hpp | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 39593a4a7d..436f3e34ae 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -44,19 +44,16 @@ void ESM4::GlobalVariable::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("XALG"): // FO76 + reader.get(mExtraFlags2); + break; case ESM4::SUB_FNAM: reader.get(mType); break; case ESM4::SUB_FLTV: reader.get(mValue); break; - case ESM4::SUB_FULL: - case ESM4::SUB_MODL: - case ESM4::SUB_MODB: - case ESM4::SUB_ICON: - case ESM4::SUB_DATA: - case ESM4::SUB_OBND: // TES5 - case ESM4::SUB_VMAD: // TES5 + case ESM::fourCC("NTWK"): // FO76 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp index c9c83f58b4..89959bcab1 100644 --- a/components/esm4/loadglob.hpp +++ b/components/esm4/loadglob.hpp @@ -42,6 +42,7 @@ namespace ESM4 { ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + std::uint64_t mExtraFlags2; std::string mEditorId; From bd2ea715b422aab0904036e8b6ad094eece8a9ea Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 18:27:33 +0300 Subject: [PATCH 0741/2167] Bring ESM4 head part reading up-to-date with FO76 --- components/esm4/loadhdpt.cpp | 21 +++++++++++++++++---- components/esm4/loadhdpt.hpp | 5 ++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index 250a687042..c560ff5fac 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -48,6 +48,9 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("XALG"): // FO76 + reader.get(mExtraFlags2); + break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; @@ -58,7 +61,7 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) reader.getZString(mModel); break; case ESM4::SUB_HNAM: - reader.getFormId(mAdditionalPart); + reader.getFormId(mExtraParts.emplace_back()); break; case ESM4::SUB_NAM0: // TES5 { @@ -87,15 +90,25 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) case ESM4::SUB_TNAM: reader.getFormId(mBaseTexture); break; + case ESM4::SUB_CNAM: + reader.getFormId(mColor); + break; + case ESM4::SUB_RNAM: + reader.getFormId(mValidRaces.emplace_back()); + break; case ESM4::SUB_PNAM: reader.get(mType); break; case ESM4::SUB_MODT: // Model data case ESM4::SUB_MODC: case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_RNAM: - case ESM4::SUB_CNAM: + case ESM4::SUB_MODF: + case ESM::fourCC("ENLM"): + case ESM::fourCC("XFLG"): + case ESM::fourCC("ENLT"): + case ESM::fourCC("ENLS"): + case ESM::fourCC("AUUV"): + case ESM::fourCC("MODD"): // Model data end case ESM4::SUB_CTDA: reader.skipSubRecordData(); break; diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index aca3b0ca7b..5d17720100 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -43,6 +43,7 @@ namespace ESM4 { ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + std::uint64_t mExtraFlags2; std::string mEditorId; std::string mFullName; @@ -70,10 +71,12 @@ namespace ESM4 Type_Eyelashes = 13, }; - ESM::FormId mAdditionalPart; + std::vector mExtraParts; std::array mTriFile; ESM::FormId mBaseTexture; + ESM::FormId mColor; + std::vector mValidRaces; void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; From 01dcca3363d2328b64c04cf7f4571790f7aff036 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 17:21:00 +0300 Subject: [PATCH 0742/2167] Make scripted animations shut down pathfinding (bug #5065) --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ apps/openmw/mwmechanics/actors.cpp | 8 ++++++++ apps/openmw/mwmechanics/actors.hpp | 1 + apps/openmw/mwmechanics/aipackage.cpp | 11 ++++++----- apps/openmw/mwmechanics/character.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 8 ++++++++ apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 1 + 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..0d9e199f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation + Bug #5065: Actors with scripted animation still try to wander and turn around without moving Bug #5066: Quirks with starting and stopping scripted animations Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b8e0fd1bde..9e99a37ec7 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -187,6 +187,8 @@ namespace MWBase virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + virtual bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const = 0; + /// Save the current animation state of managed references to their RefData. virtual void persistAnimationStates() = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 25c4c97504..bc3cc3bb6a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2034,6 +2034,14 @@ namespace MWMechanics return false; } + bool Actors::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->getCharacterController().isScriptedAnimPlaying(); + return false; + } + void Actors::persistAnimationStates() const { for (const Actor& actor : mActors) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 15a39136a6..7c676ca018 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -116,6 +116,7 @@ namespace MWMechanics const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; + bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const; void persistAnimationStates() const; void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index a265c70cf4..fe83ce11ab 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/luamanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" @@ -120,12 +121,12 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& MWBase::World* world = MWBase::Environment::get().getWorld(); const DetourNavigator::AgentBounds agentBounds = world->getPathfindingAgentBounds(actor); - /// Stops the actor when it gets too close to a unloaded cell - //... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" - // setting value - //... units from player, and exterior cells are 8192 units long and wide. + /// Stops the actor when it gets too close to a unloaded cell or when the actor is playing a scripted animation + //... At current time, the first test is unnecessary. AI shuts down when actor is more than + //... "actors processing range" setting value units from player, and exterior cells are 8192 units long and wide. //... But AI processing distance may increase in the future. - if (isNearInactiveCell(position)) + if (isNearInactiveCell(position) + || MWBase::Environment::get().getMechanicsManager()->checkScriptedAnimationPlaying(actor)) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index ee26b61a25..dc551900b5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -217,7 +217,6 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -278,6 +277,7 @@ namespace MWMechanics bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; + bool isScriptedAnimPlaying() const; enum KillResult { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 59b1392dc9..c9e8e8f322 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -771,6 +771,14 @@ namespace MWMechanics return false; } + bool MechanicsManager::checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const + { + if (ptr.getClass().isActor()) + return mActors.checkScriptedAnimationPlaying(ptr); + + return false; + } + bool MechanicsManager::onOpen(const MWWorld::Ptr& ptr) { if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 997636522e..f2483396fe 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -145,6 +145,7 @@ namespace MWMechanics const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; + bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override; void persistAnimationStates() override; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently From ed31a0354ab08b9d051744413026a2012692972c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 18:35:51 +0300 Subject: [PATCH 0743/2167] Support playing ambient and rain weather SFX at the same time (bug #7761) --- CHANGELOG.md | 1 + apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwworld/weather.cpp | 59 +++++++++++++++++++++++--------- apps/openmw/mwworld/weather.hpp | 10 ++++-- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..411ff28c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,6 +122,7 @@ Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water + Bug #7761: Rain and ambient loop sounds are mutually exclusive Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 1018724595..da038e6c58 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -65,6 +65,7 @@ namespace MWRender bool mIsStorm; ESM::RefId mAmbientLoopSoundID; + ESM::RefId mRainLoopSoundID; float mAmbientSoundVolume; std::string mParticleEffect; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..2c9b80bc5f 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -195,21 +195,19 @@ namespace MWWorld mThunderSoundID[3] = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3")); - // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both - // sounds at the same time. - if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing // Using_Precip has no effect { - mAmbientLoopSoundID + mRainLoopSoundID = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID")); - if (mAmbientLoopSoundID.empty()) // default to "rain" if not set - mAmbientLoopSoundID = ESM::RefId::stringRefId("rain"); + if (mRainLoopSoundID.empty()) // default to "rain" if not set + mRainLoopSoundID = ESM::RefId::stringRefId("rain"); + else if (mRainLoopSoundID == "None") + mRainLoopSoundID = ESM::RefId(); } - else - mAmbientLoopSoundID - = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID")); + mAmbientLoopSoundID + = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID")); if (mAmbientLoopSoundID == "None") mAmbientLoopSoundID = ESM::RefId(); } @@ -552,8 +550,6 @@ namespace MWWorld , mQueuedWeather(0) , mRegions() , mResult() - , mAmbientSound(nullptr) - , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; mTimeSettings.mNightEnd = mSunriseTime; @@ -794,24 +790,52 @@ namespace MWWorld mRendering.getSkyManager()->setWeather(mResult); // Play sounds - if (mPlayingSoundID != mResult.mAmbientLoopSoundID) + if (mPlayingAmbientSoundID != mResult.mAmbientLoopSoundID) { - stopSounds(); + if (mAmbientSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound = nullptr; + } if (!mResult.mAmbientLoopSoundID.empty()) mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); - mPlayingSoundID = mResult.mAmbientLoopSoundID; + mPlayingAmbientSoundID = mResult.mAmbientLoopSoundID; } else if (mAmbientSound) mAmbientSound->setVolume(mResult.mAmbientSoundVolume); + + if (mPlayingRainSoundID != mResult.mRainLoopSoundID) + { + if (mRainSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + mRainSound = nullptr; + } + if (!mResult.mRainLoopSoundID.empty()) + mRainSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mRainLoopSoundID, + mResult.mAmbientSoundVolume, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); + mPlayingRainSoundID = mResult.mRainLoopSoundID; + } + else if (mRainSound) + mRainSound->setVolume(mResult.mAmbientSoundVolume); } void WeatherManager::stopSounds() { if (mAmbientSound) + { MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound = nullptr; - mPlayingSoundID = ESM::RefId(); + mAmbientSound = nullptr; + } + mPlayingAmbientSoundID = ESM::RefId(); + + if (mRainSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + mRainSound = nullptr; + } + mPlayingRainSoundID = ESM::RefId(); } float WeatherManager::getWindSpeed() const @@ -1118,6 +1142,7 @@ namespace MWWorld mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = current.mRainLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mPrecipitationAlpha = 1.f; @@ -1237,6 +1262,7 @@ namespace MWWorld mResult.mAmbientSoundVolume = 1.f - factor / threshold; mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = current.mRainLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; @@ -1252,6 +1278,7 @@ namespace MWWorld mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = other.mRainLoopSoundID; mResult.mRainDiameter = other.mRainDiameter; mResult.mRainMinHeight = other.mRainMinHeight; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 65f926c096..327136a859 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -156,9 +156,11 @@ namespace MWWorld float FogOffset; } mDL; - // Sound effect + // Sound effects // This is used for Blight, Ashstorm and Blizzard (Bloodmoon) ESM::RefId mAmbientLoopSoundID; + // This is used for Rain and Thunderstorm + ESM::RefId mRainLoopSoundID; // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. @@ -369,8 +371,10 @@ namespace MWWorld std::map mRegions; MWRender::WeatherResult mResult; - MWBase::Sound* mAmbientSound; - ESM::RefId mPlayingSoundID; + MWBase::Sound* mAmbientSound{ nullptr }; + ESM::RefId mPlayingAmbientSoundID; + MWBase::Sound* mRainSound{ nullptr }; + ESM::RefId mPlayingRainSoundID; void addWeather( const std::string& name, float dlFactor, float dlOffset, const std::string& particleEffect = ""); From 1f26485c478e901978124166fb1e1e796c58ceeb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 05:18:30 +0300 Subject: [PATCH 0744/2167] Fix exterior sun direction/position (bug #4898) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 3 +++ apps/openmw/mwworld/weather.cpp | 16 ++++++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..1771d0ab81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation + Bug #4898: Odd/Incorrect lighting on meshes Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5066: Quirks with starting and stopping scripted animations diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fa92fa1420..2e3698a197 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -716,6 +716,9 @@ namespace MWRender // need to wrap this in a StateUpdater? mSunLight->setPosition(osg::Vec4(position.x(), position.y(), position.z(), 0)); + // The sun is not synchronized with the sunlight because sunlight origin can't reach the horizon + // This is based on exterior sun orbit and won't make sense for interiors, see WeatherManager::update + position.z() = 400.f - std::abs(position.x()); mSky->setSunDirection(position); mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..584f73520c 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -752,21 +752,21 @@ namespace MWWorld const float dayDuration = adjustedNightStart - mSunriseTime; const float nightDuration = 24.f - dayDuration; - double theta; + float orbit; if (!is_night) { - theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; + float t = (adjustedHour - mSunriseTime) / dayDuration; + orbit = 1.f - 2.f * t; } else { - theta = static_cast(osg::PI) - - static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; + float t = (adjustedHour - adjustedNightStart) / nightDuration; + orbit = 2.f * t - 1.f; } - osg::Vec3f final(static_cast(cos(theta)), - -0.268f, // approx tan( -15 degrees ) - static_cast(sin(theta))); - mRendering.setSunDirection(final * -1); + // Hardcoded constant from Morrowind + const osg::Vec3f sunDir(-400.f * orbit, 75.f, -100.f); + mRendering.setSunDirection(sunDir); mRendering.setNight(is_night); } From 9b8d6855784da859e77dc0ebc8d7c2c361f2e761 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 21:58:12 +0300 Subject: [PATCH 0745/2167] Expose requested apparent sun position (not normalized) to post-processing --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2e3698a197..c2c6abd1bc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -721,7 +721,7 @@ namespace MWRender position.z() = 400.f - std::abs(position.x()); mSky->setSunDirection(position); - mPostProcessor->getStateUpdater()->setSunPos(mSunLight->getPosition(), mNight); + mPostProcessor->getStateUpdater()->setSunPos(osg::Vec4f(position, 0.f), mNight); } void RenderingManager::addCell(const MWWorld::CellStore* store) From 327fafe7397928434af4654b871d078ae2b324bc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 7 Jan 2024 06:49:56 +0300 Subject: [PATCH 0746/2167] Contect selector: fix ESM date and version data encoding/decoding (#7764) --- components/contentselector/model/esmfile.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 7c62299048..39a33e710e 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -51,7 +51,8 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - stream << mFileName << mAuthor << mVersion << mModified.toString() << mPath << mDescription << mGameFiles; + stream << mFileName << mAuthor << mVersion << mModified.toString(Qt::ISODate) << mPath << mDescription + << mGameFiles; return encodedData; } @@ -113,11 +114,11 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con break; case FileProperty_Format: - mVersion = value.toInt(); + mVersion = value; break; case FileProperty_DateModified: - mModified = QDateTime::fromString(value); + mModified = QDateTime::fromString(value, Qt::ISODate); break; case FileProperty_FilePath: From 828c40c710f798ea60937bdd6045809b6c2362ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:52:10 +0400 Subject: [PATCH 0747/2167] Do not copy due to auto misuse --- apps/launcher/graphicspage.cpp | 2 +- components/stereo/stereomanager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index fa9d5eb479..b360c215e6 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -154,7 +154,7 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - auto boundMethod = Settings::shadows().mComputeSceneBounds.get(); + const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); if (boundMethod == "bounds") shadowComputeSceneBoundsComboBox->setCurrentIndex(0); else if (boundMethod == "primitives") diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index a2eea0fda2..c3d7f1d320 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -315,8 +315,7 @@ namespace Stereo else { auto* ds = osg::DisplaySettings::instance().get(); - auto viewMatrix = mMainCamera->getViewMatrix(); - auto projectionMatrix = mMainCamera->getProjectionMatrix(); + const auto& projectionMatrix = mMainCamera->getProjectionMatrix(); auto s = ds->getEyeSeparation() * Constants::UnitsPerMeter; mViewOffsetMatrix[0] = osg::Matrixd(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, s, 0.0, 0.0, 1.0); From 4f65b7167a8d7e8af51449899ad4eb2e50cf97ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:53:07 +0400 Subject: [PATCH 0748/2167] Do not copy vector for every door marker --- apps/openmw/mwgui/mapwindow.cpp | 9 ++++----- apps/openmw/mwgui/mapwindow.hpp | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 6d79b5f9d7..cb6ba79f9e 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -300,11 +300,9 @@ namespace MWGui return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); } - MyGUI::Widget* LocalMapBase::createDoorMarker( - const std::string& name, const MyGUI::VectorString& notes, float x, float y) const + MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, float x, float y) const { MarkerUserData data(mLocalMapRender); - data.notes = notes; data.caption = name; MarkerWidget* markerWidget = mLocalMap->createWidget( "MarkerButton", getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); @@ -662,8 +660,9 @@ namespace MWGui MarkerUserData* data; if (mDoorMarkersToRecycle.empty()) { - markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); + markerWidget = createDoorMarker(marker.name, marker.x, marker.y); data = markerWidget->getUserData(); + data->notes = std::move(destNotes); doorMarkerCreated(markerWidget); } else @@ -672,7 +671,7 @@ namespace MWGui mDoorMarkersToRecycle.pop_back(); data = markerWidget->getUserData(); - data->notes = destNotes; + data->notes = std::move(destNotes); data->caption = marker.name; markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); markerWidget->setVisible(true); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 5afc8c7c8a..29759a4365 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -170,8 +170,7 @@ namespace MWGui MyGUI::IntPoint getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const; MyGUI::IntCoord getMarkerCoordinates( float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const; - MyGUI::Widget* createDoorMarker( - const std::string& name, const MyGUI::VectorString& notes, float x, float y) const; + MyGUI::Widget* createDoorMarker(const std::string& name, float x, float y) const; MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const; virtual void notifyPlayerUpdate() {} From 8879d89e4a227cd6fe5571dc2042b2df0fc69477 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 19:10:09 +0400 Subject: [PATCH 0749/2167] Replace 'klass' by meaningful names --- apps/openmw/mwgui/charactercreation.cpp | 43 +++++++++++++------------ apps/openmw/mwgui/class.cpp | 16 ++++----- apps/openmw/mwgui/review.cpp | 8 ++--- apps/openmw/mwgui/review.hpp | 4 +-- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 19f7d97176..4141e61e34 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -333,10 +333,10 @@ namespace MWGui if (!classId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); - const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(classId); - if (klass) + const ESM::Class* pickedClass = MWBase::Environment::get().getESMStore()->get().find(classId); + if (pickedClass) { - mPlayerClass = *klass; + mPlayerClass = *pickedClass; } MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mPickClassDialog)); } @@ -454,30 +454,30 @@ namespace MWGui { if (mCreateClassDialog) { - ESM::Class klass; - klass.mName = mCreateClassDialog->getName(); - klass.mDescription = mCreateClassDialog->getDescription(); - klass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); - klass.mData.mIsPlayable = 0x1; - klass.mRecordFlags = 0; + ESM::Class createdClass; + createdClass.mName = mCreateClassDialog->getName(); + createdClass.mDescription = mCreateClassDialog->getDescription(); + createdClass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); + createdClass.mData.mIsPlayable = 0x1; + createdClass.mRecordFlags = 0; std::vector attributes = mCreateClassDialog->getFavoriteAttributes(); - assert(attributes.size() >= klass.mData.mAttribute.size()); - for (size_t i = 0; i < klass.mData.mAttribute.size(); ++i) - klass.mData.mAttribute[i] = ESM::Attribute::refIdToIndex(attributes[i]); + assert(attributes.size() >= createdClass.mData.mAttribute.size()); + for (size_t i = 0; i < createdClass.mData.mAttribute.size(); ++i) + createdClass.mData.mAttribute[i] = ESM::Attribute::refIdToIndex(attributes[i]); std::vector majorSkills = mCreateClassDialog->getMajorSkills(); std::vector minorSkills = mCreateClassDialog->getMinorSkills(); - assert(majorSkills.size() >= klass.mData.mSkills.size()); - assert(minorSkills.size() >= klass.mData.mSkills.size()); - for (size_t i = 0; i < klass.mData.mSkills.size(); ++i) + assert(majorSkills.size() >= createdClass.mData.mSkills.size()); + assert(minorSkills.size() >= createdClass.mData.mSkills.size()); + for (size_t i = 0; i < createdClass.mData.mSkills.size(); ++i) { - klass.mData.mSkills[i][1] = ESM::Skill::refIdToIndex(majorSkills[i]); - klass.mData.mSkills[i][0] = ESM::Skill::refIdToIndex(minorSkills[i]); + createdClass.mData.mSkills[i][1] = ESM::Skill::refIdToIndex(majorSkills[i]); + createdClass.mData.mSkills[i][0] = ESM::Skill::refIdToIndex(minorSkills[i]); } - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass); - mPlayerClass = klass; + MWBase::Environment::get().getMechanicsManager()->setPlayerClass(createdClass); + mPlayerClass = std::move(createdClass); // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); @@ -666,9 +666,10 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(mGenerateClass); + const ESM::Class* generatedClass + = MWBase::Environment::get().getESMStore()->get().find(mGenerateClass); - mPlayerClass = *klass; + mPlayerClass = *generatedClass; } void CharacterCreation::onGenerateClassBack() diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index d6b4e7f635..839f0f5072 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -248,27 +248,27 @@ namespace MWGui if (mCurrentClassId.empty()) return; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::Class* klass = store.get().search(mCurrentClassId); - if (!klass) + const ESM::Class* currentClass = store.get().search(mCurrentClassId); + if (!currentClass) return; ESM::Class::Specialization specialization - = static_cast(klass->mData.mSpecialization); + = static_cast(currentClass->mData.mSpecialization); std::string specName{ MWBase::Environment::get().getWindowManager()->getGameSettingString( ESM::Class::sGmstSpecializationIds[specialization], ESM::Class::sGmstSpecializationIds[specialization]) }; mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization); - mFavoriteAttribute[0]->setAttributeId(ESM::Attribute::indexToRefId(klass->mData.mAttribute[0])); - mFavoriteAttribute[1]->setAttributeId(ESM::Attribute::indexToRefId(klass->mData.mAttribute[1])); + mFavoriteAttribute[0]->setAttributeId(ESM::Attribute::indexToRefId(currentClass->mData.mAttribute[0])); + mFavoriteAttribute[1]->setAttributeId(ESM::Attribute::indexToRefId(currentClass->mData.mAttribute[1])); ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId()); ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId()); - for (size_t i = 0; i < klass->mData.mSkills.size(); ++i) + for (size_t i = 0; i < currentClass->mData.mSkills.size(); ++i) { - ESM::RefId minor = ESM::Skill::indexToRefId(klass->mData.mSkills[i][0]); - ESM::RefId major = ESM::Skill::indexToRefId(klass->mData.mSkills[i][1]); + ESM::RefId minor = ESM::Skill::indexToRefId(currentClass->mData.mSkills[i][0]); + ESM::RefId major = ESM::Skill::indexToRefId(currentClass->mData.mSkills[i][1]); mMinorSkill[i]->setSkillId(minor); mMajorSkill[i]->setSkillId(major); ToolTips::createSkillToolTip(mMinorSkill[i], minor); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 4ea21df00c..ddce2c5f50 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -148,11 +148,11 @@ namespace MWGui mUpdateSkillArea = true; } - void ReviewDialog::setClass(const ESM::Class& class_) + void ReviewDialog::setClass(const ESM::Class& playerClass) { - mKlass = class_; - mClassWidget->setCaption(mKlass.mName); - ToolTips::createClassToolTip(mClassWidget, mKlass); + mClass = playerClass; + mClassWidget->setCaption(mClass.mName); + ToolTips::createClassToolTip(mClassWidget, mClass); } void ReviewDialog::setBirthSign(const ESM::RefId& signId) diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 6f594c60f0..7226ad628d 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -30,7 +30,7 @@ namespace MWGui void setPlayerName(const std::string& name); void setRace(const ESM::RefId& raceId); - void setClass(const ESM::Class& class_); + void setClass(const ESM::Class& playerClass); void setBirthSign(const ESM::RefId& signId); void setHealth(const MWMechanics::DynamicStat& value); @@ -96,7 +96,7 @@ namespace MWGui std::map mSkillWidgetMap; ESM::RefId mRaceId, mBirthSignId; std::string mName; - ESM::Class mKlass; + ESM::Class mClass; std::vector mSkillWidgets; //< Skills and other information bool mUpdateSkillArea; From 084fc80efdfe3342718d3bc2d25d23c32eb1d8a3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:53:36 +0400 Subject: [PATCH 0750/2167] Use string_view for readonly string properties --- apps/openmw/mwlua/factionbindings.cpp | 3 ++- apps/openmw/mwlua/vfsbindings.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index a43cec874f..87ce6ced39 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -122,7 +122,8 @@ namespace MWLua return "ESM3_FactionRank[" + rec.mFactionId.toDebugString() + ", " + std::to_string(rec.mRankIndex + 1) + "]"; }; - rankT["name"] = sol::readonly_property([](const FactionRank& rec) { return rec.mRankName; }); + rankT["name"] + = sol::readonly_property([](const FactionRank& rec) -> std::string_view { return rec.mRankName; }); rankT["primarySkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mPrimarySkill; }); rankT["favouredSkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFavouredSkill; }); rankT["factionReaction"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFactReaction; }); diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index ad32520649..0eccb336c2 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -161,7 +161,8 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); sol::usertype handle = context.mLua->sol().new_usertype("FileHandle"); - handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; }); + handle["fileName"] + = sol::readonly_property([](const FileHandle& self) -> std::string_view { return self.mFileName; }); handle[sol::meta_function::to_string] = [](const FileHandle& self) { return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}"; }; From cc0b00a0d254e910f5ce8586d5c19ff68ac8424e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Jan 2024 12:51:48 +0100 Subject: [PATCH 0751/2167] Use settings values to declare colour settings --- apps/opencs/model/prefs/coloursetting.cpp | 2 +- apps/opencs/model/prefs/coloursetting.hpp | 3 ++- apps/opencs/model/prefs/state.cpp | 33 ++++++++++++----------- apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 9e237dd7a8..10ca9d7f68 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,7 +14,7 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index f5af627491..85e43f28bd 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -6,6 +6,7 @@ #include #include +#include #include class QMutex; @@ -30,7 +31,7 @@ namespace CSMPrefs public: explicit ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index b05a4b7473..13a2a35cc5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -126,14 +126,14 @@ void CSMPrefs::State::declare() .setRange(0, 10000); declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); declareBool(mValues->mScripts.mHighlightOccurrences, "Highlight other occurrences of selected names"); - declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); - declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); - declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); - declareColour("colour-name", "Highlight Colour: Names", QColor("grey")); - declareColour("colour-keyword", "Highlight Colour: Keywords", QColor("red")); - declareColour("colour-special", "Highlight Colour: Special Characters", QColor("darkorange")); - declareColour("colour-comment", "Highlight Colour: Comments", QColor("green")); - declareColour("colour-id", "Highlight Colour: IDs", QColor("blue")); + declareColour(mValues->mScripts.mColourHighlight, "Colour of highlighted occurrences"); + declareColour(mValues->mScripts.mColourInt, "Highlight Colour: Integer Literals"); + declareColour(mValues->mScripts.mColourFloat, "Highlight Colour: Float Literals"); + declareColour(mValues->mScripts.mColourName, "Highlight Colour: Names"); + declareColour(mValues->mScripts.mColourKeyword, "Highlight Colour: Keywords"); + declareColour(mValues->mScripts.mColourSpecial, "Highlight Colour: Special Characters"); + declareColour(mValues->mScripts.mColourComment, "Highlight Colour: Comments"); + declareColour(mValues->mScripts.mColourId, "Highlight Colour: IDs"); declareCategory("General Input"); declareBool(mValues->mGeneralInput.mCycle, "Cyclic next/previous") @@ -185,18 +185,18 @@ void CSMPrefs::State::declare() .setRange(10, 10000); declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1); declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); - declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); - declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour"); + declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the day background color. Ignored if " "the gradient option is disabled."); - declareColour("scene-bright-background-colour", "Scene Bright Background Colour", QColor(79, 87, 92, 255)); - declareColour("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneBrightBackgroundColour, "Scene Bright Background Colour"); + declareColour(mValues->mRendering.mSceneBrightGradientColour, "Scene Bright Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the bright background color. Ignored if " "the gradient option is disabled."); - declareColour("scene-night-background-colour", "Scene Night Background Colour", QColor(64, 77, 79, 255)); - declareColour("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneNightBackgroundColour, "Scene Night Background Colour"); + declareColour(mValues->mRendering.mSceneNightGradientColour, "Scene Night Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); @@ -477,13 +477,14 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(EnumSettingValue& value, con return *setting; } -CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, const QString& label, QColor default_) +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::ColourSetting* setting - = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index f3e580ea5a..ce6e89ef08 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -72,7 +72,7 @@ namespace CSMPrefs EnumSetting& declareEnum(EnumSettingValue& value, const QString& label); - ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); + ColourSetting& declareColour(Settings::SettingValue& value, const QString& label); ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); From 067957f57b7a6af55f17056181776631f3cdffcd Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 14:38:39 +0100 Subject: [PATCH 0752/2167] Use "" to quote apps/openmw includes and remove unused Using "" makes clangd to find unused includes which makes it quite easy to remove them. --- apps/launcher/utils/openalutil.cpp | 2 +- apps/openmw/mwgui/console.cpp | 3 ++- apps/openmw/mwlua/cellbindings.cpp | 2 ++ apps/openmw/mwlua/luabindings.cpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/objectbindings.cpp | 3 +++ apps/openmw/mwlua/types/activator.cpp | 4 ---- apps/openmw/mwlua/types/actor.cpp | 2 ++ apps/openmw/mwlua/types/actor.hpp | 11 +++++------ apps/openmw/mwlua/types/apparatus.cpp | 4 +--- apps/openmw/mwlua/types/armor.cpp | 4 +--- apps/openmw/mwlua/types/book.cpp | 6 ++---- apps/openmw/mwlua/types/clothing.cpp | 4 +--- apps/openmw/mwlua/types/container.cpp | 2 +- apps/openmw/mwlua/types/creature.cpp | 4 ---- apps/openmw/mwlua/types/door.cpp | 1 - apps/openmw/mwlua/types/ingredient.cpp | 5 +---- apps/openmw/mwlua/types/light.cpp | 4 +--- apps/openmw/mwlua/types/lockable.cpp | 4 ++-- apps/openmw/mwlua/types/lockpick.cpp | 4 +--- apps/openmw/mwlua/types/misc.cpp | 5 ++--- apps/openmw/mwlua/types/npc.cpp | 12 ++++++------ apps/openmw/mwlua/types/player.cpp | 13 +++++++------ apps/openmw/mwlua/types/potion.cpp | 4 +--- apps/openmw/mwlua/types/probe.cpp | 4 +--- apps/openmw/mwlua/types/repair.cpp | 2 +- apps/openmw/mwlua/types/static.cpp | 4 ---- apps/openmw/mwlua/types/types.hpp | 1 - apps/openmw/mwlua/types/weapon.cpp | 5 +---- apps/openmw/mwlua/worker.cpp | 2 +- apps/openmw/mwrender/actorspaths.hpp | 2 +- apps/openmw/mwrender/fogmanager.cpp | 2 +- apps/openmw/mwworld/cellref.cpp | 8 ++++---- apps/openmw/mwworld/groundcoverstore.cpp | 2 -- .../mwdialogue/test_keywordsearch.cpp | 1 + apps/openmw_test_suite/openmw/options.cpp | 3 ++- 36 files changed, 58 insertions(+), 84 deletions(-) diff --git a/apps/launcher/utils/openalutil.cpp b/apps/launcher/utils/openalutil.cpp index 1a332e9788..9a9ae9981b 100644 --- a/apps/launcher/utils/openalutil.cpp +++ b/apps/launcher/utils/openalutil.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include "apps/openmw/mwsound/alext.h" #include "openalutil.hpp" diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index d4553b9664..504ea560b2 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -20,6 +19,8 @@ #include #include +#include "apps/openmw/mwgui/textcolours.hpp" + #include "../mwscript/extensions.hpp" #include "../mwscript/interpretercontext.hpp" diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 62dcc69330..081df13a0e 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -43,6 +43,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/worldmodel.hpp" diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 6331bc5466..48f2f3e35d 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -21,6 +21,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/datetimemanager.hpp" diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 43092af3dc..2417e9e340 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -21,11 +21,13 @@ #include #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/scene.hpp" #include "../mwworld/worldmodel.hpp" diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index f45decea3c..748d963bdc 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -20,6 +20,9 @@ #include "../mwmechanics/creaturestats.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "luaevents.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index 5ffae0ccd7..43e03952f7 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -5,10 +5,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index a4449f6fb0..473b0d3301 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,8 +6,10 @@ #include #include +#include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/mechanicsmanager.hpp" #include "apps/openmw/mwbase/windowmanager.hpp" +#include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/actorutil.hpp" #include "apps/openmw/mwmechanics/creaturestats.hpp" #include "apps/openmw/mwmechanics/drawstate.hpp" diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 4a16b65cbf..6700b4a403 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -4,16 +4,15 @@ #include #include -#include - -#include "apps/openmw/mwworld/esmstore.hpp" -#include -#include #include #include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "../context.hpp" -#include "../object.hpp" + namespace MWLua { diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index 05eae8b2aa..282dd0669d 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index b0df533327..91a4c05d8b 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index dfdbcff1ca..ce2138127d 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -4,14 +4,12 @@ #include #include +#include #include #include #include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 74b03148cb..894748946d 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index ac2e75dea4..332cf6ac2e 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "apps/openmw/mwworld/class.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 54a3c37750..ddf90bf8c5 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -6,10 +6,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index a4185434c3..df1d10015a 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -7,7 +7,6 @@ #include #include -#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/worldmodel.hpp" namespace sol diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index 72b9f27263..abfd2329ce 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -6,10 +6,7 @@ #include #include -#include - -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index d2cf195219..5a357994a3 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/lockable.cpp b/apps/openmw/mwlua/types/lockable.cpp index e7413edef5..2569f42ee4 100644 --- a/apps/openmw/mwlua/types/lockable.cpp +++ b/apps/openmw/mwlua/types/lockable.cpp @@ -1,11 +1,11 @@ - #include "types.hpp" + #include #include #include #include -#include +#include "apps/openmw/mwworld/esmstore.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 6de1f7f670..373de4b24d 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index 9e6b2d6ae5..f83864477f 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -6,9 +6,8 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 177c0327ab..30c8fd60d9 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -5,12 +5,12 @@ #include #include -#include -#include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/mechanicsmanager.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/npcstats.hpp" +#include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "../classbindings.hpp" #include "../localscripts.hpp" diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index cef0753817..07b238fa36 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,12 +1,13 @@ #include "types.hpp" #include "../luamanagerimp.hpp" -#include -#include -#include -#include -#include -#include + +#include "apps/openmw/mwbase/inputmanager.hpp" +#include "apps/openmw/mwbase/journal.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/npcstats.hpp" +#include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/globals.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 33302a3d34..50aca6d9e7 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index 6a3784b41a..30c84326a5 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index 5e97e8c787..880a74d131 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp index 960218ca68..78f8cffd67 100644 --- a/apps/openmw/mwlua/types/static.cpp +++ b/apps/openmw/mwlua/types/static.cpp @@ -5,10 +5,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index adac372277..b52846508a 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -7,7 +7,6 @@ #include #include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index de9b2efb95..f09cd96dad 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { @@ -16,7 +14,6 @@ namespace sol { }; } -#include namespace { diff --git a/apps/openmw/mwlua/worker.cpp b/apps/openmw/mwlua/worker.cpp index e8b06cf210..193d340208 100644 --- a/apps/openmw/mwlua/worker.cpp +++ b/apps/openmw/mwlua/worker.cpp @@ -2,7 +2,7 @@ #include "luamanagerimp.hpp" -#include +#include "apps/openmw/profile.hpp" #include #include diff --git a/apps/openmw/mwrender/actorspaths.hpp b/apps/openmw/mwrender/actorspaths.hpp index d18197b974..239806576f 100644 --- a/apps/openmw/mwrender/actorspaths.hpp +++ b/apps/openmw/mwrender/actorspaths.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWRENDER_AGENTSPATHS_H #define OPENMW_MWRENDER_AGENTSPATHS_H -#include +#include "apps/openmw/mwworld/ptr.hpp" #include diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index ef8de1cb2e..b75fb507ed 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include "apps/openmw/mwworld/cell.hpp" namespace MWRender { diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index fda7157010..854348c2a8 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -8,10 +8,10 @@ #include #include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/spellutil.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp index 17bf72b5d3..f45b49babe 100644 --- a/apps/openmw/mwworld/groundcoverstore.cpp +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include "store.hpp" namespace MWWorld diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index b97f30b319..a3f0d8d3c0 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -1,4 +1,5 @@ #include "apps/openmw/mwdialogue/keywordsearch.hpp" + #include struct KeywordSearchTest : public ::testing::Test diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index d79a387645..fc89264f8c 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -1,4 +1,5 @@ -#include +#include "apps/openmw/options.hpp" + #include #include From afd6f0739ca4b30547daee82eca149ae73547357 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 26 Nov 2023 09:28:52 +0100 Subject: [PATCH 0753/2167] Format box shape indices grouping by triangle --- .../detournavigator/recastmeshbuilder.cpp | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 1684e3e542..6c15b441e7 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -189,42 +189,18 @@ namespace DetourNavigator void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { constexpr std::array indices{ { - 0, - 2, - 3, - 3, - 1, - 0, - 0, - 4, - 6, - 6, - 2, - 0, - 0, - 1, - 5, - 5, - 4, - 0, - 7, - 5, - 1, - 1, - 3, - 7, - 7, - 3, - 2, - 2, - 6, - 7, - 7, - 6, - 4, - 4, - 5, - 7, + 0, 2, 3, // triangle 0 + 3, 1, 0, // triangle 1 + 0, 4, 6, // triangle 2 + 6, 2, 0, // triangle 3 + 0, 1, 5, // triangle 4 + 5, 4, 0, // triangle 5 + 7, 5, 1, // triangle 6 + 1, 3, 7, // triangle 7 + 7, 3, 2, // triangle 8 + 2, 6, 7, // triangle 9 + 7, 6, 4, // triangle 10 + 4, 5, 7, // triangle 11 } }; for (std::size_t i = 0; i < indices.size(); i += 3) From b0b6c48a8804c9d373d043140402745deb925b8c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 26 Nov 2023 10:28:39 +0100 Subject: [PATCH 0754/2167] Add clarifying comments to detournavigator coordinates conversion functions --- components/detournavigator/settingsutils.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 10e99d002f..c9d8665d51 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -47,6 +47,7 @@ namespace DetourNavigator return position; } + // Returns value in NavMesh coordinates inline float getTileSize(const RecastSettings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; @@ -62,16 +63,19 @@ namespace DetourNavigator return static_cast(v); } + // Returns integer tile position for position in navmesh coordinates inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec2f& position) { return TilePosition(getTilePosition(settings, position.x()), getTilePosition(settings, position.y())); } + // Returns integer tile position for position in navmesh coordinates inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return getTilePosition(settings, osg::Vec2f(position.x(), position.z())); } + // Returns tile bounds in navmesh coordinates inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) { return TileBounds{ @@ -80,6 +84,7 @@ namespace DetourNavigator }; } + // Returns border size relative to cell size inline float getBorderSize(const RecastSettings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; @@ -95,6 +100,7 @@ namespace DetourNavigator return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } + // Returns tile bounds in real coordinates inline TileBounds makeRealTileBoundsWithBorder(const RecastSettings& settings, const TilePosition& tilePosition) { TileBounds result = makeTileBounds(settings, tilePosition); From a6e2ceebb832ddb43e0cb606f571a99b8477741b Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 7 Jan 2024 23:29:20 +0100 Subject: [PATCH 0755/2167] Don't clear menu UI on game load --- apps/openmw/mwgui/windowmanagerimp.cpp | 3 ++- apps/openmw/mwlua/context.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 11 ++++++++--- apps/openmw/mwlua/uibindings.cpp | 24 ++++++++++++++++++------ components/lua_ui/element.cpp | 17 +++++++++++++---- components/lua_ui/element.hpp | 20 +++++++++++++++----- components/lua_ui/util.cpp | 13 ++++++++++--- components/lua_ui/util.hpp | 3 ++- 8 files changed, 69 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 09b4bd5d5f..c6b729332a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -546,7 +546,8 @@ namespace MWGui { try { - LuaUi::clearUserInterface(); + LuaUi::clearGameInterface(); + LuaUi::clearMenuInterface(); mStatsWatcher.reset(); diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp index 68b46164d6..def38a5309 100644 --- a/apps/openmw/mwlua/context.hpp +++ b/apps/openmw/mwlua/context.hpp @@ -15,6 +15,7 @@ namespace MWLua struct Context { + bool mIsMenu; bool mIsGlobal; LuaManager* mLuaManager; LuaUtil::LuaState* mLua; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2e60d4ea2d..97e1dfae39 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -75,6 +75,7 @@ namespace MWLua void LuaManager::init() { Context context; + context.mIsMenu = false; context.mIsGlobal = true; context.mLuaManager = this; context.mLua = &mLua; @@ -86,11 +87,14 @@ namespace MWLua localContext.mIsGlobal = false; localContext.mSerializer = mLocalSerializer.get(); + Context menuContext = context; + menuContext.mIsMenu = true; + for (const auto& [name, package] : initCommonPackages(context)) mLua.addCommonPackage(name, package); for (const auto& [name, package] : initGlobalPackages(context)) mGlobalScripts.addPackage(name, package); - for (const auto& [name, package] : initMenuPackages(context)) + for (const auto& [name, package] : initMenuPackages(menuContext)) mMenuScripts.addPackage(name, package); mLocalPackages = initLocalPackages(localContext); @@ -278,7 +282,7 @@ namespace MWLua void LuaManager::clear() { - LuaUi::clearUserInterface(); + LuaUi::clearGameInterface(); mUiResourceManager.clear(); MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); mActiveLocalScripts.clear(); @@ -526,7 +530,8 @@ namespace MWLua { Log(Debug::Info) << "Reload Lua"; - LuaUi::clearUserInterface(); + LuaUi::clearGameInterface(); + LuaUi::clearMenuInterface(); MWBase::Environment::get().getWindowManager()->setConsoleMode(""); MWBase::Environment::get().getL10nManager()->dropCache(); mUiResourceManager.clear(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 3a838f4544..54d8523b4d 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -155,15 +155,27 @@ namespace MWLua } }; api["content"] = LuaUi::loadContentConstructor(context.mLua); - api["create"] = [luaManager = context.mLuaManager](const sol::table& layout) { - auto element = LuaUi::Element::make(layout); - luaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI"); + api["create"] = [context](const sol::table& layout) { + auto element + = context.mIsMenu ? LuaUi::Element::makeMenuElement(layout) : LuaUi::Element::makeGameElement(layout); + context.mLuaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI"); return element; }; api["updateAll"] = [context]() { - LuaUi::Element::forEach([](LuaUi::Element* e) { e->mUpdate = true; }); - context.mLuaManager->addAction( - []() { LuaUi::Element::forEach([](LuaUi::Element* e) { e->update(); }); }, "Update all UI elements"); + if (context.mIsMenu) + { + LuaUi::Element::forEachMenuElement([](LuaUi::Element* e) { e->mUpdate = true; }); + context.mLuaManager->addAction( + []() { LuaUi::Element::forEachMenuElement([](LuaUi::Element* e) { e->update(); }); }, + "Update all menu UI elements"); + } + else + { + LuaUi::Element::forEachGameElement([](LuaUi::Element* e) { e->mUpdate = true; }); + context.mLuaManager->addAction( + []() { LuaUi::Element::forEachGameElement([](LuaUi::Element* e) { e->update(); }); }, + "Update all game UI elements"); + } }; api["_getMenuTransparency"] = []() -> float { return Settings::gui().mMenuTransparency; }; diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index baa3438982..b74938b044 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -240,7 +240,8 @@ namespace LuaUi } } - std::map> Element::sAllElements; + std::map> Element::sGameElements; + std::map> Element::sMenuElements; Element::Element(sol::table layout) : mRoot(nullptr) @@ -251,10 +252,17 @@ namespace LuaUi { } - std::shared_ptr Element::make(sol::table layout) + std::shared_ptr Element::makeGameElement(sol::table layout) { std::shared_ptr ptr(new Element(std::move(layout))); - sAllElements[ptr.get()] = ptr; + sGameElements[ptr.get()] = ptr; + return ptr; + } + + std::shared_ptr Element::makeMenuElement(sol::table layout) + { + std::shared_ptr ptr(new Element(std::move(layout))); + sMenuElements[ptr.get()] = ptr; return ptr; } @@ -302,6 +310,7 @@ namespace LuaUi mRoot = nullptr; mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } - sAllElements.erase(this); + sGameElements.erase(this); + sMenuElements.erase(this); } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 5aadb1beab..0446f448b6 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -7,12 +7,20 @@ namespace LuaUi { struct Element { - static std::shared_ptr make(sol::table layout); + static std::shared_ptr makeGameElement(sol::table layout); + static std::shared_ptr makeMenuElement(sol::table layout); template - static void forEach(Callback callback) + static void forEachGameElement(Callback callback) { - for (auto& [e, _] : sAllElements) + for (auto& [e, _] : sGameElements) + callback(e); + } + + template + static void forEachMenuElement(Callback callback) + { + for (auto& [e, _] : sMenuElements) callback(e); } @@ -28,12 +36,14 @@ namespace LuaUi void destroy(); - friend void clearUserInterface(); + friend void clearGameInterface(); + friend void clearMenuInterface(); private: Element(sol::table layout); sol::table layout() { return LuaUtil::cast(mLayout); } - static std::map> sAllElements; + static std::map> sGameElements; + static std::map> sMenuElements; }; } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index 78237c54ea..ac5e63e405 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -44,10 +44,17 @@ namespace LuaUi return types; } - void clearUserInterface() + void clearGameInterface() { + // TODO: move settings clearing logic to Lua? clearSettings(); - while (!Element::sAllElements.empty()) - Element::sAllElements.begin()->second->destroy(); + while (!Element::sGameElements.empty()) + Element::sGameElements.begin()->second->destroy(); + } + + void clearMenuInterface() + { + while (!Element::sMenuElements.empty()) + Element::sMenuElements.begin()->second->destroy(); } } diff --git a/components/lua_ui/util.hpp b/components/lua_ui/util.hpp index 78daf6669c..2b5c4ff13c 100644 --- a/components/lua_ui/util.hpp +++ b/components/lua_ui/util.hpp @@ -10,7 +10,8 @@ namespace LuaUi const std::unordered_map& widgetTypeToName(); - void clearUserInterface(); + void clearGameInterface(); + void clearMenuInterface(); } #endif // OPENMW_LUAUI_WIDGETLIST From 3af28439d6dd4453825f69ea7c95c127d4249461 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 19:41:18 +0300 Subject: [PATCH 0756/2167] Interrupt thunder SFX indoors (bug #6402) --- CHANGELOG.md | 1 + apps/openmw/mwworld/weather.cpp | 15 ++++++++++++--- apps/openmw/mwworld/weather.hpp | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181116ca9d..aea5b6b0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile + Bug #6402: The sound of a thunderstorm does not stop playing after entering the premises Bug #6427: Enemy health bar disappears before damaging effect ends Bug #6550: Cloned body parts don't inherit texture effects Bug #6645: Enemy block sounds align with animation instead of blocked hits diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 2c9b80bc5f..655cd5aa7a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -180,7 +180,6 @@ namespace MWWorld , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) - , mThunderSoundID() , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) , mFlashBrightness(0.0f) { @@ -823,19 +822,29 @@ namespace MWWorld void WeatherManager::stopSounds() { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); if (mAmbientSound) { - MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + sndMgr->stopSound(mAmbientSound); mAmbientSound = nullptr; } mPlayingAmbientSoundID = ESM::RefId(); if (mRainSound) { - MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + sndMgr->stopSound(mRainSound); mRainSound = nullptr; } mPlayingRainSoundID = ESM::RefId(); + + for (ESM::RefId soundId : mWeatherSettings[mCurrentWeather].mThunderSoundID) + if (!soundId.empty() && sndMgr->getSoundPlaying(MWWorld::ConstPtr(), soundId)) + sndMgr->stopSound3D(MWWorld::ConstPtr(), soundId); + + if (inTransition()) + for (ESM::RefId soundId : mWeatherSettings[mNextWeather].mThunderSoundID) + if (!soundId.empty() && sndMgr->getSoundPlaying(MWWorld::ConstPtr(), soundId)) + sndMgr->stopSound3D(MWWorld::ConstPtr(), soundId); } float WeatherManager::getWindSpeed() const diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 327136a859..0643240dcd 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -162,6 +162,8 @@ namespace MWWorld // This is used for Rain and Thunderstorm ESM::RefId mRainLoopSoundID; + std::array mThunderSoundID; + // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm @@ -213,7 +215,6 @@ namespace MWWorld // non-zero values. float mThunderFrequency; float mThunderThreshold; - ESM::RefId mThunderSoundID[4]; float mFlashDecrement; float mFlashBrightness; From 9102fd4d578331ca7dd82274056a7f03be843090 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 8 Jan 2024 15:57:18 +0400 Subject: [PATCH 0757/2167] Remove unused code --- apps/openmw/mwsound/openal_output.cpp | 59 --------------------------- apps/openmw/mwsound/openal_output.hpp | 1 - apps/openmw/mwsound/sound_output.hpp | 1 - 3 files changed, 61 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 363a0d06b5..99003d5ce3 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1034,65 +1034,6 @@ namespace MWSound return ret; } - void OpenAL_Output::setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) - { - if (!mDevice || !ALC.SOFT_HRTF) - { - Log(Debug::Info) << "HRTF extension not present"; - return; - } - - LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; - getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); - - LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; - getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); - - std::vector attrs; - attrs.reserve(15); - - attrs.push_back(ALC_HRTF_SOFT); - attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE - : hrtfmode == HrtfMode::Enable ? ALC_TRUE - : - /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); - if (!hrtfname.empty()) - { - ALCint index = -1; - ALCint num_hrtf; - alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); - for (ALCint i = 0; i < num_hrtf; ++i) - { - const ALCchar* entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); - if (hrtfname == entry) - { - index = i; - break; - } - } - - if (index < 0) - Log(Debug::Warning) << "Failed to find HRTF name \"" << hrtfname << "\", using default"; - else - { - attrs.push_back(ALC_HRTF_ID_SOFT); - attrs.push_back(index); - } - } - attrs.push_back(0); - alcResetDeviceSOFT(mDevice, attrs.data()); - - ALCint hrtf_state; - alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); - if (!hrtf_state) - Log(Debug::Info) << "HRTF disabled"; - else - { - const ALCchar* hrtf = alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT); - Log(Debug::Info) << "Enabled HRTF " << hrtf; - } - } - std::pair OpenAL_Output::loadSound(const std::string& fname) { getALError(); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index eed23ac659..7636f7bda9 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -84,7 +84,6 @@ namespace MWSound void deinit() override; std::vector enumerateHrtf() override; - void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) override; std::pair loadSound(const std::string& fname) override; size_t unloadSound(Sound_Handle data) override; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 8eec20bcba..df95f0909e 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -38,7 +38,6 @@ namespace MWSound virtual void deinit() = 0; virtual std::vector enumerateHrtf() = 0; - virtual void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) = 0; virtual std::pair loadSound(const std::string& fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; From d1a7dfee877c4c2749f3c43e7f86087ccab68011 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 8 Jan 2024 16:19:40 +0400 Subject: [PATCH 0758/2167] Add missing assertion --- components/lua_ui/element.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index baa3438982..84383f89e1 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -280,6 +280,7 @@ namespace LuaUi auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); mRoot = createWidget(layout(), 0); + assert(it != children.end()); *it = mRoot; parent->setChildren(children); mRoot->updateCoord(); From 6756b8613dce96fe72a1f98e0ced7b9fa4808e16 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 17:18:08 +0100 Subject: [PATCH 0759/2167] Restore beast race animations --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 1559ebdd5d..0a837cbe11 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -510,7 +510,7 @@ namespace MWRender if (!isWerewolf) addAnimSource(base, smodel); - if (smodel != defaultSkeleton && base != defaultSkeleton) + if (!isBase || (isBase && base != defaultSkeleton)) addAnimSource(defaultSkeleton, smodel); if (!isBase) From 5043e67e06942bcf40d12bc9a36b7508497d8d39 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 13:22:11 +0300 Subject: [PATCH 0760/2167] Replicate recent save loading prompt behavior (bug #7617) --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 38 ++++++++++++++++++++----- apps/openmw/mwstate/statemanagerimp.hpp | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..00795b34cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place + Bug #7617: The death prompt asks the player if they wanted to load the character's last created save Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89b63c35e4..4358c4094e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -64,6 +64,7 @@ void MWState::StateManager::cleanup(bool force) mState = State_NoGame; mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; + mLastSavegame.clear(); MWMechanics::CreatureStats::cleanup(); } @@ -119,14 +120,27 @@ void MWState::StateManager::askLoadRecent() if (!mAskLoadRecent) { - const MWState::Character* character = getCurrentCharacter(); - if (!character || character->begin() == character->end()) // no saves + if (mLastSavegame.empty()) // no saves { MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); } else { - MWState::Slot lastSave = *character->begin(); + std::string saveName = Files::pathToUnicodeString(mLastSavegame.filename()); + // Assume the last saved game belongs to the current character's slot list. + const Character* character = getCurrentCharacter(); + if (character) + { + for (const auto& slot : *character) + { + if (slot.mPath == mLastSavegame) + { + saveName = slot.mProfile.mDescription; + break; + } + } + } + std::vector buttons; buttons.emplace_back("#{Interface:Yes}"); buttons.emplace_back("#{Interface:No}"); @@ -134,7 +148,7 @@ void MWState::StateManager::askLoadRecent() = MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "AskLoadLastSave"); std::string_view tag = "%s"; size_t pos = message.find(tag); - message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + message.replace(pos, tag.length(), saveName); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); mAskLoadRecent = true; } @@ -322,6 +336,7 @@ void MWState::StateManager::saveGame(std::string_view description, const Slot* s throw std::runtime_error("Write operation failed (file stream)"); Settings::saves().mCharacter.set(Files::pathToUnicodeString(slot->mPath.parent_path().filename())); + mLastSavegame = slot->mPath; const auto finish = std::chrono::steady_clock::now(); @@ -561,6 +576,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file if (character) Settings::saves().mCharacter.set(Files::pathToUnicodeString(character->getPath().filename())); + mLastSavegame = filepath; MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->saveLoaded(); @@ -639,7 +655,15 @@ void MWState::StateManager::quickLoad() void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) { + const std::filesystem::path savePath = slot->mPath; mCharacterManager.deleteSlot(character, slot); + if (mLastSavegame == savePath) + { + if (character->begin() != character->end()) + mLastSavegame = character->begin()->mPath; + else + mLastSavegame.clear(); + } } MWState::Character* MWState::StateManager::getCurrentCharacter() @@ -670,9 +694,9 @@ void MWState::StateManager::update(float duration) { mAskLoadRecent = false; // Load last saved game for current character - - MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, lastSave.mPath); + // loadGame resets the game state along with mLastSavegame so we want to preserve it + const std::filesystem::path filePath = std::move(mLastSavegame); + loadGame(curCharacter, filePath); } else if (iButton == 1) { diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index c293209f34..a76b829e38 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -17,6 +17,7 @@ namespace MWState State mState; CharacterManager mCharacterManager; double mTimePlayed; + std::filesystem::path mLastSavegame; private: void cleanup(bool force = false); From 164b6309a76f5983a7267a641dfc45847e15fe8d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 18:08:17 +0100 Subject: [PATCH 0761/2167] Improve legibility --- apps/openmw/mwrender/npcanimation.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0a837cbe11..84522ee86e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -510,11 +510,15 @@ namespace MWRender if (!isWerewolf) addAnimSource(base, smodel); - if (!isBase || (isBase && base != defaultSkeleton)) - addAnimSource(defaultSkeleton, smodel); - if (!isBase) + { + addAnimSource(defaultSkeleton, smodel); addAnimSource(smodel, smodel); + } + else if (base != defaultSkeleton) + { + addAnimSource(defaultSkeleton, smodel); + } if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); From 66f5d70550600027425c23c79864c21f5b959cf5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Jan 2024 12:57:04 +0100 Subject: [PATCH 0762/2167] Use settings values to declare shortcut settings --- apps/opencs/model/prefs/shortcutmanager.cpp | 4 +- apps/opencs/model/prefs/shortcutmanager.hpp | 6 +- apps/opencs/model/prefs/shortcutsetting.cpp | 2 +- apps/opencs/model/prefs/shortcutsetting.hpp | 3 +- apps/opencs/model/prefs/state.cpp | 281 ++++++++++---------- apps/opencs/model/prefs/state.hpp | 2 +- 6 files changed, 148 insertions(+), 150 deletions(-) diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 3089281c91..d6686d31d9 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -43,7 +43,7 @@ namespace CSMPrefs mEventHandler->removeShortcut(shortcut); } - bool ShortcutManager::getSequence(const std::string& name, QKeySequence& sequence) const + bool ShortcutManager::getSequence(std::string_view name, QKeySequence& sequence) const { SequenceMap::const_iterator item = mSequences.find(name); if (item != mSequences.end()) @@ -56,7 +56,7 @@ namespace CSMPrefs return false; } - void ShortcutManager::setSequence(const std::string& name, const QKeySequence& sequence) + void ShortcutManager::setSequence(std::string_view name, const QKeySequence& sequence) { // Add to map/modify SequenceMap::iterator item = mSequences.find(name); diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp index 87d2f2256c..0cfe3ad86a 100644 --- a/apps/opencs/model/prefs/shortcutmanager.hpp +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -28,8 +28,8 @@ namespace CSMPrefs /// The shortcut class will do this automatically void removeShortcut(Shortcut* shortcut); - bool getSequence(const std::string& name, QKeySequence& sequence) const; - void setSequence(const std::string& name, const QKeySequence& sequence); + bool getSequence(std::string_view name, QKeySequence& sequence) const; + void setSequence(std::string_view name, const QKeySequence& sequence); bool getModifier(const std::string& name, int& modifier) const; void setModifier(std::string_view name, int modifier); @@ -50,7 +50,7 @@ namespace CSMPrefs private: // Need a multimap in case multiple shortcuts share the same name typedef std::multimap> ShortcutMap; - typedef std::map SequenceMap; + typedef std::map> SequenceMap; typedef std::map> ModifierMap; typedef std::map NameMap; typedef std::map KeyMap; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 33bb521290..bdaf3a0fda 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -19,7 +19,7 @@ namespace CSMPrefs { ShortcutSetting::ShortcutSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 84857a9bc7..bcb7b89488 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -2,6 +2,7 @@ #define CSM_PREFS_SHORTCUTSETTING_H #include +#include #include #include @@ -24,7 +25,7 @@ namespace CSMPrefs public: explicit ShortcutSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 13a2a35cc5..c11996a6ea 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -250,157 +250,154 @@ void CSMPrefs::State::declare() declareCategory("Key Bindings"); declareSubcategory("Document"); - declareShortcut("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); - declareShortcut("document-file-newaddon", "New Addon", QKeySequence()); - declareShortcut("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); - declareShortcut("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); - declareShortcut("document-help-help", "Help", QKeySequence(Qt::Key_F1)); - declareShortcut("document-help-tutorial", "Tutorial", QKeySequence()); - declareShortcut("document-file-verify", "Verify", QKeySequence()); - declareShortcut("document-file-merge", "Merge", QKeySequence()); - declareShortcut("document-file-errorlog", "Open Load Error Log", QKeySequence()); - declareShortcut("document-file-metadata", "Meta Data", QKeySequence()); - declareShortcut("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); - declareShortcut("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); - declareShortcut("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); - declareShortcut("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); - declareShortcut("document-edit-preferences", "Open Preferences", QKeySequence()); - declareShortcut("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); - declareShortcut("document-view-newview", "New View", QKeySequence()); - declareShortcut("document-view-statusbar", "Toggle Status Bar", QKeySequence()); - declareShortcut("document-view-filters", "Open Filter List", QKeySequence()); - declareShortcut("document-world-regions", "Open Region List", QKeySequence()); - declareShortcut("document-world-cells", "Open Cell List", QKeySequence()); - declareShortcut("document-world-referencables", "Open Object List", QKeySequence()); - declareShortcut("document-world-references", "Open Instance List", QKeySequence()); - declareShortcut("document-world-lands", "Open Lands List", QKeySequence()); - declareShortcut("document-world-landtextures", "Open Land Textures List", QKeySequence()); - declareShortcut("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); - declareShortcut("document-world-regionmap", "Open Region Map", QKeySequence()); - declareShortcut("document-mechanics-globals", "Open Global List", QKeySequence()); - declareShortcut("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); - declareShortcut("document-mechanics-scripts", "Open Script List", QKeySequence()); - declareShortcut("document-mechanics-spells", "Open Spell List", QKeySequence()); - declareShortcut("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); - declareShortcut("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); - declareShortcut("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); - declareShortcut("document-character-skills", "Open Skill List", QKeySequence()); - declareShortcut("document-character-classes", "Open Class List", QKeySequence()); - declareShortcut("document-character-factions", "Open Faction List", QKeySequence()); - declareShortcut("document-character-races", "Open Race List", QKeySequence()); - declareShortcut("document-character-birthsigns", "Open Birthsign List", QKeySequence()); - declareShortcut("document-character-topics", "Open Topic List", QKeySequence()); - declareShortcut("document-character-journals", "Open Journal List", QKeySequence()); - declareShortcut("document-character-topicinfos", "Open Topic Info List", QKeySequence()); - declareShortcut("document-character-journalinfos", "Open Journal Info List", QKeySequence()); - declareShortcut("document-character-bodyparts", "Open Body Part List", QKeySequence()); - declareShortcut("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); - declareShortcut("document-assets-sounds", "Open Sound Asset List", QKeySequence()); - declareShortcut("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); - declareShortcut("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); - declareShortcut("document-assets-icons", "Open Icon Asset List", QKeySequence()); - declareShortcut("document-assets-music", "Open Music Asset List", QKeySequence()); - declareShortcut("document-assets-soundres", "Open Sound File List", QKeySequence()); - declareShortcut("document-assets-textures", "Open Texture Asset List", QKeySequence()); - declareShortcut("document-assets-videos", "Open Video Asset List", QKeySequence()); - declareShortcut("document-debug-run", "Run Debug", QKeySequence()); - declareShortcut("document-debug-shutdown", "Stop Debug", QKeySequence()); - declareShortcut("document-debug-profiles", "Debug Profiles", QKeySequence()); - declareShortcut("document-debug-runlog", "Open Run Log", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mDocumentFileNewgame, "New Game"); + declareShortcut(mValues->mKeyBindings.mDocumentFileNewaddon, "New Addon"); + declareShortcut(mValues->mKeyBindings.mDocumentFileOpen, "Open"); + declareShortcut(mValues->mKeyBindings.mDocumentFileSave, "Save"); + declareShortcut(mValues->mKeyBindings.mDocumentHelpHelp, "Help"); + declareShortcut(mValues->mKeyBindings.mDocumentHelpTutorial, "Tutorial"); + declareShortcut(mValues->mKeyBindings.mDocumentFileVerify, "Verify"); + declareShortcut(mValues->mKeyBindings.mDocumentFileMerge, "Merge"); + declareShortcut(mValues->mKeyBindings.mDocumentFileErrorlog, "Open Load Error Log"); + declareShortcut(mValues->mKeyBindings.mDocumentFileMetadata, "Meta Data"); + declareShortcut(mValues->mKeyBindings.mDocumentFileClose, "Close Document"); + declareShortcut(mValues->mKeyBindings.mDocumentFileExit, "Exit Application"); + declareShortcut(mValues->mKeyBindings.mDocumentEditUndo, "Undo"); + declareShortcut(mValues->mKeyBindings.mDocumentEditRedo, "Redo"); + declareShortcut(mValues->mKeyBindings.mDocumentEditPreferences, "Open Preferences"); + declareShortcut(mValues->mKeyBindings.mDocumentEditSearch, "Search"); + declareShortcut(mValues->mKeyBindings.mDocumentViewNewview, "New View"); + declareShortcut(mValues->mKeyBindings.mDocumentViewStatusbar, "Toggle Status Bar"); + declareShortcut(mValues->mKeyBindings.mDocumentViewFilters, "Open Filter List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldRegions, "Open Region List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldCells, "Open Cell List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldReferencables, "Open Object List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldReferences, "Open Instance List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldLands, "Open Lands List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldLandtextures, "Open Land Textures List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldPathgrid, "Open Pathgrid List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldRegionmap, "Open Region Map"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsGlobals, "Open Global List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsGamesettings, "Open Game Settings"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsScripts, "Open Script List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsSpells, "Open Spell List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsEnchantments, "Open Enchantment List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsMagiceffects, "Open Magic Effect List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsStartscripts, "Open Start Script List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterSkills, "Open Skill List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterClasses, "Open Class List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterFactions, "Open Faction List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterRaces, "Open Race List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterBirthsigns, "Open Birthsign List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterTopics, "Open Topic List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterJournals, "Open Journal List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterTopicinfos, "Open Topic Info List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterJournalinfos, "Open Journal Info List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterBodyparts, "Open Body Part List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsReload, "Reload Assets"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSounds, "Open Sound Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSoundgens, "Open Sound Generator List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsMeshes, "Open Mesh Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsIcons, "Open Icon Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsMusic, "Open Music Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSoundres, "Open Sound File List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsTextures, "Open Texture Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsVideos, "Open Video Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugRun, "Run Debug"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugShutdown, "Stop Debug"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugProfiles, "Debug Profiles"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugRunlog, "Open Run Log"); declareSubcategory("Table"); - declareShortcut("table-edit", "Edit Record", QKeySequence()); - declareShortcut("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); - declareShortcut("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); - declareShortcut("touch-record", "Touch Record", QKeySequence()); - declareShortcut("table-revert", "Revert Record", QKeySequence()); - declareShortcut("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); - declareShortcut("table-moveup", "Move Record Up", QKeySequence()); - declareShortcut("table-movedown", "Move Record Down", QKeySequence()); - declareShortcut("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); - declareShortcut("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); - declareShortcut("table-extendeddelete", "Extended Record Deletion", QKeySequence()); - declareShortcut("table-extendedrevert", "Extended Record Revertion", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mTableEdit, "Edit Record"); + declareShortcut(mValues->mKeyBindings.mTableAdd, "Add Row/Record"); + declareShortcut(mValues->mKeyBindings.mTableClone, "Clone Record"); + declareShortcut(mValues->mKeyBindings.mTouchRecord, "Touch Record"); + declareShortcut(mValues->mKeyBindings.mTableRevert, "Revert Record"); + declareShortcut(mValues->mKeyBindings.mTableRemove, "Remove Row/Record"); + declareShortcut(mValues->mKeyBindings.mTableMoveup, "Move Record Up"); + declareShortcut(mValues->mKeyBindings.mTableMovedown, "Move Record Down"); + declareShortcut(mValues->mKeyBindings.mTableView, "View Record"); + declareShortcut(mValues->mKeyBindings.mTablePreview, "Preview Record"); + declareShortcut(mValues->mKeyBindings.mTableExtendeddelete, "Extended Record Deletion"); + declareShortcut(mValues->mKeyBindings.mTableExtendedrevert, "Extended Record Revertion"); declareSubcategory("Report Table"); - declareShortcut("reporttable-show", "Show Report", QKeySequence()); - declareShortcut("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); - declareShortcut("reporttable-replace", "Replace Report", QKeySequence()); - declareShortcut("reporttable-refresh", "Refresh Report", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mReporttableShow, "Show Report"); + declareShortcut(mValues->mKeyBindings.mReporttableRemove, "Remove Report"); + declareShortcut(mValues->mKeyBindings.mReporttableReplace, "Replace Report"); + declareShortcut(mValues->mKeyBindings.mReporttableRefresh, "Refresh Report"); declareSubcategory("Scene"); - declareShortcut("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); - declareShortcut("scene-navi-secondary", "Camera Translation From Mouse Movement", - QKeySequence(Qt::ControlModifier | (int)Qt::LeftButton)); - declareShortcut("scene-open-primary", "Primary Open", QKeySequence(Qt::ShiftModifier | (int)Qt::LeftButton)); - declareShortcut("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); - declareShortcut("scene-edit-secondary", "Secondary Edit", QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); - declareShortcut("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); - declareShortcut( - "scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); - declareShortcut( - "scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton)); + declareShortcut(mValues->mKeyBindings.mSceneNaviPrimary, "Camera Rotation From Mouse Movement"); + declareShortcut(mValues->mKeyBindings.mSceneNaviSecondary, "Camera Translation From Mouse Movement"); + declareShortcut(mValues->mKeyBindings.mSceneOpenPrimary, "Primary Open"); + declareShortcut(mValues->mKeyBindings.mSceneEditPrimary, "Primary Edit"); + declareShortcut(mValues->mKeyBindings.mSceneEditSecondary, "Secondary Edit"); + declareShortcut(mValues->mKeyBindings.mSceneSelectPrimary, "Primary Select"); + declareShortcut(mValues->mKeyBindings.mSceneSelectSecondary, "Secondary Select"); + declareShortcut(mValues->mKeyBindings.mSceneSelectTertiary, "Tertiary Select"); declareModifier(mValues->mKeyBindings.mSceneSpeedModifier, "Speed Modifier"); - declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); - declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); - declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); - declareShortcut("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); - declareShortcut("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); - declareShortcut("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); - declareShortcut("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); - declareShortcut("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); - declareShortcut("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); - declareShortcut("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); - declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); - declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); - declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); - declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); - declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space)); - declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H)); - declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H)); - declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1)); - declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1)); - declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2)); - declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2)); - declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3)); - declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3)); - declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4)); - declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4)); - declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5)); - declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5)); - declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6)); - declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6)); - declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7)); - declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7)); - declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8)); - declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8)); - declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9)); - declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9)); - declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0)); - declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0)); + declareShortcut(mValues->mKeyBindings.mSceneDelete, "Delete Instance"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropTerrain, "Drop to terrain level"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropCollision, "Drop to collision"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropTerrainSeparately, "Drop to terrain level separately"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropCollisionSeparately, "Drop to collision separately"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamCell, "Load Camera Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamEastcell, "Load East Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamNorthcell, "Load North Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamWestcell, "Load West Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamSouthcell, "Load South Cell"); + declareShortcut(mValues->mKeyBindings.mSceneEditAbort, "Abort"); + declareShortcut(mValues->mKeyBindings.mSceneFocusToolbar, "Toggle Toolbar Focus"); + declareShortcut(mValues->mKeyBindings.mSceneRenderStats, "Debug Rendering Stats"); + declareShortcut(mValues->mKeyBindings.mSceneDuplicate, "Duplicate Instance"); + declareShortcut(mValues->mKeyBindings.mSceneClearSelection, "Clear Selection"); + declareShortcut(mValues->mKeyBindings.mSceneUnhideAll, "Unhide All Objects"); + declareShortcut(mValues->mKeyBindings.mSceneToggleVisibility, "Toggle Selection Visibility"); + declareShortcut(mValues->mKeyBindings.mSceneGroup0, "Selection Group 0"); + declareShortcut(mValues->mKeyBindings.mSceneSave0, "Save Group 0"); + declareShortcut(mValues->mKeyBindings.mSceneGroup1, "Select Group 1"); + declareShortcut(mValues->mKeyBindings.mSceneSave1, "Save Group 1"); + declareShortcut(mValues->mKeyBindings.mSceneGroup2, "Select Group 2"); + declareShortcut(mValues->mKeyBindings.mSceneSave2, "Save Group 2"); + declareShortcut(mValues->mKeyBindings.mSceneGroup3, "Select Group 3"); + declareShortcut(mValues->mKeyBindings.mSceneSave3, "Save Group 3"); + declareShortcut(mValues->mKeyBindings.mSceneGroup4, "Select Group 4"); + declareShortcut(mValues->mKeyBindings.mSceneSave4, "Save Group 4"); + declareShortcut(mValues->mKeyBindings.mSceneGroup5, "Selection Group 5"); + declareShortcut(mValues->mKeyBindings.mSceneSave5, "Save Group 5"); + declareShortcut(mValues->mKeyBindings.mSceneGroup6, "Selection Group 6"); + declareShortcut(mValues->mKeyBindings.mSceneSave6, "Save Group 6"); + declareShortcut(mValues->mKeyBindings.mSceneGroup7, "Selection Group 7"); + declareShortcut(mValues->mKeyBindings.mSceneSave7, "Save Group 7"); + declareShortcut(mValues->mKeyBindings.mSceneGroup8, "Selection Group 8"); + declareShortcut(mValues->mKeyBindings.mSceneSave8, "Save Group 8"); + declareShortcut(mValues->mKeyBindings.mSceneGroup9, "Selection Group 9"); + declareShortcut(mValues->mKeyBindings.mSceneSave9, "Save Group 9"); declareSubcategory("1st/Free Camera"); - declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); - declareShortcut("free-backward", "Backward", QKeySequence(Qt::Key_S)); - declareShortcut("free-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut("free-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward"); + declareShortcut(mValues->mKeyBindings.mFreeBackward, "Backward"); + declareShortcut(mValues->mKeyBindings.mFreeLeft, "Left"); + declareShortcut(mValues->mKeyBindings.mFreeRight, "Right"); + declareShortcut(mValues->mKeyBindings.mFreeRollLeft, "Roll Left"); + declareShortcut(mValues->mKeyBindings.mFreeRollRight, "Roll Right"); + declareShortcut(mValues->mKeyBindings.mFreeSpeedMode, "Toggle Speed Mode"); declareSubcategory("Orbit Camera"); - declareShortcut("orbit-up", "Up", QKeySequence(Qt::Key_W)); - declareShortcut("orbit-down", "Down", QKeySequence(Qt::Key_S)); - declareShortcut("orbit-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut("orbit-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); - declareShortcut("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); + declareShortcut(mValues->mKeyBindings.mOrbitUp, "Up"); + declareShortcut(mValues->mKeyBindings.mOrbitDown, "Down"); + declareShortcut(mValues->mKeyBindings.mOrbitLeft, "Left"); + declareShortcut(mValues->mKeyBindings.mOrbitRight, "Right"); + declareShortcut(mValues->mKeyBindings.mOrbitRollLeft, "Roll Left"); + declareShortcut(mValues->mKeyBindings.mOrbitRollRight, "Roll Right"); + declareShortcut(mValues->mKeyBindings.mOrbitSpeedMode, "Toggle Speed Mode"); + declareShortcut(mValues->mKeyBindings.mOrbitCenterSelection, "Center On Selected"); declareSubcategory("Script Editor"); - declareShortcut("script-editor-comment", "Comment Selection", QKeySequence()); - declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mScriptEditorComment, "Comment Selection"); + declareShortcut(mValues->mKeyBindings.mScriptEditorUncomment, "Uncomment Selection"); declareCategory("Models"); declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); @@ -492,7 +489,7 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( } CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( - const std::string& key, const QString& label, const QKeySequence& default_) + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -500,11 +497,11 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( // Setup with actual data QKeySequence sequence; - getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), sequence); - getShortcutManager().setSequence(key, sequence); + getShortcutManager().convertFromString(value, sequence); + getShortcutManager().setSequence(value.mName, sequence); CSMPrefs::ShortcutSetting* setting - = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index ce6e89ef08..821322d586 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -74,7 +74,7 @@ namespace CSMPrefs ColourSetting& declareColour(Settings::SettingValue& value, const QString& label); - ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); + ShortcutSetting& declareShortcut(Settings::SettingValue& value, const QString& label); StringSetting& declareString(Settings::SettingValue& value, const QString& label); From 9e3b427a985aff66f18a5fa05be610cd154b4e5c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Jan 2024 22:56:58 +0300 Subject: [PATCH 0763/2167] Unbreak un-paging (#7768) --- apps/openmw/mwworld/scene.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 72b2dc3022..80f52f2375 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,11 +111,16 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); + // Null node meant to distinguish objects that aren't in the scene from paged objects + // TODO: find a more clever way to make paging exclusion more reliable? + static const osg::ref_ptr pagedNode( + new SceneUtil::PositionAttitudeTransform); + ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else - ptr.getRefData().setBaseNode(nullptr); + ptr.getRefData().setBaseNode(pagedNode); setNodeRotation(ptr, rendering, rotation); if (ptr.getClass().useAnim()) From 539dc1ee43de7de947b68b5722afd6a10b3c9d43 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 8 Jan 2024 21:57:59 +0100 Subject: [PATCH 0764/2167] Remove confusing addPlayerQuestBindings method --- apps/openmw/mwlua/types/player.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 3acfec12b7..1be84b04fa 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -41,7 +41,7 @@ namespace MWLua throw std::runtime_error("The argument must be a player!"); } - void addPlayerQuestBindings(sol::table& player, const Context& context) + void addPlayerBindings(sol::table player, const Context& context) { MWBase::Journal* const journal = MWBase::Environment::get().getJournal(); @@ -161,10 +161,7 @@ namespace MWLua verifyPlayer(player); context.mLuaEvents->addMenuEvent({ std::move(eventName), LuaUtil::serialize(eventData) }); }; - } - void addPlayerBindings(sol::table player, const Context& context) - { player["getCrimeLevel"] = [](const Object& o) -> int { const MWWorld::Class& cls = o.ptr().getClass(); return cls.getNpcStats(o.ptr()).getBounty(); @@ -172,6 +169,5 @@ namespace MWLua player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; - addPlayerQuestBindings(player, context); } } From a3fd1b3d6f944cca94873c8be47cdf02fc1480e8 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 8 Jan 2024 21:58:07 +0100 Subject: [PATCH 0765/2167] Document menu scripts --- .../lua-scripting/engine_handlers.rst | 11 ++++ .../reference/lua-scripting/openmw_menu.rst | 7 ++ .../reference/lua-scripting/overview.rst | 10 +++ files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/menu.lua | 65 +++++++++++++++++++ files/lua_api/openmw/types.lua | 6 ++ 6 files changed, 100 insertions(+) create mode 100644 docs/source/reference/lua-scripting/openmw_menu.rst create mode 100644 files/lua_api/openmw/menu.lua diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 10ed3ee555..6ef9846d2e 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -127,3 +127,14 @@ Engine handler is a function defined by a script, that can be called by the engi - | User entered `command` in in-game console. Called if either | `mode` is not default or `command` starts with prefix `lua`. +**Only for menu scripts** + +.. list-table:: + :widths: 20 80 + * - onStateChanged() + - | Called whenever the current game changes + | (i. e. the result of `getState `_ changes) + * - | onConsoleCommand( + | mode, command, selectedObject) + - | User entered `command` in in-game console. Called if either + | `mode` is not default or `command` starts with prefix `lua`. diff --git a/docs/source/reference/lua-scripting/openmw_menu.rst b/docs/source/reference/lua-scripting/openmw_menu.rst new file mode 100644 index 0000000000..587e4337e0 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_menu.rst @@ -0,0 +1,7 @@ +Package openmw.menu +====================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_ambient.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 5515351e20..ec5ab7338c 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -70,6 +70,9 @@ Cell Global scripts Lua scripts that are not attached to any game object and are always active. Global scripts can not be started or stopped during a game session. Lists of global scripts are defined by `omwscripts` files, which should be :ref:`registered ` in `openmw.cfg`. +Menu scripts + Lua scripts that are ran regardless of a game being loaded. They can be used to add features to the main menu and manage save files. + Local scripts Lua scripts that are attached to some game object. A local script is active only if the object it is attached to is in an active cell. There are no limitations to the number of local scripts on one object. Local scripts can be attached to (or detached from) any object at any moment by a global script. In some cases inactive local scripts still can run code (for example during saving and loading), but while inactive they can not see nearby objects. @@ -173,6 +176,7 @@ The order of lines determines the script load order (i.e. script priorities). Possible flags are: - ``GLOBAL`` - a global script; always active, can not be stopped; +- ``MENU`` - a menu script; always active, even before a game is loaded - ``CUSTOM`` - dynamic local script that can be started or stopped by a global script; - ``PLAYER`` - an auto started player script; - ``ACTIVATOR`` - a local script that will be automatically attached to any activator; @@ -474,6 +478,12 @@ This is another kind of script-to-script interactions. The differences: - Event handlers can not return any data to the sender. - Event handlers have a single argument `eventData` (must be :ref:`serializable `) +There are a few methods for sending events: + +- `core.sendGlovalEvent `_ to send events to global scripts +- `GameObject:sendEvent `_ to send events to local scripts attached to a game object +- `types.Player.sendMenuEvent `_ to send events to menu scripts of the given player + Events are the main way of interacting between local and global scripts. They are not recommended for interactions between two global scripts, because in this case interfaces are more convenient. diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 96409e803e..06c90e4633 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -21,6 +21,7 @@ set(LUA_API_FILES openmw/util.lua openmw/vfs.lua openmw/world.lua + openmw/menu.lua ) foreach (f ${LUA_API_FILES}) diff --git a/files/lua_api/openmw/menu.lua b/files/lua_api/openmw/menu.lua new file mode 100644 index 0000000000..c1a1a65a62 --- /dev/null +++ b/files/lua_api/openmw/menu.lua @@ -0,0 +1,65 @@ +--- +-- `openmw.menu` can be used only in menu scripts. +-- @module menu +-- @usage local menu = require('openmw.menu') + +--- +-- @type STATE +-- @field [parent=#STATE] NoGame +-- @field [parent=#STATE] Running +-- @field [parent=#STATE] Ended + + +--- +-- Current game state +-- @function [parent=#menu] getState +-- @return #STATE + +--- +-- Start a new game +-- @function [parent=#menu] newGame + +--- +-- Load the game from a save slot +-- @function [parent=#menu] loadGame +-- @param #string directory name of the save directory (e. g. character) +-- @param #string slotName name of the save slot + +--- +-- Delete a saved game +-- @function [parent=#menu] deleteGame +-- @param #string directory name of the save directory (e. g. character) +-- @param #string slotName name of the save slot + +--- +-- Current save directory +-- @function [parent=#menu] getCurrentSaveDir +-- @return #string + +--- +-- Save the game +-- @function [parent=#menu] saveGame +-- @param #string description human readable description of the save +-- @param #string slotName name of the save slot + +--- +-- @type SaveInfo +-- @field #string description +-- @field #string playerName +-- @field #string playerLevel +-- @field #list<#string> contentFiles + +--- +-- List of all saves for the given directory +-- @function [parent=#menu] getSaves +-- @param #string directory name of the save directory (e. g. character) +-- @return #list<#SaveInfo> + +--- +-- List of all available saves +-- @function [parent=#menu] getAllSaves +-- @return #list<#SaveInfo> + +--- +-- Exit the game +-- @function [parent=#menu] quit diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a350d4dbea..6abb209b2c 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1035,6 +1035,12 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- +-- Send an event to menu scripts. +-- @function [parent=#core] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData -------------------------------------------------------------------------------- -- @{#Armor} functions From 88049ffac65f5e2308f57a5b4d57e70d8d0daefd Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 8 Jan 2024 21:14:44 +0100 Subject: [PATCH 0766/2167] Document packages available in menu scripts --- files/lua_api/openmw/ambient.lua | 2 +- files/lua_api/openmw/core.lua | 4 ++-- files/lua_api/openmw/input.lua | 2 +- files/lua_api/openmw/ui.lua | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 917ec86c85..9601ecc076 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -1,6 +1,6 @@ --- -- `openmw.ambient` controls background sounds, specific to given player (2D-sounds). --- Can be used only by local scripts, that are attached to a player. +-- Can be used only by menu scripts and local scripts, that are attached to a player. -- @module ambient -- @usage local ambient = require('openmw.ambient') diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 890532fc13..a4d8ecee45 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -1,6 +1,6 @@ --- --- `openmw.core` defines functions and types that are available in both local --- and global scripts. +-- `openmw.core` defines functions and types that are available in local, +-- global and menu scripts. -- @module core -- @usage local core = require('openmw.core') diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 0a85602bcc..a34bc040da 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -1,5 +1,5 @@ --- --- `openmw.input` can be used only in scripts attached to a player. +-- `openmw.input` can be used only in menu scripts and scripts attached to a player. -- @module input -- @usage local input = require('openmw.input') diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 451f919077..8582996c4f 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -1,6 +1,6 @@ --- -- `openmw.ui` controls user interface. --- Can be used only by local scripts, that are attached to a player. +-- Can be used only by menu scripts and local scripts, that are attached to a player. -- @module ui -- @usage -- local ui = require('openmw.ui') From 9b54f479e8fadd24bf45fe58579e86cd8e47f8cb Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 8 Jan 2024 21:55:09 +0100 Subject: [PATCH 0767/2167] Move settings rendering to Menu scripts --- files/data/builtin.omwscripts | 2 ++ files/data/scripts/omw/settings/menu.lua | 19 +++++++++++++++++++ files/data/scripts/omw/settings/player.lua | 15 ++++++++------- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 files/data/scripts/omw/settings/menu.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 30f6941059..e35e86aaaf 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,9 +1,11 @@ # UI framework PLAYER: scripts/omw/mwui/init.lua +MENU: scripts/omw/mwui/init.lua # Settings framework GLOBAL: scripts/omw/settings/global.lua PLAYER: scripts/omw/settings/player.lua +MENU: scripts/omw/settings/menu.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua new file mode 100644 index 0000000000..6fd26d40d2 --- /dev/null +++ b/files/data/scripts/omw/settings/menu.lua @@ -0,0 +1,19 @@ +local common = require('scripts.omw.settings.common') +local render = require('scripts.omw.settings.render') + +require('scripts.omw.settings.renderers')(render.registerRenderer) + +return { + interfaceName = 'Settings', + interface = { + version = 0, + registerPage = render.registerPage, + registerRenderer = render.registerRenderer, + registerGroup = common.registerGroup, + updateRendererArgument = common.updateRendererArgument, + }, + engineHandlers = { + onLoad = common.onLoad, + onSave = common.onSave, + }, +} diff --git a/files/data/scripts/omw/settings/player.lua b/files/data/scripts/omw/settings/player.lua index ae483935d5..3a71f11456 100644 --- a/files/data/scripts/omw/settings/player.lua +++ b/files/data/scripts/omw/settings/player.lua @@ -1,8 +1,6 @@ local common = require('scripts.omw.settings.common') local render = require('scripts.omw.settings.render') -require('scripts.omw.settings.renderers')(render.registerRenderer) - --- -- @type PageOptions -- @field #string key A unique key @@ -71,11 +69,11 @@ return { -- local globalSettings = storage.globalSection('SettingsGlobalMyMod') interface = { --- - -- @field [parent=#Settings] #string version - version = 0, + -- @field [parent=#Settings] #number version + version = 1, --- -- @function [parent=#Settings] registerPage Register a page to be displayed in the settings menu, - -- only available in player scripts + -- available in player and menu scripts -- @param #PageOptions options -- @usage -- I.Settings.registerPage({ @@ -87,7 +85,7 @@ return { registerPage = render.registerPage, --- -- @function [parent=#Settings] registerRenderer Register a renderer, - -- only avaialable in player scripts + -- only avaialable in menu scripts (DEPRECATED in player scripts) -- @param #string key -- @param #function renderer A renderer function, receives setting's value, -- a function to change it and an argument from the setting options @@ -107,7 +105,10 @@ return { -- }, -- } -- end) - registerRenderer = render.registerRenderer, + registerRenderer = function() + print( + 'Register setting renderers in player scripts has been deprecated and moved to menu Settings interface') + end, --- -- @function [parent=#Settings] registerGroup Register a group to be attached to a page, -- available both in player and global scripts From d16b1ca54ef81fe4adaa5b9d759cf76eaca73b61 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 8 Jan 2024 22:29:59 +0100 Subject: [PATCH 0768/2167] make macos use openal-soft --- CI/before_install.osx.sh | 3 +-- CI/before_script.osx.sh | 3 ++- CMakeLists.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index e340d501ed..dd54030dfb 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -19,9 +19,8 @@ command -v cmake >/dev/null 2>&1 || brew install cmake command -v qmake >/dev/null 2>&1 || brew install qt@5 export PATH="/opt/homebrew/opt/qt@5/bin:$PATH" - # Install deps -brew install icu4c yaml-cpp sqlite +brew install openal-soft icu4c yaml-cpp sqlite ccache --version cmake --version diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index c956f27514..cab67b6e4d 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -10,12 +10,13 @@ DEPENDENCIES_ROOT="/tmp/openmw-deps" QT_PATH=$(brew --prefix qt@5) ICU_PATH=$(brew --prefix icu4c) +OPENAL_PATH=$(brew --prefix openal-soft) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build cmake \ --D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ +-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH;$OPENAL_PATH" \ -D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 34df0216da..28109bd01b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ IF(NOT CMAKE_BUILD_TYPE) ENDIF() if (APPLE) + set(CMAKE_FIND_FRAMEWORK LAST) # prefer dylibs over frameworks set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") From 69cf507db85845446b394129dc8955f3803dfde0 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 8 Jan 2024 22:17:02 +0100 Subject: [PATCH 0769/2167] Fix navmesh update on player changing tile In cases when objects are not present on the scene (e.g. generated exterior cells) navmesh is not updated because area that suppose to be covered with it was not updated. It was updated only during cell change. This is a regression from d15e1dca84. Set TileCachedRecastMeshManager range on NavMeshManager update to make sure it always covers correct area around player. Return a union of objects, heightfields and water ranges from getLimitedObjectsRange intersected with range provided above. --- .../detournavigator/gettilespositions.cpp | 76 +++++++ .../detournavigator/navigator.cpp | 185 +++++++++++++++--- .../detournavigator/operators.hpp | 18 ++ .../detournavigator/gettilespositions.cpp | 9 + .../detournavigator/gettilespositions.hpp | 2 + components/detournavigator/navmeshmanager.cpp | 1 + .../tilecachedrecastmeshmanager.cpp | 39 +++- 7 files changed, 299 insertions(+), 31 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp index fb32022010..729d11ddb5 100644 --- a/apps/openmw_test_suite/detournavigator/gettilespositions.cpp +++ b/apps/openmw_test_suite/detournavigator/gettilespositions.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -86,4 +87,79 @@ namespace EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0))); } + + struct TilesPositionsRangeParams + { + TilesPositionsRange mA; + TilesPositionsRange mB; + TilesPositionsRange mResult; + }; + + struct DetourNavigatorGetIntersectionTest : TestWithParam + { + }; + + TEST_P(DetourNavigatorGetIntersectionTest, should_return_expected_result) + { + EXPECT_EQ(getIntersection(GetParam().mA, GetParam().mB), GetParam().mResult); + EXPECT_EQ(getIntersection(GetParam().mB, GetParam().mA), GetParam().mResult); + } + + const TilesPositionsRangeParams getIntersectionParams[] = { + { .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 2 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 1, 1 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 0, 2 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 2, 0 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{}, + }, + }; + + INSTANTIATE_TEST_SUITE_P( + GetIntersectionParams, DetourNavigatorGetIntersectionTest, ValuesIn(getIntersectionParams)); + + struct DetourNavigatorGetUnionTest : TestWithParam + { + }; + + TEST_P(DetourNavigatorGetUnionTest, should_return_expected_result) + { + EXPECT_EQ(getUnion(GetParam().mA, GetParam().mB), GetParam().mResult); + EXPECT_EQ(getUnion(GetParam().mB, GetParam().mA), GetParam().mResult); + } + + const TilesPositionsRangeParams getUnionParams[] = { + { .mA = TilesPositionsRange{}, .mB = TilesPositionsRange{}, .mResult = TilesPositionsRange{} }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 3, 3 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 3, 3 } }, + }, + { + .mA = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 1, 1 } }, + .mB = TilesPositionsRange{ .mBegin = TilePosition{ 1, 1 }, .mEnd = TilePosition{ 2, 2 } }, + .mResult = TilesPositionsRange{ .mBegin = TilePosition{ 0, 0 }, .mEnd = TilePosition{ 2, 2 } }, + }, + }; + + INSTANTIATE_TEST_SUITE_P(GetUnionParams, DetourNavigatorGetUnionTest, ValuesIn(getUnionParams)); } diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index df4d7a1e99..aba8598f18 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -39,6 +39,8 @@ namespace using namespace DetourNavigator; using namespace DetourNavigator::Tests; + constexpr int heightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); + struct DetourNavigatorNavigatorTest : Test { Settings mSettings = makeSettings(); @@ -53,7 +55,6 @@ namespace AreaCosts mAreaCosts; Loading::Listener mListener; const osg::Vec2i mCellPosition{ 0, 0 }; - const int mHeightfieldTileSize = ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1); const float mEndTolerance = 0; const btTransform mTransform{ btMatrix3x3::getIdentity(), btVector3(256, 256, 0) }; const ObjectTransform mObjectTransform{ ESM::Position{ { 256, 256, 0 }, { 0, 0, 0 } }, 0.0f }; @@ -129,7 +130,7 @@ namespace { } - T& shape() { return static_cast(*mInstance->mCollisionShape); } + T& shape() const { return static_cast(*mInstance->mCollisionShape); } const osg::ref_ptr& instance() const { return mInstance; } private: @@ -167,7 +168,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -189,7 +190,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_path_to_the_start_position_should_contain_single_point) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -211,7 +212,7 @@ namespace mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape( @@ -256,7 +257,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape( @@ -335,7 +336,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, only_one_heightfield_per_cell_is_allowed) { const HeightfieldSurface surface1 = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize1 = mHeightfieldTileSize * (surface1.mSize - 1); + const int cellSize1 = heightfieldTileSize * (surface1.mSize - 1); const std::array heightfieldData2{ { -25, -25, -25, -25, -25, // row 0 @@ -345,7 +346,7 @@ namespace -25, -25, -25, -25, -25, // row 4 } }; const HeightfieldSurface surface2 = makeSquareHeightfieldSurface(heightfieldData2); - const int cellSize2 = mHeightfieldTileSize * (surface2.mSize - 1); + const int cellSize2 = heightfieldTileSize * (surface2.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize1, surface1, nullptr); @@ -412,7 +413,7 @@ namespace 0, -50, -100, -100, -100, // row 4 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, 300, nullptr); @@ -446,7 +447,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); @@ -480,7 +481,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -513,7 +514,7 @@ namespace 0, 0, 0, 0, 0, 0, 0, // row 6 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addWater(mCellPosition, cellSize, -25, nullptr); @@ -566,7 +567,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_heightfield_remove_and_update_then_find_path_should_return_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -602,7 +603,7 @@ namespace 0, -25, -100, -100, -100, -100, // row 5 } }; const HeightfieldSurface surface = makeSquareHeightfieldSurface(heightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -629,7 +630,7 @@ namespace mSettings, std::make_unique(":memory:", std::numeric_limits::max()))); const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); const btVector3 shift = getHeightfieldShift(mCellPosition, cellSize, surface.mMinHeight, surface.mMaxHeight); std::vector> boxes; @@ -718,7 +719,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, surface, nullptr); @@ -737,7 +738,7 @@ namespace update_for_oscillating_object_that_does_not_change_navmesh_should_not_trigger_navmesh_update) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance oscillatingBox(std::make_unique(btVector3(20, 20, 20))); const btVector3 oscillatingBoxShapePosition(288, 288, 400); @@ -777,7 +778,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, should_provide_path_over_flat_heightfield) { const HeightfieldPlane plane{ 100 }; - const int cellSize = mHeightfieldTileSize * 4; + const int cellSize = heightfieldTileSize * 4; ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); mNavigator->addHeightfield(mCellPosition, cellSize, plane, nullptr); @@ -796,7 +797,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, for_not_reachable_destination_find_path_should_provide_partial_path) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), @@ -822,7 +823,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, end_tolerance_should_extent_available_destinations) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); CollisionShapeInstance compound(std::make_unique()); compound.shape().addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(204, -204, 0)), @@ -948,7 +949,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nav_mesh_position) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -966,7 +967,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_too_far) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -984,7 +985,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, find_nearest_nav_mesh_position_should_return_nullopt_when_flags_do_not_match) { const HeightfieldSurface surface = makeSquareHeightfieldSurface(defaultHeightfieldData); - const int cellSize = mHeightfieldTileSize * (surface.mSize - 1); + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); ASSERT_TRUE(mNavigator->addAgent(mAgentBounds)); auto updateGuard = mNavigator->makeUpdateGuard(); @@ -998,4 +999,142 @@ namespace EXPECT_EQ(findNearestNavMeshPosition(*mNavigator, mAgentBounds, position, searchAreaHalfExtents, Flag_swim), std::nullopt); } + + struct DetourNavigatorUpdateTest : TestWithParam> + { + }; + + std::vector getUsedTiles(const NavMeshCacheItem& navMesh) + { + std::vector result; + navMesh.forEachUsedTile([&](const TilePosition& position, const auto&...) { result.push_back(position); }); + return result; + } + + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 5; + NavigatorImpl navigator(settings, nullptr); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::allJobsDone, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTiles[] = { { 3, 4 }, { 4, 3 }, { 4, 4 }, { 4, 5 }, { 5, 4 } }; + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(4000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::allJobsDone, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTiles[] = { { 4, 4 }, { 5, 3 }, { 5, 4 }, { 5, 5 }, { 6, 4 } }; + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, UnorderedElementsAreArray(expectedTiles)) << usedTiles; + } + } + + struct AddHeightfieldSurface + { + static constexpr std::size_t sSize = 65; + static constexpr float sHeights[sSize * sSize]{}; + + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const HeightfieldSurface surface{ + .mHeights = sHeights, + .mSize = sSize, + .mMinHeight = -1, + .mMaxHeight = 1, + }; + const int cellSize = heightfieldTileSize * static_cast(surface.mSize - 1); + navigator.addHeightfield(cellPosition, cellSize, surface, nullptr); + } + }; + + struct AddHeightfieldPlane + { + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const HeightfieldPlane plane{ .mHeight = 0 }; + const int cellSize = 8192; + navigator.addHeightfield(cellPosition, cellSize, plane, nullptr); + } + }; + + struct AddWater + { + void operator()(Navigator& navigator) const + { + const osg::Vec2i cellPosition(0, 0); + const float level = 0; + const int cellSize = 8192; + navigator.addWater(cellPosition, cellSize, level, nullptr); + } + }; + + struct AddObject + { + const float mSize = 8192; + CollisionShapeInstance mBox{ std::make_unique(btVector3(mSize, mSize, 1)) }; + const ObjectTransform mTransform{ + .mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } }, + .mScale = 1.0f, + }; + + void operator()(Navigator& navigator) const + { + navigator.addObject(ObjectId(&mBox.shape()), ObjectShapes(mBox.instance(), mTransform), + btTransform::getIdentity(), nullptr); + } + }; + + struct AddAll + { + AddHeightfieldSurface mAddHeightfieldSurface; + AddHeightfieldPlane mAddHeightfieldPlane; + AddWater mAddWater; + AddObject mAddObject; + + void operator()(Navigator& navigator) const + { + mAddHeightfieldSurface(navigator); + mAddHeightfieldPlane(navigator); + mAddWater(navigator); + mAddObject(navigator); + } + }; + + const std::function addNavMeshData[] = { + AddHeightfieldSurface{}, + AddHeightfieldPlane{}, + AddWater{}, + AddObject{}, + AddAll{}, + }; + + INSTANTIATE_TEST_SUITE_P(DifferentNavMeshData, DetourNavigatorUpdateTest, ValuesIn(addNavMeshData)); } diff --git a/apps/openmw_test_suite/detournavigator/operators.hpp b/apps/openmw_test_suite/detournavigator/operators.hpp index 4e42af78e4..4c043027eb 100644 --- a/apps/openmw_test_suite/detournavigator/operators.hpp +++ b/apps/openmw_test_suite/detournavigator/operators.hpp @@ -42,12 +42,24 @@ namespace testing << ", " << value.y() << ", " << value.z() << ')'; } + template <> + inline testing::Message& Message::operator<<(const osg::Vec2i& value) + { + return (*this) << "{" << value.x() << ", " << value.y() << '}'; + } + template <> inline testing::Message& Message::operator<<(const Wrapper& value) { return (*this) << value.mValue; } + template <> + inline testing::Message& Message::operator<<(const Wrapper& value) + { + return (*this) << value.mValue; + } + template <> inline testing::Message& Message::operator<<(const Wrapper& value) { @@ -72,6 +84,12 @@ namespace testing return writeRange(*this, value, 1); } + template <> + inline testing::Message& Message::operator<<(const std::vector& value) + { + return writeRange(*this, value, 1); + } + template <> inline testing::Message& Message::operator<<(const std::vector& value) { diff --git a/components/detournavigator/gettilespositions.cpp b/components/detournavigator/gettilespositions.cpp index a3f46f3f85..343140633f 100644 --- a/components/detournavigator/gettilespositions.cpp +++ b/components/detournavigator/gettilespositions.cpp @@ -76,4 +76,13 @@ namespace DetourNavigator return {}; return TilesPositionsRange{ TilePosition(beginX, beginY), TilePosition(endX, endY) }; } + + TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept + { + const int beginX = std::min(a.mBegin.x(), b.mBegin.x()); + const int endX = std::max(a.mEnd.x(), b.mEnd.x()); + const int beginY = std::min(a.mBegin.y(), b.mBegin.y()); + const int endY = std::max(a.mEnd.y(), b.mEnd.y()); + return TilesPositionsRange{ .mBegin = TilePosition(beginX, beginY), .mEnd = TilePosition(endX, endY) }; + } } diff --git a/components/detournavigator/gettilespositions.hpp b/components/detournavigator/gettilespositions.hpp index 32733224f3..66c3a90d1b 100644 --- a/components/detournavigator/gettilespositions.hpp +++ b/components/detournavigator/gettilespositions.hpp @@ -50,6 +50,8 @@ namespace DetourNavigator } TilesPositionsRange getIntersection(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; + + TilesPositionsRange getUnion(const TilesPositionsRange& a, const TilesPositionsRange& b) noexcept; } #endif diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index 9fda0566d9..f4a82b850f 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -166,6 +166,7 @@ namespace DetourNavigator return; mLastRecastMeshManagerRevision = mRecastMeshManager.getRevision(); mPlayerTile = playerTile; + mRecastMeshManager.setRange(makeRange(playerTile, mSettings.mMaxTilesNumber), guard); const auto changedTiles = mRecastMeshManager.takeChangedTiles(guard); const TilesPositionsRange range = mRecastMeshManager.getLimitedObjectsRange(); for (const auto& [agentBounds, cached] : mCache) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 1e55719c13..0bab808300 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -54,6 +54,15 @@ namespace DetourNavigator private: const std::optional> mImpl; }; + + TilesPositionsRange getIndexRange(const auto& index) + { + const auto bounds = index.bounds(); + return TilesPositionsRange{ + .mBegin = makeTilePosition(bounds.min_corner()), + .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), + }; + } } TileCachedRecastMeshManager::TileCachedRecastMeshManager(const RecastSettings& settings) @@ -104,14 +113,28 @@ namespace DetourNavigator TilesPositionsRange TileCachedRecastMeshManager::getLimitedObjectsRange() const { - if (mObjects.empty()) - return {}; - const auto bounds = mObjectIndex.bounds(); - const TilesPositionsRange objectsRange{ - .mBegin = makeTilePosition(bounds.min_corner()), - .mEnd = makeTilePosition(bounds.max_corner()) + TilePosition(1, 1), - }; - return getIntersection(mRange, objectsRange); + std::optional result; + if (!mWater.empty()) + result = getIndexRange(mWaterIndex); + if (!mHeightfields.empty()) + { + const TilesPositionsRange range = getIndexRange(mHeightfieldIndex); + if (result.has_value()) + result = getUnion(*result, range); + else + result = range; + } + if (!mObjects.empty()) + { + const TilesPositionsRange range = getIndexRange(mObjectIndex); + if (result.has_value()) + result = getUnion(*result, range); + else + result = range; + } + if (result.has_value()) + return getIntersection(mRange, *result); + return {}; } void TileCachedRecastMeshManager::setWorldspace(std::string_view worldspace, const UpdateGuard* guard) From ad0ad625e5936090352f9ecc87093bfb566c95c7 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Feb 2023 23:55:49 +0100 Subject: [PATCH 0770/2167] Use single global static variable in Npc::getSoundIdFromSndGen for all parts To eliminate checks for local static variable initialization. --- apps/openmw/mwclass/npc.cpp | 46 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 330a48daeb..4f295f7b35 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -61,6 +61,23 @@ namespace { + struct NpcParts + { + const ESM::RefId mSwimLeft = ESM::RefId::stringRefId("Swim Left"); + const ESM::RefId mSwimRight = ESM::RefId::stringRefId("Swim Right"); + const ESM::RefId mFootWaterLeft = ESM::RefId::stringRefId("FootWaterLeft"); + const ESM::RefId mFootWaterRight = ESM::RefId::stringRefId("FootWaterRight"); + const ESM::RefId mFootBareLeft = ESM::RefId::stringRefId("FootBareLeft"); + const ESM::RefId mFootBareRight = ESM::RefId::stringRefId("FootBareRight"); + const ESM::RefId mFootLightLeft = ESM::RefId::stringRefId("footLightLeft"); + const ESM::RefId mFootLightRight = ESM::RefId::stringRefId("footLightRight"); + const ESM::RefId mFootMediumRight = ESM::RefId::stringRefId("FootMedRight"); + const ESM::RefId mFootMediumLeft = ESM::RefId::stringRefId("FootMedLeft"); + const ESM::RefId mFootHeavyLeft = ESM::RefId::stringRefId("footHeavyLeft"); + const ESM::RefId mFootHeavyRight = ESM::RefId::stringRefId("footHeavyRight"); + }; + + const NpcParts npcParts; int is_even(double d) { @@ -1225,19 +1242,6 @@ namespace MWClass ESM::RefId Npc::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { - static const ESM::RefId swimLeft = ESM::RefId::stringRefId("Swim Left"); - static const ESM::RefId swimRight = ESM::RefId::stringRefId("Swim Right"); - static const ESM::RefId footWaterLeft = ESM::RefId::stringRefId("FootWaterLeft"); - static const ESM::RefId footWaterRight = ESM::RefId::stringRefId("FootWaterRight"); - static const ESM::RefId footBareLeft = ESM::RefId::stringRefId("FootBareLeft"); - static const ESM::RefId footBareRight = ESM::RefId::stringRefId("FootBareRight"); - static const ESM::RefId footLightLeft = ESM::RefId::stringRefId("footLightLeft"); - static const ESM::RefId footLightRight = ESM::RefId::stringRefId("footLightRight"); - static const ESM::RefId footMediumRight = ESM::RefId::stringRefId("FootMedRight"); - static const ESM::RefId footMediumLeft = ESM::RefId::stringRefId("FootMedLeft"); - static const ESM::RefId footHeavyLeft = ESM::RefId::stringRefId("footHeavyLeft"); - static const ESM::RefId footHeavyRight = ESM::RefId::stringRefId("footHeavyRight"); - if (name == "left" || name == "right") { MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -1245,9 +1249,9 @@ namespace MWClass return ESM::RefId(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if (world->isSwimming(ptr)) - return (name == "left") ? swimLeft : swimRight; + return (name == "left") ? npcParts.mSwimLeft : npcParts.mSwimRight; if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return (name == "left") ? footWaterLeft : footWaterRight; + return (name == "left") ? npcParts.mFootWaterLeft : npcParts.mFootWaterRight; if (world->isOnGround(ptr)) { if (getNpcStats(ptr).isWerewolf() @@ -1262,15 +1266,15 @@ namespace MWClass const MWWorld::InventoryStore& inv = Npc::getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) - return (name == "left") ? footBareLeft : footBareRight; + return (name == "left") ? npcParts.mFootBareLeft : npcParts.mFootBareRight; ESM::RefId skill = boots->getClass().getEquipmentSkill(*boots); if (skill == ESM::Skill::LightArmor) - return (name == "left") ? footLightLeft : footLightRight; + return (name == "left") ? npcParts.mFootLightLeft : npcParts.mFootLightRight; else if (skill == ESM::Skill::MediumArmor) - return (name == "left") ? footMediumLeft : footMediumRight; + return (name == "left") ? npcParts.mFootMediumLeft : npcParts.mFootMediumRight; else if (skill == ESM::Skill::HeavyArmor) - return (name == "left") ? footHeavyLeft : footHeavyRight; + return (name == "left") ? npcParts.mFootHeavyLeft : npcParts.mFootHeavyRight; } return ESM::RefId(); } @@ -1279,9 +1283,9 @@ namespace MWClass if (name == "land") return ESM::RefId(); if (name == "swimleft") - return swimLeft; + return npcParts.mSwimLeft; if (name == "swimright") - return swimRight; + return npcParts.mSwimRight; // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? From fbd99583ca6cb120ebfd67cce7d38848529dc4ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 9 Jan 2024 16:11:44 +0400 Subject: [PATCH 0771/2167] Move local variables in GUI code --- apps/openmw/mwgui/alchemywindow.cpp | 4 +++- apps/openmw/mwgui/bookpage.cpp | 2 +- apps/openmw/mwgui/console.cpp | 2 +- apps/openmw/mwgui/formatting.cpp | 2 +- apps/openmw/mwgui/spellcreationdialog.cpp | 2 +- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 4215782e2f..5a6245fca0 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -395,8 +395,10 @@ namespace MWGui { std::string suggestedName = mAlchemy->suggestPotionName(); if (suggestedName != mSuggestedPotionName) + { mNameEdit->setCaptionWithReplacing(suggestedName); - mSuggestedPotionName = suggestedName; + mSuggestedPotionName = std::move(suggestedName); + } mSortModel->clearDragItems(); diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 5d9256b20d..1966442513 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1065,7 +1065,7 @@ namespace MWGui { createActiveFormats(newBook); - mBook = newBook; + mBook = std::move(newBook); setPage(newPage); if (newPage < mBook->mPages.size()) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 504ea560b2..b430e08142 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -440,7 +440,7 @@ namespace MWGui // If new search term reset position, otherwise continue from current position if (newSearchTerm != mCurrentSearchTerm) { - mCurrentSearchTerm = newSearchTerm; + mCurrentSearchTerm = std::move(newSearchTerm); mCurrentOccurrenceIndex = std::string::npos; } diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 7f62bbf49c..b2d9415897 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -221,7 +221,7 @@ namespace MWGui::Formatting } } - mAttributes[key] = value; + mAttributes[key] = std::move(value); } } diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index bb97ccb82f..d668db1dec 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -472,7 +472,7 @@ namespace MWGui ESM::EffectList effectList; effectList.mList = mEffects; - mSpell.mEffects = effectList; + mSpell.mEffects = std::move(effectList); mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 5337e2f798..aa29dfc156 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -172,7 +172,7 @@ namespace MWGui w += 16; ToolTipInfo* tooltipInfo = image->getUserData(); - tooltipInfo->text = sourcesDescription; + tooltipInfo->text = std::move(sourcesDescription); // Fade out if (totalDuration >= fadeTime && fadeTime > 0.f) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 9ee7d08f31..0a0343831d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -251,7 +251,7 @@ namespace MWGui if (!cost.empty() && cost != "0") info.text += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); - info.effects = effects; + info.effects = std::move(effects); tooltipSize = createToolTip(info); } else if (type == "Layout") From 521394d67b8df7883306a0ed800b8b11a6375aea Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jan 2024 12:06:01 +0100 Subject: [PATCH 0772/2167] Override launcher file info with higher priority info --- CHANGELOG.md | 1 + .../contentselector/model/contentmodel.cpp | 28 +++++++++++++------ components/contentselector/model/esmfile.hpp | 4 --- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181116ca9d..580e9a2143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,7 @@ Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves + Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 9b7bb11f09..c3ea050064 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -2,6 +2,7 @@ #include "esmfile.hpp" #include +#include #include #include @@ -447,26 +448,37 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf { QFileInfo info(dir.absoluteFilePath(path2)); - if (item(info.fileName())) - continue; - // Enabled by default in system openmw.cfg; shouldn't be shown in content list. if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) continue; + EsmFile* file = const_cast(item(info.fileName())); + bool add = file == nullptr; + std::unique_ptr newFile; + if (add) + { + newFile = std::make_unique(path2); + file = newFile.get(); + } + else + { + // We've found the same file in a higher priority dir, update our existing entry + file->setFileName(path2); + file->setGameFiles({}); + } + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { - EsmFile* file = new EsmFile(path2); file->setDate(info.lastModified()); file->setFilePath(info.absoluteFilePath()); - addFile(file); + if (add) + addFile(newFile.release()); setNew(file->fileName(), newfiles); continue; } try { - EsmFile* file = new EsmFile(path2); file->setDate(info.lastModified()); file->setFilePath(info.absoluteFilePath()); std::filesystem::path filepath = Files::pathFromQString(info.absoluteFilePath()); @@ -522,14 +534,14 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf } // Put the file in the table - addFile(file); + if (add) + addFile(newFile.release()); setNew(file->fileName(), newfiles); } catch (std::runtime_error& e) { // An error occurred while reading the .esp qWarning() << "Error reading addon file: " << e.what(); - continue; } } } diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index a65c778294..cf0e086362 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -30,15 +30,11 @@ namespace ContentSelectorModel }; EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); - // EsmFile(const EsmFile &); - - ~EsmFile() {} void setFileProperty(const FileProperty prop, const QString& value); void setFileName(const QString& fileName); void setAuthor(const QString& author); - void setSize(const int size); void setDate(const QDateTime& modified); void setFormat(const QString& format); void setFilePath(const QString& path); From 672cefd59422f97bd6257f727e1b201e2139838d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jan 2024 16:37:25 +0100 Subject: [PATCH 0773/2167] Track checked EsmFile pointers instead of full paths --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 24 +------------ .../contentselector/model/contentmodel.cpp | 36 ++++++++----------- .../contentselector/model/contentmodel.hpp | 5 +-- 4 files changed, 20 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580e9a2143..3f07def9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ 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 Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7131: MyGUI log spam when post processing HUD is open Bug #7134: Saves with an invalid last generated RefNum can be loaded diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index dc2c07d9bd..114221ce92 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -125,27 +125,6 @@ namespace Launcher { return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); } - - std::optional findFirstPath(const QStringList& directories, const QString& fileName) - { - for (const QString& directoryPath : directories) - { - const QString filePath = QDir(directoryPath).absoluteFilePath(fileName); - if (QFile::exists(filePath)) - return filePath; - } - return std::nullopt; - } - - QStringList findAllFilePaths(const QStringList& directories, const QStringList& fileNames) - { - QStringList result; - result.reserve(fileNames.size()); - for (const QString& fileName : fileNames) - if (const auto filepath = findFirstPath(directories, fileName)) - result.append(*filepath); - return result; - } } } @@ -366,8 +345,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) row++; } - mSelector->setProfileContent( - findAllFilePaths(directories, mLauncherSettings.getContentListFiles(contentModelName))); + mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName)); } void Launcher::DataFilesPage::saveSettings(const QString& profile) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index c3ea050064..5e6db2fd7e 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -128,7 +128,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index continue; noGameFiles = false; - if (isChecked(depFile->filePath())) + if (mCheckedFiles.contains(depFile)) { gamefileChecked = true; break; @@ -215,7 +215,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return mCheckStates[file->filePath()]; + return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked; } case Qt::UserRole: @@ -229,7 +229,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int } case Qt::UserRole + 1: - return isChecked(file->filePath()); + return mCheckedFiles.contains(file); } return QVariant(); } @@ -277,12 +277,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); bool setState = false; - if ((checkValue == Qt::Checked) && !isChecked(file->filePath())) + if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) { setState = true; success = true; } - else if ((checkValue == Qt::Checked) && isChecked(file->filePath())) + else if (checkValue == Qt::Checked && mCheckedFiles.contains(file)) setState = true; else if (checkValue == Qt::Unchecked) setState = true; @@ -628,14 +628,6 @@ void ContentSelectorModel::ContentModel::sortFiles() emit layoutChanged(); } -bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const -{ - const auto it = mCheckStates.find(filepath); - if (it == mCheckStates.end()) - return false; - return it.value() == Qt::Checked; -} - bool ContentSelectorModel::ContentModel::isEnabled(const QModelIndex& index) const { return (flags(index) & Qt::ItemIsEnabled); @@ -723,7 +715,7 @@ QList ContentSelectorModel::ContentModel:: } else { - if (!isChecked(dependentFile->filePath())) + if (!mCheckedFiles.contains(dependentFile)) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); } @@ -776,9 +768,13 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, Qt::CheckState state = Qt::Unchecked; if (checkState) + { state = Qt::Checked; + mCheckedFiles.insert(file); + } + else + mCheckedFiles.erase(file); - mCheckStates[filepath] = state; emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) @@ -794,8 +790,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (!upstreamFile) continue; - if (!isChecked(upstreamFile->filePath())) - mCheckStates[upstreamFile->filePath()] = Qt::Checked; + mCheckedFiles.insert(upstreamFile); emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); } @@ -810,8 +805,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive)) { - if (mCheckStates.contains(downstreamFile->filePath())) - mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; + mCheckedFiles.erase(downstreamFile); emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); } @@ -829,7 +823,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke // First search for game files and next addons, // so we get more or less correct game files vs addons order. for (EsmFile* file : mFiles) - if (isChecked(file->filePath())) + if (mCheckedFiles.contains(file)) list << file; return list; @@ -838,6 +832,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke void ContentSelectorModel::ContentModel::uncheckAll() { emit layoutAboutToBeChanged(); - mCheckStates.clear(); + mCheckedFiles.clear(); emit layoutChanged(); } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index d56f8f9a3b..1ba3090a32 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -7,6 +7,8 @@ #include #include +#include + namespace ContentSelectorModel { class EsmFile; @@ -57,7 +59,6 @@ namespace ContentSelectorModel void setCurrentGameFile(const EsmFile* file); bool isEnabled(const QModelIndex& index) const; - bool isChecked(const QString& filepath) const; bool setCheckState(const QString& filepath, bool isChecked); bool isNew(const QString& filepath) const; void setNew(const QString& filepath, bool isChecked); @@ -85,7 +86,7 @@ namespace ContentSelectorModel const EsmFile* mGameFile; ContentFileList mFiles; QStringList mArchives; - QHash mCheckStates; + std::set mCheckedFiles; QHash mNewFiles; QSet mPluginsWithLoadOrderError; QString mEncoding; From 0db8026356c4f652b02141afc136fb488c194271 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 17:08:05 +0100 Subject: [PATCH 0774/2167] Stop adding and deleting rows --- .../contentselector/model/contentmodel.cpp | 82 ++++++++----------- components/contentselector/model/esmfile.cpp | 16 ---- components/contentselector/model/esmfile.hpp | 4 - .../contentselector/view/contentselector.cpp | 6 ++ .../contentselector/view/contentselector.hpp | 1 + 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5e6db2fd7e..0aab06ac90 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -6,9 +6,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -315,34 +317,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex& parent) { - if (parent.isValid()) - return false; - - beginInsertRows(parent, position, position + rows - 1); - { - for (int row = 0; row < rows; ++row) - mFiles.insert(position, new EsmFile); - } - endInsertRows(); - - return true; + return false; } bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex& parent) { - if (parent.isValid()) - return false; - - beginRemoveRows(parent, position, position + rows - 1); - { - for (int row = 0; row < rows; ++row) - delete mFiles.takeAt(position); - } - endRemoveRows(); - - // at this point we know that drag and drop has finished. - checkForLoadOrderErrors(); - return true; + return false; } Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const @@ -358,13 +338,14 @@ QStringList ContentSelectorModel::ContentModel::mimeTypes() const QMimeData* ContentSelectorModel::ContentModel::mimeData(const QModelIndexList& indexes) const { QByteArray encodedData; + QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex& index : indexes) { if (!index.isValid()) continue; - encodedData.append(item(index.row())->encodedData()); + stream << index.row(); } QMimeData* mimeData = new QMimeData(); @@ -396,26 +377,31 @@ bool ContentSelectorModel::ContentModel::dropMimeData( QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); + std::vector toMove; while (!stream.atEnd()) { - - QString value; - QStringList values; - QStringList gamefiles; - - for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i) - { - stream >> value; - values << value; - } - - stream >> gamefiles; - - insertRows(beginRow, 1); - - QModelIndex idx = index(beginRow++, 0, QModelIndex()); - setData(idx, QStringList() << values << gamefiles, Qt::EditRole); + int sourceRow; + stream >> sourceRow; + toMove.emplace_back(mFiles.at(sourceRow)); } + int minRow = mFiles.size(); + int maxRow = 0; + for (EsmFile* file : toMove) + { + int from = mFiles.indexOf(file); + int to = beginRow; + if (from < beginRow) + to--; + else if (from > beginRow) + beginRow++; + minRow = std::min(minRow, std::min(to, from)); + maxRow = std::max(maxRow, std::max(to, from)); + mFiles.move(from, to); + } + + dataChanged(index(minRow, 0), index(maxRow, 0)); + // at this point we know that drag and drop has finished. + checkForLoadOrderErrors(); return true; } @@ -566,6 +552,7 @@ void ContentSelectorModel::ContentModel::clearFiles() if (filesCount > 0) { beginRemoveRows(QModelIndex(), 0, filesCount - 1); + qDeleteAll(mFiles); mFiles.clear(); endRemoveRows(); } @@ -688,7 +675,7 @@ void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() { for (int row = 0; row < mFiles.count(); ++row) { - EsmFile* file = item(row); + EsmFile* file = mFiles.at(row); bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; if (isRowInError) { @@ -765,13 +752,8 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (!file) return false; - Qt::CheckState state = Qt::Unchecked; - if (checkState) - { - state = Qt::Checked; mCheckedFiles.insert(file); - } else mCheckedFiles.erase(file); @@ -781,7 +763,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, refreshModel(); // if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. - if (state == Qt::Checked) + if (checkState) { for (const QString& upstreamName : file->gameFiles()) { @@ -796,7 +778,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, } } // otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. - if (state == Qt::Unchecked) + else { for (const EsmFile* downstreamFile : mFiles) { diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 39a33e710e..e4280baef7 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -1,10 +1,5 @@ #include "esmfile.hpp" -#include -#include - -int ContentSelectorModel::EsmFile::sPropertyCount = 7; - ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) : ModelItem(parent) , mFileName(fileName) @@ -46,17 +41,6 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description) mDescription = description; } -QByteArray ContentSelectorModel::EsmFile::encodedData() const -{ - QByteArray encodedData; - QDataStream stream(&encodedData, QIODevice::WriteOnly); - - stream << mFileName << mAuthor << mVersion << mModified.toString(Qt::ISODate) << mPath << mDescription - << mGameFiles; - - return encodedData; -} - bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index cf0e086362..606cc3d319 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -64,10 +64,6 @@ namespace ContentSelectorModel } bool isGameFile() const; - QByteArray encodedData() const; - - public: - static int sPropertyCount; private: QString mTooltipTemlate = tr( diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 3f75b82487..00c32e272d 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -108,6 +108,7 @@ void ContentSelectorView::ContentSelector::buildAddonView() connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::signalAddonDataChanged); + connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::slotRowsMoved); buildContextMenu(); } @@ -331,3 +332,8 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt { ui->addonView->setDragEnabled(newText.isEmpty()); } + +void ContentSelectorView::ContentSelector::slotRowsMoved() +{ + ui->addonView->selectionModel()->clearSelection(); +} \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 48377acb86..2b739645ba 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -85,6 +85,7 @@ namespace ContentSelectorView void slotUncheckMultiSelectedItems(); void slotCopySelectedItemsPaths(); void slotSearchFilterTextChanged(const QString& newText); + void slotRowsMoved(); }; } From e7d1611be34dcdbc1950afbfe33297d0de302523 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 9 Jan 2024 18:19:04 +0100 Subject: [PATCH 0775/2167] Update ActiveEffect documentation --- files/lua_api/openmw/core.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 890532fc13..7ea3c75f1c 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -682,7 +682,6 @@ --- -- @type ActiveEffect -- Magic effect that is currently active on an actor. --- Note that when this effect expires or is removed, it will remain temporarily. Magnitude will be set to 0 for effects that expire. -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #string id Effect id string From e67d6c6ebfcf4688f054604ec0d8add4b9ae2490 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 10 Jan 2024 12:30:19 +0400 Subject: [PATCH 0776/2167] Refactor Lua properties --- apps/openmw/mwlua/inputbindings.cpp | 33 ++++++++++++++++++----------- apps/openmw/mwlua/stats.cpp | 5 +++-- apps/openmw/mwlua/uibindings.cpp | 4 ++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 33b19f3b4d..b44a528f2c 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -76,13 +76,18 @@ namespace MWLua inputActions[sol::meta_function::pairs] = pairs; } - context.mLua->sol().new_usertype("ActionInfo", "key", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; })); + auto actionInfo = context.mLua->sol().new_usertype("ActionInfo"); + actionInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); + actionInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; }); + actionInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); + actionInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); + actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; }); + actionInfo["defaultValue"] + = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); auto inputTriggers = context.mLua->sol().new_usertype("InputTriggers"); inputTriggers[sol::meta_function::index] @@ -102,11 +107,15 @@ namespace MWLua inputTriggers[sol::meta_function::pairs] = pairs; } - context.mLua->sol().new_usertype("TriggerInfo", "key", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; })); + auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo"); + triggerInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); + triggerInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; }); + triggerInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); + triggerInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ff74704ed8..02bed00bf5 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -380,7 +380,7 @@ namespace MWLua addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); attributeStatT["modified"] - = sol::property([=](const AttributeStat& stat) { return stat.getModified(context); }); + = sol::readonly_property([=](const AttributeStat& stat) { return stat.getModified(context); }); addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); sol::table attributes(context.mLua->sol(), sol::create); stats["attributes"] = LuaUtil::makeReadOnly(attributes); @@ -399,7 +399,8 @@ namespace MWLua auto skillStatT = context.mLua->sol().new_usertype("SkillStat"); addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); - skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); }); + skillStatT["modified"] + = sol::readonly_property([=](const SkillStat& stat) { return stat.getModified(context); }); addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); skillStatT["progress"] = sol::property([context](const SkillStat& stat) { return stat.getProgress(context); }, [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 843917275c..30f190ad38 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -161,8 +161,8 @@ namespace MWLua api["_getMenuTransparency"] = []() -> float { return Settings::gui().mMenuTransparency; }; auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); - uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); - uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); }); + uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; From 0e1bb4534557425cab49cc1721293a07d25a3d8f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 10 Jan 2024 14:24:18 +0400 Subject: [PATCH 0777/2167] Cleanup navmesh updater --- components/detournavigator/asyncnavmeshupdater.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 3a1b6fd77a..980281240d 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -180,8 +180,8 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; - const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); - const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles); + const int maxTiles + = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); std::unique_lock lock(mMutex); @@ -376,9 +376,10 @@ namespace DetourNavigator return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); - const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + const int maxTiles + = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) + if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; navMeshCacheItem->lock()->removeTile(job.mChangedTile); From ccbc02abc3da4f1a124df9de5b4b1022cda48f92 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 10 Jan 2024 08:07:48 +0300 Subject: [PATCH 0778/2167] Handle running stats extensions on non-actors gracefully (#7770) --- CHANGELOG.md | 1 + apps/openmw/mwscript/statsextensions.cpp | 143 +++++++++++++++++++---- 2 files changed, 120 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f07def9ac..32d0a6a866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7770: Sword of the Perithia: Script execution failure Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index d617a02b9a..4bc59e1524 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -85,7 +85,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getLevel(); + Interpreter::Type_Integer value = -1; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getLevel(); runtime.push(value); } @@ -102,7 +104,8 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats(ptr).setLevel(value); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).setLevel(value); } }; @@ -121,7 +124,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); + Interpreter::Type_Float value = 0.f; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); runtime.push(value); } @@ -145,6 +150,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); attribute.setBase(value, true); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); @@ -169,6 +177,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); modStat(attribute, value); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); @@ -189,14 +200,14 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float value; + Interpreter::Type_Float value = 0.f; if (mIndex == 0 && ptr.getClass().hasItemHealth(ptr)) { // health is a special case value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); } - else + else if (ptr.getClass().isActor()) { value = ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).getCurrent(); // GetMagicka shouldn't return negative values @@ -225,6 +236,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); stat.setBase(value); @@ -254,6 +268,9 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && ptr.getCellRef().getRefId() == "dagoth_ur_1") { @@ -301,6 +318,9 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); @@ -336,6 +356,13 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + + if (!ptr.getClass().isActor()) + { + runtime.push(0.f); + return; + } + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getDynamic(mIndex).getRatio()); @@ -357,6 +384,12 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + { + runtime.push(0.f); + return; + } + Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mId); runtime.push(value); @@ -381,6 +414,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isNpc()) + return; + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); stats.getSkill(mId).setBase(value, true); @@ -405,6 +441,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isNpc()) + return; + MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mId); modStat(skill, value); } @@ -465,6 +504,9 @@ namespace MWScript ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!ptr.getClass().isActor()) + return; + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(id); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); @@ -491,6 +533,9 @@ namespace MWScript ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().remove(id); @@ -514,7 +559,8 @@ namespace MWScript ESM::RefId spellid = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); } }; @@ -529,7 +575,8 @@ namespace MWScript Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; @@ -845,7 +892,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); + if (ptr.getClass().isActor()) + runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); + else + runtime.push(0); } }; @@ -857,7 +907,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); + if (ptr.getClass().isActor()) + runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); + else + runtime.push(0); } }; @@ -872,9 +925,16 @@ namespace MWScript ESM::RefId race = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const ESM::RefId& npcRace = ptr.get()->mBase->mRace; + if (ptr.getClass().isNpc()) + { + const ESM::RefId& npcRace = ptr.get()->mBase->mRace; - runtime.push(race == npcRace); + runtime.push(race == npcRace); + } + else + { + runtime.push(0); + } } }; @@ -1043,10 +1103,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasDied(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + value = stats.hasDied(); - if (value) - ptr.getClass().getCreatureStats(ptr).clearHasDied(); + if (value) + stats.clearHasDied(); + } runtime.push(value); } @@ -1060,10 +1125,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasBeenMurdered(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + value = stats.hasBeenMurdered(); - if (value) - ptr.getClass().getCreatureStats(ptr).clearHasBeenMurdered(); + if (value) + stats.clearHasBeenMurdered(); + } runtime.push(value); } @@ -1077,7 +1147,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); runtime.push(value); } @@ -1090,7 +1162,10 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); + if (ptr.getClass().isNpc()) + runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); + else + runtime.push(0); } }; @@ -1101,7 +1176,8 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); + if (ptr.getClass().isNpc()) + MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); } }; @@ -1112,7 +1188,8 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); + if (ptr.getClass().isNpc()) + MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); } }; @@ -1124,6 +1201,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + return; + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); @@ -1192,6 +1272,12 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + { + runtime.push(0); + return; + } + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) @@ -1226,6 +1312,13 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + + int arg = runtime[0].mInteger; + runtime.pop(); + + if (!ptr.getClass().isActor()) + return; + MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) @@ -1239,8 +1332,6 @@ namespace MWScript if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); - int arg = runtime[0].mInteger; - runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; @@ -1261,10 +1352,14 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); int arg = runtime[0].mInteger; runtime.pop(); + + if (!ptr.getClass().isActor()) + return; + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); stats.getMagicEffects().modifyBase(mPositiveEffect, arg); } }; From 962ecc43298749cf08110b1613fd3d14b29ab6a3 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 9 Jan 2024 21:25:26 +0100 Subject: [PATCH 0779/2167] Allow menu scripts to read global sections while a game is loaded --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 55 +++++++++++++++++---- apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 4 +- apps/openmw_test_suite/lua/test_storage.cpp | 4 ++ components/lua/storage.cpp | 11 ++++- components/lua/storage.hpp | 13 ++++- files/lua_api/openmw/storage.lua | 5 +- 8 files changed, 80 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index f3cea83224..10d6476653 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -54,6 +54,7 @@ namespace MWBase virtual void newGameStarted() = 0; virtual void gameLoaded() = 0; + virtual void gameEnded() = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 97e1dfae39..2da2afac5d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -105,11 +105,14 @@ namespace MWLua LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); mGlobalScripts.addPackage( "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); - mMenuScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mPlayerStorage)); + mMenuScripts.addPackage( + "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage)); mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); mPlayerPackages["openmw.storage"] = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); + mPlayerStorage.setActive(true); + initConfiguration(); mInitialized = true; mMenuScripts.addAutoStartedScripts(); @@ -301,6 +304,7 @@ namespace MWLua mPlayer = MWWorld::Ptr(); } mGlobalStorage.clearTemporaryAndRemoveCallbacks(); + mGlobalStorage.setActive(false); mPlayerStorage.clearTemporaryAndRemoveCallbacks(); mInputActions.clear(); mInputTriggers.clear(); @@ -329,6 +333,7 @@ namespace MWLua void LuaManager::newGameStarted() { + mGlobalStorage.setActive(true); mInputEvents.clear(); mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; @@ -338,12 +343,20 @@ namespace MWLua void LuaManager::gameLoaded() { + mGlobalStorage.setActive(true); if (!mGlobalScriptsStarted) mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; mMenuScripts.stateChanged(); } + void LuaManager::gameEnded() + { + // TODO: disable scripts and global storage when the game is actually unloaded + // mGlobalStorage.setActive(false); + mMenuScripts.stateChanged(); + } + void LuaManager::uiModeChanged(const MWWorld::Ptr& arg) { if (mPlayer.isEmpty()) @@ -492,6 +505,10 @@ namespace MWLua throw std::runtime_error("Last generated RefNum is invalid"); MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastGenerated); + // TODO: don't execute scripts right away, it will be necessary in multiplayer where global storage requires + // initialization. For now just set global storage as active slightly before it would be set by gameLoaded() + mGlobalStorage.setActive(true); + ESM::LuaScripts globalScripts; globalScripts.load(reader); mLuaEvents.load(mLua.sol(), reader, mContentFileMapping, mGlobalLoader.get()); @@ -540,29 +557,49 @@ namespace MWLua mInputTriggers.clear(); initConfiguration(); - { // Reload global scripts + ESM::LuaScripts globalData; + + if (mGlobalScriptsStarted) + { mGlobalScripts.setSavedDataDeserializer(mGlobalSerializer.get()); - ESM::LuaScripts data; - mGlobalScripts.save(data); + mGlobalScripts.save(globalData); mGlobalStorage.clearTemporaryAndRemoveCallbacks(); - mGlobalScripts.load(data); } + std::unordered_map localData; + for (const auto& [id, ptr] : MWBase::Environment::get().getWorldModel()->getPtrRegistryView()) - { // Reload local scripts + { LocalScripts* scripts = ptr.getRefData().getLuaScripts(); if (scripts == nullptr) continue; scripts->setSavedDataDeserializer(mLocalSerializer.get()); ESM::LuaScripts data; scripts->save(data); - scripts->load(data); + localData[id] = data; } + + mMenuScripts.removeAllScripts(); + + mPlayerStorage.clearTemporaryAndRemoveCallbacks(); + + mMenuScripts.addAutoStartedScripts(); + + for (const auto& [id, ptr] : MWBase::Environment::get().getWorldModel()->getPtrRegistryView()) + { + LocalScripts* scripts = ptr.getRefData().getLuaScripts(); + if (scripts == nullptr) + continue; + scripts->load(localData[id]); + } + for (LocalScripts* scripts : mActiveLocalScripts) scripts->setActive(true); - mMenuScripts.removeAllScripts(); - mMenuScripts.addAutoStartedScripts(); + if (mGlobalScriptsStarted) + { + mGlobalScripts.load(globalData); + } } void LuaManager::handleConsoleCommand( diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index ccd5386c4a..965aa67fab 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -68,6 +68,7 @@ namespace MWLua // LuaManager queues these events and propagates to scripts on the next `update` call. void newGameStarted() override; void gameLoaded() override; + void gameEnded() override; void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void inputEvent(const InputEvent& event) override; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index f29d34c72a..a8df64e3e3 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -157,10 +157,10 @@ void MWState::StateManager::newGame(bool bypass) { Log(Debug::Info) << "Starting a new game"; MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - MWBase::Environment::get().getLuaManager()->newGameStarted(); MWBase::Environment::get().getWorld()->startNewGame(bypass); mState = State_Running; + MWBase::Environment::get().getLuaManager()->newGameStarted(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0); MWBase::Environment::get().getWindowManager()->fadeScreenIn(1); @@ -184,11 +184,13 @@ void MWState::StateManager::newGame(bool bypass) void MWState::StateManager::endGame() { mState = State_Ended; + MWBase::Environment::get().getLuaManager()->gameEnded(); } void MWState::StateManager::resumeGame() { mState = State_Running; + MWBase::Environment::get().getLuaManager()->gameLoaded(); } void MWState::StateManager::saveGame(std::string_view description, const Slot* slot) diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index 6bba813529..a36a527e0c 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -22,6 +22,7 @@ namespace sol::state_view& mLua = luaState.sol(); LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); + storage.setActive(true); std::vector callbackCalls; sol::table callbackHiddenData(mLua, sol::create); @@ -65,6 +66,7 @@ namespace sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); + storage.setActive(true); mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); @@ -82,6 +84,7 @@ namespace sol::state mLua; LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage storage(mLua); + storage.setActive(true); mLua["permanent"] = storage.getMutableSection("permanent"); mLua["temporary"] = storage.getMutableSection("temporary"); @@ -104,6 +107,7 @@ namespace mLua.safe_script("permanent:set('z', 4)"); LuaUtil::LuaStorage storage2(mLua); + storage2.setActive(true); storage2.load(tmpFile); mLua["permanent"] = storage2.getMutableSection("permanent"); mLua["temporary"] = storage2.getMutableSection("temporary"); diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index b2f972e853..dd53fdffcb 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -31,6 +31,7 @@ namespace LuaUtil const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const { + checkIfActive(); auto it = mValues.find(key); if (it != mValues.end()) return it->second; @@ -72,6 +73,7 @@ namespace LuaUtil void LuaStorage::Section::set(std::string_view key, const sol::object& value) { + checkIfActive(); throwIfCallbackRecursionIsTooDeep(); if (value != sol::nil) mValues[std::string(key)] = Value(value); @@ -88,6 +90,7 @@ namespace LuaUtil void LuaStorage::Section::setAll(const sol::optional& values) { + checkIfActive(); throwIfCallbackRecursionIsTooDeep(); mValues.clear(); if (values) @@ -102,6 +105,7 @@ namespace LuaUtil sol::table LuaStorage::Section::asTable() { + checkIfActive(); sol::table res(mStorage->mLua, sol::create); for (const auto& [k, v] : mValues) res[k] = v.getCopy(mStorage->mLua); @@ -175,12 +179,14 @@ namespace LuaUtil return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* playerStorage) + sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) { sol::table res(lua, sol::create); res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section, /*forMenuScripts=*/true); }; + res["globalSection"] + = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } @@ -244,6 +250,7 @@ namespace LuaUtil const std::shared_ptr& LuaStorage::getSection(std::string_view sectionName) { + checkIfActive(); auto it = mData.find(sectionName); if (it != mData.end()) return it->second; @@ -255,12 +262,14 @@ namespace LuaUtil sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts) { + checkIfActive(); const std::shared_ptr
& section = getSection(sectionName); return sol::make_object(mLua, SectionView{ section, readOnly, forMenuScripts }); } sol::table LuaStorage::getAllSections(bool readOnly) { + checkIfActive(); sol::table res(mLua, sol::create); for (const auto& [sectionName, _] : mData) res[sectionName] = getSection(sectionName, readOnly); diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 3376e7e50c..75e0e14a16 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "asyncpackage.hpp" #include "serialization.hpp" @@ -17,10 +18,11 @@ namespace LuaUtil static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); - static sol::table initMenuPackage(lua_State* lua, LuaStorage* playerStorage); + static sol::table initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); explicit LuaStorage(lua_State* lua) : mLua(lua) + , mActive(false) { } @@ -55,6 +57,7 @@ namespace LuaUtil virtual void sectionReplaced(std::string_view section, const sol::optional& values) const = 0; }; void setListener(const Listener* listener) { mListener = listener; } + void setActive(bool active) { mActive = active; } private: class Value @@ -95,6 +98,8 @@ namespace LuaUtil // remove them in clear() bool mPermanent = true; static Value sEmpty; + + void checkIfActive() const { mStorage->checkIfActive(); } }; struct SectionView { @@ -109,6 +114,12 @@ namespace LuaUtil std::map> mData; const Listener* mListener = nullptr; std::set mRunningCallbacks; + bool mActive; + void checkIfActive() const + { + if (!mActive) + throw std::logic_error("Trying to access inactive storage"); + } }; } diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua index 303c674319..2335719be8 100644 --- a/files/lua_api/openmw/storage.lua +++ b/files/lua_api/openmw/storage.lua @@ -17,13 +17,14 @@ --- -- Get a section of the global storage; can be used by any script, but only global scripts can change values. +-- Menu scripts can only access it when a game is running. -- Creates the section if it doesn't exist. -- @function [parent=#storage] globalSection -- @param #string sectionName -- @return #StorageSection --- --- Get a section of the player storage; can be used by player scripts only. +-- Get a section of the player storage; can only be used by player and menu scripts. -- Creates the section if it doesn't exist. -- @function [parent=#storage] playerSection -- @param #string sectionName @@ -36,7 +37,7 @@ -- @return #table --- --- Get all global sections as a table; can be used by player scripts only. +-- Get all player sections as a table; can only be used by player and menu scripts. -- Note that adding/removing items to the returned table doesn't create or remove sections. -- @function [parent=#storage] allPlayerSections -- @return #table From d1d430b431631d2c3d8ab953610e973a19084ec6 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 00:44:52 +0100 Subject: [PATCH 0780/2167] Initial Menu context Settings implementation --- files/data/CMakeLists.txt | 2 +- files/data/builtin.omwscripts | 7 +- files/data/scripts/omw/settings/common.lua | 4 +- files/data/scripts/omw/settings/global.lua | 3 +- files/data/scripts/omw/settings/menu.lua | 491 ++++++++++++++++++++- files/data/scripts/omw/settings/player.lua | 17 +- files/data/scripts/omw/settings/render.lua | 423 ------------------ 7 files changed, 506 insertions(+), 441 deletions(-) delete mode 100644 files/data/scripts/omw/settings/render.lua diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 4b36254183..e038e9f573 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -78,10 +78,10 @@ set(BUILTIN_DATA_FILES scripts/omw/console/menu.lua scripts/omw/mechanics/playercontroller.lua scripts/omw/playercontrols.lua + scripts/omw/settings/menu.lua scripts/omw/settings/player.lua scripts/omw/settings/global.lua scripts/omw/settings/common.lua - scripts/omw/settings/render.lua scripts/omw/settings/renderers.lua scripts/omw/mwui/constants.lua scripts/omw/mwui/borders.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index e35e86aaaf..3e902f6639 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,11 +1,10 @@ # UI framework -PLAYER: scripts/omw/mwui/init.lua -MENU: scripts/omw/mwui/init.lua +MENU,PLAYER: scripts/omw/mwui/init.lua # Settings framework -GLOBAL: scripts/omw/settings/global.lua -PLAYER: scripts/omw/settings/player.lua MENU: scripts/omw/settings/menu.lua +PLAYER: scripts/omw/settings/player.lua +GLOBAL: scripts/omw/settings/global.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua diff --git a/files/data/scripts/omw/settings/common.lua b/files/data/scripts/omw/settings/common.lua index cb83b4223b..9155c64ba7 100644 --- a/files/data/scripts/omw/settings/common.lua +++ b/files/data/scripts/omw/settings/common.lua @@ -6,7 +6,6 @@ local argumentSectionPostfix = 'Arguments' local contextSection = storage.playerSection or storage.globalSection local groupSection = contextSection(groupSectionKey) -groupSection:reset() groupSection:removeOnExit() local function validateSettingOptions(options) @@ -110,9 +109,11 @@ end return { getSection = function(global, key) + if global then error('Getting global section') end return (global and storage.globalSection or storage.playerSection)(key) end, getArgumentSection = function(global, key) + if global then error('Getting global section') end return (global and storage.globalSection or storage.playerSection)(key .. argumentSectionPostfix) end, updateRendererArgument = function(groupKey, settingKey, argument) @@ -120,6 +121,7 @@ return { argumentSection:set(settingKey, argument) end, setGlobalEvent = 'OMWSettingsGlobalSet', + registerPageEvent = 'OmWSettingsRegisterPage', groupSectionKey = groupSectionKey, onLoad = function(saved) if not saved then return end diff --git a/files/data/scripts/omw/settings/global.lua b/files/data/scripts/omw/settings/global.lua index d84794f61d..423c38680b 100644 --- a/files/data/scripts/omw/settings/global.lua +++ b/files/data/scripts/omw/settings/global.lua @@ -5,6 +5,7 @@ local common = require('scripts.omw.settings.common') return { interfaceName = 'Settings', interface = { + version = 1, registerGroup = common.registerGroup, updateRendererArgument = common.updateRendererArgument, }, @@ -17,4 +18,4 @@ return { storage.globalSection(e.groupKey):set(e.settingKey, e.value) end, }, -} \ No newline at end of file +} diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 6fd26d40d2..ff554df768 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -1,19 +1,498 @@ -local common = require('scripts.omw.settings.common') -local render = require('scripts.omw.settings.render') +local menu = require('openmw.menu') +local ui = require('openmw.ui') +local util = require('openmw.util') +local async = require('openmw.async') +local core = require('openmw.core') +local storage = require('openmw.storage') +local I = require('openmw.interfaces') -require('scripts.omw.settings.renderers')(render.registerRenderer) +local common = require('scripts.omw.settings.common') + +local renderers = {} +local function registerRenderer(name, renderFunction) + renderers[name] = renderFunction +end +require('scripts.omw.settings.renderers')(registerRenderer) + +local interfaceL10n = core.l10n('Interface') + +local pages = {} +local groups = {} +local pageOptions = {} + +local interval = { template = I.MWUI.templates.interval } +local growingIntreval = { + template = I.MWUI.templates.interval, + external = { + grow = 1, + }, +} +local spacer = { + props = { + size = util.vector2(0, 10), + }, +} +local bigSpacer = { + props = { + size = util.vector2(0, 50), + }, +} +local stretchingLine = { + template = I.MWUI.templates.horizontalLine, + external = { + stretch = 1, + }, +} +local spacedLines = function(count) + local content = {} + table.insert(content, spacer) + table.insert(content, stretchingLine) + for i = 2, count do + table.insert(content, interval) + table.insert(content, stretchingLine) + end + table.insert(content, spacer) + return { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(content), + } +end + +local function interlaceSeparator(layouts, separator) + local result = {} + result[1] = layouts[1] + for i = 2, #layouts do + table.insert(result, separator) + table.insert(result, layouts[i]) + end + return result +end + +local function setSettingValue(global, groupKey, settingKey, value) + if global then + core.sendGlobalEvent(common.setGlobalEvent, { + groupKey = groupKey, + settingKey = settingKey, + value = value, + }) + else + storage.playerSection(groupKey):set(settingKey, value) + end +end + +local function renderSetting(group, setting, value, global) + local renderFunction = renderers[setting.renderer] + if not renderFunction then + error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) + end + local set = function(value) + setSettingValue(global, group.key, setting.key, value) + end + local l10n = core.l10n(group.l10n) + local titleLayout = { + type = ui.TYPE.Flex, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(setting.name), + }, + }, + }, + } + if setting.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textParagraph, + props = { + text = l10n(setting.description), + size = util.vector2(300, 0), + }, + } + end + local argument = common.getArgumentSection(global, group.key):get(setting.key) + return { + name = setting.key, + type = ui.TYPE.Flex, + props = { + horizontal = true, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, + }, + content = ui.content { + titleLayout, + growingIntreval, + renderFunction(value, set, argument), + }, + } +end + +local groupLayoutName = function(key, global) + return ('%s%s'):format(global and 'global_' or 'player_', key) +end + +local function renderGroup(group, global) + local l10n = core.l10n(group.l10n) + + local valueSection = common.getSection(global, group.key) + local settingLayouts = {} + local sortedSettings = {} + for _, setting in pairs(group.settings) do + sortedSettings[setting.order] = setting + end + for _, setting in ipairs(sortedSettings) do + table.insert(settingLayouts, renderSetting(group, setting, valueSection:get(setting.key), global)) + end + local settingsContent = ui.content(interlaceSeparator(settingLayouts, spacedLines(1))) + + local resetButtonLayout = { + template = I.MWUI.templates.box, + events = { + mouseClick = async:callback(function() + for _, setting in pairs(group.settings) do + setSettingValue(global, group.key, setting.key, setting.default) + end + end), + }, + content = ui.content { + { + template = I.MWUI.templates.padding, + content = ui.content { + { + template = I.MWUI.templates.textNormal, + props = { + text = interfaceL10n('Reset') + }, + }, + }, + }, + }, + } + + local titleLayout = { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(group.name), + textSize = 20, + }, + } + }, + } + if group.description then + titleLayout.content:add(interval) + titleLayout.content:add { + template = I.MWUI.templates.textParagraph, + props = { + text = l10n(group.description), + size = util.vector2(300, 0), + }, + } + end + + return { + name = groupLayoutName(group.key, global), + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + type = ui.TYPE.Flex, + props = { + horizontal = true, + arrange = ui.ALIGNMENT.Center, + }, + external = { + stretch = 1, + }, + content = ui.content { + titleLayout, + growingIntreval, + resetButtonLayout, + }, + }, + spacedLines(2), + { + name = 'settings', + type = ui.TYPE.Flex, + content = settingsContent, + external = { + stretch = 1, + }, + }, + }, + } +end + +local function pageGroupComparator(a, b) + return a.order < b.order or ( + a.order == b.order and a.key < b.key + ) +end + +local function generateSearchHints(page) + local hints = {} + local l10n = core.l10n(page.l10n) + table.insert(hints, l10n(page.name)) + if page.description then + table.insert(hints, l10n(page.description)) + end + local pageGroups = groups[page.key] + for _, pageGroup in pairs(pageGroups) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + local l10n = core.l10n(group.l10n) + table.insert(hints, l10n(group.name)) + if group.description then + table.insert(hints, l10n(group.description)) + end + for _, setting in pairs(group.settings) do + table.insert(hints, l10n(setting.name)) + if setting.description then + table.insert(hints, l10n(setting.description)) + end + end + end + return table.concat(hints, ' ') +end + +local function renderPage(page) + local l10n = core.l10n(page.l10n) + local sortedGroups = {} + for _, group in pairs(groups[page.key]) do + table.insert(sortedGroups, group) + end + table.sort(sortedGroups, pageGroupComparator) + local groupLayouts = {} + for _, pageGroup in ipairs(sortedGroups) do + local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + end + local groupsLayout = { + name = 'groups', + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content(interlaceSeparator(groupLayouts, bigSpacer)), + } + local titleLayout = { + type = ui.TYPE.Flex, + external = { + stretch = 1, + }, + content = ui.content { + { + template = I.MWUI.templates.textHeader, + props = { + text = l10n(page.name), + textSize = 22, + }, + }, + spacedLines(3), + }, + } + if page.description then + titleLayout.content:add { + template = I.MWUI.templates.textParagraph, + props = { + text = l10n(page.description), + size = util.vector2(300, 0), + }, + } + end + local layout = { + name = page.key, + type = ui.TYPE.Flex, + props = { + position = util.vector2(10, 10), + }, + content = ui.content { + titleLayout, + bigSpacer, + groupsLayout, + bigSpacer, + }, + } + return { + name = l10n(page.name), + element = ui.create(layout), + searchHints = generateSearchHints(page), + } +end + +local function onSettingChanged(global) + return async:callback(function(groupKey, settingKey) + local group = common.getSection(global, common.groupSectionKey):get(groupKey) + if not group or not pageOptions[group.page] then return end + + local value = common.getSection(global, group.key):get(settingKey) + + local element = pageOptions[group.page].element + local groupsLayout = element.layout.content.groups + local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] + local settingsContent = groupLayout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + element:update() + end) +end + +local function onGroupRegistered(global, key) + local group = common.getSection(global, common.groupSectionKey):get(key) + groups[group.page] = groups[group.page] or {} + local pageGroup = { + key = group.key, + global = global, + order = group.order, + } + + if not groups[group.page][pageGroup.key] then + common.getSection(global, group.key):subscribe(onSettingChanged(global)) + common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey) + local group = common.getSection(global, common.groupSectionKey):get(group.key) + if not group or not pageOptions[group.page] then return end + + local value = common.getSection(global, group.key):get(settingKey) + + local element = pageOptions[group.page].element + local groupsLayout = element.layout.content.groups + local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] + local settingsContent = groupLayout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + element:update() + end)) + end + + groups[group.page][pageGroup.key] = pageGroup + + if not pages[group.page] then return end + if pageOptions[group.page] then + pageOptions[group.page].element:destroy() + else + pageOptions[group.page] = {} + end + local renderedOptions = renderPage(pages[group.page]) + for k, v in pairs(renderedOptions) do + pageOptions[group.page][k] = v + end +end + + +local function updatePlayerGroups() + local playerGroups = storage.playerSection(common.groupSectionKey) + for groupKey in pairs(playerGroups:asTable()) do + onGroupRegistered(false, groupKey) + end + playerGroups:subscribe(async:callback(function(_, key) + if key then + onGroupRegistered(false, key) + else + for groupKey in pairs(playerGroups:asTable()) do + onGroupRegistered(false, groupKey) + end + end + end)) +end + +updatePlayerGroups() + +local function updateGlobalGroups() + local globalGroups = storage.globalSection(common.groupSectionKey) + for groupKey in pairs(globalGroups:asTable()) do + onGroupRegistered(true, groupKey) + end + globalGroups:subscribe(async:callback(function(_, key) + if key then + onGroupRegistered(true, key) + else + for groupKey in pairs(globalGroups:asTable()) do + onGroupRegistered(true, groupKey) + end + end + end)) +end + +local function resetGroups() + for pageKey, page in pairs(groups) do + for groupKey in pairs(page) do + page[groupKey] = nil + end + local renderedOptions = renderPage(pages[pageKey]) + for k, v in pairs(renderedOptions) do + pageOptions[pageKey][k] = v + end + end +end + +local function registerPage(options) + if type(options) ~= 'table' then + error('Page options must be a table') + end + if type(options.key) ~= 'string' then + error('Page must have a key') + end + if type(options.l10n) ~= 'string' then + error('Page must have a localization context') + end + if type(options.name) ~= 'string' then + error('Page must have a name') + end + if options.description ~= nil and type(options.description) ~= 'string' then + error('Page description key must be a string') + end + local page = { + key = options.key, + l10n = options.l10n, + name = options.name, + description = options.description, + } + pages[page.key] = page + groups[page.key] = groups[page.key] or {} + if pageOptions[page.key] then + pageOptions[page.key].element:destroy() + end + pageOptions[page.key] = pageOptions[page.key] or {} + local renderedOptions = renderPage(page) + for k, v in pairs(renderedOptions) do + pageOptions[page.key][k] = v + end + ui.registerSettingsPage(pageOptions[page.key]) +end return { interfaceName = 'Settings', interface = { - version = 0, - registerPage = render.registerPage, - registerRenderer = render.registerRenderer, + version = 1, + registerPage = registerPage, + registerRenderer = registerRenderer, registerGroup = common.registerGroup, updateRendererArgument = common.updateRendererArgument, }, engineHandlers = { onLoad = common.onLoad, onSave = common.onSave, + onStateChanged = function() + if menu.getState() == menu.STATE.Running then + updateGlobalGroups() + else + resetGroups() + end + updatePlayerGroups() + end, }, + eventHandlers = { + [common.registerPageEvent] = function(options) + registerPage(options) + end, + } } diff --git a/files/data/scripts/omw/settings/player.lua b/files/data/scripts/omw/settings/player.lua index 3a71f11456..3898e733e1 100644 --- a/files/data/scripts/omw/settings/player.lua +++ b/files/data/scripts/omw/settings/player.lua @@ -1,5 +1,12 @@ +local storage = require('openmw.storage') +local types = require('openmw.types') +local self = require('openmw.self') + local common = require('scripts.omw.settings.common') -local render = require('scripts.omw.settings.render') + +local function registerPage(options) + types.Player.sendMenuEvent(self, common.registerPageEvent, options) +end --- -- @type PageOptions @@ -82,7 +89,7 @@ return { -- name = 'MyModName', -- description = 'MyModDescription', -- })--- - registerPage = render.registerPage, + registerPage = registerPage, --- -- @function [parent=#Settings] registerRenderer Register a renderer, -- only avaialable in menu scripts (DEPRECATED in player scripts) @@ -105,9 +112,9 @@ return { -- }, -- } -- end) - registerRenderer = function() - print( - 'Register setting renderers in player scripts has been deprecated and moved to menu Settings interface') + registerRenderer = function(name) + print(([[Can't register setting renderer "%s". registerRenderer and moved to Menu context Settings interface]]) + :format(name)) end, --- -- @function [parent=#Settings] registerGroup Register a group to be attached to a page, diff --git a/files/data/scripts/omw/settings/render.lua b/files/data/scripts/omw/settings/render.lua deleted file mode 100644 index d4de143b02..0000000000 --- a/files/data/scripts/omw/settings/render.lua +++ /dev/null @@ -1,423 +0,0 @@ -local ui = require('openmw.ui') -local util = require('openmw.util') -local async = require('openmw.async') -local core = require('openmw.core') -local storage = require('openmw.storage') -local I = require('openmw.interfaces') - -local common = require('scripts.omw.settings.common') - -local renderers = {} -local function registerRenderer(name, renderFunction) - renderers[name] = renderFunction -end - -local interfaceL10n = core.l10n('Interface') - -local pages = {} -local groups = {} -local pageOptions = {} - -local interval = { template = I.MWUI.templates.interval } -local growingIntreval = { - template = I.MWUI.templates.interval, - external = { - grow = 1, - }, -} -local spacer = { - props = { - size = util.vector2(0, 10), - }, -} -local bigSpacer = { - props = { - size = util.vector2(0, 50), - }, -} -local stretchingLine = { - template = I.MWUI.templates.horizontalLine, - external = { - stretch = 1, - }, -} -local spacedLines = function(count) - local content = {} - table.insert(content, spacer) - table.insert(content, stretchingLine) - for i = 2, count do - table.insert(content, interval) - table.insert(content, stretchingLine) - end - table.insert(content, spacer) - return { - type = ui.TYPE.Flex, - external = { - stretch = 1, - }, - content = ui.content(content), - } -end - -local function interlaceSeparator(layouts, separator) - local result = {} - result[1] = layouts[1] - for i = 2, #layouts do - table.insert(result, separator) - table.insert(result, layouts[i]) - end - return result -end - -local function setSettingValue(global, groupKey, settingKey, value) - if global then - core.sendGlobalEvent(common.setGlobalEvent, { - groupKey = groupKey, - settingKey = settingKey, - value = value, - }) - else - storage.playerSection(groupKey):set(settingKey, value) - end -end - -local function renderSetting(group, setting, value, global) - local renderFunction = renderers[setting.renderer] - if not renderFunction then - error(('Setting %s of %s has unknown renderer %s'):format(setting.key, group.key, setting.renderer)) - end - local set = function(value) - setSettingValue(global, group.key, setting.key, value) - end - local l10n = core.l10n(group.l10n) - local titleLayout = { - type = ui.TYPE.Flex, - content = ui.content { - { - template = I.MWUI.templates.textHeader, - props = { - text = l10n(setting.name), - }, - }, - }, - } - if setting.description then - titleLayout.content:add(interval) - titleLayout.content:add { - template = I.MWUI.templates.textParagraph, - props = { - text = l10n(setting.description), - size = util.vector2(300, 0), - }, - } - end - local argument = common.getArgumentSection(global, group.key):get(setting.key) - return { - name = setting.key, - type = ui.TYPE.Flex, - props = { - horizontal = true, - arrange = ui.ALIGNMENT.Center, - }, - external = { - stretch = 1, - }, - content = ui.content { - titleLayout, - growingIntreval, - renderFunction(value, set, argument), - }, - } -end - -local groupLayoutName = function(key, global) - return ('%s%s'):format(global and 'global_' or 'player_', key) -end - -local function renderGroup(group, global) - local l10n = core.l10n(group.l10n) - - local valueSection = common.getSection(global, group.key) - local settingLayouts = {} - local sortedSettings = {} - for _, setting in pairs(group.settings) do - sortedSettings[setting.order] = setting - end - for _, setting in ipairs(sortedSettings) do - table.insert(settingLayouts, renderSetting(group, setting, valueSection:get(setting.key), global)) - end - local settingsContent = ui.content(interlaceSeparator(settingLayouts, spacedLines(1))) - - local resetButtonLayout = { - template = I.MWUI.templates.box, - events = { - mouseClick = async:callback(function() - for _, setting in pairs(group.settings) do - setSettingValue(global, group.key, setting.key, setting.default) - end - end), - }, - content = ui.content { - { - template = I.MWUI.templates.padding, - content = ui.content { - { - template = I.MWUI.templates.textNormal, - props = { - text = interfaceL10n('Reset') - }, - }, - }, - }, - }, - } - - local titleLayout = { - type = ui.TYPE.Flex, - external = { - stretch = 1, - }, - content = ui.content { - { - template = I.MWUI.templates.textHeader, - props = { - text = l10n(group.name), - textSize = 20, - }, - } - }, - } - if group.description then - titleLayout.content:add(interval) - titleLayout.content:add { - template = I.MWUI.templates.textParagraph, - props = { - text = l10n(group.description), - size = util.vector2(300, 0), - }, - } - end - - return { - name = groupLayoutName(group.key, global), - type = ui.TYPE.Flex, - external = { - stretch = 1, - }, - content = ui.content { - { - type = ui.TYPE.Flex, - props = { - horizontal = true, - arrange = ui.ALIGNMENT.Center, - }, - external = { - stretch = 1, - }, - content = ui.content { - titleLayout, - growingIntreval, - resetButtonLayout, - }, - }, - spacedLines(2), - { - name = 'settings', - type = ui.TYPE.Flex, - content = settingsContent, - external = { - stretch = 1, - }, - }, - }, - } -end - -local function pageGroupComparator(a, b) - return a.order < b.order or ( - a.order == b.order and a.key < b.key - ) -end - -local function generateSearchHints(page) - local hints = {} - local l10n = core.l10n(page.l10n) - table.insert(hints, l10n(page.name)) - if page.description then - table.insert(hints, l10n(page.description)) - end - local pageGroups = groups[page.key] - for _, pageGroup in pairs(pageGroups) do - local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) - local l10n = core.l10n(group.l10n) - table.insert(hints, l10n(group.name)) - if group.description then - table.insert(hints, l10n(group.description)) - end - for _, setting in pairs(group.settings) do - table.insert(hints, l10n(setting.name)) - if setting.description then - table.insert(hints, l10n(setting.description)) - end - end - end - return table.concat(hints, ' ') -end - -local function renderPage(page) - local l10n = core.l10n(page.l10n) - local sortedGroups = {} - for i, v in ipairs(groups[page.key]) do sortedGroups[i] = v end - table.sort(sortedGroups, pageGroupComparator) - local groupLayouts = {} - for _, pageGroup in ipairs(sortedGroups) do - local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) - table.insert(groupLayouts, renderGroup(group, pageGroup.global)) - end - local groupsLayout = { - name = 'groups', - type = ui.TYPE.Flex, - external = { - stretch = 1, - }, - content = ui.content(interlaceSeparator(groupLayouts, bigSpacer)), - } - local titleLayout = { - type = ui.TYPE.Flex, - external = { - stretch = 1, - }, - content = ui.content { - { - template = I.MWUI.templates.textHeader, - props = { - text = l10n(page.name), - textSize = 22, - }, - }, - spacedLines(3), - }, - } - if page.description then - titleLayout.content:add { - template = I.MWUI.templates.textParagraph, - props = { - text = l10n(page.description), - size = util.vector2(300, 0), - }, - } - end - local layout = { - name = page.key, - type = ui.TYPE.Flex, - props = { - position = util.vector2(10, 10), - }, - content = ui.content { - titleLayout, - bigSpacer, - groupsLayout, - bigSpacer, - }, - } - return { - name = l10n(page.name), - element = ui.create(layout), - searchHints = generateSearchHints(page), - } -end - -local function onSettingChanged(global) - return async:callback(function(groupKey, settingKey) - local group = common.getSection(global, common.groupSectionKey):get(groupKey) - if not group or not pageOptions[group.page] then return end - - local value = common.getSection(global, group.key):get(settingKey) - - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) - element:update() - end) -end -local function onGroupRegistered(global, key) - local group = common.getSection(global, common.groupSectionKey):get(key) - groups[group.page] = groups[group.page] or {} - local pageGroup = { - key = group.key, - global = global, - order = group.order, - } - table.insert(groups[group.page], pageGroup) - common.getSection(global, group.key):subscribe(onSettingChanged(global)) - common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey) - local groupKey = group.key - local group = common.getSection(global, common.groupSectionKey):get(groupKey) - if not group or not pageOptions[group.page] then return end - - local value = common.getSection(global, group.key):get(settingKey) - - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) - element:update() - end)) - - if not pages[group.page] then return end - local options = renderPage(pages[group.page]) - if pageOptions[group.page] then - pageOptions[group.page].element:destroy() - else - pageOptions[group.page] = {} - end - for k, v in pairs(options) do - pageOptions[group.page][k] = v - end -end -local globalGroups = storage.globalSection(common.groupSectionKey) -for groupKey in pairs(globalGroups:asTable()) do - onGroupRegistered(true, groupKey) -end -globalGroups:subscribe(async:callback(function(_, key) - if key then onGroupRegistered(true, key) end -end)) -storage.playerSection(common.groupSectionKey):subscribe(async:callback(function(_, key) - if key then onGroupRegistered(false, key) end -end)) - -local function registerPage(options) - if type(options) ~= 'table' then - error('Page options must be a table') - end - if type(options.key) ~= 'string' then - error('Page must have a key') - end - if type(options.l10n) ~= 'string' then - error('Page must have a localization context') - end - if type(options.name) ~= 'string' then - error('Page must have a name') - end - if options.description ~= nil and type(options.description) ~= 'string' then - error('Page description key must be a string') - end - local page = { - key = options.key, - l10n = options.l10n, - name = options.name, - description = options.description, - } - pages[page.key] = page - groups[page.key] = groups[page.key] or {} - pageOptions[page.key] = renderPage(page) - ui.registerSettingsPage(pageOptions[page.key]) -end - -return { - registerPage = registerPage, - registerRenderer = registerRenderer, -} \ No newline at end of file From 2107bbc01db56c376b878718b2c011d4b6523c1a Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 19:27:39 +0100 Subject: [PATCH 0781/2167] Reuse input engine handlers in menu scripts --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/inputprocessor.hpp | 73 +++++++++++++++++++ apps/openmw/mwlua/luamanagerimp.cpp | 4 +- apps/openmw/mwlua/menuscripts.hpp | 8 ++ apps/openmw/mwlua/playerscripts.hpp | 44 ++--------- components/lua/scriptscontainer.hpp | 4 +- .../lua-scripting/engine_handlers.rst | 35 +++++---- 7 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 apps/openmw/mwlua/inputprocessor.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 645e1ba8f3..184c461cb3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata + classbindings itemdata inputprocessor types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp new file mode 100644 index 0000000000..112d10c750 --- /dev/null +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -0,0 +1,73 @@ +#ifndef MWLUA_INPUTPROCESSOR_H +#define MWLUA_INPUTPROCESSOR_H + +#include + +#include +#include +#include + +#include "../mwbase/luamanager.hpp" + +namespace MWLua +{ + class InputProcessor + { + public: + InputProcessor(LuaUtil::ScriptsContainer* scriptsContainer) + : mScriptsContainer(scriptsContainer) + { + mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, + &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed, + &mTouchpadReleased, &mTouchpadMoved }); + } + + void processInputEvent(const MWBase::LuaManager::InputEvent& event) + { + using InputEvent = MWBase::LuaManager::InputEvent; + switch (event.mType) + { + case InputEvent::KeyPressed: + mScriptsContainer->callEngineHandlers(mKeyPressHandlers, std::get(event.mValue)); + break; + case InputEvent::KeyReleased: + mScriptsContainer->callEngineHandlers(mKeyReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerPressed: + mScriptsContainer->callEngineHandlers(mControllerButtonPressHandlers, std::get(event.mValue)); + break; + case InputEvent::ControllerReleased: + mScriptsContainer->callEngineHandlers( + mControllerButtonReleaseHandlers, std::get(event.mValue)); + break; + case InputEvent::Action: + mScriptsContainer->callEngineHandlers(mActionHandlers, std::get(event.mValue)); + break; + case InputEvent::TouchPressed: + mScriptsContainer->callEngineHandlers( + mTouchpadPressed, std::get(event.mValue)); + break; + case InputEvent::TouchReleased: + mScriptsContainer->callEngineHandlers( + mTouchpadReleased, std::get(event.mValue)); + break; + case InputEvent::TouchMoved: + mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); + break; + } + } + + private: + LuaUtil::ScriptsContainer* mScriptsContainer; + LuaUtil::ScriptsContainer::EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; + LuaUtil::ScriptsContainer::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; + LuaUtil::ScriptsContainer::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; + LuaUtil::ScriptsContainer::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; + LuaUtil::ScriptsContainer::EngineHandlerList mActionHandlers{ "onInputAction" }; + LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + }; +} + +#endif // MWLUA_INPUTPROCESSOR_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2da2afac5d..5b5a1b7d0d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -229,7 +229,9 @@ namespace MWLua PlayerScripts* playerScripts = mPlayer.isEmpty() ? nullptr : dynamic_cast(mPlayer.getRefData().getLuaScripts()); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); - // TODO: handle main menu input events + + for (const auto& event : mInputEvents) + mMenuScripts.processInputEvent(event); if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu)) { for (const auto& event : mInputEvents) diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index 3fd1bce186..3bd55952ad 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -10,6 +10,7 @@ #include "../mwbase/luamanager.hpp" #include "context.hpp" +#include "inputprocessor.hpp" namespace MWLua { @@ -21,10 +22,16 @@ namespace MWLua public: MenuScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Menu") + , mInputProcessor(this) { registerEngineHandlers({ &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged }); } + void processInputEvent(const MWBase::LuaManager::InputEvent& event) + { + mInputProcessor.processInputEvent(event); + } + void stateChanged() { callEngineHandlers(mStateChanged); } bool consoleCommand(const std::string& consoleMode, const std::string& command) @@ -36,6 +43,7 @@ namespace MWLua void uiModeChanged() { callEngineHandlers(mUiModeChanged); } private: + MWLua::InputProcessor mInputProcessor; EngineHandlerList mStateChanged{ "onStateChanged" }; EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; EngineHandlerList mUiModeChanged{ "_onUiModeChanged" }; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index 2d3aa9bc78..bc3bee15ca 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -7,6 +7,7 @@ #include "../mwbase/luamanager.hpp" +#include "inputprocessor.hpp" #include "localscripts.hpp" namespace MWLua @@ -17,42 +18,14 @@ namespace MWLua public: PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj) + , mInputProcessor(this) { - registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers, - &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers, - &mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate, &mUiModeChanged }); + registerEngineHandlers({ &mConsoleCommandHandlers, &mOnFrameHandlers, &mQuestUpdate, &mUiModeChanged }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) { - using InputEvent = MWBase::LuaManager::InputEvent; - switch (event.mType) - { - case InputEvent::KeyPressed: - callEngineHandlers(mKeyPressHandlers, std::get(event.mValue)); - break; - case InputEvent::KeyReleased: - callEngineHandlers(mKeyReleaseHandlers, std::get(event.mValue)); - break; - case InputEvent::ControllerPressed: - callEngineHandlers(mControllerButtonPressHandlers, std::get(event.mValue)); - break; - case InputEvent::ControllerReleased: - callEngineHandlers(mControllerButtonReleaseHandlers, std::get(event.mValue)); - break; - case InputEvent::Action: - callEngineHandlers(mActionHandlers, std::get(event.mValue)); - break; - case InputEvent::TouchPressed: - callEngineHandlers(mTouchpadPressed, std::get(event.mValue)); - break; - case InputEvent::TouchReleased: - callEngineHandlers(mTouchpadReleased, std::get(event.mValue)); - break; - case InputEvent::TouchMoved: - callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); - break; - } + mInputProcessor.processInputEvent(event); } void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } @@ -75,16 +48,9 @@ namespace MWLua } private: + InputProcessor mInputProcessor; EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; - EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; - EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; - EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; - EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; - EngineHandlerList mActionHandlers{ "onInputAction" }; EngineHandlerList mOnFrameHandlers{ "onFrame" }; - EngineHandlerList mTouchpadPressed{ "onTouchPress" }; - EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; - EngineHandlerList mTouchpadMoved{ "onTouchMove" }; EngineHandlerList mQuestUpdate{ "onQuestUpdate" }; EngineHandlerList mUiModeChanged{ "_onUiModeChanged" }; }; diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 631b1e58a8..b3fb0bd376 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -157,7 +157,8 @@ namespace LuaUtil void collectStats(std::vector& stats) const; static int64_t getInstanceCount() { return sInstanceCount; } - protected: + public: // TODO: public to be available to MWLua::InputProcessor. Consider other ways of reusing engine handlers + // between containers struct Handler { int mScriptId; @@ -198,6 +199,7 @@ namespace LuaUtil // a public function (see how ScriptsContainer::update is implemented) that calls `callEngineHandlers`. void registerEngineHandlers(std::initializer_list handlers); + protected: const std::string mNamePrefix; LuaUtil::LuaState& mLua; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 6ef9846d2e..e4ef9b20e4 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -80,23 +80,16 @@ Engine handler is a function defined by a script, that can be called by the engi | Similarly to onActivated, the item has already been removed | from the actor's inventory, and the count was set to zero. -**Only for local scripts attached to a player** +**Only menu scripts and local scripts attached to a player** .. list-table:: :widths: 20 80 - * - onFrame(dt) - - | Called every frame (even if the game is paused) right after - | processing user input. Use it only for latency-critical stuff - | and for UI that should work on pause. - | `dt` is simulation time delta (0 when on pause). - * - onKeyPress(key) +* - onKeyPress(key) - | `Key `_ is pressed. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` - * - onQuestUpdate(questId, stage) - - | Called when a quest is updated. - * - onKeyRelease(key) + * - onKeyRelease(key) - | `Key `_ is released. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` @@ -127,6 +120,24 @@ Engine handler is a function defined by a script, that can be called by the engi - | User entered `command` in in-game console. Called if either | `mode` is not default or `command` starts with prefix `lua`. +**Only for local scripts attached to a player** + +.. list-table:: + :widths: 20 80 + * - onFrame(dt) + - | Called every frame (even if the game is paused) right after + | processing user input. Use it only for latency-critical stuff + | and for UI that should work on pause. + | `dt` is simulation time delta (0 when on pause). + * - onKeyPress(key) + - | `Key `_ is pressed. + | Usage example: + | ``if key.symbol == 'z' and key.withShift then ...`` + * - onQuestUpdate(questId, stage) + - | Called when a quest is updated. + + + **Only for menu scripts** .. list-table:: @@ -134,7 +145,3 @@ Engine handler is a function defined by a script, that can be called by the engi * - onStateChanged() - | Called whenever the current game changes | (i. e. the result of `getState `_ changes) - * - | onConsoleCommand( - | mode, command, selectedObject) - - | User entered `command` in in-game console. Called if either - | `mode` is not default or `command` starts with prefix `lua`. From 82a125fb6a55f6f00dfb28ce58fde660f363d499 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 19:34:33 +0100 Subject: [PATCH 0782/2167] Replace onUpdate with onFrame for menu scripts --- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/menuscripts.hpp | 5 +++- apps/openmw/mwworld/datetimemanager.cpp | 4 ++- .../lua-scripting/engine_handlers.rst | 28 +++++++++---------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5b5a1b7d0d..8f1abceafc 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -243,7 +243,7 @@ namespace MWLua ? 0.0 : MWBase::Environment::get().getFrameDuration(); mInputActions.update(frameDuration); - mMenuScripts.update(0); + mMenuScripts.onFrame(frameDuration); if (playerScripts) playerScripts->onFrame(frameDuration); mProcessingInputEvents = false; diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index 3bd55952ad..a010317f47 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -24,7 +24,7 @@ namespace MWLua : LuaUtil::ScriptsContainer(lua, "Menu") , mInputProcessor(this) { - registerEngineHandlers({ &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged }); + registerEngineHandlers({ &mOnFrameHandlers, &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -32,6 +32,8 @@ namespace MWLua mInputProcessor.processInputEvent(event); } + void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); } + void stateChanged() { callEngineHandlers(mStateChanged); } bool consoleCommand(const std::string& consoleMode, const std::string& command) @@ -44,6 +46,7 @@ namespace MWLua private: MWLua::InputProcessor mInputProcessor; + EngineHandlerList mOnFrameHandlers{ "onFrame" }; EngineHandlerList mStateChanged{ "onStateChanged" }; EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; EngineHandlerList mUiModeChanged{ "_onUiModeChanged" }; diff --git a/apps/openmw/mwworld/datetimemanager.cpp b/apps/openmw/mwworld/datetimemanager.cpp index 78565cef60..69374a77a9 100644 --- a/apps/openmw/mwworld/datetimemanager.cpp +++ b/apps/openmw/mwworld/datetimemanager.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -263,8 +264,9 @@ namespace MWWorld void DateTimeManager::updateIsPaused() { + auto stateManager = MWBase::Environment::get().getStateManager(); auto wm = MWBase::Environment::get().getWindowManager(); mPaused = !mPausedTags.empty() || wm->isConsoleMode() || wm->isPostProcessorHudVisible() - || wm->isInteractiveMessageBoxActive(); + || wm->isInteractiveMessageBoxActive() || stateManager->getState() == MWBase::StateManager::State_NoGame; } } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index e4ef9b20e4..bcadfeb295 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -5,10 +5,16 @@ Engine handlers reference Engine handler is a function defined by a script, that can be called by the engine. - - **Can be defined by any script** +.. list-table:: + :widths: 20 80 + * - onInterfaceOverride(base) + - | Called if the current script has an interface and overrides an interface + | (``base``) of another script. + +**Can be defined by any non-menu script** + .. list-table:: :widths: 20 80 @@ -29,9 +35,6 @@ Engine handler is a function defined by a script, that can be called by the engi | Note that ``onLoad`` means loading a script rather than loading a game. | If a script did not exist when a game was saved onLoad will not be | called, but ``onInit`` will. - * - onInterfaceOverride(base) - - | Called if the current script has an interface and overrides an interface - | (``base``) of another script. **Only for global scripts** @@ -84,8 +87,12 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 - -* - onKeyPress(key) + * - onFrame(dt) + - | Called every frame (even if the game is paused) right after + | processing user input. Use it only for latency-critical stuff + | and for UI that should work on pause. + | `dt` is simulation time delta (0 when on pause). + * - onKeyPress(key) - | `Key `_ is pressed. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` @@ -124,19 +131,12 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 - * - onFrame(dt) - - | Called every frame (even if the game is paused) right after - | processing user input. Use it only for latency-critical stuff - | and for UI that should work on pause. - | `dt` is simulation time delta (0 when on pause). * - onKeyPress(key) - | `Key `_ is pressed. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` * - onQuestUpdate(questId, stage) - | Called when a quest is updated. - - **Only for menu scripts** From 6917384fc170f5488425fec3dfff0272ae983643 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 20:32:21 +0100 Subject: [PATCH 0783/2167] Don't reset menu-registered setting groups --- files/data/scripts/omw/settings/menu.lua | 13 ++++++++++--- files/data/scripts/omw/settings/player.lua | 1 - 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index ff554df768..61fce3015e 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -47,7 +47,7 @@ local spacedLines = function(count) local content = {} table.insert(content, spacer) table.insert(content, stretchingLine) - for i = 2, count do + for _ = 2, count do table.insert(content, interval) table.insert(content, stretchingLine) end @@ -422,10 +422,14 @@ local function updateGlobalGroups() end)) end +local menuGroups = {} + local function resetGroups() for pageKey, page in pairs(groups) do for groupKey in pairs(page) do - page[groupKey] = nil + if not menuGroups[groupKey] then + page[groupKey] = nil + end end local renderedOptions = renderPage(pages[pageKey]) for k, v in pairs(renderedOptions) do @@ -475,7 +479,10 @@ return { version = 1, registerPage = registerPage, registerRenderer = registerRenderer, - registerGroup = common.registerGroup, + registerGroup = function(options) + common.registerGroup(options) + menuGroups[options.key] = true + end, updateRendererArgument = common.updateRendererArgument, }, engineHandlers = { diff --git a/files/data/scripts/omw/settings/player.lua b/files/data/scripts/omw/settings/player.lua index 3898e733e1..ea3f207df6 100644 --- a/files/data/scripts/omw/settings/player.lua +++ b/files/data/scripts/omw/settings/player.lua @@ -1,4 +1,3 @@ -local storage = require('openmw.storage') local types = require('openmw.types') local self = require('openmw.self') From 5b97a931691095ae325aa2532a731edc2bc513ee Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 20:32:34 +0100 Subject: [PATCH 0784/2167] Move camera settings to a menu script --- files/data/builtin.omwscripts | 1 + files/data/scripts/omw/camera/camera.lua | 3 +- .../data/scripts/omw/camera/head_bobbing.lua | 16 +++--- files/data/scripts/omw/camera/settings.lua | 50 +++++++++---------- .../data/scripts/omw/camera/third_person.lua | 22 ++++---- 5 files changed, 45 insertions(+), 47 deletions(-) diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 3e902f6639..6016dee28a 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -13,6 +13,7 @@ GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua +MENU: scripts/omw/camera/settings.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 6c162f3a25..f5848970dd 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -5,6 +5,7 @@ local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') local async = require('openmw.async') +local storage = require('openmw.storage') local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor @@ -28,7 +29,7 @@ input.registerAction { defaultValue = 0, } -local settings = require('scripts.omw.camera.settings').thirdPerson +local settings = storage.playerSection('SettingsOMWCameraThirdPerson') local head_bobbing = require('scripts.omw.camera.head_bobbing') local third_person = require('scripts.omw.camera.third_person') local pov_auto_switch = require('scripts.omw.camera.first_person_auto_switch') diff --git a/files/data/scripts/omw/camera/head_bobbing.lua b/files/data/scripts/omw/camera/head_bobbing.lua index 8972364ebb..c5402ee3dc 100644 --- a/files/data/scripts/omw/camera/head_bobbing.lua +++ b/files/data/scripts/omw/camera/head_bobbing.lua @@ -2,12 +2,13 @@ local camera = require('openmw.camera') local self = require('openmw.self') local util = require('openmw.util') local async = require('openmw.async') +local storage = require('openmw.storage') local Actor = require('openmw.types').Actor local M = {} -local settings = require('scripts.omw.camera.settings').headBobbing +local settings = storage.playerSection('SettingsOMWCameraHeadBobbing') local doubleStepLength, stepHeight, maxRoll @@ -31,7 +32,7 @@ local arcHeight = sampleArc(1) function M.update(dt, smoothedSpeed) local speed = Actor.getCurrentSpeed(self) - speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high + speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high totalMovement = totalMovement + speed * dt if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then effectWeight = 0 @@ -44,18 +45,17 @@ function M.update(dt, smoothedSpeed) end local doubleStepState = totalMovement / doubleStepLength - doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps - local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps - local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1 + doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps + local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps + local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1 -- Smoothly reduce the effect to zero when the player stops local coef = math.min(smoothedSpeed / 300, 1) * effectWeight - local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2 - local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll + local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2 + local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset)) camera.setExtraRoll(camera.getExtraRoll() + roll) end return M - diff --git a/files/data/scripts/omw/camera/settings.lua b/files/data/scripts/omw/camera/settings.lua index be95ce586c..8d1d51f4a8 100644 --- a/files/data/scripts/omw/camera/settings.lua +++ b/files/data/scripts/omw/camera/settings.lua @@ -3,10 +3,10 @@ local async = require('openmw.async') local I = require('openmw.interfaces') I.Settings.registerPage({ - key = 'OMWCamera', - l10n = 'OMWCamera', - name = 'Camera', - description = 'settingsPageDescription', + key = 'OMWCamera', + l10n = 'OMWCamera', + name = 'Camera', + description = 'settingsPageDescription', }) local thirdPersonGroup = 'SettingsOMWCameraThirdPerson' @@ -16,8 +16,8 @@ local function boolSetting(prefix, key, default) return { key = key, renderer = 'checkbox', - name = prefix..key, - description = prefix..key..'Description', + name = prefix .. key, + description = prefix .. key .. 'Description', default = default, } end @@ -26,8 +26,8 @@ local function floatSetting(prefix, key, default) return { key = key, renderer = 'number', - name = prefix..key, - description = prefix..key..'Description', + name = prefix .. key, + description = prefix .. key .. 'Description', default = default, } end @@ -70,33 +70,29 @@ I.Settings.registerGroup({ }, }) -local settings = { - thirdPerson = storage.playerSection(thirdPersonGroup), - headBobbing = storage.playerSection(headBobbingGroup), -} +local thirdPerson = storage.playerSection(thirdPersonGroup) +local headBobbing = storage.playerSection(headBobbingGroup) local function updateViewOverShoulderDisabled() - local shoulderDisabled = not settings.thirdPerson:get('viewOverShoulder') - I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', {disabled = shoulderDisabled}) - I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', {disabled = shoulderDisabled}) - I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', {disabled = shoulderDisabled}) - I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', {disabled = shoulderDisabled}) + local shoulderDisabled = not thirdPerson:get('viewOverShoulder') + I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', { disabled = shoulderDisabled }) + I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', { disabled = shoulderDisabled }) + I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', { disabled = shoulderDisabled }) + I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', { disabled = shoulderDisabled }) - local move360Disabled = not settings.thirdPerson:get('move360') - I.Settings.updateRendererArgument(thirdPersonGroup, 'move360TurnSpeed', {disabled = move360Disabled}) + local move360Disabled = not thirdPerson:get('move360') + I.Settings.updateRendererArgument(thirdPersonGroup, 'move360TurnSpeed', { disabled = move360Disabled }) end local function updateHeadBobbingDisabled() - local disabled = not settings.headBobbing:get('enabled') - I.Settings.updateRendererArgument(headBobbingGroup, 'step', {disabled = disabled, min = 1}) - I.Settings.updateRendererArgument(headBobbingGroup, 'height', {disabled = disabled}) - I.Settings.updateRendererArgument(headBobbingGroup, 'roll', {disabled = disabled, min = 0, max = 90}) + local disabled = not headBobbing:get('enabled') + I.Settings.updateRendererArgument(headBobbingGroup, 'step', { disabled = disabled, min = 1 }) + I.Settings.updateRendererArgument(headBobbingGroup, 'height', { disabled = disabled }) + I.Settings.updateRendererArgument(headBobbingGroup, 'roll', { disabled = disabled, min = 0, max = 90 }) end updateViewOverShoulderDisabled() updateHeadBobbingDisabled() -settings.thirdPerson:subscribe(async:callback(updateViewOverShoulderDisabled)) -settings.headBobbing:subscribe(async:callback(updateHeadBobbingDisabled)) - -return settings +thirdPerson:subscribe(async:callback(updateViewOverShoulderDisabled)) +headBobbing:subscribe(async:callback(updateHeadBobbingDisabled)) diff --git a/files/data/scripts/omw/camera/third_person.lua b/files/data/scripts/omw/camera/third_person.lua index 8c68d1596d..9d5004349a 100644 --- a/files/data/scripts/omw/camera/third_person.lua +++ b/files/data/scripts/omw/camera/third_person.lua @@ -3,10 +3,11 @@ local util = require('openmw.util') local self = require('openmw.self') local nearby = require('openmw.nearby') local async = require('openmw.async') +local storage = require('openmw.storage') local Actor = require('openmw.types').Actor -local settings = require('scripts.omw.camera.settings').thirdPerson +local settings = storage.playerSection('SettingsOMWCameraThirdPerson') local MODE = camera.MODE local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 } @@ -31,7 +32,7 @@ local function updateSettings() viewOverShoulder = settings:get('viewOverShoulder') autoSwitchShoulder = settings:get('autoSwitchShoulder') shoulderOffset = util.vector2(settings:get('shoulderOffsetX'), - settings:get('shoulderOffsetY')) + settings:get('shoulderOffsetY')) zoomOutWhenMoveCoef = settings:get('zoomOutWhenMoveCoef') defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder @@ -46,7 +47,7 @@ local state = defaultShoulder local function ray(from, angle, limit) local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0) - local res = nearby.castRay(from, to, {collisionType = camera.getCollisionType()}) + local res = nearby.castRay(from, to, { collisionType = camera.getCollisionType() }) if res.hit then return (res.hitPos - from):length() else @@ -55,8 +56,8 @@ local function ray(from, angle, limit) end local function trySwitchShoulder() - local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit - local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance + local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit + local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance local pos = camera.getTrackedPosition() local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1) @@ -79,7 +80,7 @@ end local function calculateDistance(smoothedSpeed) local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed return (M.baseDistance + math.max(camera.getPitch(), 0) * 50 - + smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef) + + smoothedSpeedSqr / (smoothedSpeedSqr + 300 * 300) * zoomOutWhenMoveCoef) end local function updateState() @@ -95,7 +96,7 @@ local function updateState() state = defaultShoulder end if (mode == MODE.ThirdPerson or Actor.getCurrentSpeed(self) > 0 or state ~= oldState or noThirdPersonLastFrame) - and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then + and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then if autoSwitchShoulder then trySwitchShoulder() else @@ -108,11 +109,11 @@ local function updateState() -- Player doesn't touch controls for a long time. Transition should be very slow. camera.setFocalTransitionSpeed(0.2) elseif (oldState == STATE.Combat or state == STATE.Combat) and - (mode ~= MODE.Preview or M.standingPreview) then + (mode ~= MODE.Preview or M.standingPreview) then -- Transition to/from combat mode and we are not in preview mode. Should be fast. camera.setFocalTransitionSpeed(5.0) else - camera.setFocalTransitionSpeed(1.0) -- Default transition speed. + camera.setFocalTransitionSpeed(1.0) -- Default transition speed. end if state == STATE.RightShoulder then @@ -149,7 +150,7 @@ function M.update(dt, smoothedSpeed) end M.preferredDistance = calculateDistance(smoothedSpeed) - if noThirdPersonLastFrame then -- just switched to third person view + if noThirdPersonLastFrame then -- just switched to third person view camera.setPreferredThirdPersonDistance(M.preferredDistance) camera.instantTransition() noThirdPersonLastFrame = false @@ -161,4 +162,3 @@ function M.update(dt, smoothedSpeed) end return M - From c6a27d06b0fc9c759b91420dd5077bc0b1ff1d22 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 20:40:16 +0100 Subject: [PATCH 0785/2167] Move controls settings to menu context --- docs/source/luadoc_data_paths.sh | 2 +- files/data/CMakeLists.txt | 3 +- files/data/builtin.omwscripts | 3 +- .../omw/{ => input}/playercontrols.lua | 32 ------------------ files/data/scripts/omw/input/settings.lua | 33 +++++++++++++++++++ 5 files changed, 38 insertions(+), 35 deletions(-) rename files/data/scripts/omw/{ => input}/playercontrols.lua (91%) create mode 100644 files/data/scripts/omw/input/settings.lua diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 7bcda5110c..1343ac818c 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -2,7 +2,7 @@ paths=( openmw_aux/*lua scripts/omw/activationhandlers.lua scripts/omw/ai.lua - scripts/omw/playercontrols.lua + scripts/omw/input/playercontrols.lua scripts/omw/camera/camera.lua scripts/omw/mwui/init.lua scripts/omw/settings/player.lua diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index e038e9f573..8027cfb6e2 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -77,7 +77,6 @@ set(BUILTIN_DATA_FILES scripts/omw/console/player.lua scripts/omw/console/menu.lua scripts/omw/mechanics/playercontroller.lua - scripts/omw/playercontrols.lua scripts/omw/settings/menu.lua scripts/omw/settings/player.lua scripts/omw/settings/global.lua @@ -93,6 +92,8 @@ set(BUILTIN_DATA_FILES scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua + scripts/omw/input/settings.lua + scripts/omw/input/playercontrols.lua scripts/omw/input/actionbindings.lua scripts/omw/input/smoothmovement.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 6016dee28a..6d47b96e0a 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -12,9 +12,10 @@ GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua -PLAYER: scripts/omw/playercontrols.lua MENU: scripts/omw/camera/settings.lua PLAYER: scripts/omw/camera/camera.lua +MENU: scripts/omw/input/settings.lua +PLAYER: scripts/omw/input/playercontrols.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua NPC,CREATURE: scripts/omw/ai.lua diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/input/playercontrols.lua similarity index 91% rename from files/data/scripts/omw/playercontrols.lua rename to files/data/scripts/omw/input/playercontrols.lua index ec7d0d238e..311b5a16a9 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/input/playercontrols.lua @@ -9,38 +9,6 @@ local Player = require('openmw.types').Player local I = require('openmw.interfaces') -local settingsGroup = 'SettingsOMWControls' - -local function boolSetting(key, default) - return { - key = key, - renderer = 'checkbox', - name = key, - description = key .. 'Description', - default = default, - } -end - -I.Settings.registerPage({ - key = 'OMWControls', - l10n = 'OMWControls', - name = 'ControlsPage', - description = 'ControlsPageDescription', -}) - -I.Settings.registerGroup({ - key = settingsGroup, - page = 'OMWControls', - l10n = 'OMWControls', - name = 'MovementSettings', - permanentStorage = true, - settings = { - boolSetting('alwaysRun', false), - boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI - boolSetting('smoothControllerMovement', true), - }, -}) - local settings = storage.playerSection('SettingsOMWControls') do diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua new file mode 100644 index 0000000000..fdd17616cc --- /dev/null +++ b/files/data/scripts/omw/input/settings.lua @@ -0,0 +1,33 @@ +local I = require('openmw.interfaces') + +local settingsGroup = 'SettingsOMWControls' + +local function boolSetting(key, default) + return { + key = key, + renderer = 'checkbox', + name = key, + description = key .. 'Description', + default = default, + } +end + +I.Settings.registerPage({ + key = 'OMWControls', + l10n = 'OMWControls', + name = 'ControlsPage', + description = 'ControlsPageDescription', +}) + +I.Settings.registerGroup({ + key = settingsGroup, + page = 'OMWControls', + l10n = 'OMWControls', + name = 'MovementSettings', + permanentStorage = true, + settings = { + boolSetting('alwaysRun', false), + boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI + boolSetting('smoothControllerMovement', true), + }, +}) From 79deb5f5595d862881cd4e2ae76a0371bd6f6838 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 21:10:10 +0100 Subject: [PATCH 0786/2167] Remove settings pages in Lua --- apps/openmw/mwlua/luamanagerimp.cpp | 7 ++++++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/uibindings.cpp | 1 + components/lua_ui/registerscriptsettings.hpp | 1 + components/lua_ui/scriptsettings.cpp | 7 +++++- components/lua_ui/util.cpp | 2 -- files/data/scripts/omw/settings/menu.lua | 25 +++++++++++++++----- files/lua_api/openmw/ui.lua | 5 ++++ 8 files changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 8f1abceafc..e7f7fa9dde 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include "../mwbase/windowmanager.hpp" @@ -62,6 +63,11 @@ namespace MWLua mGlobalScripts.setSerializer(mGlobalSerializer.get()); } + LuaManager::~LuaManager() + { + LuaUi::clearSettings(); + } + void LuaManager::initConfiguration() { mConfiguration.init(MWBase::Environment::get().getESMStore()->getLuaScriptsCfg()); @@ -551,6 +557,7 @@ namespace MWLua LuaUi::clearGameInterface(); LuaUi::clearMenuInterface(); + LuaUi::clearSettings(); MWBase::Environment::get().getWindowManager()->setConsoleMode(""); MWBase::Environment::get().getL10nManager()->dropCache(); mUiResourceManager.clear(); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 965aa67fab..56a30f24e0 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -35,6 +35,7 @@ namespace MWLua LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir); LuaManager(const LuaManager&) = delete; LuaManager(LuaManager&&) = delete; + ~LuaManager(); // Called by engine.cpp when the environment is fully initialized. void init(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 54d8523b4d..061dc4821a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -247,6 +247,7 @@ namespace MWLua { "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } })); api["registerSettingsPage"] = &LuaUi::registerSettingsPage; + api["removeSettingsPage"] = &LuaUi::registerSettingsPage; api["texture"] = [luaManager = context.mLuaManager](const sol::table& options) { LuaUi::TextureData data; diff --git a/components/lua_ui/registerscriptsettings.hpp b/components/lua_ui/registerscriptsettings.hpp index fb794468da..ba36aff904 100644 --- a/components/lua_ui/registerscriptsettings.hpp +++ b/components/lua_ui/registerscriptsettings.hpp @@ -8,6 +8,7 @@ namespace LuaUi // implemented in scriptsettings.cpp void registerSettingsPage(const sol::table& options); void clearSettings(); + void removeSettingsPage(std::string_view key); } #endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index e92d1d8958..514e6ce632 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -40,6 +40,11 @@ namespace LuaUi allPages.push_back(options); } + void removeSettingsPage(const sol::table& options) + { + std::erase_if(allPages, [options](const sol::table& it) { return it == options; }); + } + void clearSettings() { allPages.clear(); @@ -47,10 +52,10 @@ namespace LuaUi void attachPageAt(size_t index, LuaAdapter* adapter) { + adapter->detach(); if (index < allPages.size()) { ScriptSettingsPage page = parse(allPages[index]); - adapter->detach(); if (page.mElement.get()) adapter->attach(page.mElement); } diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index ac5e63e405..fe47de3b1d 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -46,8 +46,6 @@ namespace LuaUi void clearGameInterface() { - // TODO: move settings clearing logic to Lua? - clearSettings(); while (!Element::sGameElements.empty()) Element::sGameElements.begin()->second->destroy(); } diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 61fce3015e..d20d75bf30 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -423,17 +423,27 @@ local function updateGlobalGroups() end local menuGroups = {} +local menuPages = {} -local function resetGroups() +local function reset() for pageKey, page in pairs(groups) do for groupKey in pairs(page) do if not menuGroups[groupKey] then page[groupKey] = nil end end - local renderedOptions = renderPage(pages[pageKey]) - for k, v in pairs(renderedOptions) do - pageOptions[pageKey][k] = v + if pageOptions[pageKey] then + pageOptions[pageKey].element.destroy() + if not menuPages[pageKey] then + pageOptions[pageKey].element.destroy() + ui.removeSettingsPage(pageOptions[pageKey]) + pageOptions[pageKey] = nil + else + local renderedOptions = renderPage(pages[pageKey]) + for k, v in pairs(renderedOptions) do + pageOptions[pageKey][k] = v + end + end end end end @@ -477,7 +487,10 @@ return { interfaceName = 'Settings', interface = { version = 1, - registerPage = registerPage, + registerPage = function(options) + registerPage(options) + menuPages[options] = true + end, registerRenderer = registerRenderer, registerGroup = function(options) common.registerGroup(options) @@ -492,7 +505,7 @@ return { if menu.getState() == menu.STATE.Running then updateGlobalGroups() else - resetGroups() + reset() end updatePlayerGroups() end, diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 8582996c4f..a99b1e782e 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -93,6 +93,11 @@ -- @function [parent=#ui] registerSettingsPage -- @param #SettingsPageOptions page +--- +-- Removes the settings page +-- @function [parent=#ui] removeSettingsPage +-- @param #SettingsPageOptions page must be the exact same table of options as the one passed to registerSettingsPage + --- -- Table with settings page options, passed as an argument to ui.registerSettingsPage -- @type SettingsPageOptions From c2b8e318cff40f8755dd8cee16a29875411707f4 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 21:27:44 +0100 Subject: [PATCH 0787/2167] Move inputBinding renderer to menu context --- .../data/scripts/omw/input/actionbindings.lua | 82 +------------- files/data/scripts/omw/input/settings.lua | 102 ++++++++++++++++++ 2 files changed, 106 insertions(+), 78 deletions(-) diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua index 06ded80793..35a467fb52 100644 --- a/files/data/scripts/omw/input/actionbindings.lua +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -134,90 +134,16 @@ function clearBinding(id) end end -local function updateBinding(id, binding) - bindingSection:set(id, binding) +bindingSection:subscribe(async:callback(function(_, id) + if not id then return end + local binding = bindingSection:get(id) clearBinding(id) if binding ~= nil then registerBinding(binding, id) end return id -end +end)) -local interfaceL10n = core.l10n('interface') - -I.Settings.registerRenderer('inputBinding', function(id, set, arg) - if type(id) ~= 'string' then error('inputBinding: must have a string default value') end - if not arg.type then error('inputBinding: type argument is required') end - if not arg.key then error('inputBinding: key argument is required') end - local info = input.actions[arg.key] or input.triggers[arg.key] - if not info then return {} end - - local l10n = core.l10n(info.key) - - local name = { - template = I.MWUI.templates.textNormal, - props = { - text = l10n(info.name), - }, - } - - local description = { - template = I.MWUI.templates.textNormal, - props = { - text = l10n(info.description), - }, - } - - local binding = bindingSection:get(id) - local label = binding and input.getKeyName(binding.code) or interfaceL10n('None') - - local recorder = { - template = I.MWUI.templates.textEditLine, - props = { - readOnly = true, - text = label, - }, - events = { - focusGain = async:callback(function() - if binding == nil then return end - updateBinding(id, nil) - set(id) - end), - keyPress = async:callback(function(key) - if binding ~= nil or key.code == input.KEY.Escape then return end - - local newBinding = { - code = key.code, - type = arg.type, - key = arg.key, - } - updateBinding(id, newBinding) - set(id) - end), - }, - } - - local row = { - type = ui.TYPE.Flex, - props = { - horizontal = true, - }, - content = ui.content { - name, - { props = { size = util.vector2(10, 0) } }, - recorder, - }, - } - local column = { - type = ui.TYPE.Flex, - content = ui.content { - row, - description, - }, - } - - return column -end) local initiated = false diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index fdd17616cc..42fec91f82 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -1,3 +1,9 @@ +local core = require('openmw.core') +local input = require('openmw.input') +local storage = require('openmw.storage') +local ui = require('openmw.ui') +local util = require('openmw.util') +local async = require('openmw.async') local I = require('openmw.interfaces') local settingsGroup = 'SettingsOMWControls' @@ -31,3 +37,99 @@ I.Settings.registerGroup({ boolSetting('smoothControllerMovement', true), }, }) + +local interfaceL10n = core.l10n('interface') + +local bindingSection = storage.playerSection('OMWInputBindings') + +local recording = nil + + +I.Settings.registerRenderer('inputBinding', function(id, set, arg) + if type(id) ~= 'string' then error('inputBinding: must have a string default value') end + if not arg.type then error('inputBinding: type argument is required') end + if not arg.key then error('inputBinding: key argument is required') end + local info = input.actions[arg.key] or input.triggers[arg.key] + if not info then return {} end + + local l10n = core.l10n(info.key) + + local name = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.name), + }, + } + + local description = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.description), + }, + } + + local binding = bindingSection:get(id) + local label = interfaceL10n('None') + if binding then label = input.getKeyName(binding.code) end + if recording and recording.id == id then label = interfaceL10n('N/A') end + + local recorder = { + template = I.MWUI.templates.textNormal, + props = { + text = label, + }, + events = { + mouseClick = async:callback(function() + if recording ~= nil then return end + if binding ~= nil then bindingSection:set(id, nil) end + recording = { + id = id, + arg = arg, + refresh = function() set(id) end, + } + recording.refresh() + end), + }, + } + + local row = { + type = ui.TYPE.Flex, + props = { + horizontal = true, + }, + content = ui.content { + name, + { props = { size = util.vector2(10, 0) } }, + recorder, + }, + } + local column = { + type = ui.TYPE.Flex, + content = ui.content { + row, + description, + }, + } + + return column +end) + +return { + engineHandlers = { + onKeyPress = function(key) + if recording == nil then return end + local binding = { + code = key.code, + type = recording.arg.type, + key = recording.arg.key, + } + if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing + binding.code = nil + end + bindingSection:set(recording.id, binding) + local refresh = recording.refresh + recording = nil + refresh() + end, + } +} From 1afc7ecd589aa79d291140843bdd2fd36c3ff8f8 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 22:05:33 +0100 Subject: [PATCH 0788/2167] Test Lua widgets for text inputs correctly --- apps/openmw/mwgui/windowmanagerimp.cpp | 6 +++++- apps/openmw/mwlua/luamanagerimp.cpp | 8 +++++++- apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/menuscripts.hpp | 1 - components/lua_ui/textedit.hpp | 3 +++ components/lua_ui/widget.hpp | 2 ++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index c6b729332a..e463443b0c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -51,6 +51,7 @@ #include #include +#include #include @@ -1676,7 +1677,10 @@ namespace MWGui void WindowManager::onKeyFocusChanged(MyGUI::Widget* widget) { - if (widget && widget->castType(false)) + bool isEditBox = widget && widget->castType(false); + LuaUi::WidgetExtension* luaWidget = dynamic_cast(widget); + bool capturesInput = luaWidget ? luaWidget->isTextInput() : isEditBox; + if (widget && capturesInput) SDL_StartTextInput(); else SDL_StopTextInput(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index e7f7fa9dde..df1e97b885 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -118,6 +118,7 @@ namespace MWLua = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); mPlayerStorage.setActive(true); + mGlobalStorage.setActive(false); initConfiguration(); mInitialized = true; @@ -126,6 +127,8 @@ namespace MWLua void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath) { + mPlayerStorage.setActive(true); + mGlobalStorage.setActive(true); const auto globalPath = userConfigPath / "global_storage.bin"; const auto playerPath = userConfigPath / "player_storage.bin"; if (std::filesystem::exists(globalPath)) @@ -236,8 +239,9 @@ namespace MWLua = mPlayer.isEmpty() ? nullptr : dynamic_cast(mPlayer.getRefData().getLuaScripts()); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); - for (const auto& event : mInputEvents) + for (const auto& event : mMenuInputEvents) mMenuScripts.processInputEvent(event); + mMenuInputEvents.clear(); if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu)) { for (const auto& event : mInputEvents) @@ -300,6 +304,7 @@ namespace MWLua mLuaEvents.clear(); mEngineEvents.clear(); mInputEvents.clear(); + mMenuInputEvents.clear(); mObjectLists.clear(); mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; @@ -432,6 +437,7 @@ namespace MWLua { mInputEvents.push_back(event); } + mMenuInputEvents.push_back(event); } MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 56a30f24e0..8ae83308d4 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -179,6 +179,7 @@ namespace MWLua LuaEvents mLuaEvents{ mGlobalScripts, mMenuScripts }; EngineEvents mEngineEvents{ mGlobalScripts }; std::vector mInputEvents; + std::vector mMenuInputEvents; std::unique_ptr mGlobalSerializer; std::unique_ptr mLocalSerializer; diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index a010317f47..befa76a3b2 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -51,7 +51,6 @@ namespace MWLua EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; EngineHandlerList mUiModeChanged{ "_onUiModeChanged" }; }; - } #endif // MWLUA_GLOBALSCRIPTS_H diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 3492a315bc..8f23b51746 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -11,6 +11,9 @@ namespace LuaUi { MYGUI_RTTI_DERIVED(LuaTextEdit) + public: + bool isTextInput() override { return mEditBox->getEditStatic(); } + protected: void initialize() override; void deinitialize() override; diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index c72b64ae3b..591c885ce9 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -73,6 +73,8 @@ namespace LuaUi virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); MyGUI::IntCoord calculateCoord(); + virtual bool isTextInput() { return false; } + protected: virtual void initialize(); void registerEvents(MyGUI::Widget* w); From 1092d2058df9b42200fa699aa55415b78b6b8de8 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 22:05:44 +0100 Subject: [PATCH 0789/2167] Load Lua storage before menu scripts might use it --- apps/openmw/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 92483bd8c3..3200a0b6ee 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -893,8 +893,8 @@ void OMW::Engine::prepareEngine() << 100 * static_cast(result.second) / result.first << "%)"; } - mLuaManager->init(); mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath()); + mLuaManager->init(); // starts a separate lua thread if "lua num threads" > 0 mLuaWorker = std::make_unique(*mLuaManager, *mViewer); From 8cc47f53632da1b8f133d3e33cc80b5d53bfb7c3 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 23:05:13 +0100 Subject: [PATCH 0790/2167] Only allow menu scripts to register permanent groups --- files/data/scripts/omw/settings/menu.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index d20d75bf30..87c3dc6e6a 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -493,6 +493,9 @@ return { end, registerRenderer = registerRenderer, registerGroup = function(options) + if not options.permanentStorage then + error('Menu scripts are only allowed to register setting groups with permanentStorage = true') + end common.registerGroup(options) menuGroups[options.key] = true end, From bd54292ff4f75e38c796144459db1253cfd87b91 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 10 Jan 2024 23:10:19 +0100 Subject: [PATCH 0791/2167] Update I.Settings.registerGroup documentation --- files/data/scripts/omw/settings/player.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/files/data/scripts/omw/settings/player.lua b/files/data/scripts/omw/settings/player.lua index ea3f207df6..9f397c923d 100644 --- a/files/data/scripts/omw/settings/player.lua +++ b/files/data/scripts/omw/settings/player.lua @@ -117,7 +117,8 @@ return { end, --- -- @function [parent=#Settings] registerGroup Register a group to be attached to a page, - -- available both in player and global scripts + -- available in player, menu and global scripts + -- Note: menu scripts only allow group with permanentStorage = true, but can render the page before a game is loaded! -- @param #GroupOptions options -- @usage -- I.Settings.registerGroup { @@ -147,7 +148,7 @@ return { registerGroup = common.registerGroup, --- -- @function [parent=#Settings] updateRendererArgument Change the renderer argument of a setting - -- available both in player and global scripts + -- available both in player, menu and global scripts -- @param #string groupKey A settings group key -- @param #string settingKey A setting key -- @param argument A renderer argument From dd6017e81e2fdd1c4bcbec80a5dd1cc763b7d395 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 11 Jan 2024 00:55:29 +0100 Subject: [PATCH 0792/2167] Avoid making engine handler methods public --- apps/openmw/mwlua/inputprocessor.hpp | 23 +++++++++++------------ apps/openmw/mwlua/menuscripts.hpp | 3 ++- apps/openmw/mwlua/playerscripts.hpp | 3 ++- components/lua/scriptscontainer.hpp | 4 +--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index 112d10c750..fe6de5450e 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -3,18 +3,17 @@ #include -#include -#include #include #include "../mwbase/luamanager.hpp" namespace MWLua { + template class InputProcessor { public: - InputProcessor(LuaUtil::ScriptsContainer* scriptsContainer) + InputProcessor(Container* scriptsContainer) : mScriptsContainer(scriptsContainer) { mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, @@ -58,15 +57,15 @@ namespace MWLua } private: - LuaUtil::ScriptsContainer* mScriptsContainer; - LuaUtil::ScriptsContainer::EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; - LuaUtil::ScriptsContainer::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; - LuaUtil::ScriptsContainer::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; - LuaUtil::ScriptsContainer::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; - LuaUtil::ScriptsContainer::EngineHandlerList mActionHandlers{ "onInputAction" }; - LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; - LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; - LuaUtil::ScriptsContainer::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + Container* mScriptsContainer; + Container::EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; + Container::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; + Container::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; + Container::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; + Container::EngineHandlerList mActionHandlers{ "onInputAction" }; + Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; }; } diff --git a/apps/openmw/mwlua/menuscripts.hpp b/apps/openmw/mwlua/menuscripts.hpp index befa76a3b2..8721224413 100644 --- a/apps/openmw/mwlua/menuscripts.hpp +++ b/apps/openmw/mwlua/menuscripts.hpp @@ -45,7 +45,8 @@ namespace MWLua void uiModeChanged() { callEngineHandlers(mUiModeChanged); } private: - MWLua::InputProcessor mInputProcessor; + friend class MWLua::InputProcessor; + MWLua::InputProcessor mInputProcessor; EngineHandlerList mOnFrameHandlers{ "onFrame" }; EngineHandlerList mStateChanged{ "onStateChanged" }; EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index bc3bee15ca..ea7baccb76 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -48,7 +48,8 @@ namespace MWLua } private: - InputProcessor mInputProcessor; + friend class MWLua::InputProcessor; + InputProcessor mInputProcessor; EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" }; EngineHandlerList mOnFrameHandlers{ "onFrame" }; EngineHandlerList mQuestUpdate{ "onQuestUpdate" }; diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index b3fb0bd376..631b1e58a8 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -157,8 +157,7 @@ namespace LuaUtil void collectStats(std::vector& stats) const; static int64_t getInstanceCount() { return sInstanceCount; } - public: // TODO: public to be available to MWLua::InputProcessor. Consider other ways of reusing engine handlers - // between containers + protected: struct Handler { int mScriptId; @@ -199,7 +198,6 @@ namespace LuaUtil // a public function (see how ScriptsContainer::update is implemented) that calls `callEngineHandlers`. void registerEngineHandlers(std::initializer_list handlers); - protected: const std::string mNamePrefix; LuaUtil::LuaState& mLua; From c4ed812567b1ac7b8b25bd962794e8ce000cbcd2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 11 Jan 2024 03:12:13 +0300 Subject: [PATCH 0793/2167] Properly redraw the topics list when disposition bar state changes --- apps/openmw/mwgui/dialogue.cpp | 32 ++++++++++++++------------------ apps/openmw/mwgui/dialogue.hpp | 1 + 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 0e44b8c03e..ce79e2834c 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -364,9 +364,8 @@ namespace MWGui if (mCurrentWindowSize == _sender->getSize()) return; - mTopicsList->adjustSize(); + redrawTopicsList(); updateHistory(); - updateTopicFormat(); mCurrentWindowSize = _sender->getSize(); } @@ -534,6 +533,14 @@ namespace MWGui return true; } + void DialogueWindow::redrawTopicsList() + { + mTopicsList->adjustSize(); + + // The topics list has been regenerated so topic formatting needs to be updated + updateTopicFormat(); + } + void DialogueWindow::updateTopicsPane() { mTopicsList->clear(); @@ -591,11 +598,9 @@ namespace MWGui t->eventTopicActivated += MyGUI::newDelegate(this, &DialogueWindow::onTopicActivated); mTopicLinks[topicId] = std::move(t); } - mTopicsList->adjustSize(); + redrawTopicsList(); updateHistory(); - // The topics list has been regenerated so topic formatting needs to be updated - updateTopicFormat(); } void DialogueWindow::updateHistory(bool scrollbar) @@ -756,21 +761,12 @@ namespace MWGui + std::string("/100")); } - bool dispositionWasVisible = mDispositionBar->getVisible(); - - if (dispositionVisible && !dispositionWasVisible) + if (mDispositionBar->getVisible() != dispositionVisible) { - mDispositionBar->setVisible(true); - int offset = mDispositionBar->getHeight() + 5; + mDispositionBar->setVisible(dispositionVisible); + const int offset = (mDispositionBar->getHeight() + 5) * (dispositionVisible ? 1 : -1); mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0, offset, 0, -offset)); - mTopicsList->adjustSize(); - } - else if (!dispositionVisible && dispositionWasVisible) - { - mDispositionBar->setVisible(false); - int offset = mDispositionBar->getHeight() + 5; - mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0, offset, 0, -offset)); - mTopicsList->adjustSize(); + redrawTopicsList(); } } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 1b79cadca5..8a8b309401 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -190,6 +190,7 @@ namespace MWGui void updateDisposition(); void restock(); void deleteLater(); + void redrawTopicsList(); bool mIsCompanion; std::list mKeywords; From 52623ddd7d153520f956b6cc48c4d66e97e016f0 Mon Sep 17 00:00:00 2001 From: Yury Stepovikov Date: Thu, 11 Jan 2024 00:59:27 +0000 Subject: [PATCH 0794/2167] Set MacOS current_path before reading configuration files [#7706] --- AUTHORS.md | 1 + apps/launcher/main.cpp | 5 ----- apps/opencs/main.cpp | 5 ----- apps/openmw/main.cpp | 2 -- apps/wizard/main.cpp | 2 -- components/files/macospath.cpp | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 9791171b9c..e2903febe4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -245,6 +245,7 @@ Programmers xyzz Yohaulticetl Yuri Krupenin + Yury Stepovikov zelurker Documentation diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 4aac90fb6e..78323458ce 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -41,11 +41,6 @@ int runLauncher(int argc, char* argv[]) appTranslator.load(":/translations/" + locale + ".qm"); app.installTranslator(&appTranslator); - // Now we make sure the current dir is set to application path - QDir dir(QCoreApplication::applicationDirPath()); - - QDir::setCurrent(dir.absolutePath()); - Launcher::MainDialog mainWin(configurationManager); Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ecab9614a1..e7f980dc0d 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -81,11 +81,6 @@ int runApplication(int argc, char* argv[]) Application application(argc, argv); -#ifdef Q_OS_MAC - QDir dir(QCoreApplication::applicationDirPath()); - QDir::setCurrent(dir.absolutePath()); -#endif - application.setWindowIcon(QIcon(":./openmw-cs.png")); CS::Editor editor(argc, argv); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b0b49f3acd..5bbc0211c1 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -219,8 +219,6 @@ int runApplication(int argc, char* argv[]) Platform::init(); #ifdef __APPLE__ - std::filesystem::path binary_path = std::filesystem::absolute(std::filesystem::path(argv[0])); - std::filesystem::current_path(binary_path.parent_path()); setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index e2b0d3874b..03ac24c8c0 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -28,8 +28,6 @@ int main(int argc, char* argv[]) app.setLibraryPaths(libraryPaths); #endif - QDir::setCurrent(dir.absolutePath()); - Wizard::MainWizard wizard; wizard.show(); diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 2d0a409782..4b37c2fb26 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -5,13 +5,41 @@ #include #include #include +#include #include #include +#include +#include #include namespace { + std::filesystem::path getBinaryPath() + { + uint32_t bufsize = 0; + _NSGetExecutablePath(nullptr, &bufsize); + + std::vector buf(bufsize); + + if (_NSGetExecutablePath(buf.data(), &bufsize) == 0) + { + std::filesystem::path path = std::filesystem::path(buf.begin(), buf.end()); + + if (std::filesystem::is_symlink(path)) + { + return std::filesystem::read_symlink(path); + } + + return path; + } + else + { + Log(Debug::Warning) << "Not enough buffer size to get executable path: " << bufsize; + throw std::runtime_error("Failed to get executable path"); + } + } + std::filesystem::path getUserHome() { const char* dir = getenv("HOME"); @@ -36,6 +64,11 @@ namespace Files MacOsPath::MacOsPath(const std::string& application_name) : mName(application_name) { + std::filesystem::path binary_path = getBinaryPath(); + std::error_code ec; + std::filesystem::current_path(binary_path.parent_path(), ec); + if (ec.value() != 0) + Log(Debug::Warning) << "Error " << ec.message() << " when changing current directory"; } std::filesystem::path MacOsPath::getUserConfigPath() const From b5aca012eb96c41b45356c0c326a73bcfb4c3aba Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 11 Jan 2024 17:57:11 +0100 Subject: [PATCH 0795/2167] Fix typo --- docs/source/reference/lua-scripting/overview.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index ec5ab7338c..a9025fcf93 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -71,7 +71,7 @@ Global scripts Lua scripts that are not attached to any game object and are always active. Global scripts can not be started or stopped during a game session. Lists of global scripts are defined by `omwscripts` files, which should be :ref:`registered ` in `openmw.cfg`. Menu scripts - Lua scripts that are ran regardless of a game being loaded. They can be used to add features to the main menu and manage save files. + Lua scripts that are ran regardless of a game being loaded. They can be used to add features to the main menu and manage save files. Local scripts Lua scripts that are attached to some game object. A local script is active only if the object it is attached to is in an active cell. There are no limitations to the number of local scripts on one object. Local scripts can be attached to (or detached from) any object at any moment by a global script. In some cases inactive local scripts still can run code (for example during saving and loading), but while inactive they can not see nearby objects. @@ -480,7 +480,7 @@ This is another kind of script-to-script interactions. The differences: There are a few methods for sending events: -- `core.sendGlovalEvent `_ to send events to global scripts +- `core.sendGlobalEvent `_ to send events to global scripts - `GameObject:sendEvent `_ to send events to local scripts attached to a game object - `types.Player.sendMenuEvent `_ to send events to menu scripts of the given player @@ -624,7 +624,7 @@ Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemente local core = require('openmw.core') local time = require('openmw_aux.time') - -- call `doSomething()` at the end of every game day. + -- call `doSomething()` at the end of every game day. -- the second argument (`time.day`) is the interval. -- the periodical evaluation can be stopped at any moment by calling `stopFn()` local timeBeforeMidnight = time.day - core.getGameTime() % time.day From 1880894f4ad943b05561226b7f4ec6889efdb299 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Jan 2024 19:05:37 +0100 Subject: [PATCH 0796/2167] Use ciEqual to detect missing content files --- apps/openmw/mwstate/statemanagerimp.cpp | 5 ++--- components/esm3/savedgame.cpp | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 4358c4094e..631ef9a112 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -81,10 +82,8 @@ std::map MWState::StateManager::buildContentFileIndexMap(const ESM::ES for (int iPrev = 0; iPrev < static_cast(prev.size()); ++iPrev) { - std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name); - for (int iCurrent = 0; iCurrent < static_cast(current.size()); ++iCurrent) - if (id == Misc::StringUtils::lowerCase(current[iCurrent])) + if (Misc::StringUtils::ciEqual(prev[iPrev].name, current[iCurrent])) { map.insert(std::make_pair(iPrev, iCurrent)); break; diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 3ffe062d76..0dc1fb0653 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "../misc/algorithm.hpp" + namespace ESM { void SavedGame::load(ESMReader& esm) @@ -67,7 +69,9 @@ namespace ESM std::vector missingFiles; for (const std::string& contentFile : mContentFiles) { - if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end()) + auto it = std::find_if(allContentFiles.begin(), allContentFiles.end(), + [&](const std::string& file) { return Misc::StringUtils::ciEqual(file, contentFile); }); + if (it == allContentFiles.end()) { missingFiles.emplace_back(contentFile); } From 3ad79e3b3eb7d254128cc43696ca451940201ff6 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 12:16:14 +0100 Subject: [PATCH 0797/2167] Pregenerate glow texture names To avoid strings generation and allocations every time model is added to a scene. --- components/sceneutil/util.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 6ff366f76e..b71de4c2a3 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -1,6 +1,7 @@ #include "util.hpp" #include +#include #include #include @@ -17,6 +18,26 @@ namespace SceneUtil { + namespace + { + std::array generateGlowTextureNames() + { + std::array result; + for (std::size_t i = 0; i < result.size(); ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + result[i] = std::move(stream).str(); + } + return result; + } + + const std::array glowTextureNames = generateGlowTextureNames(); + } class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor { @@ -197,16 +218,9 @@ namespace SceneUtil const osg::Vec4f& glowColor, float glowDuration) { std::vector> textures; - for (int i = 0; i < 32; ++i) + for (const std::string& name : glowTextureNames) { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = resourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr image = resourceSystem->getImageManager()->getImage(name); osg::ref_ptr tex(new osg::Texture2D(image)); tex->setName("envMap"); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); From 35da9f8c50325331a674b45445809b68be994933 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 01:49:17 +0100 Subject: [PATCH 0798/2167] Remove redundant SizeProxy and RenderTarget constructors --- components/fx/types.hpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 0f33d29e1a..829bf176b7 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -29,16 +29,6 @@ namespace fx std::optional mWidth; std::optional mHeight; - SizeProxy() = default; - - SizeProxy(const SizeProxy& other) - : mWidthRatio(other.mWidthRatio) - , mHeightRatio(other.mHeightRatio) - , mWidth(other.mWidth) - , mHeight(other.mHeight) - { - } - std::tuple get(int width, int height) const { int scaledWidth = width; @@ -64,16 +54,6 @@ namespace fx SizeProxy mSize; bool mMipMap = false; osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); - - RenderTarget() = default; - - RenderTarget(const RenderTarget& other) - : mTarget(other.mTarget) - , mSize(other.mSize) - , mMipMap(other.mMipMap) - , mClearColor(other.mClearColor) - { - } }; template From 1bfcfaff341f65666fb9d9f774f9baeae1e425ce Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 03:35:57 +0100 Subject: [PATCH 0799/2167] Use proper naming for member variable --- components/esm3/aisequence.cpp | 2 +- components/esm3/aisequence.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 99c85db1bb..d5b15893bf 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -14,7 +14,7 @@ namespace ESM void AiWander::load(ESMReader& esm) { esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); - esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime + esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.mUnused); // was mStartTime mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 107fdf3bdb..099e5560e1 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -48,7 +48,7 @@ namespace ESM struct AiWanderDuration { float mRemainingDuration; - int32_t unused; + int32_t mUnused; }; struct AiTravelData { From 074ab682abc31b7f844da6cad79818a60ce016e5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 12 Jan 2024 10:04:56 +0400 Subject: [PATCH 0800/2167] Move local variables in the editor --- apps/opencs/model/world/data.cpp | 4 ++-- apps/opencs/model/world/idcompletionmanager.cpp | 2 +- apps/opencs/view/doc/adjusterwidget.cpp | 2 +- apps/opencs/view/doc/viewmanager.cpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/terraintexturemode.cpp | 4 ++-- apps/opencs/view/widget/scenetooltexturebrush.cpp | 2 +- apps/opencs/view/world/extendedcommandconfigurator.cpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6322a77e66..601ee8d263 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1107,7 +1107,7 @@ void CSMWorld::Data::loadFallbackEntries() newMarker.mModel = model; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); - record->mBase = newMarker; + record->mBase = std::move(newMarker); record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Static); } @@ -1123,7 +1123,7 @@ void CSMWorld::Data::loadFallbackEntries() newMarker.mModel = model; newMarker.mRecordFlags = 0; auto record = std::make_unique>(); - record->mBase = newMarker; + record->mBase = std::move(newMarker); record->mState = CSMWorld::RecordBase::State_BaseOnly; mReferenceables.appendRecord(std::move(record), CSMWorld::UniversalId::Type_Door); } diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 263f462b6e..a4fdb4776d 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -117,7 +117,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data& data) completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - mCompleters[current->first] = completer; + mCompleters[current->first] = std::move(completer); } } } diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index d4cfdc6d3e..a282ebcaff 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -91,7 +91,7 @@ void CSVDoc::AdjusterWidget::setName(const QString& name, bool addon) { // path already points to the local data directory message = "Will be saved as: " + Files::pathToQString(path); - mResultPath = path; + mResultPath = std::move(path); } // in all other cases, ensure the path points to data-local and do an existing file check else diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index dff4426ba5..812a1bd534 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -410,7 +410,7 @@ bool CSVDoc::ViewManager::removeDocument(CSVDoc::View* view) remainingViews.push_back(*iter); } mDocumentManager.removeDocument(document); - mViews = remainingViews; + mViews = std::move(remainingViews); } return true; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 3d5c6fe565..214618a627 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -514,7 +514,7 @@ void CSVRender::PagedWorldspaceWidget::moveCellSelection(int x, int y) addCellToScene(*iter); } - mSelection = newSelection; + mSelection = std::move(newSelection); } void CSVRender::PagedWorldspaceWidget::addCellToSceneFromCamera(int offsetX, int offsetY) diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 79e9959cd6..684958da34 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -541,7 +541,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe = landTable.data(landTable.getModelIndex(cellId, textureColumn)) .value(); newTerrainOtherCell[yInOtherCell * landTextureSize + xInOtherCell] = brushInt; - pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); + pushEditToCommand(newTerrainOtherCell, document, landTable, std::move(cellId)); } } } @@ -702,7 +702,7 @@ void CSVRender::TerrainTextureMode::createTexture(const std::string& textureFile QModelIndex index(ltexTable.getModelIndex(newId, ltexTable.findColumnIndex(CSMWorld::Columns::ColumnId_Texture))); undoStack.push(new CSMWorld::ModifyCommand(ltexTable, index, textureFileNameVariant)); undoStack.endMacro(); - mBrushTexture = newId; + mBrushTexture = std::move(newId); } bool CSVRender::TerrainTextureMode::allowLandTextureEditing(const std::string& cellId) diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index cc372753f6..9c3e723009 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -213,7 +213,7 @@ void CSVWidget::TextureBrushWindow::setBrushTexture(std::string brushTexture) mSelectedBrush->setText(QString::fromStdString(mBrushTextureLabel)); } - mBrushTexture = newBrushTextureId; + mBrushTexture = std::move(newBrushTextureId); emit passTextureId(mBrushTexture); emit passBrushShape(mBrushShape); // updates the icon tooltip diff --git a/apps/opencs/view/world/extendedcommandconfigurator.cpp b/apps/opencs/view/world/extendedcommandconfigurator.cpp index 69659be8a6..97494fa076 100644 --- a/apps/opencs/view/world/extendedcommandconfigurator.cpp +++ b/apps/opencs/view/world/extendedcommandconfigurator.cpp @@ -146,7 +146,7 @@ void CSVWorld::ExtendedCommandConfigurator::setupCheckBoxes(const std::vectorfirst->setText(QString::fromUtf8(type.getTypeName().c_str())); current->first->setChecked(true); - current->second = type; + current->second = std::move(type); ++counter; } else diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index c9e09e2d6a..1d4dc37529 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -169,7 +169,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vectorgetId(); - filterData.columns = col; + filterData.columns = std::move(col); sourceFilter.emplace_back(filterData); } From 3592dc4c8835d49a076007296714019a9ec83230 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 03:01:49 +0100 Subject: [PATCH 0801/2167] Add tests for saving and loading AiSequence::AiWander --- apps/openmw_test_suite/esm3/testsaveload.cpp | 82 ++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index ff68d0d4f1..501a0b47c0 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -410,6 +411,87 @@ namespace ESM EXPECT_EQ(result.mStringId, record.mStringId); } + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiWanderShouldNotChange) + { + AiSequence::AiWander record; + record.mData.mDistance = 1; + record.mData.mDuration = 2; + record.mData.mTimeOfDay = 3; + constexpr std::uint8_t idle[8] = { 4, 5, 6, 7, 8, 9, 10, 11 }; + static_assert(std::size(idle) == std::size(record.mData.mIdle)); + std::copy(std::begin(idle), std::end(idle), record.mData.mIdle); + record.mData.mShouldRepeat = 12; + record.mDurationData.mRemainingDuration = 13; + record.mDurationData.mUnused = 14; + record.mStoredInitialActorPosition = true; + constexpr float initialActorPosition[3] = { 15, 16, 17 }; + static_assert(std::size(initialActorPosition) == std::size(record.mInitialActorPosition.mValues)); + std::copy( + std::begin(initialActorPosition), std::end(initialActorPosition), record.mInitialActorPosition.mValues); + + AiSequence::AiWander result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mDistance, record.mData.mDistance); + EXPECT_EQ(result.mData.mDuration, record.mData.mDuration); + EXPECT_EQ(result.mData.mTimeOfDay, record.mData.mTimeOfDay); + EXPECT_THAT(result.mData.mIdle, ElementsAreArray(record.mData.mIdle)); + EXPECT_EQ(result.mData.mShouldRepeat, record.mData.mShouldRepeat); + EXPECT_EQ(result.mDurationData.mRemainingDuration, record.mDurationData.mRemainingDuration); + EXPECT_EQ(result.mDurationData.mUnused, record.mDurationData.mUnused); + EXPECT_EQ(result.mStoredInitialActorPosition, record.mStoredInitialActorPosition); + EXPECT_THAT(result.mInitialActorPosition.mValues, ElementsAreArray(record.mInitialActorPosition.mValues)); + } + + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiTravelShouldNotChange) + { + AiSequence::AiTravel record; + record.mData.mX = 1; + record.mData.mY = 2; + record.mData.mZ = 3; + record.mHidden = true; + record.mRepeat = true; + + AiSequence::AiTravel result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mX, record.mData.mX); + EXPECT_EQ(result.mData.mY, record.mData.mY); + EXPECT_EQ(result.mData.mZ, record.mData.mZ); + EXPECT_EQ(result.mHidden, record.mHidden); + EXPECT_EQ(result.mRepeat, record.mRepeat); + } + + TEST_P(Esm3SaveLoadRecordTest, aiSequenceAiEscortShouldNotChange) + { + AiSequence::AiEscort record; + record.mData.mX = 1; + record.mData.mY = 2; + record.mData.mZ = 3; + record.mData.mDuration = 4; + record.mTargetActorId = 5; + record.mTargetId = generateRandomRefId(32); + record.mCellId = generateRandomString(257); + record.mRemainingDuration = 6; + record.mRepeat = true; + + AiSequence::AiEscort result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mX, record.mData.mX); + EXPECT_EQ(result.mData.mY, record.mData.mY); + EXPECT_EQ(result.mData.mZ, record.mData.mZ); + if (GetParam() <= MaxOldAiPackageFormatVersion) + EXPECT_EQ(result.mData.mDuration, record.mRemainingDuration); + else + EXPECT_EQ(result.mData.mDuration, record.mData.mDuration); + EXPECT_EQ(result.mTargetActorId, record.mTargetActorId); + EXPECT_EQ(result.mTargetId, record.mTargetId); + EXPECT_EQ(result.mCellId, record.mCellId); + EXPECT_EQ(result.mRemainingDuration, record.mRemainingDuration); + EXPECT_EQ(result.mRepeat, record.mRepeat); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } From dd09c9b362068a52a577df399b22971d1f645a0a Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 13 Jan 2024 00:42:55 +0100 Subject: [PATCH 0802/2167] Don't save global storage if global scripts didn't run --- apps/openmw/mwlua/luamanagerimp.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 6f1d265340..6e62c8b7e3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -141,7 +141,8 @@ namespace MWLua void LuaManager::savePermanentStorage(const std::filesystem::path& userConfigPath) { - mGlobalStorage.save(userConfigPath / "global_storage.bin"); + if (mGlobalScriptsStarted) + mGlobalStorage.save(userConfigPath / "global_storage.bin"); mPlayerStorage.save(userConfigPath / "player_storage.bin"); } @@ -318,6 +319,7 @@ namespace MWLua mPlayer.getRefData().setLuaScripts(nullptr); mPlayer = MWWorld::Ptr(); } + mGlobalStorage.setActive(true); mGlobalStorage.clearTemporaryAndRemoveCallbacks(); mGlobalStorage.setActive(false); mPlayerStorage.clearTemporaryAndRemoveCallbacks(); From c2d1a4c861048727abea56458fce06e1b88bb7cf Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 01:18:15 +0000 Subject: [PATCH 0803/2167] Initial stab at OSG plugin checker It doesn't work yet due to osgDB::listAllAvailablePlugins returning a list of paths to dynamic libraries. That means: * the check fails when the required plugin is linked statically. * we're going to have to do something to slice up the filenames. * there'll probably be unicode errors when the OpenMW installation path isn't representable by the current eight-bit code page on Windows. Alternatively, we can switch to listing the required file extension support, and use osgDB::Registry::instance()->getReaderWriterList() and each element's supportedExtensions() function, but I don't think we've actually got that list of extensions anywhere and it might get desynced with the existing list of plugins if we add more. --- apps/openmw/main.cpp | 4 +++ components/CMakeLists.txt | 9 +++-- components/misc/osgpluginchecker.cpp.in | 47 +++++++++++++++++++++++++ components/misc/osgpluginchecker.hpp | 9 +++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 components/misc/osgpluginchecker.cpp.in create mode 100644 components/misc/osgpluginchecker.hpp diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5bbc0211c1..adf50bea0e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -228,6 +229,9 @@ int runApplication(int argc, char* argv[]) if (parseOptions(argc, argv, *engine, cfgMgr)) { + if (!Misc::checkRequiredOSGPluginsArePresent()) + return 1; + engine->go(); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f25a4cc621..ce4b431979 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -40,6 +40,11 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +# OSG plugin checker +set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") +configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") +list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") + # source files add_component_dir (lua @@ -274,8 +279,8 @@ add_component_dir (esm4 add_component_dir (misc barrier budgetmeasurement color compression constants convert coordinateconverter display endianness float16 frameratelimiter - guarded math mathutil messageformatparser notnullptr objectpool osguservalues progressreporter resourcehelpers rng - strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows + guarded math mathutil messageformatparser notnullptr objectpool osgpluginchecker osguservalues progressreporter resourcehelpers + rng strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows ) add_component_dir (misc/strings diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in new file mode 100644 index 0000000000..56a7814d33 --- /dev/null +++ b/components/misc/osgpluginchecker.cpp.in @@ -0,0 +1,47 @@ +#include "components/misc/osgpluginchecker.hpp" + +#include + +#include + +#include +#include + +namespace Misc +{ + namespace + { + std::string_view USED_OSG_PLUGINS = "${USED_OSG_PLUGINS}"; + + constexpr std::vector getRequiredOSGPlugins() + { + std::vector requiredOSGPlugins; + auto currentStart = USED_OSG_PLUGINS.begin(); + while (currentStart != USED_OSG_PLUGINS.end()) + { + auto currentEnd = std::find(currentStart, USED_OSG_PLUGINS.end(), ';'); + requiredOSGPlugins.emplace_back(currentStart, currentEnd); + if (currentEnd == USED_OSG_PLUGINS.end()) + break; + currentStart = currentEnd + 1; + } + return requiredOSGPlugins; + } + } + + bool checkRequiredOSGPluginsArePresent() + { + auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); + auto requiredOSGPlugins = getRequiredOSGPlugins(); + bool haveAllPlugins = true; + for (std::string_view plugin : requiredOSGPlugins) + { + if (std::find(availableOSGPlugins.begin(), availableOSGPlugins.end(), plugin) == availableOSGPlugins.end()) + { + Log(Debug::Error) << "Missing OSG plugin: " << plugin; + haveAllPlugins = false; + } + } + return haveAllPlugins; + } +} diff --git a/components/misc/osgpluginchecker.hpp b/components/misc/osgpluginchecker.hpp new file mode 100644 index 0000000000..2f5ea09700 --- /dev/null +++ b/components/misc/osgpluginchecker.hpp @@ -0,0 +1,9 @@ +#ifndef OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP +#define OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP + +namespace Misc +{ + bool checkRequiredOSGPluginsArePresent(); +} + +#endif From ef65f0c70d4f3659a2703003a89af236281a753e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 17:32:54 +0000 Subject: [PATCH 0804/2167] Make OSG plugin checker barely functional * Work out what module filenames should be in CMake, and give those to C++ * Compare just the module filenames instead of the full strings * Deal with OSG trying to support both UTF-8 and system-eight-bit-code-page file paths on Windows. * Add a comment complaining about the constexpr situation. * Use a stub implementation when using static OSG - apparently we don't actually support mixing and matching static and dynamic OSG plugins even though OSG itself does. --- components/CMakeLists.txt | 3 ++ components/misc/osgpluginchecker.cpp.in | 38 ++++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ce4b431979..72a16c04fb 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,6 +41,9 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker +list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") + set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 56a7814d33..5a7e5e92c7 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -1,27 +1,42 @@ #include "components/misc/osgpluginchecker.hpp" #include +#include +#include #include #include +#include #include namespace Misc { +#ifdef OSG_LIBRARY_STATIC + + bool checkRequiredOSGPluginsArePresent() + { + // assume they were linked in at build time and CMake would have failed if they were missing + return true; + } + +#else + namespace { - std::string_view USED_OSG_PLUGINS = "${USED_OSG_PLUGINS}"; + std::string_view USED_OSG_PLUGIN_FILENAMES = "${USED_OSG_PLUGIN_FILENAMES}"; - constexpr std::vector getRequiredOSGPlugins() + constexpr auto getRequiredOSGPlugins() { + // TODO: this is only constexpr due to an MSVC extension, so doesn't work on GCC and Clang + // I tried replacing it with std::views::split_range, but we support compilers without that bit of C++20, and it wasn't obvious how to use the result as a string_view without C++23 std::vector requiredOSGPlugins; - auto currentStart = USED_OSG_PLUGINS.begin(); - while (currentStart != USED_OSG_PLUGINS.end()) + auto currentStart = USED_OSG_PLUGIN_FILENAMES.begin(); + while (currentStart != USED_OSG_PLUGIN_FILENAMES.end()) { - auto currentEnd = std::find(currentStart, USED_OSG_PLUGINS.end(), ';'); + auto currentEnd = std::find(currentStart, USED_OSG_PLUGIN_FILENAMES.end(), ';'); requiredOSGPlugins.emplace_back(currentStart, currentEnd); - if (currentEnd == USED_OSG_PLUGINS.end()) + if (currentEnd == USED_OSG_PLUGIN_FILENAMES.end()) break; currentStart = currentEnd + 1; } @@ -36,7 +51,14 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : requiredOSGPlugins) { - if (std::find(availableOSGPlugins.begin(), availableOSGPlugins.end(), plugin) == availableOSGPlugins.end()) + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string availablePlugin) { +#ifdef OSG_USE_UTF8_FILENAME + std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; +#else + std::filesystem::path pluginPath {availablePlugin}; +#endif + return pluginPath.filename() == plugin; + }) == availableOSGPlugins.end()) { Log(Debug::Error) << "Missing OSG plugin: " << plugin; haveAllPlugins = false; @@ -44,4 +66,6 @@ namespace Misc } return haveAllPlugins; } + +#endif } From de107c6a98abf9b84e6e4876a3dd34093041b690 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 17:35:52 +0000 Subject: [PATCH 0805/2167] Add missing _view --- components/misc/osgpluginchecker.cpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 5a7e5e92c7..759b893d08 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -51,7 +51,7 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : requiredOSGPlugins) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string availablePlugin) { + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; #else From 62f5c46f253f3fac78764b12a7bf8e360db8edf3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Jan 2024 20:16:53 +0000 Subject: [PATCH 0806/2167] Split list in CMake instead of C++ That avoids the need for constexpr work, and therefore the need for an MSVC-specific extension --- components/CMakeLists.txt | 3 +++ components/misc/osgpluginchecker.cpp.in | 23 +++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 72a16c04fb..536d1098c5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -43,6 +43,9 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") +list(JOIN USED_OSG_PLUGIN_FILENAMES_FORMATTED ", " USED_OSG_PLUGIN_FILENAMES_FORMATTED) set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 759b893d08..9bc165c5d6 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -24,32 +25,14 @@ namespace Misc namespace { - std::string_view USED_OSG_PLUGIN_FILENAMES = "${USED_OSG_PLUGIN_FILENAMES}"; - - constexpr auto getRequiredOSGPlugins() - { - // TODO: this is only constexpr due to an MSVC extension, so doesn't work on GCC and Clang - // I tried replacing it with std::views::split_range, but we support compilers without that bit of C++20, and it wasn't obvious how to use the result as a string_view without C++23 - std::vector requiredOSGPlugins; - auto currentStart = USED_OSG_PLUGIN_FILENAMES.begin(); - while (currentStart != USED_OSG_PLUGIN_FILENAMES.end()) - { - auto currentEnd = std::find(currentStart, USED_OSG_PLUGIN_FILENAMES.end(), ';'); - requiredOSGPlugins.emplace_back(currentStart, currentEnd); - if (currentEnd == USED_OSG_PLUGIN_FILENAMES.end()) - break; - currentStart = currentEnd + 1; - } - return requiredOSGPlugins; - } + constexpr auto USED_OSG_PLUGIN_FILENAMES = std::to_array({${USED_OSG_PLUGIN_FILENAMES_FORMATTED}}); } bool checkRequiredOSGPluginsArePresent() { auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); - auto requiredOSGPlugins = getRequiredOSGPlugins(); bool haveAllPlugins = true; - for (std::string_view plugin : requiredOSGPlugins) + for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) { if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME From 7cc0eae46117299c4395913019383b0eb78360d9 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 13 Jan 2024 00:46:06 +0100 Subject: [PATCH 0807/2167] Fix Menu Lua settings reset between states --- components/lua_ui/registerscriptsettings.hpp | 2 +- files/data/scripts/omw/settings/common.lua | 3 --- files/data/scripts/omw/settings/global.lua | 1 + files/data/scripts/omw/settings/menu.lua | 28 ++++++++++++-------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/components/lua_ui/registerscriptsettings.hpp b/components/lua_ui/registerscriptsettings.hpp index ba36aff904..58b033fc59 100644 --- a/components/lua_ui/registerscriptsettings.hpp +++ b/components/lua_ui/registerscriptsettings.hpp @@ -8,7 +8,7 @@ namespace LuaUi // implemented in scriptsettings.cpp void registerSettingsPage(const sol::table& options); void clearSettings(); - void removeSettingsPage(std::string_view key); + void removeSettingsPage(const sol::table& options); } #endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS diff --git a/files/data/scripts/omw/settings/common.lua b/files/data/scripts/omw/settings/common.lua index 9155c64ba7..1d62a54dc4 100644 --- a/files/data/scripts/omw/settings/common.lua +++ b/files/data/scripts/omw/settings/common.lua @@ -6,7 +6,6 @@ local argumentSectionPostfix = 'Arguments' local contextSection = storage.playerSection or storage.globalSection local groupSection = contextSection(groupSectionKey) -groupSection:removeOnExit() local function validateSettingOptions(options) if type(options) ~= 'table' then @@ -109,11 +108,9 @@ end return { getSection = function(global, key) - if global then error('Getting global section') end return (global and storage.globalSection or storage.playerSection)(key) end, getArgumentSection = function(global, key) - if global then error('Getting global section') end return (global and storage.globalSection or storage.playerSection)(key .. argumentSectionPostfix) end, updateRendererArgument = function(groupKey, settingKey, argument) diff --git a/files/data/scripts/omw/settings/global.lua b/files/data/scripts/omw/settings/global.lua index 423c38680b..f7356d15c4 100644 --- a/files/data/scripts/omw/settings/global.lua +++ b/files/data/scripts/omw/settings/global.lua @@ -1,6 +1,7 @@ local storage = require('openmw.storage') local common = require('scripts.omw.settings.common') +common.getSection(true, common.groupSectionKey):removeOnExit() return { interfaceName = 'Settings', diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 87c3dc6e6a..59d15ee2cd 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -7,6 +7,8 @@ local storage = require('openmw.storage') local I = require('openmw.interfaces') local common = require('scripts.omw.settings.common') +-- :reset on startup instead of :removeOnExit +common.getSection(false, common.groupSectionKey):reset() local renderers = {} local function registerRenderer(name, renderFunction) @@ -276,6 +278,9 @@ local function renderPage(page) local groupLayouts = {} for _, pageGroup in ipairs(sortedGroups) do local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key) + if not group then + error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key)) + end table.insert(groupLayouts, renderGroup(group, pageGroup.global)) end local groupsLayout = { @@ -425,17 +430,16 @@ end local menuGroups = {} local menuPages = {} -local function reset() +local function resetPlayerGroups() for pageKey, page in pairs(groups) do - for groupKey in pairs(page) do - if not menuGroups[groupKey] then + for groupKey, group in pairs(page) do + if not menuGroups[groupKey] and not group.global then page[groupKey] = nil end end if pageOptions[pageKey] then - pageOptions[pageKey].element.destroy() + pageOptions[pageKey].element:destroy() if not menuPages[pageKey] then - pageOptions[pageKey].element.destroy() ui.removeSettingsPage(pageOptions[pageKey]) pageOptions[pageKey] = nil else @@ -483,6 +487,8 @@ local function registerPage(options) ui.registerSettingsPage(pageOptions[page.key]) end +local lastState + return { interfaceName = 'Settings', interface = { @@ -502,15 +508,15 @@ return { updateRendererArgument = common.updateRendererArgument, }, engineHandlers = { - onLoad = common.onLoad, - onSave = common.onSave, onStateChanged = function() - if menu.getState() == menu.STATE.Running then - updateGlobalGroups() - else - reset() + if lastState == menu.STATE.Running then + resetPlayerGroups() end updatePlayerGroups() + if menu.getState() == menu.STATE.Running then + updateGlobalGroups() + end + lastState = menu.getState() end, }, eventHandlers = { From e0eb3feb89e6789bd16908d031ca275700e0be27 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Jan 2024 23:49:53 +0000 Subject: [PATCH 0808/2167] Use OSG_PLUGIN_PREFIX instead of CMAKE_SHARED_MODULE_PREFIX Logic to generate it copied from OSG's CMake instead of guessed. --- components/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 536d1098c5..a7f6d666d0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,14 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker -list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) +# Helpfully, OSG doesn't export this to its CMake config as it doesn't have one +set(OSG_PLUGIN_PREFIX "") +if (CYGWIN) + SET(OSG_PLUGIN_PREFIX "cygwin_") +elseif(MINGW) + SET(OSG_PLUGIN_PREFIX "mingw_") +endif() +list(TRANSFORM USED_OSG_PLUGINS PREPEND "${OSG_PLUGIN_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") From dd706aab0e118fdd185615558e6fc1b9fdf6e9e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 23:01:09 +0100 Subject: [PATCH 0809/2167] Add missing SubPass::mMinMap initialization --- components/fx/technique.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 0d17128e56..fa66996aeb 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -55,7 +55,7 @@ namespace fx osg::ref_ptr mRenderTexture; bool mResolve = false; Types::SizeProxy mSize; - bool mMipMap; + bool mMipMap = false; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) From 384a1dd13acbbaaa91f35bafed473b9cbb6799f3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 13 Jan 2024 00:44:07 +0100 Subject: [PATCH 0810/2167] Update PrecipitationOccluder only when there is precipitation --- apps/openmw/mwrender/precipitationocclusion.cpp | 17 +++++++++++++---- apps/openmw/mwrender/precipitationocclusion.hpp | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/precipitationocclusion.cpp b/apps/openmw/mwrender/precipitationocclusion.cpp index 204b5c07bd..31712410b8 100644 --- a/apps/openmw/mwrender/precipitationocclusion.cpp +++ b/apps/openmw/mwrender/precipitationocclusion.cpp @@ -1,5 +1,7 @@ #include "precipitationocclusion.hpp" +#include + #include #include @@ -120,16 +122,19 @@ namespace MWRender void PrecipitationOccluder::update() { + if (!mRange.has_value()) + return; + 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 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 left = -mRange->x() / 2; const float right = -left; - const float top = mRange.y() / 2; + const float top = mRange->y() / 2; const float bottom = -top; if (SceneUtil::AutoDepth::isReversed()) @@ -163,10 +168,14 @@ namespace MWRender mSkyCullCallback = nullptr; mRootNode->removeChild(mCamera); + mRange = std::nullopt; } void PrecipitationOccluder::updateRange(const osg::Vec3f range) { + assert(range.x() != 0); + assert(range.y() != 0); + assert(range.z() != 0); const osg::Vec3f margin = { -50, -50, 0 }; mRange = range - margin; } diff --git a/apps/openmw/mwrender/precipitationocclusion.hpp b/apps/openmw/mwrender/precipitationocclusion.hpp index 26114ed42f..9d2992637e 100644 --- a/apps/openmw/mwrender/precipitationocclusion.hpp +++ b/apps/openmw/mwrender/precipitationocclusion.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWRender { class PrecipitationOccluder @@ -27,7 +29,7 @@ namespace MWRender osg::ref_ptr mCamera; osg::ref_ptr mSceneCamera; osg::ref_ptr mDepthTexture; - osg::Vec3f mRange; + std::optional mRange; }; } From b9afd7245cd0ceea18658c97df4bd19c86994d17 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 13 Jan 2024 00:47:08 +0100 Subject: [PATCH 0811/2167] Create separate UI api tables for menu and player contexts --- apps/openmw/mwlua/inputprocessor.hpp | 16 ++-- apps/openmw/mwlua/uibindings.cpp | 119 ++++++++++++++------------- components/lua_ui/element.cpp | 17 ++-- components/lua_ui/element.hpp | 18 ++-- components/lua_ui/scriptsettings.hpp | 1 - components/lua_ui/util.cpp | 4 +- 6 files changed, 85 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index fe6de5450e..e005183098 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -58,14 +58,14 @@ namespace MWLua private: Container* mScriptsContainer; - Container::EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; - Container::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; - Container::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; - Container::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; - Container::EngineHandlerList mActionHandlers{ "onInputAction" }; - Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; - Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; - Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + typename Container::EngineHandlerList mKeyPressHandlers{ "onKeyPress" }; + typename Container::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" }; + typename Container::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" }; + typename Container::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" }; + typename Container::EngineHandlerList mActionHandlers{ "onInputAction" }; + typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; + typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; + typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; }; } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 061dc4821a..354fd83ec2 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -89,39 +89,10 @@ namespace MWLua }(); } - sol::table initUserInterfacePackage(const Context& context) + sol::table registerUiApi(const Context& context, bool menu) { - { - sol::state_view& lua = context.mLua->sol(); - if (lua["openmw_ui"] != sol::nil) - return lua["openmw_ui"]; - } - MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); - auto element = context.mLua->sol().new_usertype("Element"); - element[sol::meta_function::to_string] = [](const LuaUi::Element& element) { - std::stringstream res; - res << "UiElement"; - if (element.mLayer != "") - res << "[" << element.mLayer << "]"; - return res.str(); - }; - element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; }, - [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); - element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy || element->mUpdate) - return; - element->mUpdate = true; - luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); - }; - element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy) - return; - element->mDestroy = true; - luaManager->addAction([element] { wrapAction(element, [&] { element->destroy(); }); }, "Destroy UI"); - }; - sol::table api = context.mLua->newTable(); api["_setHudVisibility"] = [luaManager = context.mLuaManager](bool state) { luaManager->addAction([state] { MWBase::Environment::get().getWindowManager()->setHudVisibility(state); }); @@ -155,36 +126,20 @@ namespace MWLua } }; api["content"] = LuaUi::loadContentConstructor(context.mLua); - api["create"] = [context](const sol::table& layout) { - auto element - = context.mIsMenu ? LuaUi::Element::makeMenuElement(layout) : LuaUi::Element::makeGameElement(layout); - context.mLuaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI"); + + api["create"] = [luaManager = context.mLuaManager, menu](const sol::table& layout) { + auto element = LuaUi::Element::make(layout, menu); + luaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI"); return element; }; - api["updateAll"] = [context]() { - if (context.mIsMenu) - { - LuaUi::Element::forEachMenuElement([](LuaUi::Element* e) { e->mUpdate = true; }); - context.mLuaManager->addAction( - []() { LuaUi::Element::forEachMenuElement([](LuaUi::Element* e) { e->update(); }); }, - "Update all menu UI elements"); - } - else - { - LuaUi::Element::forEachGameElement([](LuaUi::Element* e) { e->mUpdate = true; }); - context.mLuaManager->addAction( - []() { LuaUi::Element::forEachGameElement([](LuaUi::Element* e) { e->update(); }); }, - "Update all game UI elements"); - } + + api["updateAll"] = [luaManager = context.mLuaManager, menu]() { + LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; }); + luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); }, + "Update all menu UI elements"); }; api["_getMenuTransparency"] = []() -> float { return Settings::gui().mMenuTransparency; }; - auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); - uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); - uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); - uiLayer[sol::meta_function::to_string] - = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; - sol::table layersTable = context.mLua->newTable(); layersTable["indexOf"] = [](std::string_view name) -> sol::optional { size_t index = LuaUi::Layer::indexOf(name); @@ -247,7 +202,7 @@ namespace MWLua { "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } })); api["registerSettingsPage"] = &LuaUi::registerSettingsPage; - api["removeSettingsPage"] = &LuaUi::registerSettingsPage; + api["removeSettingsPage"] = &LuaUi::removeSettingsPage; api["texture"] = [luaManager = context.mLuaManager](const sol::table& options) { LuaUi::TextureData data; @@ -325,8 +280,56 @@ namespace MWLua // TODO // api["_showMouseCursor"] = [](bool) {}; + return api; + } + + sol::table initUserInterfacePackage(const Context& context) + { + std::string_view menuCache = "openmw_ui_menu"; + std::string_view gameCache = "openmw_ui_game"; + std::string_view cacheKey = context.mIsMenu ? menuCache : gameCache; + { + sol::state_view& lua = context.mLua->sol(); + if (lua[cacheKey] != sol::nil) + return lua[cacheKey]; + } + + auto element = context.mLua->sol().new_usertype("UiElement"); + element[sol::meta_function::to_string] = [](const LuaUi::Element& element) { + std::stringstream res; + res << "UiElement"; + if (element.mLayer != "") + res << "[" << element.mLayer << "]"; + return res.str(); + }; + element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, + [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); + element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mDestroy || element->mUpdate) + return; + element->mUpdate = true; + luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); + }; + element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mDestroy) + return; + element->mDestroy = true; + luaManager->addAction( + [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); + }; + + auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); + uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) { return self.name(); }); + uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer[sol::meta_function::to_string] + = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; + + sol::table menuApi = registerUiApi(context, true); + sol::table gameApi = registerUiApi(context, false); + sol::state_view& lua = context.mLua->sol(); - lua["openmw_ui"] = LuaUtil::makeReadOnly(api); - return lua["openmw_ui"]; + lua[menuCache] = LuaUtil::makeReadOnly(menuApi); + lua[gameCache] = LuaUtil::makeReadOnly(gameApi); + return lua[cacheKey]; } } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9d124d2a0e..da39cca1b8 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -240,8 +240,8 @@ namespace LuaUi } } - std::map> Element::sGameElements; std::map> Element::sMenuElements; + std::map> Element::sGameElements; Element::Element(sol::table layout) : mRoot(nullptr) @@ -252,18 +252,19 @@ namespace LuaUi { } - std::shared_ptr Element::makeGameElement(sol::table layout) + std::shared_ptr Element::make(sol::table layout, bool menu) { std::shared_ptr ptr(new Element(std::move(layout))); - sGameElements[ptr.get()] = ptr; + auto& container = menu ? sMenuElements : sGameElements; + container[ptr.get()] = ptr; return ptr; } - std::shared_ptr Element::makeMenuElement(sol::table layout) + void Element::erase(Element* element) { - std::shared_ptr ptr(new Element(std::move(layout))); - sMenuElements[ptr.get()] = ptr; - return ptr; + element->destroy(); + sMenuElements.erase(element); + sGameElements.erase(element); } void Element::create() @@ -311,7 +312,5 @@ namespace LuaUi mRoot = nullptr; mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } - sGameElements.erase(this); - sMenuElements.erase(this); } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 0446f448b6..4398a769df 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -7,21 +7,15 @@ namespace LuaUi { struct Element { - static std::shared_ptr makeGameElement(sol::table layout); - static std::shared_ptr makeMenuElement(sol::table layout); + static std::shared_ptr make(sol::table layout, bool menu); + static void erase(Element* element); template - static void forEachGameElement(Callback callback) + static void forEach(bool menu, Callback callback) { - for (auto& [e, _] : sGameElements) - callback(e); - } - - template - static void forEachMenuElement(Callback callback) - { - for (auto& [e, _] : sMenuElements) - callback(e); + auto& container = menu ? sMenuElements : sGameElements; + for (auto& [_, element] : container) + callback(element.get()); } WidgetExtension* mRoot; diff --git a/components/lua_ui/scriptsettings.hpp b/components/lua_ui/scriptsettings.hpp index ab68bde839..94ed065bbc 100644 --- a/components/lua_ui/scriptsettings.hpp +++ b/components/lua_ui/scriptsettings.hpp @@ -3,7 +3,6 @@ #include #include -#include namespace LuaUi { diff --git a/components/lua_ui/util.cpp b/components/lua_ui/util.cpp index fe47de3b1d..8dfda3d4cb 100644 --- a/components/lua_ui/util.cpp +++ b/components/lua_ui/util.cpp @@ -47,12 +47,12 @@ namespace LuaUi void clearGameInterface() { while (!Element::sGameElements.empty()) - Element::sGameElements.begin()->second->destroy(); + Element::erase(Element::sGameElements.begin()->second.get()); } void clearMenuInterface() { while (!Element::sMenuElements.empty()) - Element::sMenuElements.begin()->second->destroy(); + Element::erase(Element::sMenuElements.begin()->second.get()); } } From 14e6af8bea50938bb610624166d1c90626af08b8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 13 Jan 2024 15:42:17 +0400 Subject: [PATCH 0812/2167] Add a table with fadeOut argument for streamMusic --- apps/openmw/mwlua/soundbindings.cpp | 21 +++++++++++++++++++-- files/lua_api/openmw/ambient.lua | 12 +++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index dc45a672b4..c3c810e591 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -23,6 +23,11 @@ namespace float mTimeOffset = 0.f; }; + struct StreamMusicArgs + { + float mFade = 1.f; + }; + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -55,6 +60,17 @@ namespace return MWSound::PlayMode::NoEnvNoScaling; return MWSound::PlayMode::NoEnv; } + + StreamMusicArgs getStreamMusicArgs(const sol::optional& options) + { + StreamMusicArgs args; + + if (options.has_value()) + { + args.mFade = options->get_or("fadeOut", 1.f); + } + return args; + } } namespace MWLua @@ -95,9 +111,10 @@ namespace MWLua return MWBase::Environment::get().getSoundManager()->getSoundPlaying(MWWorld::Ptr(), fileName); }; - api["streamMusic"] = [](std::string_view fileName) { + api["streamMusic"] = [](std::string_view fileName, const sol::optional& options) { + auto args = getStreamMusicArgs(options); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted); + sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); }; api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index 917ec86c85..7153a6fb44 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -12,7 +12,7 @@ -- @param #string soundId ID of Sound record to play -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); @@ -32,7 +32,7 @@ -- @param #string fileName Path to sound file in VFS -- @param #table options An optional table with additional optional arguments. Can contain: -- --- * `timeOffset` - a floating point number >= 0, to some time (in second) from beginning of sound file (default: 0); +-- * `timeOffset` - a floating point number >= 0, to skip some time (in seconds) from beginning of sound file (default: 0); -- * `volume` - a floating point number >= 0, to set a sound volume (default: 1); -- * `pitch` - a floating point number >= 0, to set a sound pitch (default: 1); -- * `scale` - a boolean, to set if sound pitch should be scaled by simulation time scaling (default: true); @@ -76,7 +76,13 @@ -- Play a sound file as a music track -- @function [parent=#ambient] streamMusic -- @param #string fileName Path to file in VFS --- @usage ambient.streamMusic("Music\\Test\\Test.mp3"); +-- @param #table options An optional table with additional optional arguments. Can contain: +-- +-- * `fadeOut` - a floating point number >= 0, time (in seconds) to fade out current track before playing this one (default 1.0); +-- @usage local params = { +-- fadeOut=2.0 +-- }; +-- ambient.streamMusic("Music\\Test\\Test.mp3", params) --- -- Stop to play current music From f8c1d48c0befb1b5a9bc089ad2454406caa723ed Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 10:54:51 +0400 Subject: [PATCH 0813/2167] Get rid of redundant casts --- apps/opencs/view/render/worldspacewidget.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index da02c1e179..f7732d752d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -184,11 +184,11 @@ void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() void CSVRender::WorldspaceWidget::centerOrbitCameraOnSelection() { - std::vector> selection = getSelection(~0u); + std::vector> selection = getSelection(Mask_Reference); for (std::vector>::iterator it = selection.begin(); it != selection.end(); ++it) { - if (CSVRender::ObjectTag* objectTag = dynamic_cast(it->get())) + if (CSVRender::ObjectTag* objectTag = static_cast(it->get())) { mOrbitCamControl->setCenter(objectTag->mObject->getPosition().asVec3()); } @@ -757,13 +757,14 @@ void CSVRender::WorldspaceWidget::toggleHiddenInstances() if (selection.empty()) return; - const CSVRender::ObjectTag* firstSelection = dynamic_cast(selection.begin()->get()); + const CSVRender::ObjectTag* firstSelection = static_cast(selection.begin()->get()); + assert(firstSelection != nullptr); const CSVRender::Mask firstMask = firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden; for (const auto& object : selection) - if (const auto objectTag = dynamic_cast(object.get())) + if (const auto objectTag = static_cast(object.get())) objectTag->mObject->getRootNode()->setNodeMask(firstMask); } From 98b281e4ad1145e07a6fb984ac9b290c6c95910c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 10:59:39 +0400 Subject: [PATCH 0814/2167] Add a missing assertion --- apps/openmw/mwrender/pingpongcanvas.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 9c8b08adfd..3af937045f 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -288,6 +288,8 @@ namespace MWRender pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) .getTexture())); + assert(texture != nullptr); + texture->setTextureSize(w, h); texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); texture->dirtyTextureObject(); From 1a629cbf076a077d6d6ae9266fa9603a729ac2be Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 14 Jan 2024 10:09:46 +0300 Subject: [PATCH 0815/2167] Play shield hit sound for the shield that was hit (#7774) --- apps/openmw/mwclass/actor.cpp | 17 ----------------- apps/openmw/mwclass/actor.hpp | 2 -- apps/openmw/mwclass/creature.cpp | 3 --- apps/openmw/mwclass/npc.cpp | 3 --- apps/openmw/mwmechanics/combat.cpp | 9 +++++++++ apps/openmw/mwworld/class.cpp | 5 ----- apps/openmw/mwworld/class.hpp | 4 ---- 7 files changed, 9 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 9c197a70d2..0a45a85a74 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -37,23 +37,6 @@ namespace MWClass return true; } - void Actor::block(const MWWorld::Ptr& ptr) const - { - const MWWorld::InventoryStore& inv = getInventoryStore(ptr); - MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield == inv.end()) - return; - - MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield); - if (skill == ESM::Skill::LightArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); - else if (skill == ESM::Skill::MediumArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); - else if (skill == ESM::Skill::HeavyArmor) - sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); - } - osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const { MWMechanics::Movement& movement = getMovementSettings(ptr); diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 41d06cf5bd..cf0cb1eaa5 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -45,8 +45,6 @@ namespace MWClass bool useAnim() const override; - void block(const MWWorld::Ptr& ptr) const override; - osg::Vec3f getRotationVector(const MWWorld::Ptr& ptr) const override; ///< Return desired rotations, as euler angles. Sets getMovementSettings(ptr).mRotation to zero. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index bb9c1bc277..ab68cddda7 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -339,10 +339,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) - { damage = 0; - victim.getClass().block(victim); - } MWMechanics::diseaseContact(victim, ptr); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f295f7b35..f669547d1a 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -678,10 +678,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) - { damage = 0; - victim.getClass().block(victim); - } if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) damage = 0; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3f17df96fd..3208ea2293 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -135,6 +135,15 @@ namespace MWMechanics auto& prng = MWBase::Environment::get().getWorld()->getPrng(); if (Misc::Rng::roll0to99(prng) < x) { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); + const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield); + if (skill == ESM::Skill::LightArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::MediumArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); + else if (skill == ESM::Skill::HeavyArmor) + sndMgr->playSound3D(blocker, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); + // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d5062d6add..88d9d744e2 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -118,11 +118,6 @@ namespace MWWorld throw std::runtime_error("class cannot hit"); } - void Class::block(const Ptr& ptr) const - { - throw std::runtime_error("class cannot block"); - } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const { diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 87e70b3198..55cc62c78d 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -151,10 +151,6 @@ namespace MWWorld /// actor responsible for the attack. \a successful specifies if the hit is /// successful or not. \a sourceType classifies the damage source. - virtual void block(const Ptr& ptr) const; - ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield - /// (default implementation: throw an exception) - virtual std::unique_ptr activate(const Ptr& ptr, const Ptr& actor) const; ///< Generate action for activation (default implementation: return a null action). From 6cefe2c118c8de160d61008a40d02bfc09b43901 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 13 Jan 2024 21:34:44 +0400 Subject: [PATCH 0816/2167] Rework launcher tabs --- apps/launcher/graphicspage.cpp | 106 -- apps/launcher/graphicspage.hpp | 1 - apps/launcher/settingspage.cpp | 101 ++ apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/graphicspage.ui | 632 +++---- apps/launcher/ui/importpage.ui | 22 +- apps/launcher/ui/mainwindow.ui | 18 +- apps/launcher/ui/settingspage.ui | 1482 ++++++++++------- .../source/reference/modding/settings/GUI.rst | 6 +- .../source/reference/modding/settings/fog.rst | 8 + .../reference/modding/settings/game.rst | 44 +- .../reference/modding/settings/general.rst | 4 +- .../reference/modding/settings/shadows.rst | 20 + .../reference/modding/settings/sound.rst | 9 +- .../reference/modding/settings/video.rst | 14 +- 15 files changed, 1263 insertions(+), 1205 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index b360c215e6..735bcf1df1 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -34,7 +34,6 @@ Launcher::GraphicsPage::GraphicsPage(QWidget* parent) connect(standardRadioButton, &QRadioButton::toggled, this, &GraphicsPage::slotStandardToggled); connect(screenComboBox, qOverload(&QComboBox::currentIndexChanged), this, &GraphicsPage::screenChanged); connect(framerateLimitCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotFramerateLimitToggled); - connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &GraphicsPage::slotShadowDistLimitToggled); } bool Launcher::GraphicsPage::setupSDL() @@ -126,58 +125,6 @@ bool Launcher::GraphicsPage::loadSettings() framerateLimitSpinBox->setValue(fpsLimit); } - // Lighting - int lightingMethod = 1; - switch (Settings::shaders().mLightingMethod) - { - case SceneUtil::LightingMethod::FFP: - lightingMethod = 0; - break; - case SceneUtil::LightingMethod::PerObjectUniform: - lightingMethod = 1; - break; - case SceneUtil::LightingMethod::SingleUBO: - lightingMethod = 2; - break; - } - lightingMethodComboBox->setCurrentIndex(lightingMethod); - - // Shadows - if (Settings::shadows().mActorShadows) - actorShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mPlayerShadows) - playerShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mTerrainShadows) - terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mObjectShadows) - objectShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::shadows().mEnableIndoorShadows) - indoorShadowsCheckBox->setCheckState(Qt::Checked); - - const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); - if (boundMethod == "bounds") - shadowComputeSceneBoundsComboBox->setCurrentIndex(0); - else if (boundMethod == "primitives") - shadowComputeSceneBoundsComboBox->setCurrentIndex(1); - else - shadowComputeSceneBoundsComboBox->setCurrentIndex(2); - - const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; - if (shadowDistLimit > 0) - { - shadowDistanceCheckBox->setCheckState(Qt::Checked); - shadowDistanceSpinBox->setValue(shadowDistLimit); - } - - const float shadowFadeStart = Settings::shadows().mShadowFadeStart; - if (shadowFadeStart != 0) - fadeStartSpinBox->setValue(shadowFadeStart); - - const int shadowRes = Settings::shadows().mShadowMapResolution; - int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); - if (shadowResIndex != -1) - shadowResolutionComboBox->setCurrentIndex(shadowResIndex); - return true; } @@ -220,53 +167,6 @@ void Launcher::GraphicsPage::saveSettings() { Settings::video().mFramerateLimit.set(0); } - - // Lighting - static constexpr std::array lightingMethodMap = { - SceneUtil::LightingMethod::FFP, - SceneUtil::LightingMethod::PerObjectUniform, - SceneUtil::LightingMethod::SingleUBO, - }; - Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); - - // Shadows - const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); - const float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0) - Settings::shadows().mShadowFadeStart.set(cFadeStart); - - const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; - const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; - if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) - { - Settings::shadows().mEnableShadows.set(true); - Settings::shadows().mActorShadows.set(cActorShadows); - Settings::shadows().mPlayerShadows.set(cPlayerShadows); - Settings::shadows().mObjectShadows.set(cObjectShadows); - Settings::shadows().mTerrainShadows.set(cTerrainShadows); - } - else - { - Settings::shadows().mEnableShadows.set(false); - Settings::shadows().mActorShadows.set(false); - Settings::shadows().mPlayerShadows.set(false); - Settings::shadows().mObjectShadows.set(false); - Settings::shadows().mTerrainShadows.set(false); - } - - Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); - Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); - - auto index = shadowComputeSceneBoundsComboBox->currentIndex(); - if (index == 0) - Settings::shadows().mComputeSceneBounds.set("bounds"); - else if (index == 1) - Settings::shadows().mComputeSceneBounds.set("primitives"); - else - Settings::shadows().mComputeSceneBounds.set("none"); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) @@ -377,9 +277,3 @@ void Launcher::GraphicsPage::slotFramerateLimitToggled(bool checked) { framerateLimitSpinBox->setEnabled(checked); } - -void Launcher::GraphicsPage::slotShadowDistLimitToggled(bool checked) -{ - shadowDistanceSpinBox->setEnabled(checked); - fadeStartSpinBox->setEnabled(checked); -} diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 85f91d1ff1..928ec9f1a2 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -31,7 +31,6 @@ namespace Launcher void slotFullScreenChanged(int state); void slotStandardToggled(bool checked); void slotFramerateLimitToggled(bool checked); - void slotShadowDistLimitToggled(bool checked); private: QVector mResolutionsPerScreen; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index b8539671b5..9492326cb0 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -212,6 +212,55 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::fog().mExponentialFog, *exponentialFogCheckBox); loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); + + int lightingMethod = 1; + switch (Settings::shaders().mLightingMethod) + { + case SceneUtil::LightingMethod::FFP: + lightingMethod = 0; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + lightingMethod = 1; + break; + case SceneUtil::LightingMethod::SingleUBO: + lightingMethod = 2; + break; + } + lightingMethodComboBox->setCurrentIndex(lightingMethod); + + loadSettingBool(Settings::shadows().mActorShadows, *actorShadowsCheckBox); + loadSettingBool(Settings::shadows().mPlayerShadows, *playerShadowsCheckBox); + loadSettingBool(Settings::shadows().mTerrainShadows, *terrainShadowsCheckBox); + loadSettingBool(Settings::shadows().mObjectShadows, *objectShadowsCheckBox); + loadSettingBool(Settings::shadows().mEnableIndoorShadows, *indoorShadowsCheckBox); + + const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); + if (boundMethod == "bounds") + shadowComputeSceneBoundsComboBox->setCurrentIndex(0); + else if (boundMethod == "primitives") + shadowComputeSceneBoundsComboBox->setCurrentIndex(1); + else + shadowComputeSceneBoundsComboBox->setCurrentIndex(2); + + const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; + if (shadowDistLimit > 0) + { + shadowDistanceCheckBox->setCheckState(Qt::Checked); + shadowDistanceSpinBox->setValue(shadowDistLimit); + shadowDistanceSpinBox->setEnabled(true); + fadeStartSpinBox->setEnabled(true); + } + + const float shadowFadeStart = Settings::shadows().mShadowFadeStart; + if (shadowFadeStart != 0) + fadeStartSpinBox->setValue(shadowFadeStart); + + const int shadowRes = Settings::shadows().mShadowMapResolution; + int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); + if (shadowResIndex != -1) + shadowResolutionComboBox->setCurrentIndex(shadowResIndex); + + connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); } // Audio @@ -359,6 +408,52 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*exponentialFogCheckBox, Settings::fog().mExponentialFog); saveSettingBool(*skyBlendingCheckBox, Settings::fog().mSkyBlending); Settings::fog().mSkyBlendingStart.set(skyBlendingStartComboBox->value()); + + static constexpr std::array lightingMethodMap = { + SceneUtil::LightingMethod::FFP, + SceneUtil::LightingMethod::PerObjectUniform, + SceneUtil::LightingMethod::SingleUBO, + }; + Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); + + const int cShadowDist + = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; + Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); + const float cFadeStart = fadeStartSpinBox->value(); + if (cShadowDist > 0) + Settings::shadows().mShadowFadeStart.set(cFadeStart); + + const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; + if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) + { + Settings::shadows().mEnableShadows.set(true); + Settings::shadows().mActorShadows.set(cActorShadows); + Settings::shadows().mPlayerShadows.set(cPlayerShadows); + Settings::shadows().mObjectShadows.set(cObjectShadows); + Settings::shadows().mTerrainShadows.set(cTerrainShadows); + } + else + { + Settings::shadows().mEnableShadows.set(false); + Settings::shadows().mActorShadows.set(false); + Settings::shadows().mPlayerShadows.set(false); + Settings::shadows().mObjectShadows.set(false); + Settings::shadows().mTerrainShadows.set(false); + } + + Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); + Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); + + auto index = shadowComputeSceneBoundsComboBox->currentIndex(); + if (index == 0) + Settings::shadows().mComputeSceneBounds.set("bounds"); + else if (index == 1) + Settings::shadows().mComputeSceneBounds.set("primitives"); + else + Settings::shadows().mComputeSceneBounds.set("none"); } // Audio @@ -461,3 +556,9 @@ void Launcher::SettingsPage::slotSkyBlendingToggled(bool checked) skyBlendingStartComboBox->setEnabled(checked); skyBlendingStartLabel->setEnabled(checked); } + +void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) +{ + shadowDistanceSpinBox->setEnabled(checked); + fadeStartSpinBox->setEnabled(checked); +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 9f7d6b1f43..a8a6b7c26d 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -32,6 +32,7 @@ namespace Launcher void slotAnimSourcesToggled(bool checked); void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); + void slotShadowDistLimitToggled(bool checked); private: Config::GameSettings& mGameSettings; diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index c0e2b0be06..fa92c7b789 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -11,459 +11,229 @@ - - - - 0 - - - - Display - - - - - - + + + + + + + + + Screen + + + + + + + Window mode + + + + + + + + + + + 800 + + + + + + + × + + + + + + + 600 + + + + + + + + + Custom: + + + + + + + Standard: + + + true + + + + + + + + + + + - Screen: + 0 - - - - - - - 0 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - - - - - + + - Window Mode: + 2 - - - - + + - Resolution: + 4 - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - + + - Vertical Sync: + 8 - - - - + + - Anti-aliasing: + 16 - - - - - - - - - - 800 - - - - - - - × - - - - - - - 600 - - - - - - - - - Custom: - - - - - - - Standard: - - - true - - - - - - - - - - - - 0 - - - - Fullscreen - - - - - Windowed Fullscreen - - - - - Windowed - - - - - - + + + + + + + Framerate limit + + + + + + + Window border + + + + + + + + + + 0 + + - Window Border + Disabled - - - - - - 0 - - - - Disabled - - - - - Enabled - - - - - Adaptive - - - - - - + + - Framerate Limit: + Enabled - - - - - - false - - - FPS - - - 1 - - - 1.000000000000000 - - - 1000.000000000000000 - - - 15.000000000000000 - - - 300.000000000000000 - - - - - - - - - - Lighting - - - - - - + + - Lighting Method: - - - - - - - - legacy - - - - - shaders compatibility - - - - - shaders - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Shadows - - - - - - - - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + Adaptive + + + + + + + 0 + + - Enable Player Shadows - - - - - - - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + Fullscreen + + - Enable Actor Shadows - - - - - - - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + Windowed Fullscreen + + - Enable Object Shadows + Windowed - - - - - - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - - - Enable Terrain Shadows - - - - - - - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - - - Enable Indoor Shadows - - - - - - - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - - - Shadow Near Far Computation Method: - - - - - - - - bounds - - - - - primitives - - - - - none - - - - - - - - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - - - Shadow Map Resolution: - - - - - - - - 512 - - - - - 1024 - - - - - 2048 - - - - - 4096 - - - - - - - - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - - - Shadow Distance Limit: - - - - - - - false - - - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - - - unit(s) - - - 512 - - - 81920 - - - 8192 - - - - - - - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - - - Fade Start Multiplier: - - - - - - - false - - - 2 - - - 0.000000000000000 - - - 1.000000000000000 - - - 0.900000000000000 - - - - - - - + + + + + + + Resolution + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + false + + + FPS + + + 1 + + + 1.000000000000000 + + + 1000.000000000000000 + + + 15.000000000000000 + + + 300.000000000000000 + + + + + + + Anti-aliasing + + + + + + + Vertical synchronization + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + diff --git a/apps/launcher/ui/importpage.ui b/apps/launcher/ui/importpage.ui index 3e2b0c5e64..4626d29e8a 100644 --- a/apps/launcher/ui/importpage.ui +++ b/apps/launcher/ui/importpage.ui @@ -6,7 +6,7 @@ 0 0 - 514 + 515 397 @@ -129,16 +129,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov + + + + Qt::Vertical + + + + 0 + 0 + + + + - - - - Qt::Vertical - - - diff --git a/apps/launcher/ui/mainwindow.ui b/apps/launcher/ui/mainwindow.ui index 54a369999d..b05a9d2c3b 100644 --- a/apps/launcher/ui/mainwindow.ui +++ b/apps/launcher/ui/mainwindow.ui @@ -7,20 +7,20 @@ 0 0 720 - 565 + 635 720 - 565 + 635 OpenMW Launcher - + :/images/openmw.png:/images/openmw.png @@ -120,7 +120,7 @@ QToolButton { true - + :/images/openmw-plugin.png:/images/openmw-plugin.png @@ -141,11 +141,11 @@ QToolButton { true - + :/images/preferences-video.png:/images/preferences-video.png - Graphics + Display Allows to change graphics settings @@ -156,7 +156,7 @@ QToolButton { true - + :/images/preferences.png:/images/preferences.png @@ -171,7 +171,7 @@ QToolButton { true - + :/images/preferences-advanced.png:/images/preferences-advanced.png @@ -183,7 +183,7 @@ QToolButton { - + diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 0340509205..cf8215ef30 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -6,7 +6,7 @@ 0 0 - 732 + 741 503 @@ -14,36 +14,16 @@ - 1 + 0 - + - Game Mechanics + Gameplay - - - - <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - - Permanent barter disposition changes - - - - - - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - - Followers defend immediately - - - - + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> @@ -53,7 +33,37 @@ - + + + + Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + + + Always allow actors to follow over water + + + + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + Permanent barter disposition changes + + + + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + Racial variation in speed fix + + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> @@ -63,7 +73,17 @@ - + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + + + NPCs avoid collisions + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> @@ -73,47 +93,27 @@ - - + + - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Swim upward correction + Day night switch nodes - - + + - <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - Enchanted weapons are magical + Followers defend immediately - - - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - - Always allow stealing from knocked out actors - - - - - - - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> - - - Classic reflected Absorb spells behavior - - - - + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> @@ -124,26 +124,66 @@ - + - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - NPCs avoid collisions + Only magical ammo bypass resistance - - + + - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - Racial variation in speed fix + Graphic herbalism - + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + Swim upward correction + + + + + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + Enchanted weapons are magical + + + + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + Merchant equipping fix + + + + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + Trainers choose offered skills by base value + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> @@ -153,44 +193,44 @@ - - + + - Give NPC an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - Always allow NPC to follow over water surface + Steal from knocked out actors in combat + + + + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + + + + + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + + + Unarmed creature attacks damage armor - - - - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - - - Unarmed creature attacks damage armor - - - - - - - <html><head/><body><p>Allow non-standard ammunition solely to bypass normal weapon resistance or weakness.</p></body></html> - - - Only appropriate ammunition bypasses normal weapon resistance - - - - Factor strength into hand-to-hand combat: + Factor strength into hand-to-hand combat @@ -222,7 +262,7 @@ <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads: + Background physics threads @@ -232,14 +272,14 @@ - Actor collision shape type: + Actor collision shape type - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency bewtween available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. Axis-aligned bounding box @@ -284,492 +324,746 @@ - - - true - - - - - 0 - 0 - 671 - 774 - - - - - - - Animations - - - - - - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - - Use magic item animation - - - - - - - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - - - Smooth movement - - - - - - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - - Use additional animation sources - - - - - - - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - - - Turn to movement direction - - - - - - - 20 - - - - - false - - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - - Weapon sheathing - - - - - - - false - - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - - Shield sheathing - - - - - - - - - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - - - Player movement ignores animation - - - - - - - - - - Shaders - - - - - - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + + + + + 0 + + + + Animations + + + + + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + Smooth movement + + + + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + Use additional animation sources + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + Turn to movement direction + + + + + + + false + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + Weapon sheathing + + + + + + + false + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + Shield sheathing + + + + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + Player movement ignores animation + + + + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + Use magic item animation + + + + + + + + + + Shaders + + + + + + + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - - - Auto use object normal maps - - - - - - - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - - - Auto use terrain normal maps - - - - - - - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + + + Auto use object normal maps + + + + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + Soft particles + + + + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.osg file, not supported in .nif files). Affects objects.</p></body></html> - - - Auto use object specular maps - - - - - - - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - - - Auto use terrain specular maps - - - - - - - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + + + Auto use object specular maps + + + + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + Auto use terrain normal maps + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + Auto use terrain specular maps + + + + + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + + + Adjust coverage for alpha test + + + + + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + + + Use anti-alias alpha testing + + + + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. Affected objects will use shaders. </p></body></html> - - - Bump/reflect map local lighting - - - - - - - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - - - Use anti-alias alpha testing - - - - - - - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - - - Soft Particles - - - - - - - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - - - Adjust coverage for alpha test - - - - - - - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - - - Weather Particle Occlusion - - - - - - - - - - Fog - - - - - - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + + + Bump/reflect map local lighting + + + + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + Weather particle occlusion + + + + + + + + + + + + Fog + + + + + + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + + + Exponential fog + + + + + + + false + + + 3 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.005000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - - - Radial fog - - - - - - - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - - - Exponential fog - - - - - - - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - - - Sky blending - - - - - - - false - - - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - - - Sky blending start - - - - - - - false - - - 3 - - - 0.000000000000000 - - - 1.000000000000000 - - - 0.005000000000000 - - - - - - - - - - Terrain - - - - - - + + + Radial fog + + + + + + + false + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + + + Sky blending start + + + + + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + + + Sky blending + + + + + + + + + + Terrain + + + + + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + Distant land + + + + + + + cells + + + 0.000000000000000 + + + 0.500000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + Object paging min size + + + + + + + Viewing distance + + + + + + + 3 + + + 0.000000000000000 + + + 0.250000000000000 + + + 0.005000000000000 + + + + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + Active grid object paging + + + + + + + + + + Post Processing + + + + + + + + false + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + + Auto exposure speed + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + + + Transparent postpass + + + + + + + false + + + 3 + + + 0.010000000000000 + + + 10.000000000000000 + + + 0.001000000000000 + + + + + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + + + Enable post processing + + + + + + + + + + Shadows + + + + + + + - Viewing distance - - - - - - - Cells - - - 0.000000000000000 - - - 0.500000000000000 - - - - - - - - - - - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + bounds + + - Object paging min size - - - - - - - 3 - - - 0.000000000000000 - - - 0.250000000000000 - - - 0.005000000000000 - - - - - - - - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - Distant land - - - - - - - 20 - - - - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + primitives + + - Active grid object paging - - - - - - - - - - - - Models - - - - - - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - - - Day night switch nodes - - - - - - - - - - Post Processing - - - - - - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - - - Enable post processing - - - - - - - 20 - - - - - false - - - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + none + + + + + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + + + Shadow planes computation method + + + + + + + false + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + unit(s) + + + 512 + + + 81920 + + + 8192 + + + + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + + Enable actor shadows + + + + + + - Transparent postpass + 512 - - - - - - - - false - - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - - Auto exposure speed - - - - - - - false - - - 3 - - - 0.010000000000000 - - - 10.000000000000000 - - - 0.001000000000000 - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - + + + + 1024 + + + + + 2048 + + + + + 4096 + + + + + + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + + + Fade start multiplier + + + + + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + + + Enable player shadows + + + + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + Shadow map resolution + + + + + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + + + Shadow distance limit: + + + + + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + + Enable object shadows + + + + + + + false + + + 2 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.900000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + Enable indoor shadows + + + + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + + + Enable terrain shadows + + + + + + + + + + Lighting + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + legacy + + + + + shaders compatibility + + + + + shaders + + + + + + + + Lighting method + + + + + + + + + + @@ -786,7 +1080,7 @@ Select your preferred audio device. - Audio Device + Audio device @@ -872,7 +1166,7 @@ Select your preferred HRTF profile. - HRTF Profile + HRTF profile @@ -936,17 +1230,17 @@ - Tool Tip Only + Tooltip - Crosshair Only + Crosshair - Tool Tip and Crosshair + Tooltip and crosshair @@ -1036,16 +1330,6 @@ - - - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - - - Enable graphic herbalism - - - @@ -1110,46 +1394,6 @@ - - - Bug Fixes - - - - - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - - Merchant equipping fix - - - - - - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - - - Trainers choose their training skills based on their base skill points - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - Miscellaneous @@ -1176,7 +1420,7 @@ - Maximum Quicksaves + Maximum quicksaves @@ -1193,9 +1437,9 @@ - + - Other + Screenshots @@ -1203,7 +1447,7 @@ - Screenshot Format + Screenshot format diff --git a/docs/source/reference/modding/settings/GUI.rst b/docs/source/reference/modding/settings/GUI.rst index 76c83be4da..edacdc730a 100644 --- a/docs/source/reference/modding/settings/GUI.rst +++ b/docs/source/reference/modding/settings/GUI.rst @@ -11,7 +11,7 @@ scaling factor This setting scales GUI windows. A value of 1.0 results in the normal scale. Larger values are useful to increase the scale of the GUI for high resolution displays. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. font size --------- @@ -24,7 +24,7 @@ Allows to specify glyph size for in-game fonts. Note: default bitmap fonts are supposed to work with 16px size, otherwise glyphs will be blurry. TrueType fonts do not have this issue. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. menu transparency ----------------- @@ -65,7 +65,7 @@ The Bethesda provided assets have a 4:3 aspect ratio, but other assets are permi If this setting is false, the assets will be centered in the mentioned 4:3 aspect ratio, with black bars filling the remainder of the screen. -This setting can be configured in the Interface section of the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. subtitles --------- diff --git a/docs/source/reference/modding/settings/fog.rst b/docs/source/reference/modding/settings/fog.rst index b05112162e..20bfdaf14d 100644 --- a/docs/source/reference/modding/settings/fog.rst +++ b/docs/source/reference/modding/settings/fog.rst @@ -125,6 +125,8 @@ By default, the fog becomes thicker proportionally to your distance from the cli This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + exponential fog --------------- @@ -135,6 +137,8 @@ exponential fog Similar to "radial fog" but uses an exponential formula for the fog. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + sky blending ------------ @@ -146,6 +150,8 @@ Whether to use blending with the sky for everything that is close to the clippin If enabled the clipping plane becomes invisible. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. +This setting can be controlled in the Settings tab of the launcher. + sky blending start ------------------ @@ -155,6 +161,8 @@ sky blending start The fraction of the maximum distance at which blending with the sky starts. +This setting can be controlled in the Settings tab of the launcher. + sky rtt resolution ------------------ diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 31cc2703f2..368401f5c5 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -16,7 +16,7 @@ If the setting is 2, the crosshair is the colour of the colour crosshair owned s If the setting is 3, both the tool tip background and the crosshair are coloured. The crosshair is not visible if crosshair is false. -This setting can be configured in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show projectile damage ---------------------- @@ -27,7 +27,7 @@ show projectile damage If this setting is true, the damage bonus of arrows and bolts will show on their tooltip. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show melee info --------------- @@ -38,7 +38,7 @@ show melee info If this setting is true, the reach and speed of weapons will show on their tooltip. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. show enchant chance ------------------- @@ -49,7 +49,7 @@ show enchant chance Whether or not the chance of success will be displayed in the enchanting menu. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. best attack ----------- @@ -78,7 +78,7 @@ If this setting is false, player has to wait until end of death animation in all Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. difficulty ---------- @@ -123,7 +123,7 @@ and the caster will absorb their own stat resulting in no effect on either the c This makes the gameplay as a mage easier, but these spells become imbalanced. This is how Morrowind behaves. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. classic calm spells behavior ---------------------------------------- @@ -137,7 +137,7 @@ This means that a Calm spell of any magnitude will always take actors out of com This is how Morrowind behaves without the Morrowind Code Patch. If this setting is off, Calm spells will only take their target out of combat once. Allowing them to re-engage if the spell was not sufficiently strong. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. use magic item animations ------------------------- @@ -161,7 +161,7 @@ show effect duration Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. enchanted weapons are magical ----------------------------- @@ -173,7 +173,7 @@ enchanted weapons are magical Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. This is how Morrowind behaves. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. prevent merchant equipping -------------------------- @@ -184,7 +184,7 @@ prevent merchant equipping Prevent merchants from equipping items that are sold to them. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. followers attack on sight ------------------------- @@ -196,7 +196,8 @@ followers attack on sight Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. -This setting can be toggled in the Settings tab of the launcher. + +This setting can be controlled in the Settings tab of the launcher. shield sheathing ---------------- @@ -214,6 +215,8 @@ To avoid conflicts, you can use _sh mesh without "Bip01 Sheath" node for such "s Also you can use an _sh node with empty "Bip01 Sheath" node. In this case the engine will use basic shield model, but will use transformations from the "Bip01 Sheath" node. +This setting can be controlled in the Settings tab of the launcher. + weapon sheathing ---------------- @@ -226,6 +229,8 @@ If this setting is true, OpenMW will utilize weapon sheathing-compatible assets To make use of this, you need to have an xbase_anim_sh.nif file with weapon bones that will be injected into the skeleton. Additional _sh suffix models are not essential for weapon sheathing to work but will act as quivers or scabbards for the weapons they correspond to. +This setting can be controlled in the Settings tab of the launcher. + use additional anim sources --------------------------- @@ -238,7 +243,8 @@ For example, if the main animation mesh has name Meshes/x.nif, the engine will load all KF-files from Animations/x folder and its child folders. This can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have their own format and are not supposed to be directly loaded in-game! -This setting can only be configured by editing the settings configuration file. + +This setting can be controlled in the Settings tab of the launcher. barter disposition change is permanent -------------------------------------- @@ -251,7 +257,7 @@ If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. This imitates the option that Morrowind Code Patch offers. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. only appropriate ammunition bypasses resistance ----------------------------------------------- @@ -264,7 +270,7 @@ If this setting is true, you will have to use the appropriate ammunition to bypa An enchanted bow with chitin arrows will no longer be enough for the purpose, while a steel longbow with glass arrows will still work. This was previously the default engine behavior that diverged from Morrowind design. -This setting can be toggled in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. strength influences hand to hand -------------------------------- @@ -454,6 +460,8 @@ Some mods add harvestable container models. When this setting is enabled, activa When this setting is turned off or when activating a regular container, the menu will open as usual. +This setting can be controlled in the Settings tab of the launcher. + allow actors to follow over water surface ----------------------------------------- @@ -489,6 +497,8 @@ day night switches Some mods add models which change visuals based on time of day. When this setting is enabled, supporting models will automatically make use of Day/night state. +This setting can be controlled in the Settings tab of the launcher. + unarmed creature attacks damage armor ------------------------------------- @@ -500,7 +510,7 @@ If disabled unarmed creature attacks do not reduce armor condition, just as with If enabled unarmed creature attacks reduce armor condition, the same as attacks from NPCs and armed creatures. -This setting can be controlled in the Settings tab of the launcher, under Game Mechanics. +This setting can be controlled in the Settings tab of the launcher. actor collision shape type -------------------------- @@ -518,6 +528,8 @@ will not be useful with another. * 1: Rotating box * 2: Cylinder +This setting can be controlled in the Settings tab of the launcher. + player movement ignores animation --------------------------------- @@ -528,4 +540,4 @@ player movement ignores animation In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. -This setting can be controlled in the Settings tab of the launcher, under Visuals. +This setting can be controlled in the Settings tab of the launcher. diff --git a/docs/source/reference/modding/settings/general.rst b/docs/source/reference/modding/settings/general.rst index e56b84ab89..d7afc04349 100644 --- a/docs/source/reference/modding/settings/general.rst +++ b/docs/source/reference/modding/settings/general.rst @@ -29,7 +29,7 @@ Specify the format for screen shots taken by pressing the screen shot key (bound This setting should be the file extension commonly associated with the desired format. The formats supported will be determined at compilation, but "jpg", "png", and "tga" should be allowed. -This setting can be configured in the Settings tab of the launcher. +This setting can be controlled in the Settings tab of the launcher. texture mag filter ------------------ @@ -69,6 +69,8 @@ notify on saved screenshot Show message box when screenshot is saved to a file. +This setting can be controlled in the Settings tab of the launcher. + preferred locales ----------------- diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst index 0670a81092..c7f7958edd 100644 --- a/docs/source/reference/modding/settings/shadows.rst +++ b/docs/source/reference/modding/settings/shadows.rst @@ -16,6 +16,8 @@ Unlike in the original Morrowind engine, 'Shadow Mapping' is used, which can hav Bear in mind that this will force OpenMW to use shaders as if :ref:`force shaders` was enabled. A keen developer may be able to implement compatibility with fixed-function mode using the advice of `this post `_, but it may be more difficult than it seems. +This setting can be controlled in the Settings tab of the launcher. + number of shadow maps --------------------- @@ -38,6 +40,8 @@ The maximum distance from the camera shadows cover, limiting their overall area and improving their quality and performance at the cost of removing shadows of distant objects or terrain. Set this to a non-positive value to remove the limit. +This setting can be controlled in the Settings tab of the launcher. + shadow fade start ------------------- @@ -49,6 +53,8 @@ The fraction of the maximum shadow map distance at which the shadows will begin Tweaking it will make the transition proportionally more or less smooth. This setting has no effect if the maximum shadow map distance is non-positive (infinite). +This setting can be controlled in the Settings tab of the launcher. + allow shadow map overlap ------------------------ @@ -90,6 +96,8 @@ compute scene bounds Two different ways to make better use of shadow map(s) by making them cover a smaller area. While primitives give better shadows at expense of more CPU, bounds gives better performance overall but with lower quality shadows. There is also the ability to disable this computation with none. +This setting can be controlled in the Settings tab of the launcher. + shadow map resolution --------------------- @@ -101,6 +109,8 @@ Control How large to make the shadow map(s). Higher values increase GPU load but can produce better-looking results. Power-of-two values may turn out to be faster than smaller values which are not powers of two on some GPU/driver combinations. +This setting can be controlled in the Settings tab of the launcher. + actor shadows ------------- @@ -111,6 +121,8 @@ actor shadows Allow actors to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + player shadows -------------- @@ -121,6 +133,8 @@ player shadows Allow the player to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + terrain shadows --------------- @@ -131,6 +145,8 @@ terrain shadows Allow terrain to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + object shadows -------------- @@ -141,6 +157,8 @@ object shadows Allow static objects to cast shadows. Potentially decreases performance. +This setting can be controlled in the Settings tab of the launcher. + enable indoor shadows --------------------- @@ -152,6 +170,8 @@ Allow shadows indoors. Due to limitations with Morrowind's data, only actors can cast shadows indoors without the ceiling casting a shadow everywhere. Some might feel this is distracting as shadows can be cast through other objects, so indoor shadows can be disabled completely. +This setting can be controlled in the Settings tab of the launcher. + Expert settings *************** diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 4cc665582b..7a5718735c 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -13,7 +13,7 @@ which should usually be sufficient, but if you need to explicitly specify a devi The names of detected devices can be found in the openmw.log file in your configuration directory. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. +This setting can be controlled in the Settings tab of the launcher. master volume ------------- @@ -111,7 +111,8 @@ Enabling HRTF may also require an OpenAL Soft version greater than 1.17.0, and possibly some operating system configuration. A value of 0 disables HRTF processing, while a value of 1 explicitly enables HRTF processing. The default value is -1, which should enable the feature automatically for most users when possible. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. + +This setting can be controlled in the Settings tab of the launcher. hrtf ---- @@ -123,6 +124,6 @@ hrtf This setting specifies which HRTF profile to use when HRTF is enabled. Blank means use the default. This setting has no effect if HRTF is not enabled based on the hrtf enable setting. Allowed values for this field are enumerated in openmw.log file is an HRTF enabled audio system is installed. - The default value is empty, which uses the default profile. -This setting can be configured by editing the settings configuration file, or in the Audio tab of the OpenMW Launcher. + +This setting can be controlled in the Settings tab of the launcher. diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 801cf63d5b..46016247ff 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -13,8 +13,8 @@ Larger values produce more detailed images within the constraints of your graphi but may reduce the frame rate. The window resolution can be selected from a menu of common screen sizes -in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. -The horizontal resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher. +in the Video tab of the Video Panel of the Options menu, or in the Display tab of the launcher. +The horizontal resolution can also be set to a custom value in the Display tab of the launcher. resolution y ------------ @@ -28,8 +28,8 @@ Larger values produce more detailed images within the constraints of your graphi but may reduce the frame rate. The window resolution can be selected from a menu of common screen sizes -in the Video tab of the Video Panel of the Options menu, or in the Graphics tab of the OpenMW Launcher. -The vertical resolution can also be set to a custom value in the Graphics tab of the OpenMW Launcher. +in the Video tab of the Video Panel of the Options menu, or in the Display tab of the launcher. +The vertical resolution can also be set to a custom value in the Display tab of the launcher. window mode ----------- @@ -48,7 +48,7 @@ This setting determines the window mode. This setting can be toggled in game using the dropdown list in the Video tab of the Video panel in the Options menu. -It can also be toggled with the window mode dropdown in the Graphics tab of the OpenMW Launcher. +It can also be toggled with the window mode dropdown in the Display tab of the launcher. screen ------ @@ -63,7 +63,7 @@ since this is the only way to control which screen is used, but it can also be used to control which screen a normal window or a borderless window opens on as well. The screens are numbered in increasing order, beginning with 0. -This setting can be selected from a pull down menu in the Graphics tab of the OpenMW Launcher, +This setting can be selected from a pull down menu in the Display tab of the OpenMW Launcher, but cannot be changed during game play. minimize on focus loss @@ -143,7 +143,7 @@ cannot reach your display's refresh rate. This prevents the input lag from becom Some hardware might not support this mode, in which case traditional vsync will be used. This setting can be adjusted in game using the VSync combo box in the Video tab of the Video panel in the Options menu. -It can also be changed by toggling the Vertical Sync combo box in the Graphics tab of the OpenMW Launcher. +It can also be changed by toggling the Vertical Sync combo box in the Display tab of the launcher. framerate limit --------------- From 6ff14e19d18fe19ff474e2de1f21d05391bde51d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 14 Jan 2024 16:41:55 +0100 Subject: [PATCH 0817/2167] Make cell models preloading a const operation --- apps/openmw/mwclass/creature.cpp | 11 ++-- apps/openmw/mwclass/creature.hpp | 4 +- apps/openmw/mwclass/creaturelevlist.cpp | 19 ------ apps/openmw/mwclass/creaturelevlist.hpp | 4 -- apps/openmw/mwclass/npc.cpp | 79 +++++++++++++------------ apps/openmw/mwclass/npc.hpp | 4 +- apps/openmw/mwworld/cellpreloader.cpp | 13 +--- apps/openmw/mwworld/cellstore.hpp | 12 ++-- apps/openmw/mwworld/class.cpp | 4 +- apps/openmw/mwworld/class.hpp | 4 +- 10 files changed, 63 insertions(+), 91 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index bb9c1bc277..fbe232093a 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -183,16 +183,17 @@ namespace MWClass return getClassModel(ptr); } - void Creature::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const + void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) models.push_back(model); - // FIXME: use const version of InventoryStore functions once they are available - if (hasInventoryStore(ptr)) + const MWWorld::CustomData* customData = ptr.getRefData().getCustomData(); + if (customData && hasInventoryStore(ptr)) { - const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); + const auto& invStore + = static_cast(*customData->asCreatureCustomData().mContainerStore); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); @@ -513,7 +514,7 @@ namespace MWClass throw std::runtime_error("this creature has no inventory store"); } - bool Creature::hasInventoryStore(const MWWorld::Ptr& ptr) const + bool Creature::hasInventoryStore(const MWWorld::ConstPtr& ptr) const { return isFlagBitSet(ptr, ESM::Creature::Weapon); } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index b407852242..38b7bb0ec1 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -79,7 +79,7 @@ namespace MWClass MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; ///< Return inventory store - bool hasInventoryStore(const MWWorld::Ptr& ptr) const override; + bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr @@ -107,7 +107,7 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr& ptr) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index fbae54737c..f16601531d 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -99,25 +99,6 @@ namespace MWClass customData.mSpawn = true; } - void CreatureLevList::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const - { - // disable for now, too many false positives - /* - const MWWorld::LiveCellRef *ref = ptr.get(); - for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != - ref->mBase->mList.end(); ++it) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (it->mLevel > player.getClass().getCreatureStats(player).getLevel()) - continue; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - MWWorld::ManualRef ref(store, it->mId); - ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); - } - */ - } - void CreatureLevList::insertObjectRendering( const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index d689d1770e..ded8f77de5 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -20,10 +20,6 @@ namespace MWClass bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; - ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: - ///< list getModel(). - void insertObjectRendering(const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4f295f7b35..7ff26fbdb6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -436,10 +436,11 @@ namespace MWClass return model; } - void Npc::getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const + void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { const MWWorld::LiveCellRef* npc = ptr.get(); - const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mRace); + const auto& esmStore = MWBase::Environment::get().getESMStore(); + const ESM::Race* race = esmStore->get().search(npc->mBase->mRace); if (race && race->mData.mFlags & ESM::Race::Beast) models.push_back(Settings::models().mBaseanimkna); @@ -453,56 +454,57 @@ namespace MWClass if (!npc->mBase->mHead.empty()) { - const ESM::BodyPart* head - = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHead); + const ESM::BodyPart* head = esmStore->get().search(npc->mBase->mHead); if (head) models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); } if (!npc->mBase->mHair.empty()) { - const ESM::BodyPart* hair - = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHair); + const ESM::BodyPart* hair = esmStore->get().search(npc->mBase->mHair); if (hair) models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); - // FIXME: use const version of InventoryStore functions once they are available - // preload equipped items - const MWWorld::InventoryStore& invStore = getInventoryStore(ptr); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + const MWWorld::CustomData* customData = ptr.getRefData().getCustomData(); + if (customData) { - MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); - if (equipped != invStore.end()) + const MWWorld::InventoryStore& invStore = customData->asNpcCustomData().mInventoryStore; + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - std::vector parts; - if (equipped->getType() == ESM::Clothing::sRecordId) + MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot); + if (equipped != invStore.end()) { - const ESM::Clothing* clothes = equipped->get()->mBase; - parts = clothes->mParts.mParts; - } - else if (equipped->getType() == ESM::Armor::sRecordId) - { - const ESM::Armor* armor = equipped->get()->mBase; - parts = armor->mParts.mParts; - } - else - { - std::string model = equipped->getClass().getModel(*equipped); - if (!model.empty()) - models.push_back(model); - } + const auto addParts = [&](const std::vector& parts) { + for (const ESM::PartReference& partRef : parts) + { + const ESM::RefId& partname + = (female && !partRef.mFemale.empty()) || (!female && partRef.mMale.empty()) + ? partRef.mFemale + : partRef.mMale; - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) - { - const ESM::RefId& partname - = (female && !it->mFemale.empty()) || (!female && it->mMale.empty()) ? it->mFemale : it->mMale; - - const ESM::BodyPart* part - = MWBase::Environment::get().getESMStore()->get().search(partname); - if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + const ESM::BodyPart* part = esmStore->get().search(partname); + if (part && !part->mModel.empty()) + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + } + }; + if (equipped->getType() == ESM::Clothing::sRecordId) + { + const ESM::Clothing* clothes = equipped->get()->mBase; + addParts(clothes->mParts.mParts); + } + else if (equipped->getType() == ESM::Armor::sRecordId) + { + const ESM::Armor* armor = equipped->get()->mBase; + addParts(armor->mParts.mParts); + } + else + { + std::string model = equipped->getClass().getModel(*equipped); + if (!model.empty()) + models.push_back(model); + } } } } @@ -512,9 +514,8 @@ namespace MWClass { const std::vector& parts = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); - for (std::vector::const_iterator it = parts.begin(); it != parts.end(); ++it) + for (const ESM::BodyPart* part : parts) { - const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index ca0d0ac95d..95245bb994 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -74,7 +74,7 @@ namespace MWClass MWWorld::InventoryStore& getInventoryStore(const MWWorld::Ptr& ptr) const override; ///< Return inventory store - bool hasInventoryStore(const MWWorld::Ptr& ptr) const override { return true; } + bool hasInventoryStore(const MWWorld::ConstPtr& ptr) const override { return true; } bool evaluateHit(const MWWorld::Ptr& ptr, MWWorld::Ptr& victim, osg::Vec3f& hitPosition) const override; @@ -85,7 +85,7 @@ namespace MWClass const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const override; - void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 7da5e8f848..fe14856364 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -52,20 +52,13 @@ namespace MWWorld struct ListModelsVisitor { - ListModelsVisitor(std::vector& out) - : mOut(out) - { - } - - virtual bool operator()(const MWWorld::Ptr& ptr) + bool operator()(const MWWorld::ConstPtr& ptr) { ptr.getClass().getModelsToPreload(ptr, mOut); return true; } - virtual ~ListModelsVisitor() = default; - std::vector& mOut; }; @@ -90,8 +83,8 @@ namespace MWWorld { mTerrainView = mTerrain->createView(); - ListModelsVisitor visitor(mMeshes); - cell->forEach(visitor); + ListModelsVisitor visitor{ mMeshes }; + cell->forEachConst(visitor); } void abort() override { mAbort = true; } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 0c6527ce22..a8f296045a 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -224,12 +224,12 @@ namespace MWWorld mHasState = true; - for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + for (LiveCellRefBase* mergedRef : mMergedRefs) { - if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; - if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) + if (!visitor(MWWorld::Ptr(mergedRef, this))) return false; } return true; @@ -249,12 +249,12 @@ namespace MWWorld if (mMergedRefsNeedsUpdate) updateMergedRefs(); - for (unsigned int i = 0; i < mMergedRefs.size(); ++i) + for (const LiveCellRefBase* mergedRef : mMergedRefs) { - if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) + if (!isAccessible(mergedRef->mData, mergedRef->mRef)) continue; - if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) + if (!visitor(MWWorld::ConstPtr(mergedRef, this))) return false; } return true; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d5062d6add..c8ba337cee 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -149,7 +149,7 @@ namespace MWWorld throw std::runtime_error("class does not have an inventory store"); } - bool Class::hasInventoryStore(const Ptr& ptr) const + bool Class::hasInventoryStore(const ConstPtr& ptr) const { return false; } @@ -320,7 +320,7 @@ namespace MWWorld return false; } - void Class::getModelsToPreload(const Ptr& ptr, std::vector& models) const + void Class::getModelsToPreload(const ConstPtr& ptr, std::vector& models) const { std::string model = getModel(ptr); if (!model.empty()) diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 87e70b3198..694a40cb32 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -170,7 +170,7 @@ namespace MWWorld ///< Return inventory store or throw an exception, if class does not have a /// inventory store (default implementation: throw an exception) - virtual bool hasInventoryStore(const Ptr& ptr) const; + virtual bool hasInventoryStore(const ConstPtr& ptr) const; ///< Does this object have an inventory store, i.e. equipment slots? (default implementation: false) virtual bool canLock(const ConstPtr& ptr) const; @@ -284,7 +284,7 @@ namespace MWWorld virtual bool useAnim() const; ///< Whether or not to use animated variant of model (default false) - virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; + virtual void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). From 94d782c4bee5a11492648bad4e7761add3518934 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 14 Jan 2024 13:38:35 +0100 Subject: [PATCH 0818/2167] Fix doc typos and add menu package to necessary lists --- docs/source/reference/lua-scripting/api.rst | 1 + docs/source/reference/lua-scripting/engine_handlers.rst | 6 +++++- docs/source/reference/lua-scripting/openmw_menu.rst | 2 +- docs/source/reference/lua-scripting/tables/packages.rst | 2 ++ files/data/scripts/omw/settings/player.lua | 2 +- files/lua_api/openmw/menu.lua | 5 +++++ 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 6d27db0515..857374b2b7 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -27,6 +27,7 @@ Lua API reference openmw_camera openmw_postprocessing openmw_debug + openmw_menu openmw_aux_calendar openmw_aux_util openmw_aux_time diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index bcadfeb295..754a63b314 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -9,6 +9,7 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 + * - onInterfaceOverride(base) - | Called if the current script has an interface and overrides an interface | (``base``) of another script. @@ -87,6 +88,7 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 + * - onFrame(dt) - | Called every frame (even if the game is paused) right after | processing user input. Use it only for latency-critical stuff @@ -96,7 +98,7 @@ Engine handler is a function defined by a script, that can be called by the engi - | `Key `_ is pressed. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` - * - onKeyRelease(key) + * - onKeyRelease(key) - | `Key `_ is released. | Usage example: | ``if key.symbol == 'z' and key.withShift then ...`` @@ -131,6 +133,7 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 + * - onKeyPress(key) - | `Key `_ is pressed. | Usage example: @@ -142,6 +145,7 @@ Engine handler is a function defined by a script, that can be called by the engi .. list-table:: :widths: 20 80 + * - onStateChanged() - | Called whenever the current game changes | (i. e. the result of `getState `_ changes) diff --git a/docs/source/reference/lua-scripting/openmw_menu.rst b/docs/source/reference/lua-scripting/openmw_menu.rst index 587e4337e0..ae1803a4f2 100644 --- a/docs/source/reference/lua-scripting/openmw_menu.rst +++ b/docs/source/reference/lua-scripting/openmw_menu.rst @@ -4,4 +4,4 @@ Package openmw.menu .. include:: version.rst .. raw:: html - :file: generated_html/openmw_ambient.html + :file: generated_html/openmw_menu.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 67709bbf7b..667b91ef63 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -29,6 +29,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.ui ` | by player scripts | | Controls :ref:`user interface `. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.menu ` | by menu scripts | | Main menu functionality, such as managing game saves | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.camera ` | by player scripts | | Controls camera. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.postprocessing `| by player scripts | | Controls post-process shaders. | diff --git a/files/data/scripts/omw/settings/player.lua b/files/data/scripts/omw/settings/player.lua index 9f397c923d..4ec61a73c7 100644 --- a/files/data/scripts/omw/settings/player.lua +++ b/files/data/scripts/omw/settings/player.lua @@ -91,7 +91,7 @@ return { registerPage = registerPage, --- -- @function [parent=#Settings] registerRenderer Register a renderer, - -- only avaialable in menu scripts (DEPRECATED in player scripts) + -- only available in menu scripts (DEPRECATED in player scripts) -- @param #string key -- @param #function renderer A renderer function, receives setting's value, -- a function to change it and an argument from the setting options diff --git a/files/lua_api/openmw/menu.lua b/files/lua_api/openmw/menu.lua index c1a1a65a62..3e2e02954a 100644 --- a/files/lua_api/openmw/menu.lua +++ b/files/lua_api/openmw/menu.lua @@ -9,6 +9,9 @@ -- @field [parent=#STATE] Running -- @field [parent=#STATE] Ended +--- +-- All possible game states returned by @{#menu.getState} +-- @field [parent=#menu] #STATE STATE --- -- Current game state @@ -63,3 +66,5 @@ --- -- Exit the game -- @function [parent=#menu] quit + +return nil From 0a2adfee1667a5239916a9d18b376872557fa327 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 14 Jan 2024 16:40:46 +0100 Subject: [PATCH 0819/2167] SaveInfo.timePlayed field --- apps/openmw/mwlua/menuscripts.cpp | 1 + files/lua_api/openmw/menu.lua | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp index 16e57961b1..e4a3962912 100644 --- a/apps/openmw/mwlua/menuscripts.cpp +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -85,6 +85,7 @@ namespace MWLua slotInfo["description"] = slot.mProfile.mDescription; slotInfo["playerName"] = slot.mProfile.mPlayerName; slotInfo["playerLevel"] = slot.mProfile.mPlayerLevel; + slotInfo["timePlayed"] = slot.mProfile.mTimePlayed; sol::table contentFiles(lua, sol::create); for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i) contentFiles[i + 1] = Misc::StringUtils::lowerCase(slot.mProfile.mContentFiles[i]); diff --git a/files/lua_api/openmw/menu.lua b/files/lua_api/openmw/menu.lua index 3e2e02954a..4deb5d00a3 100644 --- a/files/lua_api/openmw/menu.lua +++ b/files/lua_api/openmw/menu.lua @@ -50,6 +50,7 @@ -- @field #string description -- @field #string playerName -- @field #string playerLevel +-- @field #number timePlayed Gameplay time for this saved game. Note: available even with [time played](../modding/settings/saves.html#timeplayed) turned off -- @field #list<#string> contentFiles --- @@ -59,9 +60,9 @@ -- @return #list<#SaveInfo> --- --- List of all available saves +-- List of all available saves, grouped by directory -- @function [parent=#menu] getAllSaves --- @return #list<#SaveInfo> +-- @return #map<#string, #list<#SaveInfo>> --- -- Exit the game From a91e557c68845fdfa66801a4976f1c4c91bed040 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Jan 2024 22:10:18 +0400 Subject: [PATCH 0820/2167] Fix Touch command (bug 7765) --- CHANGELOG.md | 1 + apps/opencs/model/world/commands.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e6f05dd2..e0dbe3960a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index da49caef10..b2ad84966f 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -36,7 +36,7 @@ CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUnd void CSMWorld::TouchCommand::redo() { - mOld.reset(mTable.getRecord(mId).clone().get()); + mOld = mTable.getRecord(mId).clone(); mChanged = mTable.touchRecord(mId); } @@ -181,9 +181,8 @@ const std::string& CSMWorld::TouchLandCommand::getDestinationId() const void CSMWorld::TouchLandCommand::onRedo() { + mOld = mLands.getRecord(mId).clone(); mChanged = mLands.touchRecord(mId); - if (mChanged) - mOld.reset(mLands.getRecord(mId).clone().get()); } void CSMWorld::TouchLandCommand::onUndo() From 645175089016127a252e2172ef5855d0dda499d5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Jan 2024 02:15:54 +0100 Subject: [PATCH 0821/2167] Write AiSequence and Script data field by field via decompose function Use the same function to load and save to have single place with field order definition. Use concepts for overload over different types. --- apps/openmw_test_suite/esm3/testsaveload.cpp | 2 - components/esm/decompose.hpp | 10 ++++ components/esm3/aisequence.cpp | 50 +++++++++++++++----- components/esm3/aisequence.hpp | 13 +++-- components/esm3/esmreader.hpp | 12 +++++ components/esm3/esmwriter.hpp | 17 ++++++- components/esm3/loadscpt.cpp | 15 +++--- components/misc/concepts.hpp | 13 +++++ 8 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 components/esm/decompose.hpp create mode 100644 components/misc/concepts.hpp diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 501a0b47c0..f8ef23e887 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -422,7 +422,6 @@ namespace ESM std::copy(std::begin(idle), std::end(idle), record.mData.mIdle); record.mData.mShouldRepeat = 12; record.mDurationData.mRemainingDuration = 13; - record.mDurationData.mUnused = 14; record.mStoredInitialActorPosition = true; constexpr float initialActorPosition[3] = { 15, 16, 17 }; static_assert(std::size(initialActorPosition) == std::size(record.mInitialActorPosition.mValues)); @@ -438,7 +437,6 @@ namespace ESM EXPECT_THAT(result.mData.mIdle, ElementsAreArray(record.mData.mIdle)); EXPECT_EQ(result.mData.mShouldRepeat, record.mData.mShouldRepeat); EXPECT_EQ(result.mDurationData.mRemainingDuration, record.mDurationData.mRemainingDuration); - EXPECT_EQ(result.mDurationData.mUnused, record.mDurationData.mUnused); EXPECT_EQ(result.mStoredInitialActorPosition, record.mStoredInitialActorPosition); EXPECT_THAT(result.mInitialActorPosition.mValues, ElementsAreArray(record.mInitialActorPosition.mValues)); } diff --git a/components/esm/decompose.hpp b/components/esm/decompose.hpp new file mode 100644 index 0000000000..eb6f5070d4 --- /dev/null +++ b/components/esm/decompose.hpp @@ -0,0 +1,10 @@ +#ifndef OPENMW_COMPONENTS_ESM_DECOMPOSE_H +#define OPENMW_COMPONENTS_ESM_DECOMPOSE_H + +namespace ESM +{ + template + void decompose(T&& value, const auto& apply) = delete; +} + +#endif diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index d5b15893bf..c316c2db86 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -3,32 +3,58 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + #include #include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat); + } + + template T> + void decompose(T&& v, const auto& f) + { + std::uint32_t unused = 0; + f(v.mRemainingDuration, unused); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mZ); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mZ, v.mDuration); + } + namespace AiSequence { - void AiWander::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); - esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.mUnused); // was mStartTime + esm.getNamedComposite("DATA", mData); + esm.getNamedComposite("STAR", mDurationData); // was mStartTime mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } void AiWander::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); - esm.writeHNT("STAR", mDurationData); + esm.writeNamedComposite("DATA", mData); + esm.writeNamedComposite("STAR", mDurationData); // was mStartTime if (mStoredInitialActorPosition) - esm.writeHNT("POS_", mInitialActorPosition); + esm.writeHNT("POS_", mInitialActorPosition.mValues); } void AiTravel::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ); + esm.getNamedComposite("DATA", mData); esm.getHNT(mHidden, "HIDD"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); @@ -36,7 +62,7 @@ namespace ESM void AiTravel::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNT("HIDD", mHidden); if (mRepeat) esm.writeHNT("REPT", mRepeat); @@ -44,7 +70,7 @@ namespace ESM void AiEscort::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); + esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); mTargetActorId = -1; esm.getHNOT(mTargetActorId, "TAID"); @@ -64,7 +90,7 @@ namespace ESM void AiEscort::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("DURA", mRemainingDuration); @@ -76,7 +102,7 @@ namespace ESM void AiFollow::load(ESMReader& esm) { - esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ, mData.mDuration); + esm.getNamedComposite("DATA", mData); mTargetId = esm.getHNRefId("TARG"); mTargetActorId = -1; esm.getHNOT(mTargetActorId, "TAID"); @@ -101,7 +127,7 @@ namespace ESM void AiFollow::save(ESMWriter& esm) const { - esm.writeHNT("DATA", mData); + esm.writeNamedComposite("DATA", mData); esm.writeHNRefId("TARG", mTargetId); esm.writeHNT("TAID", mTargetActorId); esm.writeHNT("DURA", mRemainingDuration); diff --git a/components/esm3/aisequence.hpp b/components/esm3/aisequence.hpp index 099e5560e1..d6d6259f6b 100644 --- a/components/esm3/aisequence.hpp +++ b/components/esm3/aisequence.hpp @@ -36,32 +36,31 @@ namespace ESM virtual ~AiPackage() {} }; -#pragma pack(push, 1) struct AiWanderData { int16_t mDistance; int16_t mDuration; - unsigned char mTimeOfDay; - unsigned char mIdle[8]; - unsigned char mShouldRepeat; + std::uint8_t mTimeOfDay; + std::uint8_t mIdle[8]; + std::uint8_t mShouldRepeat; }; + struct AiWanderDuration { float mRemainingDuration; - int32_t mUnused; }; + struct AiTravelData { float mX, mY, mZ; }; + struct AiEscortData { float mX, mY, mZ; int16_t mDuration; }; -#pragma pack(pop) - struct AiWander : AiPackage { AiWanderData mData; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 461f154001..276adf749c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -12,8 +12,10 @@ #include +#include "components/esm/decompose.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm/refid.hpp" + #include "loadtes3.hpp" namespace ESM @@ -177,6 +179,16 @@ namespace ESM (getT(args), ...); } + void getNamedComposite(NAME name, auto& value) + { + decompose(value, [&](auto&... args) { getHNT(name, args...); }); + } + + void getComposite(auto& value) + { + decompose(value, [&](auto&... args) { (getT(args), ...); }); + } + template >> void skipHT() { diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 5086005b1f..101246fe43 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -5,6 +5,7 @@ #include #include +#include "components/esm/decompose.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm/refid.hpp" @@ -121,6 +122,20 @@ namespace ESM endRecord(name); } + void writeNamedComposite(NAME name, const auto& value) + { + decompose(value, [&](const auto&... args) { + startSubRecord(name); + (writeT(args), ...); + endRecord(name); + }); + } + + void writeComposite(const auto& value) + { + decompose(value, [&](const auto&... args) { (writeT(args), ...); }); + } + // Prevent using writeHNT with strings. This already happened by accident and results in // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. @@ -132,7 +147,7 @@ namespace ESM void writeHNT(NAME name, const T (&data)[size], int) = delete; template - void writeHNT(NAME name, const T& data, int size) + void writeHNT(NAME name, const T& data, std::size_t size) { startSubRecord(name); writeT(data, size); diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index c1dc759cdc..f79f4989ef 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -4,12 +4,19 @@ #include #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mNumShorts, v.mNumLongs, v.mNumFloats, v.mScriptDataSize, v.mStringTableSize); + } + void Script::loadSCVR(ESMReader& esm) { uint32_t s = mData.mStringTableSize; @@ -99,11 +106,7 @@ namespace ESM { esm.getSubHeader(); mId = esm.getMaybeFixedRefIdSize(32); - esm.getT(mData.mNumShorts); - esm.getT(mData.mNumLongs); - esm.getT(mData.mNumFloats); - esm.getT(mData.mScriptDataSize); - esm.getT(mData.mStringTableSize); + esm.getComposite(mData); hasHeader = true; break; @@ -157,7 +160,7 @@ namespace ESM esm.startSubRecord("SCHD"); esm.writeMaybeFixedSizeRefId(mId, 32); - esm.writeT(mData, 20); + esm.writeComposite(mData); esm.endRecord("SCHD"); if (isDeleted) diff --git a/components/misc/concepts.hpp b/components/misc/concepts.hpp new file mode 100644 index 0000000000..d8573e94ab --- /dev/null +++ b/components/misc/concepts.hpp @@ -0,0 +1,13 @@ +#ifndef OPENMW_COMPONENTS_MISC_CONCEPTS_H +#define OPENMW_COMPONENTS_MISC_CONCEPTS_H + +#include +#include + +namespace Misc +{ + template + concept SameAsWithoutCvref = std::same_as, std::remove_cvref_t>; +} + +#endif From 4d6350539c860aa9c0711d79b338f4d87ad44f2d Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 12:36:23 +0100 Subject: [PATCH 0822/2167] Move FindLowestUnusedTexUnitVisitor to unnamed namespace It's not used anywhere except this translation unit so no need to make the symbol available everywhere else. --- components/sceneutil/util.cpp | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index b71de4c2a3..ce48702a74 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -37,27 +37,27 @@ namespace SceneUtil } const std::array glowTextureNames = generateGlowTextureNames(); + + struct FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor + { + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::Node& node) override + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit + = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + + int mLowestUnusedTexUnit = 0; + }; } - class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor - { - public: - FindLowestUnusedTexUnitVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mLowestUnusedTexUnit(0) - { - } - - void apply(osg::Node& node) override - { - if (osg::StateSet* stateset = node.getStateSet()) - mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); - - traverse(node); - } - int mLowestUnusedTexUnit; - }; - GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector>& textures, osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) From a2147d70cc45d60a4f2d048980a5bcd5496d4b55 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 00:30:41 +0100 Subject: [PATCH 0823/2167] Use forward declaration for some VFS types This will allow to save on preprocessed code size in the future changes. --- apps/niftest/niftest.cpp | 1 + apps/opencs/model/world/resources.cpp | 1 + apps/openmw/mwgui/loadingscreen.cpp | 1 + apps/openmw/mwgui/settingswindow.cpp | 1 + apps/openmw/mwlua/vfsbindings.cpp | 1 + apps/openmw/mwrender/animation.cpp | 1 + apps/openmw/mwrender/postprocessor.cpp | 1 + apps/openmw/mwsound/soundmanagerimp.cpp | 1 + apps/openmw_test_suite/testing_util.hpp | 1 + components/vfs/archive.hpp | 22 ++------ components/vfs/bsaarchive.hpp | 1 + components/vfs/file.hpp | 21 ++++++++ components/vfs/filemap.hpp | 14 +++++ components/vfs/filesystemarchive.hpp | 3 +- components/vfs/manager.cpp | 9 +++- components/vfs/manager.hpp | 49 +++-------------- components/vfs/recursivedirectoryiterator.hpp | 53 +++++++++++++++++++ 17 files changed, 120 insertions(+), 61 deletions(-) create mode 100644 components/vfs/file.hpp create mode 100644 components/vfs/filemap.hpp create mode 100644 components/vfs/recursivedirectoryiterator.hpp diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 29488fb677..32fd65c348 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index bfab0193b0..345f6008ec 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -11,6 +11,7 @@ #include #include +#include CSMWorld::Resources::Resources( const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char* const* extensions) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 1723841b32..8ba2bb8312 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbd54586df..0ffadc43a3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index 0eccb336c2..c9b1a45fe2 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7fa8e43c37..feed9719b6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1aaeb460b7..c31f49f35a 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 64f8959218..383d316c91 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 0c941053a7..aa76f7f944 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 79c876b391..42b88219d7 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -1,27 +1,13 @@ -#ifndef OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H -#define OPENMW_COMPONENTS_RESOURCE_ARCHIVE_H +#ifndef OPENMW_COMPONENTS_VFS_ARCHIVE_H +#define OPENMW_COMPONENTS_VFS_ARCHIVE_H -#include -#include +#include #include -#include +#include "filemap.hpp" namespace VFS { - - class File - { - public: - virtual ~File() = default; - - virtual Files::IStreamPtr open() = 0; - - virtual std::filesystem::path getPath() = 0; - }; - - using FileMap = std::map>; - class Archive { public: diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 29098db45d..304fc438ad 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -2,6 +2,7 @@ #define VFS_BSAARCHIVE_HPP_ #include "archive.hpp" +#include "file.hpp" #include "pathutil.hpp" #include diff --git a/components/vfs/file.hpp b/components/vfs/file.hpp new file mode 100644 index 0000000000..f2dadb1162 --- /dev/null +++ b/components/vfs/file.hpp @@ -0,0 +1,21 @@ +#ifndef OPENMW_COMPONENTS_VFS_FILE_H +#define OPENMW_COMPONENTS_VFS_FILE_H + +#include + +#include + +namespace VFS +{ + class File + { + public: + virtual ~File() = default; + + virtual Files::IStreamPtr open() = 0; + + virtual std::filesystem::path getPath() = 0; + }; +} + +#endif diff --git a/components/vfs/filemap.hpp b/components/vfs/filemap.hpp new file mode 100644 index 0000000000..808153fc05 --- /dev/null +++ b/components/vfs/filemap.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_VFS_FILEMAP_H +#define OPENMW_COMPONENTS_VFS_FILEMAP_H + +#include +#include + +namespace VFS +{ + class File; + + using FileMap = std::map>; +} + +#endif diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index e31ef9bd30..00fe5ba971 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -2,8 +2,9 @@ #define OPENMW_COMPONENTS_RESOURCE_FILESYSTEMARCHIVE_H #include "archive.hpp" -#include +#include "file.hpp" +#include #include namespace VFS diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index cc231847f5..5315f17252 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -5,12 +5,19 @@ #include #include +#include #include "archive.hpp" +#include "file.hpp" #include "pathutil.hpp" +#include "recursivedirectoryiterator.hpp" namespace VFS { + Manager::Manager() = default; + + Manager::~Manager() = default; + void Manager::reset() { mIndex.clear(); @@ -70,7 +77,7 @@ namespace VFS return found->second->getPath(); } - Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const { if (path.empty()) return { mIndex.begin(), mIndex.end() }; diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 76405aae2c..05990a8607 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -4,32 +4,17 @@ #include #include -#include #include #include +#include #include -#include "archive.hpp" +#include "filemap.hpp" namespace VFS { - - template - class IteratorPair - { - public: - IteratorPair(Iterator first, Iterator last) - : mFirst(first) - , mLast(last) - { - } - Iterator begin() const { return mFirst; } - Iterator end() const { return mLast; } - - private: - Iterator mFirst; - Iterator mLast; - }; + class Archive; + class RecursiveDirectoryRange; /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) @@ -38,29 +23,11 @@ namespace VFS /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { - class RecursiveDirectoryIterator - { - public: - RecursiveDirectoryIterator(FileMap::const_iterator it) - : mIt(it) - { - } - const std::string& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first; } - bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } - RecursiveDirectoryIterator& operator++() - { - ++mIt; - return *this; - } - - private: - FileMap::const_iterator mIt; - }; - - using RecursiveDirectoryRange = IteratorPair; - public: + Manager(); + + ~Manager(); + // Empty the file index and unregister archives. void reset(); diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp new file mode 100644 index 0000000000..82f8e594fd --- /dev/null +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_COMPONENTS_VFS_RECURSIVEDIRECTORYITERATOR_H +#define OPENMW_COMPONENTS_VFS_RECURSIVEDIRECTORYITERATOR_H + +#include + +#include "filemap.hpp" + +namespace VFS +{ + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(FileMap::const_iterator it) + : mIt(it) + { + } + + const std::string& operator*() const { return mIt->first; } + + const std::string* operator->() const { return &mIt->first; } + + RecursiveDirectoryIterator& operator++() + { + ++mIt; + return *this; + } + + friend bool operator==(const RecursiveDirectoryIterator& lhs, const RecursiveDirectoryIterator& rhs) = default; + + private: + FileMap::const_iterator mIt; + }; + + class RecursiveDirectoryRange + { + public: + RecursiveDirectoryRange(RecursiveDirectoryIterator first, RecursiveDirectoryIterator last) + : mBegin(first) + , mEnd(last) + { + } + + RecursiveDirectoryIterator begin() const { return mBegin; } + + RecursiveDirectoryIterator end() const { return mEnd; } + + private: + RecursiveDirectoryIterator mBegin; + RecursiveDirectoryIterator mEnd; + }; +} + +#endif From a340b49cbc51455ff38910f3a941a3ca2d5f4fb6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 16 Jan 2024 10:23:13 +0400 Subject: [PATCH 0824/2167] Enhance light settings tweaking --- apps/launcher/settingspage.cpp | 54 +++++-- apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/settingspage.ui | 138 +++++++++++++++++- apps/openmw/mwgui/settingswindow.cpp | 2 +- files/data/l10n/OMWEngine/de.yaml | 4 + files/data/l10n/OMWEngine/en.yaml | 4 + files/data/l10n/OMWEngine/fr.yaml | 4 + files/data/l10n/OMWEngine/ru.yaml | 4 + files/data/l10n/OMWEngine/sv.yaml | 4 + .../data/mygui/openmw_settings_window.layout | 9 +- 10 files changed, 200 insertions(+), 24 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 9492326cb0..c274b75f79 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -213,21 +213,6 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::fog().mSkyBlending, *skyBlendingCheckBox); skyBlendingStartComboBox->setValue(Settings::fog().mSkyBlendingStart); - int lightingMethod = 1; - switch (Settings::shaders().mLightingMethod) - { - case SceneUtil::LightingMethod::FFP: - lightingMethod = 0; - break; - case SceneUtil::LightingMethod::PerObjectUniform: - lightingMethod = 1; - break; - case SceneUtil::LightingMethod::SingleUBO: - lightingMethod = 2; - break; - } - lightingMethodComboBox->setCurrentIndex(lightingMethod); - loadSettingBool(Settings::shadows().mActorShadows, *actorShadowsCheckBox); loadSettingBool(Settings::shadows().mPlayerShadows, *playerShadowsCheckBox); loadSettingBool(Settings::shadows().mTerrainShadows, *terrainShadowsCheckBox); @@ -261,6 +246,31 @@ bool Launcher::SettingsPage::loadSettings() shadowResolutionComboBox->setCurrentIndex(shadowResIndex); connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); + + lightsMaxLightsSpinBox->setValue(Settings::shaders().mMaxLights); + lightsMaximumDistanceSpinBox->setValue(Settings::shaders().mMaximumLightDistance); + lightFadeMultiplierSpinBox->setValue(Settings::shaders().mLightFadeStart); + lightsBoundingSphereMultiplierSpinBox->setValue(Settings::shaders().mLightBoundsMultiplier); + lightsMinimumInteriorBrightnessSpinBox->setValue(Settings::shaders().mMinimumInteriorBrightness); + + connect(lightingMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this, + &SettingsPage::slotLightTypeCurrentIndexChanged); + + int lightingMethod = 1; + switch (Settings::shaders().mLightingMethod) + { + case SceneUtil::LightingMethod::FFP: + lightingMethod = 0; + break; + case SceneUtil::LightingMethod::PerObjectUniform: + lightingMethod = 1; + break; + case SceneUtil::LightingMethod::SingleUBO: + lightingMethod = 2; + break; + } + lightingMethodComboBox->setCurrentIndex(lightingMethod); + slotLightTypeCurrentIndexChanged(lightingMethod); } // Audio @@ -454,6 +464,12 @@ void Launcher::SettingsPage::saveSettings() Settings::shadows().mComputeSceneBounds.set("primitives"); else Settings::shadows().mComputeSceneBounds.set("none"); + + Settings::shaders().mMaxLights.set(lightsMaxLightsSpinBox->value()); + Settings::shaders().mMaximumLightDistance.set(lightsMaximumDistanceSpinBox->value()); + Settings::shaders().mLightFadeStart.set(lightFadeMultiplierSpinBox->value()); + Settings::shaders().mLightBoundsMultiplier.set(lightsBoundingSphereMultiplierSpinBox->value()); + Settings::shaders().mMinimumInteriorBrightness.set(lightsMinimumInteriorBrightnessSpinBox->value()); } // Audio @@ -562,3 +578,11 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) shadowDistanceSpinBox->setEnabled(checked); fadeStartSpinBox->setEnabled(checked); } + +void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) +{ + lightsMaximumDistanceSpinBox->setEnabled(index != 0); + lightsMaxLightsSpinBox->setEnabled(index != 0); + lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); + lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); +} diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index a8a6b7c26d..ea675857ea 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -33,6 +33,7 @@ namespace Launcher void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); void slotShadowDistLimitToggled(bool checked); + void slotLightTypeCurrentIndexChanged(int index); private: Config::GameSettings& mGameSettings; diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index cf8215ef30..7006238e71 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1018,7 +1018,17 @@ - + + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + + + Lights maximum distance + + + + Qt::Vertical @@ -1031,29 +1041,143 @@ + + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + + + Max light sources + + + + + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + + + Lights fade multiplier + + + + + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + + + Lighting method + + + - legacy + Legacy - shaders compatibility + Shaders (compatibility) - shaders + Shaders - - + + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + - Lighting method + Lights bounding sphere multiplier + + + + + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + + + Lights minimum interior brightness + + + + + + + 5.000000000000000 + + + 0.050000000000000 + + + 1.650000000000000 + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.850000000000000 + + + + + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.080000000000000 + + + + + + + unit(s) + + + 8192 + + + 128 + + + 8192 + + + + + + + 2 + + + 64 + + + 2 diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index fbd54586df..6a1cfaefe2 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -131,7 +131,7 @@ namespace void updateMaxLightsComboBox(MyGUI::ComboBox* box) { constexpr int min = 8; - constexpr int max = 32; + constexpr int max = 64; constexpr int increment = 8; const int maxLights = Settings::shaders().mMaxLights; // show increments of 8 in dropdown diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 0f729d0077..2874001309 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -87,6 +87,10 @@ LightsBoundingSphereMultiplier: "Bounding-Sphere-Multiplikator" LightsBoundingSphereMultiplierTooltip: "Standard: 1.65\nMultiplikator für Begrenzungskugel.\nHöhere Zahlen ermöglichen einen sanften Abfall, erfordern jedoch eine Erhöhung der Anzahl der maximalen Lichter.\n\nBeeinflusst nicht die Beleuchtung oder Lichtstärke." LightsFadeStartMultiplier: "Licht Verblassungs-Start-Multiplikator" LightsFadeStartMultiplierTooltip: "Standard: 0.85\nBruchteil der maximalen Entfernung, bei der die Lichter zu verblassen beginnen.\n\nStellen Sie hier einen niedrigen Wert für langsamere Übergänge oder einen hohen Wert für schnellere Übergänge ein." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximale Lichtreichweite" LightsMaximumDistanceTooltip: "Standard: 8192\nMaximale Entfernung, bei der Lichter erscheinen (gemessen in Einheiten).\n\nSetzen Sie dies auf 0, um eine unbegrenzte Entfernung zu verwenden." LightsMinimumInteriorBrightness: "Minimale Innenhelligkeit" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 09db2b496d..0455d11e07 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Bounding Sphere Multiplier" LightsBoundingSphereMultiplierTooltip: "Default: 1.65\nMultipler for bounding sphere of lights.\nHigher numbers allows for smooth falloff but require an increase in number of max lights.\n\nDoes not effect the illumination or strength of lights." LightsFadeStartMultiplier: "Fade Start Multiplier" LightsFadeStartMultiplierTooltip: "Default: 0.85\nFraction of maximum distance at which lights will start to fade.\n\nSet this to a low value for slower transitions or a high value for quicker transitions." +LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n + \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n + \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n + \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximum Light Distance" LightsMaximumDistanceTooltip: "Default: 8192\nMaximum distance at which lights will appear (measured in units).\n\nSet this to 0 to use an unlimited distance." LightsMinimumInteriorBrightness: "Minimum Interior Brightness" diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index f2772b017e..85bac08612 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Multiplicateur de sphère englobante" LightsBoundingSphereMultiplierTooltip: "valeur par défaut: 1.65\nMultiplicateur pour le rayon de la sphère incluant les sources lumineuses.\nUn multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.\n\nCe paramètre ne modifie ni l'intensité ni la luminance des lumières." LightsFadeStartMultiplier: "Seuil de perte d'éclat lumineux" LightsFadeStartMultiplierTooltip: "valeur par défaut: 0.85\nFraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.\n\nSélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Distance maximale des sources lumineuses" LightsMaximumDistanceTooltip: "valeur par défaut: 8192\nDistance maximale d'affichage des sources lumineuses (en unité de distance).\n\nMettez cette valeur à 0 pour une distance d'affichage infinie." LightsMinimumInteriorBrightness: "Luminosité intérieure minimale" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 2bcb76a442..8d221fe33c 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Множитель размера ограни LightsBoundingSphereMultiplierTooltip: "Значение по умолчанию: 1.65\nМножитель размера ограничивающей сферы источников света.\nВысокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.\n\nНастройка не влияет на уровень освещения или мощность источников света." LightsFadeStartMultiplier: "Множитель начала затухания" LightsFadeStartMultiplierTooltip: "Значение по умолчанию: 0.85\nДоля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.\n\nНизкие значения ведут к плавному затуханию, высокие - к резкому." +LightsLightingMethodTooltip: "Задает способ обработки источников света.\n\n + \"Устаревший\" всегда использует 8 источников света на объект и выдает освещение, наиболее близкое к таковому в оригинальной игре.\n\n + \"Шейдеры (режим совместимости)\" убирает ограничение в 8 источников света. Этот режим также позволяет освещению влиять на анимированную траву и позволяет настроить угасание света на расстоянии. Рекомендуется использовать этот режим на устаревшем аппаратном обеспечении и с количеством источников света на объект около 8.\n\n + \"Шейдеры\" работает аналогично режиму \"Шейдеры (режим совместимости)\", но использует более современный подход, позволяющий использовать большее количество источников света с минимальным влиянием на производительность на современном аппаратном обеспечении." LightsMaximumDistance: "Дальность отображения источников света" LightsMaximumDistanceTooltip: "Значение по умолчанию: 8192\nМаксимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).\n\nЕсли 0, то расстояние не ограничено." LightsMinimumInteriorBrightness: "Минимальный уровень освещения в помещениях" diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index dc65726fdd..134fab0e95 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -107,6 +107,10 @@ LightsBoundingSphereMultiplier: "Gränssfärsmultiplikator" LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljusens gränssfär.\nHögre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.\n\nPåverkar inte ljusstyrkan." LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." +#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n +# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n +# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n +# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index e912ababfd..27298b9756 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -536,6 +536,9 @@ + + + @@ -561,6 +564,10 @@ + + + + @@ -570,7 +577,7 @@ - + From 27fa411f4f701985079da35f91c22c54a3626fe8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 16 Jan 2024 20:56:58 +0100 Subject: [PATCH 0825/2167] Convert strings in nif files to utf8 --- apps/bulletobjecttool/main.cpp | 2 +- apps/navmeshtool/main.cpp | 2 +- apps/niftest/niftest.cpp | 2 +- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/engine.cpp | 3 ++- components/nif/niffile.cpp | 5 +++-- components/nif/niffile.hpp | 8 +++++++- components/nif/nifstream.cpp | 4 ++++ components/nif/nifstream.hpp | 11 ++++++++++- components/resource/keyframemanager.cpp | 6 ++++-- components/resource/keyframemanager.hpp | 9 ++++++++- components/resource/niffilemanager.cpp | 9 +++++---- components/resource/niffilemanager.hpp | 9 ++++++++- components/resource/resourcesystem.cpp | 6 +++--- components/resource/resourcesystem.hpp | 7 ++++++- 15 files changed, 64 insertions(+), 21 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 7d87899f4a..2165f93804 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -174,7 +174,7 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs, &encoder); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 8604bcdfb0..a9c30cf23e 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -221,7 +221,7 @@ namespace NavMeshTool constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs); + Resource::NifFileManager nifFileManager(&vfs, &encoder); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 29488fb677..5d0f723ee5 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -84,7 +84,7 @@ void readNIF( try { Nif::NIFFile file(fullPath); - Nif::Reader reader(file); + Nif::Reader reader(file, nullptr); if (vfs != nullptr) reader.parse(vfs->get(pathStr)); else diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6322a77e66..ef2e289ee2 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,7 +149,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourcesManager.setVFS(mVFS.get()); constexpr double expiryDelay = 0; - mResourceSystem = std::make_unique(mVFS.get(), expiryDelay); + mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 92483bd8c3..c2e57208b4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -706,7 +706,8 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay); + mResourceSystem + = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay, mEncoder.get()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d6d063a254..7a38b0dc1a 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -24,7 +24,7 @@ namespace Nif { - Reader::Reader(NIFFile& file) + Reader::Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder) : mVersion(file.mVersion) , mUserVersion(file.mUserVersion) , mBethVersion(file.mBethVersion) @@ -33,6 +33,7 @@ namespace Nif , mRecords(file.mRecords) , mRoots(file.mRoots) , mUseSkinning(file.mUseSkinning) + , mEncoder(encoder) { } @@ -519,7 +520,7 @@ namespace Nif const std::array fileHash = Files::getHash(mFilename, *stream); mHash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); - NIFStream nif(*this, std::move(stream)); + NIFStream nif(*this, std::move(stream), mEncoder); // Check the header string std::string head = nif.getVersionString(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6f0030af47..38b0712373 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -11,6 +11,11 @@ #include "record.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Nif { @@ -112,6 +117,7 @@ namespace Nif std::vector mStrings; bool& mUseSkinning; + const ToUTF8::Utf8Encoder* mEncoder; static std::atomic_bool sLoadUnsupportedFiles; static std::atomic_bool sWriteNifDebugLog; @@ -122,7 +128,7 @@ namespace Nif public: /// Open a NIF stream. The name is used for error messages. - explicit Reader(NIFFile& file); + Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder); /// Parse the file void parse(Files::IStreamPtr&& stream); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 2eba746ccf..93714e37f0 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -4,6 +4,8 @@ #include "niffile.hpp" +#include "../to_utf8/to_utf8.hpp" + namespace { @@ -58,6 +60,8 @@ namespace Nif size_t end = str.find('\0'); if (end != std::string::npos) str.erase(end); + if (mEncoder) + str = mEncoder->getStatelessEncoder().getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); return str; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 95205c4fda..958aef7254 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,11 @@ #include "niftypes.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Nif { @@ -67,11 +73,14 @@ namespace Nif { const Reader& mReader; Files::IStreamPtr mStream; + const ToUTF8::Utf8Encoder* mEncoder; + std::string mBuffer; public: - explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream) + explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::Utf8Encoder* encoder) : mReader(reader) , mStream(std::move(stream)) + , mEncoder(encoder) { } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 574d761d09..6895a0238f 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -207,9 +207,11 @@ namespace Resource namespace Resource { - KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay) + KeyframeManager::KeyframeManager( + const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) : ResourceManager(vfs, expiryDelay) , mSceneManager(sceneManager) + , mEncoder(encoder) { } @@ -226,7 +228,7 @@ namespace Resource if (Misc::getFileExtension(normalized) == "kf") { auto file = std::make_shared(normalized); - Nif::Reader reader(*file); + Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->getNormalized(normalized)); NifOsg::Loader::loadKf(*file, *loaded.get()); } diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index 0c92553949..f684e22ee7 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -9,6 +9,11 @@ #include "resourcemanager.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { /// @brief extract animations from OSG formats to OpenMW's animation system @@ -48,7 +53,8 @@ namespace Resource class KeyframeManager : public ResourceManager { public: - explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay); + explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, + const ToUTF8::Utf8Encoder* encoder); ~KeyframeManager() = default; /// Retrieve a read-only keyframe resource by name (case-insensitive). @@ -59,6 +65,7 @@ namespace Resource private: SceneManager* mSceneManager; + const ToUTF8::Utf8Encoder* mEncoder; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 5e457cdfaa..fb57bc8c85 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -24,21 +24,22 @@ namespace Resource { } - NifFileHolder() {} + NifFileHolder() = default; META_Object(Resource, NifFileHolder) Nif::NIFFilePtr mNifFile; }; - NifFileManager::NifFileManager(const VFS::Manager* vfs) + NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder) // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, // so no point in using an expiry delay. : ResourceManager(vfs, 0) + , mEncoder(encoder) { } - NifFileManager::~NifFileManager() {} + NifFileManager::~NifFileManager() = default; Nif::NIFFilePtr NifFileManager::get(const std::string& name) { @@ -48,7 +49,7 @@ namespace Resource else { auto file = std::make_shared(name); - Nif::Reader reader(*file); + Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); mCache->addEntryToObjectCache(name, obj); diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 5aef3f3016..2225a9fd4c 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -5,6 +5,11 @@ #include "resourcemanager.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { @@ -12,8 +17,10 @@ namespace Resource /// @note May be used from any thread. class NifFileManager : public ResourceManager { + const ToUTF8::Utf8Encoder* mEncoder; + public: - NifFileManager(const VFS::Manager* vfs); + NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder); ~NifFileManager(); /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 0bee08e9ac..7d704e7d1e 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -10,13 +10,13 @@ namespace Resource { - ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay) + ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) : mVFS(vfs) { - mNifFileManager = std::make_unique(vfs); + mNifFileManager = std::make_unique(vfs, encoder); mImageManager = std::make_unique(vfs, expiryDelay); mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); - mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay); + mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); addResourceManager(mKeyframeManager.get()); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index d06ac79640..554083852e 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -15,6 +15,11 @@ namespace osg class State; } +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace Resource { @@ -30,7 +35,7 @@ namespace Resource class ResourceSystem { public: - explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay); + explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder); ~ResourceSystem(); SceneManager* getSceneManager(); From 48db113149b1219073a4a7a43393292b53b96339 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 17 Jan 2024 18:10:42 +0100 Subject: [PATCH 0826/2167] Address feedback --- CHANGELOG.md | 1 + apps/bulletobjecttool/main.cpp | 2 +- apps/navmeshtool/main.cpp | 2 +- apps/opencs/model/world/data.cpp | 3 ++- apps/openmw/engine.cpp | 4 ++-- components/nif/niffile.cpp | 2 +- components/nif/niffile.hpp | 6 +++--- components/nif/nifstream.cpp | 2 +- components/nif/nifstream.hpp | 7 ++++--- components/resource/keyframemanager.cpp | 4 ++-- components/resource/keyframemanager.hpp | 6 +++--- components/resource/niffilemanager.cpp | 2 +- components/resource/niffilemanager.hpp | 6 +++--- components/resource/resourcesystem.cpp | 3 ++- components/resource/resourcesystem.hpp | 5 +++-- components/to_utf8/to_utf8.hpp | 2 +- 16 files changed, 31 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e6f05dd2..a35f44fe2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,7 @@ Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7770: Sword of the Perithia: Script execution failure + Bug #7780: Non-ASCII texture paths in NIF files don't work Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 2165f93804..504aef7e67 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -174,7 +174,7 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs, &encoder); + Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index a9c30cf23e..9ed7fb4c2e 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -221,7 +221,7 @@ namespace NavMeshTool constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); - Resource::NifFileManager nifFileManager(&vfs, &encoder); + Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ef2e289ee2..428ffb2737 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,7 +149,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourcesManager.setVFS(mVFS.get()); constexpr double expiryDelay = 0; - mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder); + mResourceSystem + = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c2e57208b4..5b8d725583 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -706,8 +706,8 @@ void OMW::Engine::prepareEngine() VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); - mResourceSystem - = std::make_unique(mVFS.get(), Settings::cells().mCacheExpiryDelay, mEncoder.get()); + mResourceSystem = std::make_unique( + mVFS.get(), Settings::cells().mCacheExpiryDelay, &mEncoder.get()->getStatelessEncoder()); mResourceSystem->getSceneManager()->getShaderManager().setMaxTextureUnits(mGlMaxTextureImageUnits); mResourceSystem->getSceneManager()->setUnRefImageDataAfterApply( false); // keep to Off for now to allow better state sharing diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 7a38b0dc1a..74c3b391a1 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -24,7 +24,7 @@ namespace Nif { - Reader::Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder) + Reader::Reader(NIFFile& file, const ToUTF8::StatelessUtf8Encoder* encoder) : mVersion(file.mVersion) , mUserVersion(file.mUserVersion) , mBethVersion(file.mBethVersion) diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 38b0712373..993e9b7eea 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -13,7 +13,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Nif @@ -117,7 +117,7 @@ namespace Nif std::vector mStrings; bool& mUseSkinning; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; static std::atomic_bool sLoadUnsupportedFiles; static std::atomic_bool sWriteNifDebugLog; @@ -128,7 +128,7 @@ namespace Nif public: /// Open a NIF stream. The name is used for error messages. - Reader(NIFFile& file, const ToUTF8::Utf8Encoder* encoder); + explicit Reader(NIFFile& file, const ToUTF8::StatelessUtf8Encoder* encoder); /// Parse the file void parse(Files::IStreamPtr&& stream); diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 93714e37f0..f960e8d972 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -61,7 +61,7 @@ namespace Nif if (end != std::string::npos) str.erase(end); if (mEncoder) - str = mEncoder->getStatelessEncoder().getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); + str = mEncoder->getUtf8(str, ToUTF8::BufferAllocationPolicy::UseGrowFactor, mBuffer); return str; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 958aef7254..062f7c6512 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -25,7 +25,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Nif @@ -73,11 +73,12 @@ namespace Nif { const Reader& mReader; Files::IStreamPtr mStream; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; std::string mBuffer; public: - explicit NIFStream(const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::Utf8Encoder* encoder) + explicit NIFStream( + const Reader& reader, Files::IStreamPtr&& stream, const ToUTF8::StatelessUtf8Encoder* encoder) : mReader(reader) , mStream(std::move(stream)) , mEncoder(encoder) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 6895a0238f..d60129cb86 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -207,8 +207,8 @@ namespace Resource namespace Resource { - KeyframeManager::KeyframeManager( - const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) + KeyframeManager::KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, + const ToUTF8::StatelessUtf8Encoder* encoder) : ResourceManager(vfs, expiryDelay) , mSceneManager(sceneManager) , mEncoder(encoder) diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index f684e22ee7..ed8d4a04ab 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -11,7 +11,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -54,7 +54,7 @@ namespace Resource { public: explicit KeyframeManager(const VFS::Manager* vfs, SceneManager* sceneManager, double expiryDelay, - const ToUTF8::Utf8Encoder* encoder); + const ToUTF8::StatelessUtf8Encoder* encoder); ~KeyframeManager() = default; /// Retrieve a read-only keyframe resource by name (case-insensitive). @@ -65,7 +65,7 @@ namespace Resource private: SceneManager* mSceneManager; - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index fb57bc8c85..352d367f9b 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -31,7 +31,7 @@ namespace Resource Nif::NIFFilePtr mNifFile; }; - NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder) + NifFileManager::NifFileManager(const VFS::Manager* vfs, const ToUTF8::StatelessUtf8Encoder* encoder) // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, // so no point in using an expiry delay. : ResourceManager(vfs, 0) diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index 2225a9fd4c..dab4b70748 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -7,7 +7,7 @@ namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -17,10 +17,10 @@ namespace Resource /// @note May be used from any thread. class NifFileManager : public ResourceManager { - const ToUTF8::Utf8Encoder* mEncoder; + const ToUTF8::StatelessUtf8Encoder* mEncoder; public: - NifFileManager(const VFS::Manager* vfs, const ToUTF8::Utf8Encoder* encoder); + NifFileManager(const VFS::Manager* vfs, const ToUTF8::StatelessUtf8Encoder* encoder); ~NifFileManager(); /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 7d704e7d1e..65a83a60ab 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -10,7 +10,8 @@ namespace Resource { - ResourceSystem::ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder) + ResourceSystem::ResourceSystem( + const VFS::Manager* vfs, double expiryDelay, const ToUTF8::StatelessUtf8Encoder* encoder) : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 554083852e..f7f09b9277 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -17,7 +17,7 @@ namespace osg namespace ToUTF8 { - class Utf8Encoder; + class StatelessUtf8Encoder; } namespace Resource @@ -35,7 +35,8 @@ namespace Resource class ResourceSystem { public: - explicit ResourceSystem(const VFS::Manager* vfs, double expiryDelay, const ToUTF8::Utf8Encoder* encoder); + explicit ResourceSystem( + const VFS::Manager* vfs, double expiryDelay, const ToUTF8::StatelessUtf8Encoder* encoder); ~ResourceSystem(); SceneManager* getSceneManager(); diff --git a/components/to_utf8/to_utf8.hpp b/components/to_utf8/to_utf8.hpp index 11a466e44c..80af6586c9 100644 --- a/components/to_utf8/to_utf8.hpp +++ b/components/to_utf8/to_utf8.hpp @@ -68,7 +68,7 @@ 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; } + const StatelessUtf8Encoder& getStatelessEncoder() const { return mImpl; } private: std::string mBuffer; From 35d9b18b4c009f6adf56421ee59dd7d8f34e69f9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Jan 2024 22:26:56 +0100 Subject: [PATCH 0827/2167] Add type for normalized VFS path and use for VFS::Manager file map key This will reduce the number of path normalizations while more places will use this type. In some cases it also will reduce number of temporary allocations for new strings. For now make conversion from and to std::string_view implicit to allow gradual migration to this type. --- apps/openmw_test_suite/testing_util.hpp | 2 +- components/vfs/filemap.hpp | 7 +- components/vfs/manager.cpp | 2 +- components/vfs/pathutil.hpp | 68 +++++++++++++++++++ components/vfs/recursivedirectoryiterator.hpp | 5 +- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index aa76f7f944..b819848a8f 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -61,7 +61,7 @@ namespace TestingOpenMW void listResources(VFS::FileMap& out) override { for (const auto& [key, value] : mFiles) - out.emplace(VFS::Path::normalizeFilename(key), value); + out.emplace(key, value); } bool contains(std::string_view file) const override { return mFiles.contains(file); } diff --git a/components/vfs/filemap.hpp b/components/vfs/filemap.hpp index 808153fc05..1b7d390d88 100644 --- a/components/vfs/filemap.hpp +++ b/components/vfs/filemap.hpp @@ -8,7 +8,12 @@ namespace VFS { class File; - using FileMap = std::map>; + namespace Path + { + class Normalized; + } + + using FileMap = std::map>; } #endif diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 5315f17252..d312ce9d84 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -83,7 +83,7 @@ namespace VFS return { mIndex.begin(), mIndex.end() }; std::string normalized = Path::normalizeFilename(path); const auto it = mIndex.lower_bound(normalized); - if (it == mIndex.end() || !it->first.starts_with(normalized)) + if (it == mIndex.end() || !it->first.view().starts_with(normalized)) return { it, it }; ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 724b406f1d..9bcc263842 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -51,6 +52,73 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + + class Normalized + { + public: + Normalized() = default; + + Normalized(std::string_view value) + : mValue(normalizeFilename(value)) + { + } + + Normalized(const char* value) + : Normalized(std::string_view(value)) + { + } + + Normalized(const std::string& value) + : Normalized(std::string_view(value)) + { + } + + explicit Normalized(std::string&& value) + : mValue(std::move(value)) + { + normalizeFilenameInPlace(mValue); + } + + const std::string& value() const& { return mValue; } + + std::string value() && { return std::move(mValue); } + + std::string_view view() const { return mValue; } + + operator std::string_view() const { return mValue; } + + operator const std::string&() const { return mValue; } + + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; + + template + friend bool operator==(const Normalized& lhs, const T& rhs) + { + return lhs.mValue == rhs; + } + + friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; } + + template + friend bool operator<(const Normalized& lhs, const T& rhs) + { + return lhs.mValue < rhs; + } + + template + friend bool operator<(const T& lhs, const Normalized& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) + { + return stream << value.mValue; + } + + private: + std::string mValue; + }; } #endif diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp index 82f8e594fd..39fb26e873 100644 --- a/components/vfs/recursivedirectoryiterator.hpp +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -4,6 +4,7 @@ #include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -15,9 +16,9 @@ namespace VFS { } - const std::string& operator*() const { return mIt->first; } + const std::string& operator*() const { return mIt->first.value(); } - const std::string* operator->() const { return &mIt->first; } + const std::string* operator->() const { return &mIt->first.value(); } RecursiveDirectoryIterator& operator++() { From 3cdb9496c4db9b9bbd103ce06d1659e713d69606 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Thu, 18 Jan 2024 07:15:35 -0800 Subject: [PATCH 0828/2167] dont clear empty FBOs, fix doc example --- apps/openmw/mwrender/postprocessor.cpp | 5 +++++ docs/source/reference/postprocessing/omwfx.rst | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 1aaeb460b7..c82104ee4a 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -662,6 +662,11 @@ namespace MWRender for (const auto& name : pass->getRenderTargets()) { + if (name.empty()) + { + continue; + } + auto& renderTarget = technique->getRenderTargetsMap()[name]; subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 7a7cdc198b..b47e509925 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -561,7 +561,7 @@ color buffer will accumulate. source_format = rgb; internal_format = rgb16f; source_type = float; - clear_color = vec4(1,0,0,1); + clear_color = vec4(0,0,0,1); } fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { From fba405587731f799899d815a6381b7cb4f121869 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 18 Jan 2024 22:32:46 +0100 Subject: [PATCH 0829/2167] Move return comments to a new line --- apps/openmw/mwworld/cellstore.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index a8f296045a..0a78746479 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -209,8 +209,8 @@ namespace MWWorld /// false will abort the iteration. /// \note Prefer using forEachConst when possible. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEach(Visitor&& visitor) { @@ -238,8 +238,8 @@ namespace MWWorld /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEachConst(Visitor&& visitor) const { @@ -263,8 +263,8 @@ namespace MWWorld /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in - /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration - /// completed? + /// unintended behaviour. \attention This function also lists deleted (count 0) objects! + /// \return Iteration completed? template bool forEachType(Visitor&& visitor) { From ffa52dfe7c3f2a9fa9a05b14dc174c4ec4e387e1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Jan 2024 11:55:37 +0300 Subject: [PATCH 0830/2167] Don't use height cull callback when there's no terrain --- components/terrain/world.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 93a9c563af..9c409b3bc2 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -145,7 +145,7 @@ namespace Terrain osg::Callback* World::getHeightCullCallback(float highz, unsigned int mask) { - if (!mHeightCullCallback) + if (!mHeightCullCallback || mTerrainRoot->getNumChildren() == 0) return nullptr; mHeightCullCallback->setHighZ(highz); From e997c44db6df01c62882fba5e166e6d9c5143bc5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Jan 2024 12:53:35 +0300 Subject: [PATCH 0831/2167] Restore unwrapped Bullet triangle shape shallow copying --- components/resource/bulletshape.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 360b92ffc0..70348e956d 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -39,6 +39,13 @@ namespace Resource const_cast(trishape->getChildShape()), trishape->getLocalScaling())); } + if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + const btBvhTriangleMeshShape* trishape = static_cast(shape); + return CollisionShapePtr(new btScaledBvhTriangleMeshShape( + const_cast(trishape), btVector3(1.f, 1.f, 1.f))); + } + if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) { const btBoxShape* boxshape = static_cast(shape); From b37aee21e3796a8d7f7633a80311e120fe87cf04 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 15:16:46 +0400 Subject: [PATCH 0832/2167] Fix tooltips in the main menu --- apps/openmw/engine.cpp | 6 +----- apps/openmw/mwworld/worldimp.cpp | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 92483bd8c3..a059e7477f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -321,11 +321,7 @@ bool OMW::Engine::frame(float frametime) // update GUI by world data { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - - if (mStateManager->getState() != MWBase::StateManager::State_NoGame) - { - mWorld->updateWindowManager(); - } + mWorld->updateWindowManager(); } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1b6af6038e..f20cbd208f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -60,6 +60,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -997,6 +998,9 @@ namespace MWWorld { MWWorld::Ptr facedObject; + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + return facedObject; + if (MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager()->isConsoleMode()) facedObject = getFacedObject(getMaxActivationDistance() * 50, false); From 87c9f395f11712470bcd6c134b79002d3d6031da Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 16:01:48 +0400 Subject: [PATCH 0833/2167] Move local variables in components --- components/bsa/ba2dx10file.cpp | 2 +- components/config/gamesettings.cpp | 2 +- components/detournavigator/asyncnavmeshupdater.cpp | 2 +- components/esm3/inventorystate.cpp | 2 +- components/esm3/spellstate.cpp | 4 ++-- components/esm4/loadfurn.cpp | 2 +- components/esmterrain/storage.cpp | 6 +++--- components/files/configurationmanager.cpp | 2 +- components/lua/asyncpackage.cpp | 2 +- components/lua_ui/element.cpp | 2 +- components/lua_ui/scriptsettings.cpp | 2 +- components/nif/niffile.cpp | 2 +- components/settings/parser.cpp | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 945e8dd931..593ca64949 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -76,7 +76,7 @@ namespace Bsa fail("Corrupted BSA"); } - mFolders[dirHash][{ nameHash, extHash }] = file; + mFolders[dirHash][{ nameHash, extHash }] = std::move(file); FileStruct fileStruct{}; mFiles.push_back(fileStruct); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 42c11628df..ad7c73d3d9 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -371,7 +371,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) { if ((keyMatch.captured(1) + "=" + keyMatch.captured(2)) == keyVal) { - *iter = settingLine; + *iter = std::move(settingLine); break; } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 980281240d..ec6313d6f1 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -601,7 +601,7 @@ namespace DetourNavigator if (mSettings.get().mEnableRecastMeshFileNameRevision) recastMeshRevision = revision; if (mSettings.get().mEnableNavMeshFileNameRevision) - navMeshRevision = revision; + navMeshRevision = std::move(revision); } if (recastMesh && mSettings.get().mEnableWriteRecastMeshToFile) writeToFile(*recastMesh, diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 1947be23e9..f3dce52f29 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -74,7 +74,7 @@ namespace ESM esm.getHNT(multiplier, "MULT"); params.emplace_back(rand, multiplier); } - mPermanentMagicEffectMagnitudes[id] = params; + mPermanentMagicEffectMagnitudes[id] = std::move(params); } while (esm.isNextSub("EQUI")) diff --git a/components/esm3/spellstate.cpp b/components/esm3/spellstate.cpp index 41591f56b7..39c98e7c0f 100644 --- a/components/esm3/spellstate.cpp +++ b/components/esm3/spellstate.cpp @@ -33,7 +33,7 @@ namespace ESM state.mPurgedEffects.insert(index); } - mSpellParams[id] = state; + mSpellParams[id] = std::move(state); mSpells.emplace_back(id); } } @@ -69,7 +69,7 @@ namespace ESM esm.getHNT(info.mMagnitude, "MAGN"); permEffectList.push_back(info); } - mPermanentSpellEffects[spellId] = permEffectList; + mPermanentSpellEffects[spellId] = std::move(permEffectList); } // Obsolete diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 40ea04955e..41ddca07a2 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -50,7 +50,7 @@ void ESM4::Furniture::load(ESM4::Reader& reader) reader.getLocalizedString(name); // FIXME: subsequent FULL subrecords name object combinations (FO4) if (mFullName.empty()) - mFullName = name; + mFullName = std::move(name); break; } case ESM4::SUB_MODL: diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index d8cf964f71..a00cca0904 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -586,7 +586,7 @@ namespace ESMTerrain Misc::StringUtils::replaceLast(texture_, ".", mNormalHeightMapPattern + "."); if (mVFS->exists(texture_)) { - info.mNormalMap = texture_; + info.mNormalMap = std::move(texture_); info.mParallax = true; } else @@ -594,7 +594,7 @@ namespace ESMTerrain texture_ = texture; Misc::StringUtils::replaceLast(texture_, ".", mNormalMapPattern + "."); if (mVFS->exists(texture_)) - info.mNormalMap = texture_; + info.mNormalMap = std::move(texture_); } } @@ -604,7 +604,7 @@ namespace ESMTerrain Misc::StringUtils::replaceLast(texture_, ".", mSpecularMapPattern + "."); if (mVFS->exists(texture_)) { - info.mDiffuseMap = texture_; + info.mDiffuseMap = std::move(texture_); info.mSpecular = true; } } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index ece30e5b3f..943f514676 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -309,7 +309,7 @@ namespace Files tempPath /= str.substr(pos + 1, str.length() - pos); } - path = tempPath; + path = std::move(tempPath); } else { diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index b60238de13..8316ab2cde 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -85,7 +85,7 @@ namespace LuaUtil auto initializer = [](sol::table hiddenData) { ScriptId id = hiddenData[ScriptsContainer::sScriptIdKey]; - return AsyncPackageId{ id.mContainer, id.mIndex, hiddenData }; + return AsyncPackageId{ id.mContainer, id.mIndex, std::move(hiddenData) }; }; return sol::make_object(lua, initializer); } diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 84383f89e1..e993fba9fd 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -38,7 +38,7 @@ namespace LuaUi if (typeField != sol::nil && templateType != type) throw std::logic_error(std::string("Template layout type ") + type + std::string(" doesn't match template type ") + templateType); - type = templateType; + type = std::move(templateType); } return type; } diff --git a/components/lua_ui/scriptsettings.cpp b/components/lua_ui/scriptsettings.cpp index e92d1d8958..d552b7b3d6 100644 --- a/components/lua_ui/scriptsettings.cpp +++ b/components/lua_ui/scriptsettings.cpp @@ -21,7 +21,7 @@ namespace LuaUi Log(Debug::Warning) << "A script settings page has an empty name"; if (!element.get()) Log(Debug::Warning) << "A script settings page has no UI element assigned"; - return { std::move(name), std::move(searchHints), element }; + return { std::move(name), std::move(searchHints), std::move(element) }; } } diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index d6d063a254..7b20637c70 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -672,7 +672,7 @@ namespace Nif assert(r != nullptr); assert(r->recType != RC_MISSING); - r->recName = rec; + r->recName = std::move(rec); r->recIndex = i; r->read(&nif); mRecords[i] = std::move(r); diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index 5ec41c5f4b..ff6bc5ca48 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -77,7 +77,7 @@ void Settings::SettingsFileParser::loadSettingsFile( Misc::StringUtils::trim(value); if (overrideExisting) - settings[std::make_pair(currentCategory, setting)] = value; + settings[std::make_pair(currentCategory, setting)] = std::move(value); else if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } From 8af8f331cb67d7a49c44a494aa2bb07d710d3f2b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 19 Jan 2024 18:19:01 +0400 Subject: [PATCH 0834/2167] Avoid possible race in videoplayer --- extern/osg-ffmpeg-videoplayer/videostate.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/extern/osg-ffmpeg-videoplayer/videostate.cpp b/extern/osg-ffmpeg-videoplayer/videostate.cpp index 096651dfd8..c062c99b65 100644 --- a/extern/osg-ffmpeg-videoplayer/videostate.cpp +++ b/extern/osg-ffmpeg-videoplayer/videostate.cpp @@ -598,8 +598,17 @@ public: if(av_read_frame(pFormatCtx, packet.get()) < 0) { - if (self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0 && self->pictq_size == 0) - self->mVideoEnded = true; + if (self->audioq.nb_packets == 0 && self->videoq.nb_packets == 0) + { + self->pictq_mutex.lock(); + bool videoEnded = self->pictq_size == 0; + self->pictq_mutex.unlock(); + if (videoEnded) + self->mVideoEnded = true; + else + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + continue; } else From 98844a692d0e3b19be2ac6ca64f32e54096ec8d5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 13:36:01 +0100 Subject: [PATCH 0835/2167] Regroup crash catcher includes --- components/crashcatcher/crashcatcher.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 566b3a34fc..8b14194bd7 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -1,22 +1,22 @@ #include -#include #include #include + +#include #include +#include +#include #include #include #include #include +#include #include #include #include #include #include -#include -#include -#include - #include #include From 48c3268bcbe784e3033a72d093f9e9ed4ed31ac7 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 13:38:25 +0100 Subject: [PATCH 0836/2167] Reduce code duplication for finding signal description --- components/crashcatcher/crashcatcher.cpp | 140 +++++++++-------------- 1 file changed, 56 insertions(+), 84 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 8b14194bd7..21026110e8 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -1,6 +1,8 @@ +#include #include #include #include +#include #include #include @@ -64,57 +66,66 @@ static struct char buf[1024]; } crash_info; -static const struct +namespace { - const char* name; - int signum; -} signals[] = { { "Segmentation fault", SIGSEGV }, { "Illegal instruction", SIGILL }, { "FPU exception", SIGFPE }, - { "System BUS error", SIGBUS }, { nullptr, 0 } }; + struct SignalInfo + { + int mCode; + const char* mDescription; + }; -static const struct -{ - int code; - const char* name; -} sigill_codes[] = { + constexpr SignalInfo signals[] = { + { SIGSEGV, "Segmentation fault" }, + { SIGILL, "Illegal instruction" }, + { SIGFPE, "FPU exception" }, + { SIGBUS, "System BUS error" }, + }; + + constexpr SignalInfo sigIllCodes[] = { #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) - { ILL_ILLOPC, "Illegal opcode" }, { ILL_ILLOPN, "Illegal operand" }, { ILL_ILLADR, "Illegal addressing mode" }, - { ILL_ILLTRP, "Illegal trap" }, { ILL_PRVOPC, "Privileged opcode" }, { ILL_PRVREG, "Privileged register" }, - { ILL_COPROC, "Coprocessor error" }, { ILL_BADSTK, "Internal stack error" }, + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, #endif - { 0, nullptr } -}; + }; -static const struct -{ - int code; - const char* name; -} sigfpe_codes[] = { { FPE_INTDIV, "Integer divide by zero" }, { FPE_INTOVF, "Integer overflow" }, - { FPE_FLTDIV, "Floating point divide by zero" }, { FPE_FLTOVF, "Floating point overflow" }, - { FPE_FLTUND, "Floating point underflow" }, { FPE_FLTRES, "Floating point inexact result" }, - { FPE_FLTINV, "Floating point invalid operation" }, { FPE_FLTSUB, "Subscript out of range" }, { 0, nullptr } }; + constexpr SignalInfo sigFpeCodes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + }; -static const struct -{ - int code; - const char* name; -} sigsegv_codes[] = { + constexpr SignalInfo sigSegvCodes[] = { #ifndef __FreeBSD__ - { SEGV_MAPERR, "Address not mapped to object" }, { SEGV_ACCERR, "Invalid permissions for mapped object" }, + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, #endif - { 0, nullptr } -}; + }; -static const struct -{ - int code; - const char* name; -} sigbus_codes[] = { + constexpr SignalInfo sigBusCodes[] = { #ifndef __FreeBSD__ - { BUS_ADRALN, "Invalid address alignment" }, { BUS_ADRERR, "Non-existent physical address" }, - { BUS_OBJERR, "Object specific hardware error" }, + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, #endif - { 0, nullptr } -}; + }; + + const char* findSignalDescription(std::span info, int code) + { + const auto it = std::find_if(info.begin(), info.end(), [&](const SignalInfo& v) { return v.mCode == code; }); + return it == info.end() ? "" : it->mDescription; + } +} static int (*cc_user_info)(char*, char*); @@ -298,71 +309,32 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* context) static void crash_handler(const char* logfile) { - const char* sigdesc = ""; - int i; - if (fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) { fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); exit(1); } - /* Get the signal description */ - for (i = 0; signals[i].name; ++i) - { - if (signals[i].signum == crash_info.signum) - { - sigdesc = signals[i].name; - break; - } - } + const char* sigdesc = findSignalDescription(signals, crash_info.signum); if (crash_info.has_siginfo) { switch (crash_info.signum) { case SIGSEGV: - for (i = 0; sigsegv_codes[i].name; ++i) - { - if (sigsegv_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigsegv_codes[i].name; - break; - } - } + sigdesc = findSignalDescription(sigSegvCodes, crash_info.siginfo.si_code); break; case SIGFPE: - for (i = 0; sigfpe_codes[i].name; ++i) - { - if (sigfpe_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigfpe_codes[i].name; - break; - } - } + sigdesc = findSignalDescription(sigFpeCodes, crash_info.siginfo.si_code); break; case SIGILL: - for (i = 0; sigill_codes[i].name; ++i) - { - if (sigill_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigill_codes[i].name; - break; - } - } + sigdesc = findSignalDescription(sigIllCodes, crash_info.siginfo.si_code); break; case SIGBUS: - for (i = 0; sigbus_codes[i].name; ++i) - { - if (sigbus_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigbus_codes[i].name; - break; - } - } + sigdesc = findSignalDescription(sigBusCodes, crash_info.siginfo.si_code); break; } } From c95c8fcc3646931c865daf1a3722a6cf34907f4c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:23:39 +0100 Subject: [PATCH 0837/2167] Add missing description for SIGABRT --- components/crashcatcher/crashcatcher.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 21026110e8..7a2e2b0a12 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -79,6 +79,7 @@ namespace { SIGILL, "Illegal instruction" }, { SIGFPE, "FPU exception" }, { SIGBUS, "System BUS error" }, + { SIGABRT, "Abnormal termination condition" }, }; constexpr SignalInfo sigIllCodes[] = { From 388a73376c57a61de0b4ad3625a3ba6244339f5f Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 13:43:26 +0100 Subject: [PATCH 0838/2167] Use std::optional for siginfo --- components/crashcatcher/crashcatcher.cpp | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 7a2e2b0a12..92f33d0708 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -61,8 +62,7 @@ static struct { int signum; pid_t pid; - int has_siginfo; - siginfo_t siginfo; + std::optional siginfo; char buf[1024]; } crash_info; @@ -261,8 +261,9 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* context) crash_info.signum = signum; crash_info.pid = getpid(); - crash_info.has_siginfo = !!siginfo; - if (siginfo) + if (siginfo == nullptr) + crash_info.siginfo = std::nullopt; + else crash_info.siginfo = *siginfo; if (cc_user_info) cc_user_info(crash_info.buf, crash_info.buf + sizeof(crash_info.buf)); @@ -318,30 +319,30 @@ static void crash_handler(const char* logfile) const char* sigdesc = findSignalDescription(signals, crash_info.signum); - if (crash_info.has_siginfo) + if (crash_info.siginfo.has_value()) { switch (crash_info.signum) { case SIGSEGV: - sigdesc = findSignalDescription(sigSegvCodes, crash_info.siginfo.si_code); + sigdesc = findSignalDescription(sigSegvCodes, crash_info.siginfo->si_code); break; case SIGFPE: - sigdesc = findSignalDescription(sigFpeCodes, crash_info.siginfo.si_code); + sigdesc = findSignalDescription(sigFpeCodes, crash_info.siginfo->si_code); break; case SIGILL: - sigdesc = findSignalDescription(sigIllCodes, crash_info.siginfo.si_code); + sigdesc = findSignalDescription(sigIllCodes, crash_info.siginfo->si_code); break; case SIGBUS: - sigdesc = findSignalDescription(sigBusCodes, crash_info.siginfo.si_code); + sigdesc = findSignalDescription(sigBusCodes, crash_info.siginfo->si_code); break; } } fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); - if (crash_info.has_siginfo) - fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + if (crash_info.siginfo.has_value()) + fprintf(stderr, "Address: %p\n", crash_info.siginfo->si_addr); fputc('\n', stderr); if (logfile) @@ -358,8 +359,8 @@ static void crash_handler(const char* logfile) "*** Fatal Error ***\n" "%s (signal %i)\n", sigdesc, crash_info.signum); - if (crash_info.has_siginfo) - printf("Address: %p\n", crash_info.siginfo.si_addr); + if (crash_info.siginfo.has_value()) + printf("Address: %p\n", crash_info.siginfo->si_addr); fputc('\n', stdout); fflush(stdout); } From d6f1fbe2c998299e8c4447f681d77b4d846eb27e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 13:50:50 +0100 Subject: [PATCH 0839/2167] Remove unused user info function --- components/crashcatcher/crashcatcher.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 92f33d0708..1aaac2345b 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -63,7 +63,6 @@ static struct int signum; pid_t pid; std::optional siginfo; - char buf[1024]; } crash_info; namespace @@ -128,8 +127,6 @@ namespace } } -static int (*cc_user_info)(char*, char*); - static void gdb_info(pid_t pid) { char respfile[64]; @@ -265,8 +262,6 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* context) crash_info.siginfo = std::nullopt; else crash_info.siginfo = *siginfo; - if (cc_user_info) - cc_user_info(crash_info.buf, crash_info.buf + sizeof(crash_info.buf)); /* Fork off to start a crash handler */ switch ((dbg_pid = fork())) @@ -367,8 +362,6 @@ static void crash_handler(const char* logfile) sys_info(); - crash_info.buf[sizeof(crash_info.buf) - 1] = '\0'; - printf("%s\n", crash_info.buf); fflush(stdout); if (crash_info.pid > 0) @@ -425,8 +418,7 @@ static void getExecPath(char** argv) } } -int crashCatcherInstallHandlers( - int argc, char** argv, int num_signals, int* signals, const char* logfile, int (*user_info)(char*, char*)) +int crashCatcherInstallHandlers(int argc, char** argv, int num_signals, int* signals, const char* logfile) { struct sigaction sa; stack_t altss; @@ -435,8 +427,6 @@ int crashCatcherInstallHandlers( if (argc == 2 && strcmp(argv[1], crash_switch) == 0) crash_handler(logfile); - cc_user_info = user_info; - getExecPath(argv); /* Set an alternate signal stack so SIGSEGVs caused by stack overflows @@ -534,7 +524,7 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), nullptr) == -1) + if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str()) == -1) { Log(Debug::Warning) << "Installing crash handler failed"; } From d54bb5cb5a3053bdfa39b0d67f7db9a591ba073c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 13:57:04 +0100 Subject: [PATCH 0840/2167] Declare variables closer to where they are used --- components/crashcatcher/crashcatcher.cpp | 48 +++++++++--------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 1aaac2345b..517912f7dc 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -129,17 +129,17 @@ namespace static void gdb_info(pid_t pid) { - char respfile[64]; - FILE* f; - int fd; - /* * Create a temp file to put gdb commands into. * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. * Modern systems implement it and suggest to do not touch masks in multithreaded applications. * So CoverityScan warning is valid only for ancient versions of stdlib. */ - strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); + char respfile[64] = "/tmp/gdb-respfile-XXXXXX"; + + FILE* f; + int fd; + #ifdef __COVERITY__ umask(0600); #endif @@ -223,8 +223,8 @@ static size_t safe_write(int fd, const void* buf, size_t len) size_t ret = 0; while (ret < len) { - ssize_t rem; - if ((rem = write(fd, (const char*)buf + ret, len - ret)) == -1) + const ssize_t rem = write(fd, (const char*)buf + ret, len - ret); + if (rem == -1) { if (errno == EINTR) continue; @@ -235,12 +235,8 @@ static size_t safe_write(int fd, const void* buf, size_t len) return ret; } -static void crash_catcher(int signum, siginfo_t* siginfo, void* context) +static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) { - // ucontext_t *ucontext = (ucontext_t*)context; - pid_t dbg_pid; - int fd[2]; - /* Make sure the effective uid is the real uid */ if (getuid() != geteuid()) { @@ -249,6 +245,7 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* context) } safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err) - 1); + int fd[2]; if (pipe(fd) == -1) { safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err) - 1); @@ -263,8 +260,9 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* context) else crash_info.siginfo = *siginfo; + const pid_t dbg_pid = fork(); /* Fork off to start a crash handler */ - switch ((dbg_pid = fork())) + switch (dbg_pid) { /* Error */ case -1: @@ -399,7 +397,6 @@ static void getExecPath(char** argv) if (proc_pidpath(getpid(), argv0, sizeof(argv0)) > 0) return; #endif - int cwdlen; const char* statusPaths[] = { "/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file" }; memset(argv0, 0, sizeof(argv0)); @@ -413,17 +410,13 @@ static void getExecPath(char** argv) snprintf(argv0, sizeof(argv0), "%s", argv[0]); else if (getcwd(argv0, sizeof(argv0)) != nullptr) { - cwdlen = strlen(argv0); + const int cwdlen = strlen(argv0); snprintf(argv0 + cwdlen, sizeof(argv0) - cwdlen, "/%s", argv[0]); } } int crashCatcherInstallHandlers(int argc, char** argv, int num_signals, int* signals, const char* logfile) { - struct sigaction sa; - stack_t altss; - int retval; - if (argc == 2 && strcmp(argv[1], crash_switch) == 0) crash_handler(logfile); @@ -432,17 +425,19 @@ int crashCatcherInstallHandlers(int argc, char** argv, int num_signals, int* sig /* Set an alternate signal stack so SIGSEGVs caused by stack overflows * still run */ static char* altstack = new char[SIGSTKSZ]; + stack_t altss; altss.ss_sp = altstack; altss.ss_flags = 0; altss.ss_size = SIGSTKSZ; sigaltstack(&altss, nullptr); + struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_catcher; sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); - retval = 0; + int retval = 0; while (num_signals--) { if ((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT @@ -477,10 +472,7 @@ static bool is_debugger_present() } return false; #elif defined(__APPLE__) - int junk; - int mib[4]; struct kinfo_proc info; - size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. @@ -489,16 +481,12 @@ static bool is_debugger_present() // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. - - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; // Call sysctl. - size = sizeof(info); - junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); + size_t size = sizeof(info); + const int junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. From 7763fe73d8813478069c53ebddbad5fbc58d7a13 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:00:17 +0100 Subject: [PATCH 0841/2167] Make it clear handled signals are always the same --- components/crashcatcher/crashcatcher.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 517912f7dc..044431eca0 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -415,7 +415,7 @@ static void getExecPath(char** argv) } } -int crashCatcherInstallHandlers(int argc, char** argv, int num_signals, int* signals, const char* logfile) +int crashCatcherInstallHandlers(int argc, char** argv, const char* logfile) { if (argc == 2 && strcmp(argv[1], crash_switch) == 0) crash_handler(logfile); @@ -437,17 +437,15 @@ int crashCatcherInstallHandlers(int argc, char** argv, int num_signals, int* sig sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; sigemptyset(&sa.sa_mask); + constexpr int signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + int retval = 0; - while (num_signals--) + for (const int signal : signals) { - if ((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT - && *signals != SIGBUS) - || sigaction(*signals, &sa, nullptr) == -1) + if (sigaction(signal, &sa, nullptr) == -1) { - *signals = 0; retval = -1; } - ++signals; } return retval; } @@ -511,8 +509,7 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra { if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { - int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str()) == -1) + if (crashCatcherInstallHandlers(argc, argv, crashLogPath.c_str()) == -1) { Log(Debug::Warning) << "Installing crash handler failed"; } From 8348557893e91046f2466113d17bc4ac42a04584 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:02:48 +0100 Subject: [PATCH 0842/2167] Move crash handling out of crash handler installation --- components/crashcatcher/crashcatcher.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 044431eca0..f478e2dff8 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -415,11 +415,8 @@ static void getExecPath(char** argv) } } -int crashCatcherInstallHandlers(int argc, char** argv, const char* logfile) +static int crashCatcherInstallHandlers(char** argv) { - if (argc == 2 && strcmp(argv[1], crash_switch) == 0) - crash_handler(logfile); - getExecPath(argv); /* Set an alternate signal stack so SIGSEGVs caused by stack overflows @@ -509,10 +506,11 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra { if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { - if (crashCatcherInstallHandlers(argc, argv, crashLogPath.c_str()) == -1) - { + if (argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(crashLogPath.c_str()); + + if (crashCatcherInstallHandlers(argv) == -1) Log(Debug::Warning) << "Installing crash handler failed"; - } else Log(Debug::Info) << "Crash handler installed"; } From fc12728d25f5f23ddab79d20d67f659afbb4be36 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:04:03 +0100 Subject: [PATCH 0843/2167] Move crash_switch variable to cpp file --- components/crashcatcher/crashcatcher.cpp | 2 ++ components/crashcatcher/crashcatcher.hpp | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index f478e2dff8..0a06557e3a 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -67,6 +67,8 @@ static struct namespace { + constexpr char crash_switch[] = "--cc-handle-crash"; + struct SignalInfo { int mCode; diff --git a/components/crashcatcher/crashcatcher.hpp b/components/crashcatcher/crashcatcher.hpp index a9efcaccdc..3cd1480dd5 100644 --- a/components/crashcatcher/crashcatcher.hpp +++ b/components/crashcatcher/crashcatcher.hpp @@ -11,8 +11,6 @@ #define USE_CRASH_CATCHER 0 #endif -constexpr char crash_switch[] = "--cc-handle-crash"; - #if USE_CRASH_CATCHER extern void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); #else From 55501a02c087bddbb57d185cd6dfcd24c2b0a5ea Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:09:02 +0100 Subject: [PATCH 0844/2167] Define only one crashCatcherInstall function --- components/crashcatcher/crashcatcher.cpp | 6 +++++- components/crashcatcher/crashcatcher.hpp | 14 +------------- components/debug/debugging.cpp | 3 +-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 0a06557e3a..39d076677c 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -21,6 +21,7 @@ #include #include +#include #include @@ -506,14 +507,17 @@ static bool is_debugger_present() void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath) { +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ + || defined(__posix)) if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { if (argc == 2 && strcmp(argv[1], crash_switch) == 0) - crash_handler(crashLogPath.c_str()); + crash_handler(Files::pathToUnicodeString(crashLogPath).c_str()); if (crashCatcherInstallHandlers(argv) == -1) Log(Debug::Warning) << "Installing crash handler failed"; else Log(Debug::Info) << "Crash handler installed"; } +#endif } diff --git a/components/crashcatcher/crashcatcher.hpp b/components/crashcatcher/crashcatcher.hpp index 3cd1480dd5..9dd1000385 100644 --- a/components/crashcatcher/crashcatcher.hpp +++ b/components/crashcatcher/crashcatcher.hpp @@ -2,19 +2,7 @@ #define CRASHCATCHER_H #include -#include -#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ - || defined(__posix)) -#define USE_CRASH_CATCHER 1 -#else -#define USE_CRASH_CATCHER 0 -#endif - -#if USE_CRASH_CATCHER -extern void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); -#else -inline void crashCatcherInstall(int, char**, const std::string& crashLogPath) {} -#endif +void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); #endif diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 3be977657a..d170cf1929 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -352,8 +352,7 @@ int wrapApplication(int (*innerApplication)(int argc, char* argv[]), int argc, c #else const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; // install the crash handler as soon as possible. - crashCatcherInstall( - argc, argv, Files::pathToUnicodeString(std::filesystem::temp_directory_path() / crashLogName)); + crashCatcherInstall(argc, argv, std::filesystem::temp_directory_path() / crashLogName); #endif ret = innerApplication(argc, argv); } From fffc6101b7484e7643614930238bd7738643f597 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:13:07 +0100 Subject: [PATCH 0845/2167] Remove unnecessary nesting --- components/crashcatcher/crashcatcher.cpp | 58 +++++++++++------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 39d076677c..0f2cb0ed7b 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -341,25 +341,22 @@ static void crash_handler(const char* logfile) fprintf(stderr, "Address: %p\n", crash_info.siginfo->si_addr); fputc('\n', stderr); - if (logfile) + /* Create crash log file and redirect shell output to it */ + if (freopen(logfile, "wa", stdout) != stdout) { - /* Create crash log file and redirect shell output to it */ - if (freopen(logfile, "wa", stdout) != stdout) - { - fprintf(stderr, "!!! Could not create %s following signal\n", logfile); - exit(1); - } - fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); - - printf( - "*** Fatal Error ***\n" - "%s (signal %i)\n", - sigdesc, crash_info.signum); - if (crash_info.siginfo.has_value()) - printf("Address: %p\n", crash_info.siginfo->si_addr); - fputc('\n', stdout); - fflush(stdout); + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf( + "*** Fatal Error ***\n" + "%s (signal %i)\n", + sigdesc, crash_info.signum); + if (crash_info.siginfo.has_value()) + printf("Address: %p\n", crash_info.siginfo->si_addr); + fputc('\n', stdout); + fflush(stdout); sys_info(); @@ -376,12 +373,9 @@ static void crash_handler(const char* logfile) // even faulty applications shouldn't be able to freeze the X server. usleep(100000); - if (logfile) - { - std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) - + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; - SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); - } + const std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + + "'.\n Please report this to https://gitlab.com/OpenMW/openmw/issues !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr); exit(0); } @@ -509,15 +503,15 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra { #if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ || defined(__posix)) - if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) - { - if (argc == 2 && strcmp(argv[1], crash_switch) == 0) - crash_handler(Files::pathToUnicodeString(crashLogPath).c_str()); + if (argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(Files::pathToUnicodeString(crashLogPath).c_str()); - if (crashCatcherInstallHandlers(argv) == -1) - Log(Debug::Warning) << "Installing crash handler failed"; - else - Log(Debug::Info) << "Crash handler installed"; - } + if (is_debugger_present()) + return; + + if (crashCatcherInstallHandlers(argv) == -1) + Log(Debug::Warning) << "Installing crash handler failed"; + else + Log(Debug::Info) << "Crash handler installed"; #endif } From 2ef286b27a41beb553a84c3a778229cce15c533a Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:13:47 +0100 Subject: [PATCH 0846/2167] Make sure function handling crash does not return --- components/crashcatcher/crashcatcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 0f2cb0ed7b..0cf1a28aca 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -305,7 +305,7 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) } } -static void crash_handler(const char* logfile) +[[noreturn]] static void handleCrash(const char* logfile) { if (fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) { @@ -504,7 +504,7 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra #if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ || defined(__posix)) if (argc == 2 && strcmp(argv[1], crash_switch) == 0) - crash_handler(Files::pathToUnicodeString(crashLogPath).c_str()); + handleCrash(Files::pathToUnicodeString(crashLogPath).c_str()); if (is_debugger_present()) return; From 140cc53b55d48e9a5f5c404fedf0334f22863397 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:19:04 +0100 Subject: [PATCH 0847/2167] Report errors on installing crash handler --- components/crashcatcher/crashcatcher.cpp | 69 ++++++++++++++++-------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 0cf1a28aca..7f8f27c442 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -74,14 +74,15 @@ namespace { int mCode; const char* mDescription; + const char* mName = ""; }; constexpr SignalInfo signals[] = { - { SIGSEGV, "Segmentation fault" }, - { SIGILL, "Illegal instruction" }, - { SIGFPE, "FPU exception" }, - { SIGBUS, "System BUS error" }, - { SIGABRT, "Abnormal termination condition" }, + { SIGSEGV, "Segmentation fault", "SIGSEGV" }, + { SIGILL, "Illegal instruction", "SIGILL" }, + { SIGFPE, "FPU exception", "SIGFPE" }, + { SIGBUS, "System BUS error", "SIGBUS" }, + { SIGABRT, "Abnormal termination condition", "SIGABRT" }, }; constexpr SignalInfo sigIllCodes[] = { @@ -388,11 +389,15 @@ static void getExecPath(char** argv) if (sysctl(mib, 4, argv0, &size, nullptr, 0) == 0) return; + + Log(Debug::Warning) << "Failed to call sysctl: " << std::generic_category().message(errno); #endif #if defined(__APPLE__) if (proc_pidpath(getpid(), argv0, sizeof(argv0)) > 0) return; + + Log(Debug::Warning) << "Failed to call proc_pidpath: " << std::generic_category().message(errno); #endif const char* statusPaths[] = { "/proc/self/exe", "/proc/self/file", "/proc/curproc/exe", "/proc/curproc/file" }; memset(argv0, 0, sizeof(argv0)); @@ -401,18 +406,28 @@ static void getExecPath(char** argv) { if (readlink(path, argv0, sizeof(argv0)) != -1) return; + + Log(Debug::Warning) << "Failed to call readlink for \"" << path + << "\": " << std::generic_category().message(errno); } if (argv[0][0] == '/') - snprintf(argv0, sizeof(argv0), "%s", argv[0]); - else if (getcwd(argv0, sizeof(argv0)) != nullptr) { - const int cwdlen = strlen(argv0); - snprintf(argv0 + cwdlen, sizeof(argv0) - cwdlen, "/%s", argv[0]); + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + return; } + + if (getcwd(argv0, sizeof(argv0)) == nullptr) + { + Log(Debug::Error) << "Failed to call getcwd: " << std::generic_category().message(errno); + return; + } + + const int cwdlen = strlen(argv0); + snprintf(argv0 + cwdlen, sizeof(argv0) - cwdlen, "/%s", argv[0]); } -static int crashCatcherInstallHandlers(char** argv) +static bool crashCatcherInstallHandlers(char** argv) { getExecPath(argv); @@ -423,25 +438,33 @@ static int crashCatcherInstallHandlers(char** argv) altss.ss_sp = altstack; altss.ss_flags = 0; altss.ss_size = SIGSTKSZ; - sigaltstack(&altss, nullptr); + if (sigaltstack(&altss, nullptr) == -1) + { + Log(Debug::Error) << "Failed to call sigaltstack: " << std::generic_category().message(errno); + return false; + } struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_catcher; sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; - sigemptyset(&sa.sa_mask); - - constexpr int signals[] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - - int retval = 0; - for (const int signal : signals) + if (sigemptyset(&sa.sa_mask) == -1) { - if (sigaction(signal, &sa, nullptr) == -1) + Log(Debug::Error) << "Failed to call sigemptyset: " << std::generic_category().message(errno); + return false; + } + + for (const SignalInfo& signal : signals) + { + if (sigaction(signal.mCode, &sa, nullptr) == -1) { - retval = -1; + Log(Debug::Error) << "Failed to call sigaction for signal " << signal.mName << " (" << signal.mCode + << "): " << std::generic_category().message(errno); + return false; } } - return retval; + + return true; } static bool is_debugger_present() @@ -509,9 +532,9 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra if (is_debugger_present()) return; - if (crashCatcherInstallHandlers(argv) == -1) - Log(Debug::Warning) << "Installing crash handler failed"; - else + if (crashCatcherInstallHandlers(argv)) Log(Debug::Info) << "Crash handler installed"; + else + Log(Debug::Warning) << "Installing crash handler failed"; #endif } From acb246cbf2f158ecb417e752bb161392095bacb7 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:45:24 +0100 Subject: [PATCH 0848/2167] Report errors on printing gdb info --- components/crashcatcher/crashcatcher.cpp | 60 ++++++++++++------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 7f8f27c442..0fa6b0fe47 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -131,7 +131,7 @@ namespace } } -static void gdb_info(pid_t pid) +static void printGdbInfo(pid_t pid) { /* * Create a temp file to put gdb commands into. @@ -141,13 +141,24 @@ static void gdb_info(pid_t pid) */ char respfile[64] = "/tmp/gdb-respfile-XXXXXX"; - FILE* f; - int fd; - #ifdef __COVERITY__ umask(0600); #endif - if ((fd = mkstemp(respfile)) >= 0 && (f = fdopen(fd, "w")) != nullptr) + + const int fd = mkstemp(respfile); + if (fd == -1) + { + printf("Failed to call mkstemp: %s\n", std::generic_category().message(errno).c_str()); + return; + } + + FILE* const f = fdopen(fd, "w"); + if (f == nullptr) + { + printf("Failed to open file for gdb output \"%s\": %s\n", respfile, + std::generic_category().message(errno).c_str()); + } + else { fprintf(f, "attach %d\n" @@ -179,33 +190,24 @@ static void gdb_info(pid_t pid) int ret = system(cmd_buf); - if (ret != 0) + if (ret == -1) printf( - "\nFailed to create a crash report. Please make sure that 'gdb' is installed and present in PATH then " - "crash again." - "\nCurrent PATH: %s\n", + "\nFailed to create a crash report: %s.\n" + "Please make sure that 'gdb' is installed and present in PATH then crash again.\n" + "Current PATH: %s\n", + std::generic_category().message(errno).c_str(), getenv("PATH")); + else if (ret != 0) + printf( + "\nFailed to create a crash report.\n" + "Please make sure that 'gdb' is installed and present in PATH then crash again.\n" + "Current PATH: %s\n", getenv("PATH")); - fflush(stdout); - /* Clean up */ - if (remove(respfile) != 0) - Log(Debug::Warning) << "Warning: can not remove file '" << respfile - << "': " << std::generic_category().message(errno); - } - else - { - /* Error creating temp file */ - if (fd >= 0) - { - if (close(fd) != 0) - Log(Debug::Warning) << "Warning: can not close file '" << respfile - << "': " << std::generic_category().message(errno); - else if (remove(respfile) != 0) - Log(Debug::Warning) << "Warning: can not remove file '" << respfile - << "': " << std::generic_category().message(errno); - } - printf("!!! Could not create gdb command file\n"); + fflush(stdout); } + + close(fd); + remove(respfile); fflush(stdout); } @@ -365,7 +367,7 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) if (crash_info.pid > 0) { - gdb_info(crash_info.pid); + printGdbInfo(crash_info.pid); kill(crash_info.pid, SIGKILL); } From 4efc0e20a3680abaaca413247c61b26a91dc3ed4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 14:54:19 +0100 Subject: [PATCH 0849/2167] Report errors on handling the crash --- components/crashcatcher/crashcatcher.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 0fa6b0fe47..2cafa3e11a 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -211,12 +211,12 @@ static void printGdbInfo(pid_t pid) fflush(stdout); } -static void sys_info(void) +static void printSystemInfo(void) { #ifdef __unix__ struct utsname info; - if (uname(&info)) - printf("!!! Failed to get system information\n"); + if (uname(&info) == -1) + printf("Failed to get system information: %s\n", std::generic_category().message(errno).c_str()); else printf("System: %s %s %s %s %s\n", info.sysname, info.nodename, info.release, info.version, info.machine); @@ -312,7 +312,8 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) { if (fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) { - fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + fprintf(stderr, "Failed to retrieve info from crashed process: %s\n", + std::generic_category().message(errno).c_str()); exit(1); } @@ -347,7 +348,8 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) /* Create crash log file and redirect shell output to it */ if (freopen(logfile, "wa", stdout) != stdout) { - fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + fprintf(stderr, "Could not create %s following signal: %s\n", logfile, + std::generic_category().message(errno).c_str()); exit(1); } fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); @@ -361,7 +363,7 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) fputc('\n', stdout); fflush(stdout); - sys_info(); + printSystemInfo(); fflush(stdout); From 0095cb604ff9ea7d54d23a4374f2662f9382f78a Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 21:07:27 +0100 Subject: [PATCH 0850/2167] Handle sysctl errors without assert --- components/crashcatcher/crashcatcher.cpp | 51 ++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 2cafa3e11a..77950191e8 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -471,7 +471,22 @@ static bool crashCatcherInstallHandlers(char** argv) return true; } -static bool is_debugger_present() +namespace +{ +#if defined(__APPLE__) + bool isDebuggerPresent(const auto& info) + { + return (info.kp_proc.p_flag & P_TRACED) != 0; + } +#elif defined(__FreeBSD__) + bool isDebuggerPresent(const auto& info) + { + return (info.ki_flag & P_TRACED) != 0; + } +#endif +} + +static bool isDebuggerPresent() { #if defined(__linux__) std::filesystem::path procstatus = std::filesystem::path("/proc/self/status"); @@ -490,37 +505,23 @@ static bool is_debugger_present() } } return false; -#elif defined(__APPLE__) +#elif defined(__APPLE__) || defined(__FreeBSD__) struct kinfo_proc info; - - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - - info.kp_proc.p_flag = 0; + std::memset(&info, 0, sizeof(info)); // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; - // Call sysctl. - size_t size = sizeof(info); - const int junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0); - assert(junk == 0); + if (sysctl(mib, std::size(mib), &info, &size, nullptr, 0) == -1) + { + Log(Debug::Warning) << "Failed to call sysctl, assuming no debugger: " + << std::generic_category().message(errno); + return false; + } - // We're being debugged if the P_TRACED flag is set. - - return (info.kp_proc.p_flag & P_TRACED) != 0; -#elif defined(__FreeBSD__) - struct kinfo_proc info; - size_t size = sizeof(info); - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; - - if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) == 0) - return (info.ki_flag & P_TRACED) != 0; - else - perror("Failed to retrieve process info"); - return false; + return isDebuggerPresent(info); #else return false; #endif @@ -533,7 +534,7 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra if (argc == 2 && strcmp(argv[1], crash_switch) == 0) handleCrash(Files::pathToUnicodeString(crashLogPath).c_str()); - if (is_debugger_present()) + if (isDebuggerPresent()) return; if (crashCatcherInstallHandlers(argv)) From b96e32144c75ba32d138ef8e8d5c1d30b93061c4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Jan 2024 15:19:09 +0100 Subject: [PATCH 0851/2167] Support lldb in crash catcher --- components/crashcatcher/crashcatcher.cpp | 172 +++++++++++++++-------- 1 file changed, 113 insertions(+), 59 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 77950191e8..9a662c4a92 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -129,86 +129,140 @@ namespace const auto it = std::find_if(info.begin(), info.end(), [&](const SignalInfo& v) { return v.mCode == code; }); return it == info.end() ? "" : it->mDescription; } -} -static void printGdbInfo(pid_t pid) -{ - /* - * Create a temp file to put gdb commands into. - * Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. - * Modern systems implement it and suggest to do not touch masks in multithreaded applications. - * So CoverityScan warning is valid only for ancient versions of stdlib. - */ - char respfile[64] = "/tmp/gdb-respfile-XXXXXX"; + struct Close + { + void operator()(const int* value) { close(*value); } + }; + + struct CloseFile + { + void operator()(FILE* value) { fclose(value); } + }; + + struct Remove + { + void operator()(const char* value) { remove(value); } + }; + + template + bool printDebuggerInfo(pid_t pid) + { + // Create a temp file to put gdb commands into. + // Note: POSIX.1-2008 declares that the file should be already created with mode 0600 by default. + // Modern systems implement it and suggest to do not touch masks in multithreaded applications. + // So CoverityScan warning is valid only for ancient versions of stdlib. + char scriptPath[64]; + + std::snprintf(scriptPath, sizeof(scriptPath), "/tmp/%s-script-XXXXXX", T::sName); #ifdef __COVERITY__ - umask(0600); + umask(0600); #endif - const int fd = mkstemp(respfile); - if (fd == -1) - { - printf("Failed to call mkstemp: %s\n", std::generic_category().message(errno).c_str()); - return; - } + const int fd = mkstemp(scriptPath); + if (fd == -1) + { + printf("Failed to call mkstemp: %s\n", std::generic_category().message(errno).c_str()); + return false; + } + std::unique_ptr tempFile(scriptPath); + std::unique_ptr scopedFd(&fd); - FILE* const f = fdopen(fd, "w"); - if (f == nullptr) - { - printf("Failed to open file for gdb output \"%s\": %s\n", respfile, - std::generic_category().message(errno).c_str()); - } - else - { - fprintf(f, - "attach %d\n" - "shell echo \"\"\n" - "shell echo \"* Loaded Libraries\"\n" - "info sharedlibrary\n" - "shell echo \"\"\n" - "shell echo \"* Threads\"\n" - "info threads\n" - "shell echo \"\"\n" - "shell echo \"* FPU Status\"\n" - "info float\n" - "shell echo \"\"\n" - "shell echo \"* Registers\"\n" - "info registers\n" - "shell echo \"\"\n" - "shell echo \"* Backtrace\"\n" - "thread apply all backtrace full 1000\n" - "detach\n" - "quit\n", - pid); - fclose(f); + FILE* const file = fdopen(fd, "w"); + if (file == nullptr) + { + printf("Failed to open file for %s output \"%s\": %s\n", T::sName, scriptPath, + std::generic_category().message(errno).c_str()); + return false; + } + std::unique_ptr scopedFile(file); - /* Run gdb and print process info. */ - char cmd_buf[128]; - snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); - printf("Executing: %s\n", cmd_buf); + if (fprintf(file, "%s", T::sScript) < 0) + { + printf("Failed to write debugger script to file \"%s\": %s\n", scriptPath, + std::generic_category().message(errno).c_str()); + return false; + } + + scopedFile = nullptr; + scopedFd = nullptr; + + char command[128]; + snprintf(command, sizeof(command), T::sCommandTemplate, pid, scriptPath); + printf("Executing: %s\n", command); fflush(stdout); - int ret = system(cmd_buf); + const int ret = system(command); + + const bool result = (ret == 0); if (ret == -1) printf( "\nFailed to create a crash report: %s.\n" - "Please make sure that 'gdb' is installed and present in PATH then crash again.\n" + "Please make sure that '%s' is installed and present in PATH then crash again.\n" "Current PATH: %s\n", - std::generic_category().message(errno).c_str(), getenv("PATH")); + T::sName, std::generic_category().message(errno).c_str(), getenv("PATH")); else if (ret != 0) printf( "\nFailed to create a crash report.\n" - "Please make sure that 'gdb' is installed and present in PATH then crash again.\n" + "Please make sure that '%s' is installed and present in PATH then crash again.\n" "Current PATH: %s\n", - getenv("PATH")); + T::sName, getenv("PATH")); fflush(stdout); + + return result; } - close(fd); - remove(respfile); - fflush(stdout); + struct Gdb + { + static constexpr char sName[] = "gdb"; + static constexpr char sScript[] = R"(shell echo "" +shell echo "* Loaded Libraries" +info sharedlibrary +shell echo "" +shell echo "* Threads" +info threads +shell echo "" +shell echo "* FPU Status" +info float +shell echo "" +shell echo "* Registers" +info registers +shell echo "" +shell echo "* Backtrace" +thread apply all backtrace full 1000 +detach +quit +)"; + static constexpr char sCommandTemplate[] = "gdb --pid %d --quiet --batch --command %s"; + }; + + struct Lldb + { + static constexpr char sName[] = "lldb"; + static constexpr char sScript[] = R"(script print("\n* Loaded Libraries") +image list +script print('\n* Threads') +thread list +script print('\n* Registers') +register read --all +script print('\n* Backtrace') +script print(''.join(f'{t}\n' + ''.join(''.join([f' {f}\n', ''.join(f' {s}\n' for s in f.statics), ''.join(f' {v}\n' for v in f.variables)]) for f in t.frames) for t in lldb.process.threads)) +detach +quit +)"; + static constexpr char sCommandTemplate[] = "lldb --attach-pid %d --batch --source %s"; + }; + + void printProcessInfo(pid_t pid) + { + if (printDebuggerInfo(pid)) + return; + if (printDebuggerInfo(pid)) + return; + } } static void printSystemInfo(void) @@ -369,7 +423,7 @@ static void crash_catcher(int signum, siginfo_t* siginfo, void* /*context*/) if (crash_info.pid > 0) { - printGdbInfo(crash_info.pid); + printProcessInfo(crash_info.pid); kill(crash_info.pid, SIGKILL); } From 1f416d7c8ae0ec882c99b554619b4e33d1a40989 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 20 Jan 2024 11:56:36 +0000 Subject: [PATCH 0852/2167] Lua: Creature skill bindings --- apps/openmw/mwlua/types/creature.cpp | 15 +++++++++++++++ files/lua_api/openmw/types.lua | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index ddf90bf8c5..dd4b1bd67b 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -1,3 +1,4 @@ +#include "../stats.hpp" #include "actor.hpp" #include "types.hpp" @@ -42,6 +43,20 @@ namespace MWLua record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); record["type"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mType; }); record["baseGold"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mGold; }); + record["combatSkill"] + = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mCombat; }); + record["magicSkill"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mMagic; }); + record["stealthSkill"] + = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mStealth; }); + record["attack"] = sol::readonly_property([context](const ESM::Creature& rec) -> sol::table { + sol::state_view& lua = context.mLua->sol(); + sol::table res(lua, sol::create); + int index = 1; + for (auto attack : rec.mData.mAttack) + res[index++] = attack; + return LuaUtil::makeReadOnly(res); + }); + addActorServicesBindings(record, context); } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ad30994fe8..ba0b2dd58b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -717,6 +717,11 @@ -- @param #any objectOrRecordId -- @return #CreatureRecord +--- +-- @type CreatureAttack +-- @field #number minDamage Minimum attack damage. +-- @field #number maxDamage Maximum attack damage. + --- -- @type CreatureRecord -- @field #string id The record ID of the creature @@ -727,6 +732,10 @@ -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature +-- @field #number combatSkill The base combat skill of the creature. This is the skill value used for all skills with a 'combat' specialization +-- @field #number magicSkill The base magic skill of the creature. This is the skill value used for all skills with a 'magic' specialization +-- @field #number stealthSkill The base stealth skill of the creature. This is the skill value used for all skills with a 'stealth' specialization +-- @field #list<#number> attack A table of the 3 randomly selected attacks used by creatures that do not carry weapons. The table consists of 6 numbers split into groups of 2 values corresponding to minimum and maximum damage in that order. -- @field #map<#string, #boolean> servicesOffered The services of the creature, in a table. Value is if the service is provided or not, and they are indexed by: Spells, Spellmaking, Enchanting, Training, Repair, Barter, Weapon, Armor, Clothing, Books, Ingredients, Picks, Probes, Lights, Apparatus, RepairItems, Misc, Potions, MagicItems, Travel. From 467220e6d7bae60130b460b9102600ea55f4003a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 20 Jan 2024 16:50:51 +0100 Subject: [PATCH 0853/2167] Base GetColliding script functions on collisions detected by the movement solver --- CHANGELOG.md | 1 + apps/openmw/mwphysics/movementsolver.cpp | 18 ++++++++++++++---- apps/openmw/mwphysics/object.cpp | 17 +++++++++++++++++ apps/openmw/mwphysics/object.hpp | 12 ++++++++++++ apps/openmw/mwphysics/physicssystem.cpp | 19 +++++++++++++------ apps/openmw/mwphysics/physicssystem.hpp | 9 +++++---- apps/openmw/mwworld/worldimp.cpp | 7 ++----- 7 files changed, 64 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..9b5a75229d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ 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 #7053: Running into objects doesn't trigger GetCollidingPC Bug #7054: Quests aren't sorted by name Bug #7064: NPCs don't report crime if the player is casting offensive spells on them while sneaking Bug #7077: OpenMW fails to load certain particle effects in .osgt format diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index c0b5014b31..fe5c1a955e 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -15,6 +15,7 @@ #include "collisiontype.hpp" #include "constants.hpp" #include "contacttestwrapper.h" +#include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" #include "projectileconvexcallback.hpp" @@ -243,11 +244,20 @@ namespace MWPhysics float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + actor.mHalfExtentsZ; osg::Vec3f oldPosition = newPosition; bool usedStepLogic = false; - if (hitHeight < Constants::sStepSizeUp && !isActor(tracer.mHitObject)) + if (!isActor(tracer.mHitObject)) { - // Try to step up onto it. - // NOTE: this modifies newPosition and velocity on its own if successful - usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); + if (hitHeight < Constants::sStepSizeUp) + { + // Try to step up onto it. + // NOTE: this modifies newPosition and velocity on its own if successful + usedStepLogic = stepper.step(newPosition, velocity, remainingTime, seenGround, iterations == 0); + } + auto* ptrHolder = static_cast(tracer.mHitObject->getUserPointer()); + if (Object* hitObject = dynamic_cast(ptrHolder)) + { + hitObject->addCollision( + actor.mIsPlayer ? ScriptedCollisionType_Player : ScriptedCollisionType_Actor); + } } if (usedStepLogic) { diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 9c97ac7c32..5936083f1d 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -23,6 +23,7 @@ namespace MWPhysics , mPosition(ptr.getRefData().getPosition().asVec3()) , mRotation(rotation) , mTaskScheduler(scheduler) + , mCollidedWith(ScriptedCollisionType_None) { mCollisionObject = BulletHelpers::makeCollisionObject(mShapeInstance->mCollisionShape.get(), Misc::Convert::toBullet(mPosition), Misc::Convert::toBullet(rotation)); @@ -166,4 +167,20 @@ namespace MWPhysics } return result; } + + bool Object::collidedWith(ScriptedCollisionType type) const + { + return mCollidedWith & type; + } + + void Object::addCollision(ScriptedCollisionType type) + { + std::unique_lock lock(mPositionMutex); + mCollidedWith |= type; + } + + void Object::resetCollisions() + { + mCollidedWith = ScriptedCollisionType_None; + } } diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index 875f12d879..15b7c46926 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -18,6 +18,14 @@ namespace MWPhysics { class PhysicsTaskScheduler; + enum ScriptedCollisionType : char + { + ScriptedCollisionType_None = 0, + ScriptedCollisionType_Actor = 1, + // Note that this isn't 3, colliding with a player doesn't count as colliding with an actor + ScriptedCollisionType_Player = 2 + }; + class Object final : public PtrHolder { public: @@ -38,6 +46,9 @@ namespace MWPhysics /// @brief update object shape /// @return true if shape changed bool animateCollisionShapes(); + bool collidedWith(ScriptedCollisionType type) const; + void addCollision(ScriptedCollisionType type); + void resetCollisions(); private: osg::ref_ptr mShapeInstance; @@ -50,6 +61,7 @@ namespace MWPhysics bool mTransformUpdatePending = false; mutable std::mutex mPositionMutex; PhysicsTaskScheduler* mTaskScheduler; + char mCollidedWith; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2196834a50..149113dfb1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -673,12 +673,13 @@ namespace MWPhysics // Slow fall reduces fall speed by a factor of (effect magnitude / 200) const float slowFall = 1.f - std::clamp(effects.getOrDefault(ESM::MagicEffect::SlowFall).getMagnitude() * 0.005f, 0.f, 1.f); - const bool godmode = ptr == world->getPlayerConstPtr() && world->getGodModeState(); + const bool isPlayer = ptr == world->getPlayerConstPtr(); + const bool godmode = isPlayer && world->getGodModeState(); const bool inert = stats.isDead() || (!godmode && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Paralyze).getModifier() > 0); simulations.emplace_back(ActorSimulation{ - physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel } }); + physicActor, ActorFrameData{ *physicActor, inert, waterCollision, slowFall, waterlevel, isPlayer } }); // if the simulation will run, a jump request will be fulfilled. Update mechanics accordingly. if (willSimulate) @@ -708,6 +709,8 @@ namespace MWPhysics changed = false; } } + for (auto& [_, object] : mObjects) + object->resetCollisions(); #ifndef BT_NO_PROFILE CProfileManager::Reset(); @@ -782,10 +785,12 @@ namespace MWPhysics } } - bool PhysicsSystem::isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const + bool PhysicsSystem::isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const { - std::vector collisions = getCollisions(object, CollisionType_World, CollisionType_Actor); - return (std::find(collisions.begin(), collisions.end(), actor) != collisions.end()); + auto found = mObjects.find(object.mRef); + if (found != mObjects.end()) + return found->second->collidedWith(type); + return false; } void PhysicsSystem::getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const @@ -890,7 +895,8 @@ namespace MWPhysics mDebugDrawer->addCollision(position, normal); } - ActorFrameData::ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel) + ActorFrameData::ActorFrameData( + Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer) : mPosition() , mStandingOn(nullptr) , mIsOnGround(actor.getOnGround()) @@ -917,6 +923,7 @@ namespace MWPhysics , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(!actor.getCollisionMode()) + , mIsPlayer(isPlayer) { } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ad56581eb3..6734682092 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -56,6 +56,7 @@ namespace MWPhysics class Actor; class PhysicsTaskScheduler; class Projectile; + enum ScriptedCollisionType : char; using ActorMap = std::unordered_map>; @@ -79,7 +80,7 @@ namespace MWPhysics struct ActorFrameData { - ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel); + ActorFrameData(Actor& actor, bool inert, bool waterCollision, float slowFall, float waterlevel, bool isPlayer); osg::Vec3f mPosition; osg::Vec3f mInertia; const btCollisionObject* mStandingOn; @@ -102,6 +103,7 @@ namespace MWPhysics const bool mIsAquatic; const bool mWaterCollision; const bool mSkipCollisionDetection; + const bool mIsPlayer; }; struct ProjectileFrameData @@ -258,9 +260,8 @@ namespace MWPhysics /// Get the handle of all actors standing on \a object in this frame. void getActorsStandingOn(const MWWorld::ConstPtr& object, std::vector& out) const; - /// Return true if \a actor has collided with \a object in this frame. - /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. - bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object) const; + /// Return true if an object of the given type has collided with this object + bool isObjectCollidingWith(const MWWorld::ConstPtr& object, ScriptedCollisionType type) const; /// Get the handle of all actors colliding with \a object in this frame. void getActorsCollidingWith(const MWWorld::ConstPtr& object, std::vector& out) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1b6af6038e..701895e4f8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2425,15 +2425,12 @@ namespace MWWorld bool World::getPlayerCollidingWith(const MWWorld::ConstPtr& object) { - MWWorld::Ptr player = getPlayerPtr(); - return mPhysics->isActorCollidingWith(player, object); + return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Player); } bool World::getActorCollidingWith(const MWWorld::ConstPtr& object) { - std::vector actors; - mPhysics->getActorsCollidingWith(object, actors); - return !actors.empty(); + return mPhysics->isObjectCollidingWith(object, MWPhysics::ScriptedCollisionType_Actor); } void World::hurtStandingActors(const ConstPtr& object, float healthPerSecond) From 251d01304fc1aa7bd0f199a9636214bd8afcc00a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Jan 2024 13:48:33 +0400 Subject: [PATCH 0854/2167] Use move semantics for osg::ref_ptr --- apps/opencs/view/render/worldspacewidget.cpp | 2 +- apps/openmw/engine.cpp | 2 +- apps/openmw/mwrender/actoranimation.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 7 ++++--- apps/openmw/mwrender/luminancecalculator.cpp | 2 +- apps/openmw/mwrender/objects.cpp | 2 +- apps/openmw/mwrender/ripples.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 4 ++-- apps/openmw/mwrender/water.cpp | 4 ++-- components/nifosg/nifloader.cpp | 2 +- components/resource/keyframemanager.cpp | 4 ++-- components/sceneutil/util.cpp | 2 +- components/shader/shadervisitor.cpp | 10 +++++----- 13 files changed, 23 insertions(+), 22 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index f7732d752d..2af84fb36d 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -440,7 +440,7 @@ CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick( osg::Node* node = *nodeIter; if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) { - WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; + WorldspaceHitResult hit = { true, std::move(tag), 0, 0, 0, intersection.getWorldIntersectPoint() }; if (intersection.indexList.size() >= 3) { hit.index0 = intersection.indexList[0]; diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3f3d5fe558..dfc11c309b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -823,7 +823,7 @@ void OMW::Engine::prepareEngine() } listener->loadingOff(); - mWorld->init(mViewer, rootNode, mWorkQueue.get(), *mUnrefQueue); + mWorld->init(mViewer, std::move(rootNode), mWorkQueue.get(), *mUnrefQueue); mEnvironment.setWorldScene(mWorld->getWorldScene()); mWorld->setupPlayer(); mWorld->setRandomSeed(mRandomSeed); diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index e31a1eb711..2c70cd0436 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -101,7 +101,7 @@ namespace MWRender templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager(), &rotation); } return SceneUtil::attach( - templateNode, mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); + std::move(templateNode), mObjectRoot, bonefilter, found->second, mResourceSystem->getSceneManager()); } std::string ActorAnimation::getShieldMesh(const MWWorld::ConstPtr& shield, bool female) const diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index ac7a8a9351..e58f987a44 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -422,7 +422,8 @@ namespace MWRender if (cellX > mMaxX || cellX < mMinX || cellY > mMaxY || cellY < mMinY) return; - requestOverlayTextureUpdate(originX, mHeight - originY, cellSize, cellSize, localMapTexture, false, true); + requestOverlayTextureUpdate( + originX, mHeight - originY, cellSize, cellSize, std::move(localMapTexture), false, true); } void GlobalMap::clear() @@ -554,7 +555,7 @@ namespace MWRender { mOverlayImage = image; - requestOverlayTextureUpdate(0, 0, mWidth, mHeight, texture, true, false); + requestOverlayTextureUpdate(0, 0, mWidth, mHeight, std::move(texture), true, false); } else { @@ -562,7 +563,7 @@ namespace MWRender // In the latter case, we'll want filtering. // Create a RTT Camera and draw the image onto mOverlayImage in the next frame. requestOverlayTextureUpdate(destBox.mLeft, destBox.mTop, destBox.mRight - destBox.mLeft, - destBox.mBottom - destBox.mTop, texture, true, true, srcBox.mLeft / float(imageWidth), + destBox.mBottom - destBox.mTop, std::move(texture), true, true, srcBox.mLeft / float(imageWidth), srcBox.mTop / float(imageHeight), srcBox.mRight / float(imageWidth), srcBox.mBottom / float(imageHeight)); } diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index ae29b7fdcc..30918db87c 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -19,7 +19,7 @@ namespace MWRender auto resolveFragment = shaderManager.getShader("luminance/resolve.frag", defines); mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); - mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); + mLuminanceProgram = shaderManager.getProgram(std::move(vertex), std::move(luminanceFragment)); for (auto& buffer : mBuffers) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 89e192f6c8..d93dc47641 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -68,7 +68,7 @@ namespace MWRender ptr.getClass().adjustScale(ptr, scaleVec, true); insert->setScale(scaleVec); - ptr.getRefData().setBaseNode(insert); + ptr.getRefData().setBaseNode(std::move(insert)); } void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 130e005729..dea372666e 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -106,7 +106,7 @@ namespace MWRender mProgramBlobber = shaderManager.getProgram( vertex, shaderManager.getShader("ripples_blobber.frag", defineMap, osg::Shader::FRAGMENT)); mProgramSimulation = shaderManager.getProgram( - vertex, shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); + std::move(vertex), shaderManager.getShader("ripples_simulate.frag", defineMap, osg::Shader::FRAGMENT)); } void RipplesSurface::setupComputePipeline() diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 6df3734252..060b6ee5de 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -764,7 +764,7 @@ namespace MWRender cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mCloudUpdater->setTexture(cloudTex); + mCloudUpdater->setTexture(std::move(cloudTex)); } if (mStormDirection != weather.mStormDirection) @@ -786,7 +786,7 @@ namespace MWRender cloudTex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); cloudTex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); - mNextCloudUpdater->setTexture(cloudTex); + mNextCloudUpdater->setTexture(std::move(cloudTex)); mNextStormDirection = weather.mStormDirection; } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 553bdeeaaa..d5fb01242f 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -722,8 +722,8 @@ namespace MWRender mRainSettingsUpdater = new RainSettingsUpdater(); node->setUpdateCallback(mRainSettingsUpdater); - mShaderWaterStateSetUpdater - = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, std::move(program), normalMap); + mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater( + this, mReflection, mRefraction, mRipples, std::move(program), std::move(normalMap)); node->addCullCallback(mShaderWaterStateSetUpdater); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2f7574d68b..8d46b0f751 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1660,7 +1660,7 @@ namespace NifOsg && bsTriShape->mVertDesc.mFlags & Nif::BSVertexDesc::VertexAttribute::Skinned) { osg::ref_ptr rig(new SceneUtil::RigGeometry); - rig->setSourceGeometry(geometry); + rig->setSourceGeometry(std::move(geometry)); const Nif::BSSkinInstance* skin = static_cast(bsTriShape->mSkin.getPtr()); const Nif::BSSkinBoneData* data = skin->mData.getPtr(); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index d60129cb86..68b7adbe9a 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -123,7 +123,7 @@ namespace Resource mergedAnimationTrack->addChannel(channel.get()->clone()); } - callback->addMergedAnimationTrack(mergedAnimationTrack); + callback->addMergedAnimationTrack(std::move(mergedAnimationTrack)); float startTime = animation->getStartTime(); float stopTime = startTime + animation->getDuration(); @@ -239,7 +239,7 @@ namespace Resource = dynamic_cast(scene->getUpdateCallback()); if (bam) { - Resource::RetrieveAnimationsVisitor rav(*loaded.get(), bam, normalized, mVFS); + Resource::RetrieveAnimationsVisitor rav(*loaded.get(), std::move(bam), normalized, mVFS); scene->accept(rav); } } diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index ce48702a74..ab600de11d 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -248,7 +248,7 @@ namespace SceneUtil } writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - resourceSystem->getSceneManager()->recreateShaders(node); + resourceSystem->getSceneManager()->recreateShaders(std::move(node)); return glowUpdater; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 70464f571e..e281f64448 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -740,7 +740,7 @@ namespace Shader auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); - addedState->setAttributeAndModes(program); + addedState->setAttributeAndModes(std::move(program)); for (const auto& [unit, name] : reqs.mTextures) { @@ -934,13 +934,13 @@ namespace Shader { osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) - rig->setSourceGeometry(sourceGeometry); + rig->setSourceGeometry(std::move(sourceGeometry)); } else if (auto morph = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) - morph->setSourceGeometry(sourceGeometry); + morph->setSourceGeometry(std::move(sourceGeometry)); } else if (auto osgaRig = dynamic_cast(&drawable)) { @@ -948,8 +948,8 @@ namespace Shader osg::ref_ptr sourceGeometry = sourceOsgaRigGeometry->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) { - sourceOsgaRigGeometry->setSourceGeometry(sourceGeometry); - osgaRig->setSourceRigGeometry(sourceOsgaRigGeometry); + sourceOsgaRigGeometry->setSourceGeometry(std::move(sourceGeometry)); + osgaRig->setSourceRigGeometry(std::move(sourceOsgaRigGeometry)); } } From e01e2f1ae0ce45240e8f4129998d8fa3c999c156 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Jan 2024 17:50:45 +0400 Subject: [PATCH 0855/2167] Fix magic effects in the editor --- apps/opencs/model/world/collection.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9db6b3b042..c10266a101 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -15,11 +15,13 @@ #include #include +#include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" +#include "columnimp.hpp" #include "info.hpp" #include "land.hpp" #include "landtexture.hpp" @@ -82,6 +84,17 @@ namespace CSMWorld record.mIndex = index; } + inline ESM::RefId getRecordId(const ESM::MagicEffect& record) + { + return ESM::RefId::stringRefId(CSMWorld::getStringId(record.mId)); + } + + inline void setRecordId(const ESM::RefId& id, ESM::MagicEffect& record) + { + int index = ESM::MagicEffect::indexNameToIndex(id.getRefIdString()); + record.mId = ESM::RefId::index(ESM::REC_MGEF, static_cast(index)); + } + inline ESM::RefId getRecordId(const LandTexture& record) { return ESM::RefId::stringRefId(LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex)); From 737d3b499b90290e69365d0e8306a657ef64e4f7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Jan 2024 20:20:37 +0400 Subject: [PATCH 0856/2167] Use move semantics for tools --- apps/essimporter/convertacdt.cpp | 2 +- apps/essimporter/converter.cpp | 6 +++--- apps/opencs/model/filter/parser.cpp | 2 +- apps/opencs/model/tools/reportmodel.cpp | 2 +- apps/opencs/model/world/actoradapter.cpp | 8 ++++---- apps/opencs/model/world/collection.hpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 2 +- apps/wizard/unshield/unshieldworker.cpp | 7 +++---- 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 8342310cf6..a737e0a3a2 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -85,7 +85,7 @@ namespace ESSImport Misc::StringUtils::lowerCaseInPlace(group); ESM::AnimationState::ScriptedAnimation scriptedAnim; - scriptedAnim.mGroup = group; + scriptedAnim.mGroup = std::move(group); scriptedAnim.mTime = anis.mTime; scriptedAnim.mAbsolute = true; // Neither loop count nor queueing seems to be supported by the ess format. diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 07146fc388..4c4bd1e438 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -306,12 +306,12 @@ namespace ESSImport mMarkers.push_back(marker); } - newcell.mRefs = cellrefs; + newcell.mRefs = std::move(cellrefs); if (cell.isExterior()) - mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; + mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = std::move(newcell); else - mIntCells[cell.mName] = newcell; + mIntCells[cell.mName] = std::move(newcell); } void ConvertCell::writeCell(const Cell& cell, ESM::ESMWriter& esm) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 5443db2854..aadad5f8f5 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -624,7 +624,7 @@ bool CSMFilter::Parser::parse(const std::string& filter, bool allowPredefined) } if (node) - mFilter = node; + mFilter = std::move(node); else { // Empty filter string equals to filter "true". diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index 84a8c71f95..f9251acdab 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -171,7 +171,7 @@ void CSMTools::ReportModel::flagAsReplaced(int index) hint[0] = 'r'; - line.mHint = hint; + line.mHint = std::move(hint); emit dataChanged(this->index(index, 0), this->index(index, columnCount())); } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 0e3725bbb7..37aaf08445 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -468,13 +468,13 @@ namespace CSMWorld if (type == UniversalId::Type_Creature) { // Valid creature record - setupCreature(id, data); + setupCreature(id, std::move(data)); emit actorChanged(id); } else if (type == UniversalId::Type_Npc) { // Valid npc record - setupNpc(id, data); + setupNpc(id, std::move(data)); emit actorChanged(id); } else @@ -665,7 +665,7 @@ namespace CSMWorld RaceDataPtr data = mCachedRaces.get(race); if (data) { - setupRace(race, data); + setupRace(race, std::move(data)); // Race was changed. Need to mark actor dependencies as dirty. // Cannot use markDirtyDependency because that would invalidate // the current iterator. @@ -683,7 +683,7 @@ namespace CSMWorld ActorDataPtr data = mCachedActors.get(actor); if (data) { - setupActor(actor, data); + setupActor(actor, std::move(data)); } } mDirtyActors.clear(); diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9db6b3b042..dbbff2ed4a 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -504,7 +504,7 @@ namespace CSMWorld auto record2 = std::make_unique>(); record2->mState = Record::State_ModifiedOnly; - record2->mModified = record; + record2->mModified = std::move(record); insertRecord(std::move(record2), getAppendIndex(id, type), type); } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 1d4dc37529..891d954ad4 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -195,7 +195,7 @@ void CSVWorld::TableSubView::createFilterRequest(std::vector Date: Sun, 21 Jan 2024 12:54:33 +0400 Subject: [PATCH 0857/2167] Fix error message about savegame format --- apps/openmw/mwstate/statemanagerimp.cpp | 99 +++++++++++++++++-------- apps/openmw/mwstate/statemanagerimp.hpp | 2 + 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 631ef9a112..8d3b84df13 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -409,9 +409,38 @@ void MWState::StateManager::loadGame(const std::filesystem::path& filepath) loadGame(character, filepath); } -struct VersionMismatchError : public std::runtime_error +struct SaveFormatVersionError : public std::exception { - using std::runtime_error::runtime_error; + using std::exception::exception; + + SaveFormatVersionError(ESM::FormatVersion savegameFormat, const std::string& message) + : mSavegameFormat(savegameFormat) + , mErrorMessage(message) + { + } + + const char* what() const noexcept override { return mErrorMessage.c_str(); } + ESM::FormatVersion getFormatVersion() const { return mSavegameFormat; } + +protected: + ESM::FormatVersion mSavegameFormat = ESM::DefaultFormatVersion; + std::string mErrorMessage; +}; + +struct SaveVersionTooOldError : SaveFormatVersionError +{ + SaveVersionTooOldError(ESM::FormatVersion savegameFormat) + : SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too old") + { + } +}; + +struct SaveVersionTooNewError : SaveFormatVersionError +{ + SaveVersionTooNewError(ESM::FormatVersion savegameFormat) + : SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too new") + { + } }; void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath) @@ -427,23 +456,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file ESM::FormatVersion version = reader.getFormatVersion(); if (version > ESM::CurrentSaveGameFormatVersion) - throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}"); + throw SaveVersionTooNewError(version); else if (version < ESM::MinSupportedSaveGameFormatVersion) - { - const char* release; - // Report the last version still capable of reading this save - if (version <= ESM::OpenMW0_48SaveGameFormatVersion) - release = "OpenMW 0.48.0"; - else - { - // Insert additional else if statements above to cover future releases - static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); - release = "OpenMW 0.49.0"; - } - auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); - std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); - throw VersionMismatchError(message); - } + throw SaveVersionTooOldError(version); std::map contentFileMap = buildContentFileIndexMap(reader); reader.setContentFileMapping(&contentFileMap); @@ -625,23 +640,49 @@ void MWState::StateManager::loadGame(const Character* character, const std::file MWBase::Environment::get().getLuaManager()->gameLoaded(); } + catch (const SaveVersionTooNewError& e) + { + std::string error = "#{OMWEngine:LoadingRequiresNewVersionError}"; + printSavegameFormatError(e.what(), error); + } + catch (const SaveVersionTooOldError& e) + { + const char* release; + // Report the last version still capable of reading this save + if (e.getFormatVersion() <= ESM::OpenMW0_48SaveGameFormatVersion) + release = "OpenMW 0.48.0"; + else + { + // Insert additional else if statements above to cover future releases + static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); + release = "OpenMW 0.49.0"; + } + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); + printSavegameFormatError(e.what(), error); + } catch (const std::exception& e) { - Log(Debug::Error) << "Failed to load saved game: " << e.what(); - - cleanup(true); - - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); - - std::vector buttons; - buttons.emplace_back("#{Interface:OK}"); - std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); - - MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons); + printSavegameFormatError(e.what(), error); } } +void MWState::StateManager::printSavegameFormatError( + const std::string& exceptionText, const std::string& messageBoxText) +{ + Log(Debug::Error) << "Failed to load saved game: " << exceptionText; + + cleanup(true); + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); + + std::vector buttons; + buttons.emplace_back("#{Interface:OK}"); + + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(messageBoxText, buttons); +} + void MWState::StateManager::quickLoad() { if (Character* currentCharacter = getCurrentCharacter()) diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index a76b829e38..c25e43cc23 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -22,6 +22,8 @@ namespace MWState private: void cleanup(bool force = false); + void printSavegameFormatError(const std::string& exceptionText, const std::string& messageBoxText); + bool confirmLoading(const std::vector& missingFiles) const; void writeScreenshot(std::vector& imageData) const; From 84ab7afd444f37e122e250ac4ca2e922472c42f2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 21 Jan 2024 22:54:54 +0300 Subject: [PATCH 0858/2167] Make BA2 extension hash calculation safer (#7784) --- components/bsa/ba2dx10file.cpp | 2 +- components/bsa/ba2file.cpp | 8 ++++++++ components/bsa/ba2file.hpp | 1 + components/bsa/ba2gnrlfile.cpp | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 593ca64949..aa3f8d0581 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -177,7 +177,7 @@ namespace Bsa return std::nullopt; // folder not found uint32_t fileHash = generateHash(fileName); - uint32_t extHash = *reinterpret_cast(ext.data() + 1); + uint32_t extHash = generateExtensionHash(ext); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return std::nullopt; // file not found diff --git a/components/bsa/ba2file.cpp b/components/bsa/ba2file.cpp index b4fc7f9ec2..17cfb03866 100644 --- a/components/bsa/ba2file.cpp +++ b/components/bsa/ba2file.cpp @@ -46,4 +46,12 @@ namespace Bsa return result; } + uint32_t generateExtensionHash(std::string_view extension) + { + uint32_t result = 0; + for (size_t i = 0; i < 4 && i < extension.size() - 1; i++) + result |= static_cast(extension[i + 1]) << (8 * i); + return result; + } + } // namespace Bsa diff --git a/components/bsa/ba2file.hpp b/components/bsa/ba2file.hpp index e5a68d3caa..75a2ce8d61 100644 --- a/components/bsa/ba2file.hpp +++ b/components/bsa/ba2file.hpp @@ -7,6 +7,7 @@ namespace Bsa { uint32_t generateHash(const std::string& name); + uint32_t generateExtensionHash(std::string_view extension); } #endif diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index f3961a3bc4..02df12593c 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -172,7 +172,7 @@ namespace Bsa return FileRecord(); // folder not found, return default which has offset of sInvalidOffset uint32_t fileHash = generateHash(fileName); - uint32_t extHash = *reinterpret_cast(ext.data() + 1); + uint32_t extHash = generateExtensionHash(ext); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return FileRecord(); // file not found, return default which has offset of sInvalidOffset From 0ea88df46dd7ceb7b7fb4477fd68db159792340c Mon Sep 17 00:00:00 2001 From: Diego Date: Sun, 21 Jan 2024 19:48:13 -0500 Subject: [PATCH 0859/2167] added instructions for extracting .esm files using innoextract --- .../installation/install-game-files.rst | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 57460c4983..1925d16d97 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -68,6 +68,51 @@ You will find ``Morrowind.esm`` there. Users of other platforms running Wine, will find it at ``~/.wine/drive_c/Program Files/Bethesda Softworks/Morrowind`` +Innoextract +^^^^^^^^^^^ + +Linux +~~~~~ + +If you have purchased "The Elder Scrolls III: Morrowind" from GOG and wish to extract the game files on a Linux system without using Wine, you can do so using ``innoextract``. + +For Distributions Using `apt` (e.g., Ubuntu, Debian) +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. code:: bash + + sudo apt update + sudo apt install innoextract + +Other Distributions ++++++++++++++++++++ + +If you are using a Linux distribution that doesn't use apt, search your package manager for the program and install it if it exists. + +.. code:: bash + + # openSUSE + sudo zypper refresh + sudo zypper search innoextract + sudo zypper install innoextract + +.. code:: bash + + # Arch Linux/Manjaro + sudo pacman -Sy + sudo pacman -Ss innoextract + sudo pacman -S innoextract + +Once the program is installed, download the game from GOG. The file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When you run ``innoextract`` it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. + +.. code:: bash + + innoextract setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ + +If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. + +Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. + ----- Steam ----- From 2575801ba2a1367d151acef3b8a024cbcf51e702 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Jan 2024 09:52:32 +0400 Subject: [PATCH 0860/2167] Improve esmtool output --- apps/esmtool/esmtool.cpp | 4 ++-- apps/esmtool/record.cpp | 42 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 13f222ed72..0473676f93 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -69,7 +69,7 @@ Allowed options)"); addOption("name,n", bpo::value(), "Show only the record with this name. Only affects dump mode."); addOption("plain,p", "Print contents of dialogs, books and scripts. " - "(skipped by default)" + "(skipped by default) " "Only affects dump mode."); addOption("quiet,q", "Suppress all record information. Useful for speed tests."); addOption("loadcells,C", "Browse through contents of all cells."); @@ -390,7 +390,7 @@ namespace if (!quiet && interested) { - std::cout << "\nRecord: " << n.toStringView() << " '" << record->getId() << "'\n" + std::cout << "\nRecord: " << n.toStringView() << " " << record->getId() << "\n" << "Record flags: " << recordFlags(record->getFlags()) << '\n'; record->print(); } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 044fbf9f93..e3b81daf41 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -464,7 +464,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -516,7 +517,8 @@ namespace EsmTool std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; @@ -679,7 +681,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Flags: " << creatureFlags((int)mData.mFlags) << std::endl; std::cout << " Blood Type: " << mData.mBloodType + 1 << std::endl; std::cout << " Original: " << mData.mOriginal << std::endl; @@ -747,7 +750,8 @@ namespace EsmTool { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; - std::cout << " Script: " << mData.mScript << std::endl; + if (!mData.mScript.empty()) + std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; @@ -1338,28 +1342,26 @@ namespace EsmTool template <> void Record::print() { - std::cout << " Id:" << std::endl; - std::cout << " CellId: " << mData.mCellState.mId << std::endl; - std::cout << " Index:" << std::endl; - std::cout << " WaterLevel: " << mData.mCellState.mWaterLevel << std::endl; - std::cout << " HasFogOfWar: " << mData.mCellState.mHasFogOfWar << std::endl; - std::cout << " LastRespawn:" << std::endl; + std::cout << " Cell Id: \"" << mData.mCellState.mId.toString() << "\"" << std::endl; + std::cout << " Water Level: " << mData.mCellState.mWaterLevel << std::endl; + std::cout << " Has Fog Of War: " << mData.mCellState.mHasFogOfWar << std::endl; + std::cout << " Last Respawn:" << std::endl; std::cout << " Day:" << mData.mCellState.mLastRespawn.mDay << std::endl; std::cout << " Hour:" << mData.mCellState.mLastRespawn.mHour << std::endl; if (mData.mCellState.mHasFogOfWar) { - std::cout << " NorthMarkerAngle: " << mData.mFogState.mNorthMarkerAngle << std::endl; + std::cout << " North Marker Angle: " << mData.mFogState.mNorthMarkerAngle << std::endl; std::cout << " Bounds:" << std::endl; - std::cout << " MinX: " << mData.mFogState.mBounds.mMinX << std::endl; - std::cout << " MinY: " << mData.mFogState.mBounds.mMinY << std::endl; - std::cout << " MaxX: " << mData.mFogState.mBounds.mMaxX << std::endl; - std::cout << " MaxY: " << mData.mFogState.mBounds.mMaxY << std::endl; + std::cout << " Min X: " << mData.mFogState.mBounds.mMinX << std::endl; + std::cout << " Min Y: " << mData.mFogState.mBounds.mMinY << std::endl; + std::cout << " Max X: " << mData.mFogState.mBounds.mMaxX << std::endl; + std::cout << " Max Y: " << mData.mFogState.mBounds.mMaxY << std::endl; for (const ESM::FogTexture& fogTexture : mData.mFogState.mFogTextures) { - std::cout << " FogTexture:" << std::endl; + std::cout << " Fog Texture:" << std::endl; std::cout << " X: " << fogTexture.mX << std::endl; std::cout << " Y: " << fogTexture.mY << std::endl; - std::cout << " ImageData: (" << fogTexture.mImageData.size() << ")" << std::endl; + std::cout << " Image Data: (" << fogTexture.mImageData.size() << ")" << std::endl; } } } @@ -1367,7 +1369,7 @@ namespace EsmTool template <> std::string Record::getId() const { - return mData.mName; + return std::string(); // No ID for Cell record } template <> @@ -1397,9 +1399,7 @@ namespace EsmTool template <> std::string Record::getId() const { - std::ostringstream stream; - stream << mData.mCellState.mId; - return stream.str(); + return std::string(); // No ID for CellState record } } // end namespace From 0f3c4f2043ceca1a2585e1427de4e228a081a591 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:41:53 +0000 Subject: [PATCH 0861/2167] Applying Feedback --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 1925d16d97..57f8d65a88 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -74,7 +74,7 @@ Innoextract Linux ~~~~~ -If you have purchased "The Elder Scrolls III: Morrowind" from GOG and wish to extract the game files on a Linux system without using Wine, you can do so using ``innoextract``. +If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, you can do so using `innoextract `_. For Distributions Using `apt` (e.g., Ubuntu, Debian) ++++++++++++++++++++++++++++++++++++++++++++++++++++ From 6a1979e5f1ded54e9b40cde58071f9cca9613655 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:42:42 +0000 Subject: [PATCH 0862/2167] Applying feedback to fix the verbiage of using innoextract --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 57f8d65a88..21f01983f3 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -103,7 +103,7 @@ If you are using a Linux distribution that doesn't use apt, search your package sudo pacman -Ss innoextract sudo pacman -S innoextract -Once the program is installed, download the game from GOG. The file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When you run ``innoextract`` it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. +Once the innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. .. code:: bash From d6d1cb099f3e4908bcf088f7e5cc312bc81dc430 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:43:20 +0000 Subject: [PATCH 0863/2167] applying feedback to switch from bash to console --- docs/source/manuals/installation/install-game-files.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 21f01983f3..c8464c4f57 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -105,9 +105,9 @@ If you are using a Linux distribution that doesn't use apt, search your package Once the innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. -.. code:: bash +.. code:: console - innoextract setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ + $ innoextract ./setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. From a7473a2134e1fb43e641bcd93e3f4268b2c8b20c Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:44:07 +0000 Subject: [PATCH 0864/2167] Applying feedback to remove redundant instructions --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index c8464c4f57..5d5350bde2 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -109,7 +109,7 @@ Once the innoextract is installed, download the game from GOG. The downloaded fi $ innoextract ./setup_tes_morrowind_goty_2.0.0.7.exe -d ~/Documents/Games/Morrowind/ -If not just run the command without the ``-d`` flag. Assuming you used the filepath above, your ``.esm`` files will be located in this diredctory ``~/Documents/Games/Morrowind/app/Data Files/``. +Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. From 6029545bc7ff9806eedda2f5bf470f50051fe7ef Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 22 Jan 2024 12:45:01 +0000 Subject: [PATCH 0865/2167] Applying feedback to make verbiage less casual --- docs/source/manuals/installation/install-game-files.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 5d5350bde2..4980f902a7 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -111,7 +111,7 @@ Once the innoextract is installed, download the game from GOG. The downloaded fi Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. -Now you can run the OpenMW launcher and run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and enjoy playing Morrowind. +You can now run the OpenMW launcher and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ----- Steam From 5c135551e611e274a798855c696f92551c8ead14 Mon Sep 17 00:00:00 2001 From: Diego Date: Mon, 22 Jan 2024 07:49:25 -0500 Subject: [PATCH 0866/2167] removed zypper and pacman commands --- .../installation/install-game-files.rst | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 4980f902a7..413061f8c4 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -79,31 +79,12 @@ If you have purchased "The Elder Scrolls III: Morrowind" `from GOG Date: Mon, 22 Jan 2024 14:33:52 +0000 Subject: [PATCH 0867/2167] added instructions to install innoextract using homebrew --- .../manuals/installation/install-game-files.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 413061f8c4..6da5d3d55a 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -71,10 +71,10 @@ Users of other platforms running Wine, will find it at Innoextract ^^^^^^^^^^^ -Linux -~~~~~ +macOS and Linux +~~~~~~~~~~~~~~~ -If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, you can do so using `innoextract `_. +If you have purchased "The Elder Scrolls III: Morrowind" `from GOG `_ and wish to extract the game files on a Linux system without using Wine, or on macOS, you can do so using `innoextract `_. First install innoextract. For Distributions Using `apt` (e.g., Ubuntu, Debian) ++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -84,6 +84,13 @@ For Distributions Using `apt` (e.g., Ubuntu, Debian) sudo apt update sudo apt install innoextract +For macOS using Homebrew +++++++++++++++++++++++++ + +.. code:: console + + brew install innoextract + Once innoextract is installed, download the game from GOG. The downloaded file should be called ``setup_tes_morrowind_goty_2.0.0.7.exe`` or something similar. When ``innoextract`` is run on it, it will extract the files directly into the folder the ``setup.exe`` file is located. If you have a specific folder where you want it to be extracted to, for example in ``~/Documents/Games/Morrowind`` You can specify it with the ``-d`` flag. .. code:: console @@ -92,7 +99,7 @@ Once innoextract is installed, download the game from GOG. The downloaded file s Assuming you used the filepath above, your ``.esm`` files will be located in ``~/Documents/Games/Morrowind/app/Data Files/``. -You can now run the OpenMW launcher and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. +You can now run the OpenMW launcher, and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ----- Steam From 4dfe6078c8c62bfe3a02bfb0984559dafaad1c42 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 22 Jan 2024 22:07:49 +0100 Subject: [PATCH 0868/2167] Make StartCombat a no-op for dead targets and don't play an attack line when already in combat --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 4 +++- apps/openmw/mwscript/aiextensions.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..95b04c2741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,7 @@ Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7765: OpenMW-CS: Touch Record option is broken + Bug #7769: Sword of the Perithia: Broken NPCs Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work Feature #2566: Handle NAM9 records for manual cell references diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c9e8e8f322..fd9d98f3b9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1681,6 +1681,7 @@ namespace MWMechanics return; } + const bool inCombat = stats.getAiSequence().isInCombat(); stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { @@ -1715,7 +1716,8 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); + if (!inCombat) + MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); } void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index b6acbd246b..a44b391e13 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -507,7 +507,7 @@ namespace MWScript runtime.pop(); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); - if (!target.isEmpty()) + if (!target.isEmpty() && !target.getClass().getCreatureStats(target).isDead()) MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); } }; From 54429cd23bf7118aac2e475d0221fe927f29ba32 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 24 Jan 2024 18:31:04 +0100 Subject: [PATCH 0869/2167] Parse special characters that have been put back as names too --- .../mwscript/test_scripts.cpp | 28 +++++++++++++++++++ components/compiler/scanner.cpp | 15 +++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index dbed262235..0de542bdc6 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -326,6 +326,8 @@ End)mwscript"; Addtopic -spells... Addtopic -magicka... +player->PositionCell, -97274, -94273, 8064, -12,-12 + End)mwscript"; const std::string sIssue4061 = R"mwscript(Begin 01_Rz_neuvazhay-koryto2 @@ -763,7 +765,33 @@ End)mwscript"; mTopics.erase(mTopics.begin()); } }; + class PositionCell : public Interpreter::Opcode0 + { + public: + void execute(Interpreter::Runtime& runtime) + { + std::string_view target = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + Interpreter::Type_Float x = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float y = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float z = runtime[0].mFloat; + runtime.pop(); + Interpreter::Type_Float zRot = runtime[0].mFloat; + runtime.pop(); + std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + EXPECT_EQ(target, "player"); + EXPECT_EQ(x, -97274); + EXPECT_EQ(y, -94273); + EXPECT_EQ(z, 8064); + EXPECT_EQ(zRot, -12); + EXPECT_EQ(cellID, "-12"); + } + }; installOpcode(Compiler::Dialogue::opcodeAddTopic, topics); + installOpcode(Compiler::Transformation::opcodePositionCellExplicit); TestInterpreterContext context; run(*script, context); EXPECT_TRUE(topics.empty()); diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 4794f569b5..49f3999740 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -63,9 +63,22 @@ namespace Compiler switch (mPutback) { case Putback_Special: - + { mPutback = Putback_None; + // Replicate behaviour from scanSpecial so putting something back doesn't change the way it's handled + if (mExpectName && (mPutbackCode == S_member || mPutbackCode == S_minus)) + { + mExpectName = false; + bool cont = false; + bool tolerant = mTolerantNames; + mTolerantNames = true; + MultiChar c{ mPutbackCode == S_member ? '.' : '-' }; + scanName(c, parser, cont); + mTolerantNames = tolerant; + return cont; + } return parser.parseSpecial(mPutbackCode, mPutbackLoc, *this); + } case Putback_Integer: From 199d97d32a49e285cc3b19764c1db45193889126 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 17 Jan 2024 22:57:53 +0100 Subject: [PATCH 0870/2167] Use forward declaration for VFS::Manager --- components/esm4/reader.cpp | 1 + components/esm4/reader.hpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index ce7f534786..a3ea438d65 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include "grouptype.hpp" diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index c63dbd1548..92dc00b96d 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -36,13 +36,17 @@ #include #include -#include namespace ToUTF8 { class StatelessUtf8Encoder; } +namespace VFS +{ + class Manager; +} + namespace ESM4 { #pragma pack(push, 1) From d549cfd66b1132d7835c600a5e91a4e16b9ef7d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 23:53:55 +0100 Subject: [PATCH 0871/2167] Check path for being normalized --- components/vfs/manager.cpp | 2 ++ components/vfs/pathutil.hpp | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index d312ce9d84..1bf789402d 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -1,6 +1,7 @@ #include "manager.hpp" #include +#include #include #include @@ -44,6 +45,7 @@ namespace VFS Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { + assert(Path::isNormalized(normalizedName)); const auto found = mIndex.find(normalizedName); if (found == mIndex.end()) throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found"); diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 9bcc263842..0856bfffa2 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -15,6 +15,11 @@ namespace VFS::Path return c == '\\' ? '/' : Misc::StringUtils::toLower(c); } + inline constexpr bool isNormalized(std::string_view name) + { + return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); }); + } + inline void normalizeFilenameInPlace(std::string& name) { std::transform(name.begin(), name.end(), name.begin(), normalize); From 9279138fb0c9e138846b259ea0e566f4c8ae01d7 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 16 Jan 2024 23:55:42 +0100 Subject: [PATCH 0872/2167] Accept normalized path by VFS::Manager functions --- components/vfs/manager.cpp | 13 ++++++------- components/vfs/manager.hpp | 7 ++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 1bf789402d..a6add0861a 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -38,9 +38,9 @@ namespace VFS archive->listResources(mIndex); } - Files::IStreamPtr Manager::get(std::string_view name) const + Files::IStreamPtr Manager::get(const Path::Normalized& name) const { - return getNormalized(Path::normalizeFilename(name)); + return getNormalized(name); } Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const @@ -52,17 +52,16 @@ namespace VFS return found->second->open(); } - bool Manager::exists(std::string_view name) const + bool Manager::exists(const Path::Normalized& name) const { - return mIndex.find(Path::normalizeFilename(name)) != mIndex.end(); + return mIndex.find(name) != mIndex.end(); } - std::string Manager::getArchive(std::string_view name) const + std::string Manager::getArchive(const Path::Normalized& name) const { - std::string normalized = Path::normalizeFilename(name); for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) { - if ((*it)->contains(normalized)) + if ((*it)->contains(name)) return (*it)->getDescription(); } return {}; diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 05990a8607..7598b77e68 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -10,6 +10,7 @@ #include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -40,19 +41,19 @@ namespace VFS /// Does a file with this name exist? /// @note May be called from any thread once the index has been built. - bool exists(std::string_view name) const; + bool exists(const Path::Normalized& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. - Files::IStreamPtr get(std::string_view name) const; + Files::IStreamPtr get(const Path::Normalized& name) const; /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. Files::IStreamPtr getNormalized(std::string_view normalizedName) const; - std::string getArchive(std::string_view name) const; + std::string getArchive(const Path::Normalized& name) const; /// Recursively iterate over the elements of the given path /// In practice it return all files of the VFS starting with the given path From 70061329a11c6a76cf79dce30aae959d537b4594 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 17 Jan 2024 23:32:15 +0100 Subject: [PATCH 0873/2167] Return Path::Normalized from RecursiveDirectoryIterator --- apps/niftest/niftest.cpp | 4 ++-- apps/opencs/model/world/resources.cpp | 15 +++++++-------- components/vfs/recursivedirectoryiterator.hpp | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 9352651030..ee6e84295f 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -113,9 +113,9 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - if (isNIF(name)) + if (isNIF(name.value())) { - readNIF(archivePath, name, &vfs, quiet); + readNIF(archivePath, name.value(), &vfs, quiet); } } diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 345f6008ec..9957af3d66 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -30,18 +30,18 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - if (filepath.size() < baseSize + 1 || filepath.substr(0, baseSize) != mBaseDirectory - || (filepath[baseSize] != '/' && filepath[baseSize] != '\\')) + const std::string_view view = filepath.view(); + if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') continue; if (extensions) { - std::string::size_type extensionIndex = filepath.find_last_of('.'); + const auto extensionIndex = view.find_last_of('.'); - if (extensionIndex == std::string::npos) + if (extensionIndex == std::string_view::npos) continue; - std::string extension = filepath.substr(extensionIndex + 1); + std::string_view extension = view.substr(extensionIndex + 1); int i = 0; @@ -53,10 +53,9 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e continue; } - std::string file = filepath.substr(baseSize + 1); + std::string file(view.substr(baseSize + 1)); mFiles.push_back(file); - std::replace(file.begin(), file.end(), '\\', '/'); - mIndex.insert(std::make_pair(Misc::StringUtils::lowerCase(file), static_cast(mFiles.size()) - 1)); + mIndex.emplace(std::move(file), static_cast(mFiles.size()) - 1); } } diff --git a/components/vfs/recursivedirectoryiterator.hpp b/components/vfs/recursivedirectoryiterator.hpp index 39fb26e873..3410a2092b 100644 --- a/components/vfs/recursivedirectoryiterator.hpp +++ b/components/vfs/recursivedirectoryiterator.hpp @@ -16,9 +16,9 @@ namespace VFS { } - const std::string& operator*() const { return mIt->first.value(); } + const Path::Normalized& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first.value(); } + const Path::Normalized* operator->() const { return &mIt->first; } RecursiveDirectoryIterator& operator++() { From 9e55425b71cfd906294cabb8223b88926875f16b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 24 Jan 2024 20:39:04 +0400 Subject: [PATCH 0874/2167] Use std::move() in /apps/openmw --- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwgui/charactercreation.cpp | 12 ++++++------ apps/openmw/mwgui/dialogue.cpp | 4 ++-- apps/openmw/mwgui/postprocessorhud.cpp | 6 +++--- apps/openmw/mwlua/cellbindings.cpp | 2 +- apps/openmw/mwlua/objectbindings.cpp | 4 ++-- apps/openmw/mwmechanics/character.cpp | 6 +++--- apps/openmw/mwrender/characterpreview.cpp | 4 ++-- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/globalmap.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwscript/miscextensions.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 12 ++++++------ apps/openmw/mwworld/containerstore.cpp | 2 +- apps/openmw/mwworld/store.cpp | 2 +- 15 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 5225170be7..6bd28103f8 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -143,7 +143,7 @@ namespace MWClass list.push_back(params); } - info.effects = list; + info.effects = std::move(list); info.text = std::move(text); info.isIngredient = true; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 4141e61e34..c5280d1615 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -63,17 +63,17 @@ namespace switch (order) { case 0: - return { question, { r0, r1, r2 }, sound }; + return { std::move(question), { std::move(r0), std::move(r1), std::move(r2) }, std::move(sound) }; case 1: - return { question, { r0, r2, r1 }, sound }; + return { std::move(question), { std::move(r0), std::move(r2), std::move(r1) }, std::move(sound) }; case 2: - return { question, { r1, r0, r2 }, sound }; + return { std::move(question), { std::move(r1), std::move(r0), std::move(r2) }, std::move(sound) }; case 3: - return { question, { r1, r2, r0 }, sound }; + return { std::move(question), { std::move(r1), std::move(r2), std::move(r0) }, std::move(sound) }; case 4: - return { question, { r2, r0, r1 }, sound }; + return { std::move(question), { std::move(r2), std::move(r0), std::move(r1) }, std::move(sound) }; default: - return { question, { r2, r1, r0 }, sound }; + return { std::move(question), { std::move(r2), std::move(r1), std::move(r0) }, std::move(sound) }; } } } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index ce79e2834c..56f69eb906 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -196,7 +196,7 @@ namespace MWGui std::string topicName = Misc::StringUtils::lowerCase(windowManager->getTranslationDataStorage().topicStandardForm(link)); - std::string displayName = link; + std::string displayName = std::move(link); while (displayName[displayName.size() - 1] == '*') displayName.erase(displayName.size() - 1, 1); @@ -248,7 +248,7 @@ namespace MWGui i = match.mEnd; } if (i != text.end()) - addTopicLink(typesetter, 0, i - text.begin(), text.size()); + addTopicLink(std::move(typesetter), 0, i - text.begin(), text.size()); } } diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index ab5bdf791d..6f73c5a9fd 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -136,9 +136,9 @@ namespace MWGui return; if (enabled) - processor->enableTechnique(technique); + processor->enableTechnique(std::move(technique)); else - processor->disableTechnique(technique); + processor->disableTechnique(std::move(technique)); processor->saveChain(); } } @@ -171,7 +171,7 @@ namespace MWGui if (technique->getDynamic()) return; - if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error) + if (processor->enableTechnique(std::move(technique), index) != MWRender::PostProcessor::Status_Error) processor->saveChain(); } } diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 081df13a0e..39dad5a867 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -270,7 +270,7 @@ namespace MWLua if (!ok) throw std::runtime_error( std::string("Incorrect type argument in cell:getAll: " + LuaUtil::toString(*type))); - return GObjectList{ res }; + return GObjectList{ std::move(res) }; }; } } diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 748d963bdc..47c55e86f0 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -613,7 +613,7 @@ namespace MWLua MWBase::Environment::get().getWorldModel()->registerPtr(item); list->push_back(getId(item)); } - return ObjectList{ list }; + return ObjectList{ std::move(list) }; }; inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) { @@ -661,7 +661,7 @@ namespace MWLua list->push_back(getId(item)); } } - return ObjectList{ list }; + return ObjectList{ std::move(list) }; }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 20c7fd0a92..f1bec8ce9d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -684,7 +684,7 @@ namespace MWMechanics if (!mAnimation->hasAnimation(weapMovementAnimName)) weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - movementAnimName = weapMovementAnimName; + movementAnimName = std::move(weapMovementAnimName); } if (!mAnimation->hasAnimation(movementAnimName)) @@ -798,7 +798,7 @@ namespace MWMechanics weapIdleGroup += weapShortGroup; if (!mAnimation->hasAnimation(weapIdleGroup)) weapIdleGroup = fallbackShortWeaponGroup(idleGroup); - idleGroup = weapIdleGroup; + idleGroup = std::move(weapIdleGroup); // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation @@ -820,7 +820,7 @@ namespace MWMechanics mAnimation->getInfo(mCurrentIdle, &startPoint); clearStateAnimation(mCurrentIdle); - mCurrentIdle = idleGroup; + mCurrentIdle = std::move(idleGroup); mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 88ceeabd23..269f7cab75 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -436,7 +436,7 @@ namespace MWRender // We still should use one-handed animation as fallback if (mAnimation->hasAnimation(inventoryGroup)) - groupname = inventoryGroup; + groupname = std::move(inventoryGroup); else { static const std::string oneHandFallback @@ -456,7 +456,7 @@ namespace MWRender mAnimation->showCarriedLeft(showCarriedLeft); - mCurrentAnimGroup = groupname; + mCurrentAnimGroup = std::move(groupname); mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 040ba320a1..58e03aa0a2 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -170,7 +170,7 @@ namespace MWRender else source = mAnimationTimePtr[0]; - SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::move(source)); attached->accept(assignVisitor); } catch (std::exception& e) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index e58f987a44..f9dae65c40 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -363,7 +363,7 @@ namespace MWRender imageDest.mImage = image; imageDest.mX = x; imageDest.mY = y; - mPendingImageDest[camera] = imageDest; + mPendingImageDest[camera] = std::move(imageDest); } // Create a quad rendering the updated texture diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 84522ee86e..b9ea257c9f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -853,7 +853,7 @@ namespace MWRender src = mWeaponAnimationTime; else src = mAnimationTimePtr[0]; - SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); + SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::move(src)); node->accept(assignVisitor); } } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9d75334f00..476cacafd0 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1473,7 +1473,7 @@ namespace MWScript if (lastTextureSrc.empty() || textureSrc != lastTextureSrc) { - lastTextureSrc = textureSrc; + lastTextureSrc = std::move(textureSrc); if (lastTextureSrc.empty()) lastTextureSrc = "[No Source]"; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 383d316c91..0cc276807f 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -223,7 +223,7 @@ namespace MWSound params.mFlags = PlayMode::NoEnv | Type::Voice | Play_2D; return params; }()); - played = mOutput->streamSound(decoder, sound.get(), true); + played = mOutput->streamSound(std::move(decoder), sound.get(), true); } else { @@ -236,7 +236,7 @@ namespace MWSound params.mFlags = PlayMode::Normal | Type::Voice | Play_3D; return params; }()); - played = mOutput->streamSound3D(decoder, sound.get(), true); + played = mOutput->streamSound3D(std::move(decoder), sound.get(), true); } if (!played) return nullptr; @@ -282,7 +282,7 @@ namespace MWSound params.mFlags = PlayMode::NoEnvNoScaling | Type::Music | Play_2D; return params; }()); - mOutput->streamSound(decoder, mMusic.get()); + mOutput->streamSound(std::move(decoder), mMusic.get()); } void SoundManager::advanceMusic(const std::string& filename, float fadeOut) @@ -366,7 +366,7 @@ namespace MWSound for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) filelist.push_back(name); - mMusicFiles[playlist] = filelist; + mMusicFiles[playlist] = std::move(filelist); } // No Battle music? Use Explore playlist @@ -393,7 +393,7 @@ namespace MWSound const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans(); stopSay(ptr); - StreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer())); + StreamPtr sound = playVoice(std::move(decoder), pos, (ptr == MWMechanics::getPlayer())); if (!sound) return; @@ -422,7 +422,7 @@ namespace MWSound return; stopSay(MWWorld::ConstPtr()); - StreamPtr sound = playVoice(decoder, osg::Vec3f(), true); + StreamPtr sound = playVoice(std::move(decoder), osg::Vec3f(), true); if (!sound) return; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d30ea21494..f48f73f48a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -719,7 +719,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() fill(container.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, container.mCell); } - return { listener }; + return { std::move(listener) }; } void MWWorld::ContainerStore::unresolve() diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index ac3ee72a94..10d9fb3f3b 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -893,7 +893,7 @@ namespace MWWorld // Try to overwrite existing record auto ret = mStatic.emplace(cell, pathgrid); if (!ret.second) - ret.first->second = pathgrid; + ret.first->second = std::move(pathgrid); return RecordId(ESM::RefId(), isDeleted); } From 29a40c212fe58a753307d9802b05cd3a6b6266c1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 25 Jan 2024 15:40:49 +0300 Subject: [PATCH 0875/2167] Support parsing KF files in niftest Mention BA2 support in help messages --- apps/niftest/niftest.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 9352651030..a0dbb1233b 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -34,7 +34,7 @@ bool hasExtension(const std::filesystem::path& filename, const std::string& exte /// See if the file has the "nif" extension. bool isNIF(const std::filesystem::path& filename) { - return hasExtension(filename, ".nif"); + return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); } /// See if the file has the "bsa" extension. bool isBSA(const std::filesystem::path& filename) @@ -76,7 +76,10 @@ void readNIF( const std::string pathStr = Files::pathToUnicodeString(path); if (!quiet) { - std::cout << "Reading NIF file '" << pathStr << "'"; + if (hasExtension(path, ".kf")) + std::cout << "Reading KF file '" << pathStr << "'"; + else + std::cout << "Reading NIF file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; @@ -139,10 +142,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool& writeDebugLog, bool& quiet) { - bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files + bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files Usages: - niftest + niftest Scan the file or directories for NIF errors. Allowed options)"); @@ -241,7 +244,8 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << pathStr << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; + std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory" + << std::endl; } } catch (std::exception& e) From bdc6119b31d4fdda2a178c3e79055eeb41eb69f5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 25 Jan 2024 19:36:41 +0100 Subject: [PATCH 0876/2167] Bring attack voice lines in line with research Only play them when starting combat when not in combat or not in combat with one of the target's allies. Don't play them when casting spells whose first effect isn't ranged. --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 4 +- apps/openmw/mwmechanics/actors.cpp | 10 ++--- apps/openmw/mwmechanics/aicombat.cpp | 25 +++++++---- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/aicombataction.hpp | 2 + apps/openmw/mwmechanics/aisequence.hpp | 3 -- .../mwmechanics/mechanicsmanagerimp.cpp | 41 +++++++++++++++---- .../mwmechanics/mechanicsmanagerimp.hpp | 3 +- apps/openmw/mwscript/aiextensions.cpp | 2 +- 10 files changed, 66 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b04c2741..c467fd35a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X + Bug #5413: Enemies do a battlecry everytime the player summons a creature Bug #5714: Touch spells cast using ExplodeSpell don't always explode Bug #5849: Paralysis breaks landing Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 9e99a37ec7..e1cce6acc4 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -109,7 +109,9 @@ namespace MWBase virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - virtual void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + virtual void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) + = 0; /// Removes an actor and its allies from combat with the actor's targets. virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bc3cc3bb6a..d0ae5c6ea4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -620,7 +620,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, nullptr); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -655,11 +655,11 @@ namespace MWMechanics { if (ally2 != actor2 && ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, &allies2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) if (ally1 != player) - mechanicsManager->startCombat(ally1, actor2); + mechanicsManager->startCombat(ally1, actor2, &allies2); return; } } @@ -747,7 +747,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, nullptr); } } @@ -1133,7 +1133,7 @@ namespace MWMechanics = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { - mechanicsManager->startCombat(ptr, player); + mechanicsManager->startCombat(ptr, player, nullptr); creatureStats.setHitAttemptActorId( playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 12072df2ac..0b3c2b8bd2 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -270,7 +270,15 @@ namespace MWMechanics { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack - storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); + bool canShout = true; + ESM::RefId spellId = storage.mCurrentAction->getSpell(); + if (!spellId.empty()) + { + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + canShout = false; + } + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); } // If actor uses custom destination it has to try to rebuild path because environment can change @@ -635,7 +643,7 @@ namespace MWMechanics } void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat) + const ESM::Weapon* weapon, bool distantCombat, bool canShout) { if (mReadyToAttack && characterController.readyToStartAttack()) { @@ -658,12 +666,15 @@ namespace MWMechanics baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); } - // Say a provoking combat phrase - const int iVoiceAttackOdds - = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + if (canShout) { - MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + // Say a provoking combat phrase + const int iVoiceAttackOdds + = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + { + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + } } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 494f038d6e..d5a9c3464c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -64,7 +64,7 @@ namespace MWMechanics void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat); + const ESM::Weapon* weapon, bool distantCombat, bool canShout); void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 05d2a18e25..9eb7df211a 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,6 +16,7 @@ namespace MWMechanics virtual float getCombatRange(bool& isRanged) const = 0; virtual float getActionCooldown() const { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return nullptr; } + virtual ESM::RefId getSpell() const { return {}; } virtual bool isAttackingOrSpell() const { return true; } virtual bool isFleeing() const { return false; } }; @@ -43,6 +44,7 @@ namespace MWMechanics void prepare(const MWWorld::Ptr& actor) override; float getCombatRange(bool& isRanged) const override; + ESM::RefId getSpell() const override { return mSpellId; } }; class ActionEnchantedItem : public Action diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 92c1724ea6..6b02da6633 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -132,9 +132,6 @@ namespace MWMechanics /// Are we in combat with this particular actor? bool isInCombat(const MWWorld::Ptr& actor) const; - bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; - ///< Function assumes that actor can have only 1 target apart player - /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fd9d98f3b9..2c556185ce 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1169,9 +1169,9 @@ namespace MWMechanics && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) reported = reportCrime(player, victim, type, ESM::RefId(), arg); + // TODO: combat should be started with an "unaware" flag, which makes the victim flee? if (!reported) - startCombat(victim, - player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? + startCombat(victim, player, &playerFollowers); } return crimeSeen; } @@ -1412,7 +1412,7 @@ namespace MWMechanics observerStats.modCrimeDispositionModifier(dispositionModifier); } - startCombat(actor, player); + startCombat(actor, player, &playerFollowers); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off @@ -1463,7 +1463,7 @@ namespace MWMechanics // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. // Note: accidental or collateral damage attacks are ignored. if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) - startCombat(victim, player); + startCombat(victim, player, &playerFollowers); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. @@ -1508,14 +1508,14 @@ namespace MWMechanics if (!peaceful) { - startCombat(target, attacker); + startCombat(target, attacker, nullptr); // Force friendly actors into combat to prevent infighting between followers std::set followersTarget; getActorsSidingWith(target, followersTarget); for (const auto& follower : followersTarget) { if (follower != attacker && follower != player) - startCombat(follower, attacker); + startCombat(follower, attacker, nullptr); } } } @@ -1664,7 +1664,8 @@ namespace MWMechanics } } - void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) + void MechanicsManager::startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -1682,6 +1683,30 @@ namespace MWMechanics } const bool inCombat = stats.getAiSequence().isInCombat(); + bool shout = !inCombat; + if (inCombat) + { + const auto isInCombatWithOneOf = [&](const auto& allies) { + for (const MWWorld::Ptr& ally : allies) + { + if (stats.getAiSequence().isInCombat(ally)) + return true; + } + return false; + }; + if (targetAllies) + shout = !isInCombatWithOneOf(*targetAllies); + else + { + shout = stats.getAiSequence().isInCombat(target); + if (!shout) + { + std::set sidingActors; + getActorsSidingWith(target, sidingActors); + shout = !isInCombatWithOneOf(sidingActors); + } + } + } stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { @@ -1716,7 +1741,7 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - if (!inCombat) + if (shout) MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index f2483396fe..027fa9bb62 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -107,7 +107,8 @@ namespace MWMechanics bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) override; void stopCombat(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index a44b391e13..3493c98f41 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -508,7 +508,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); if (!target.isEmpty() && !target.getClass().getCreatureStats(target).isDead()) - MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); + MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target, nullptr); } }; From a8ee3dfae87f4ac1a4e6f5661938d855758bc561 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 25 Jan 2024 20:44:18 +0100 Subject: [PATCH 0877/2167] Move the caching getActorsSidingWith to its own type --- apps/openmw/mwmechanics/actors.cpp | 83 +++++++++---------- apps/openmw/mwmechanics/actors.hpp | 27 ++++-- .../mwmechanics/mechanicsmanagerimp.cpp | 11 +-- 3 files changed, 65 insertions(+), 56 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d0ae5c6ea4..5650ad9d2a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -577,8 +577,8 @@ namespace MWMechanics } } - void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, - std::map>& cachedAllies, bool againstPlayer) const + void Actors::engageCombat( + const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, bool againstPlayer) const { // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) @@ -606,9 +606,8 @@ namespace MWMechanics // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting // those actors, (recursive) and any actor currently being followed or escorted by actor1 - std::set allies1; - - getActorsSidingWith(actor1, allies1, cachedAllies); + const std::set allies1 = cachedAllies.getActorsSidingWith(actor1); + const std::set allies2 = cachedAllies.getActorsSidingWith(actor2); const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and @@ -620,7 +619,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2, nullptr); + mechanicsManager->startCombat(actor1, actor2, &allies2); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -633,9 +632,8 @@ namespace MWMechanics aggressive = true; } - std::set playerAllies; MWWorld::Ptr player = MWMechanics::getPlayer(); - getActorsSidingWith(player, playerAllies, cachedAllies); + const std::set& playerAllies = cachedAllies.getActorsSidingWith(player); bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); @@ -646,10 +644,6 @@ namespace MWMechanics // Check that actor2 is in combat with actor1 if (creatureStats2.getAiSequence().isInCombat(actor1)) { - std::set allies2; - - getActorsSidingWith(actor2, allies2, cachedAllies); - // Check that an ally of actor2 is also in combat with actor1 for (const MWWorld::Ptr& ally2 : allies2) { @@ -747,7 +741,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2, nullptr); + mechanicsManager->startCombat(actor1, actor2, &allies2); } } @@ -1093,7 +1087,7 @@ namespace MWMechanics } } - void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const + void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration, SidingCache& cachedAllies) const { const MWWorld::Ptr player = getPlayer(); if (ptr == player) @@ -1133,7 +1127,7 @@ namespace MWMechanics = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { - mechanicsManager->startCombat(ptr, player, nullptr); + mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player)); creatureStats.setHitAttemptActorId( playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable @@ -1508,8 +1502,7 @@ namespace MWMechanics /// \todo move update logic to Actor class where appropriate - std::map> - cachedAllies; // will be filled as engageCombat iterates + SidingCache cachedAllies{ *this, true }; // will be filled as engageCombat iterates const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); @@ -1597,7 +1590,7 @@ namespace MWMechanics updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl); if (actor.getPtr().getClass().isNpc() && !isPlayer) - updateCrimePursuit(actor.getPtr(), duration); + updateCrimePursuit(actor.getPtr(), duration, cachedAllies); if (!isPlayer) { @@ -2156,32 +2149,6 @@ namespace MWMechanics getActorsSidingWith(follower, out, excludeInfighting); } - void Actors::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, - std::map>& cachedAllies) const - { - // If we have already found actor's allies, use the cache - std::map>::const_iterator search = cachedAllies.find(actor); - if (search != cachedAllies.end()) - out.insert(search->second.begin(), search->second.end()); - else - { - for (const MWWorld::Ptr& follower : getActorsSidingWith(actor, true)) - if (out.insert(follower).second && follower != actor) - getActorsSidingWith(follower, out, cachedAllies); - - // Cache ptrs and their sets of allies - cachedAllies.insert(std::make_pair(actor, out)); - for (const MWWorld::Ptr& iter : out) - { - if (iter == actor) - continue; - search = cachedAllies.find(iter); - if (search == cachedAllies.end()) - cachedAllies.insert(std::make_pair(iter, out)); - } - } - } - std::vector Actors::getActorsFollowingIndices(const MWWorld::Ptr& actor) const { std::vector list; @@ -2380,4 +2347,32 @@ namespace MWMechanics seq.fastForward(ptr); } } + + const std::set& SidingCache::getActorsSidingWith(const MWWorld::Ptr& actor) + { + // If we have already found actor's allies, use the cache + auto search = mCache.find(actor); + if (search != mCache.end()) + return search->second; + std::set& out = mCache[actor]; + for (const MWWorld::Ptr& follower : mActors.getActorsSidingWith(actor, mExcludeInfighting)) + { + if (out.insert(follower).second && follower != actor) + { + const auto& allies = getActorsSidingWith(follower); + out.insert(allies.begin(), allies.end()); + } + } + + // Cache ptrs and their sets of allies + for (const MWWorld::Ptr& iter : out) + { + if (iter == actor) + continue; + search = mCache.find(iter); + if (search == mCache.end()) + mCache.emplace(iter, out); + } + return out; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7c676ca018..b7f3eb0c41 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -36,6 +36,7 @@ namespace MWMechanics class Actor; class CharacterController; class CreatureStats; + class SidingCache; class Actors { @@ -182,7 +183,7 @@ namespace MWMechanics void calculateRestoration(const MWWorld::Ptr& ptr, float duration) const; - void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const; + void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration, SidingCache& cachedAllies) const; void killDeadActors(); @@ -194,13 +195,25 @@ namespace MWMechanics @Notes: If againstPlayer = true then actor2 should be the Player. If one of the combatants is creature it should be actor1. */ - void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, - std::map>& cachedAllies, bool againstPlayer) const; + void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, + bool againstPlayer) const; + }; - /// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of - /// actors mapped to their allies. Excludes infighting - void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, - std::map>& cachedAllies) const; + class SidingCache + { + const Actors& mActors; + const bool mExcludeInfighting; + std::map> mCache; + + public: + SidingCache(const Actors& actors, bool excludeInfighting) + : mActors(actors) + , mExcludeInfighting(excludeInfighting) + { + } + + /// Recursive version of getActorsSidingWith that takes, returns a cached set of allies + const std::set& getActorsSidingWith(const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2c556185ce..7a85c31cb3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -30,6 +30,7 @@ #include "../mwbase/world.hpp" #include "actor.hpp" +#include "actors.hpp" #include "actorutil.hpp" #include "aicombat.hpp" #include "aipursue.hpp" @@ -1508,14 +1509,14 @@ namespace MWMechanics if (!peaceful) { - startCombat(target, attacker, nullptr); + SidingCache cachedAllies{ mActors, false }; + const std::set& attackerAllies = cachedAllies.getActorsSidingWith(attacker); + startCombat(target, attacker, &attackerAllies); // Force friendly actors into combat to prevent infighting between followers - std::set followersTarget; - getActorsSidingWith(target, followersTarget); - for (const auto& follower : followersTarget) + for (const auto& follower : cachedAllies.getActorsSidingWith(target)) { if (follower != attacker && follower != player) - startCombat(follower, attacker, nullptr); + startCombat(follower, attacker, &attackerAllies); } } } From 3b0d654a3f7ea77fa78526a3cf031badee3e0db4 Mon Sep 17 00:00:00 2001 From: Pharis Date: Thu, 25 Jan 2024 18:06:14 -0600 Subject: [PATCH 0878/2167] Return active spell id from pairs --- apps/openmw/mwlua/magicbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 3d57ab24fc..6e35776cba 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -730,7 +730,7 @@ namespace MWLua auto id = sol::make_object(lua, self.mIterator->getId().serializeText()); auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator }); self.advance(); - return { params, params }; + return { id, params }; } else { From ad64c7175351ae81e3111983533b7a5bca804b58 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 17:02:13 +0300 Subject: [PATCH 0879/2167] Correct MaxNumberRipples and Timescale Clouds validation categories --- components/fallback/validate.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 8dfcd97570..571f0f9b19 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -7,12 +7,12 @@ static const std::set allowedKeysInt = { "LightAttenuation_LinearMethod", "LightAttenuation_OutQuadInLin", "LightAttenuation_QuadraticMethod", "LightAttenuation_UseConstant", - "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_NearWaterRadius", "Water_NearWaterPoints", - "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", - "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", - "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", - "Weather_Snow_Ripples" }; + "LightAttenuation_UseLinear", "LightAttenuation_UseQuadratic", "Water_MaxNumberRipples", "Water_NearWaterRadius", + "Water_NearWaterPoints", "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", + "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", + "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", + "Weather_Ashstorm_Using_Precip", "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", + "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", "Weather_Snow_Ripples", "Weather_Timescale_Clouds" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", @@ -186,7 +186,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Thunderstorm_Sun_Disc_Sunset_Color", "Weather_Thunderstorm_Sun_Night_Color", "Weather_Thunderstorm_Sun_Sunrise_Color", "Weather_Thunderstorm_Sun_Sunset_Color", "Weather_Thunderstorm_Thunder_Sound_ID_0", "Weather_Thunderstorm_Thunder_Sound_ID_1", - "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Timescale_Clouds", + "Weather_Thunderstorm_Thunder_Sound_ID_2", "Weather_Thunderstorm_Thunder_Sound_ID_3", "Weather_Clear_Thunder_Sound_ID_0", "Weather_Clear_Thunder_Sound_ID_1", "Weather_Clear_Thunder_Sound_ID_2", "Weather_Clear_Thunder_Sound_ID_3", "Weather_Cloudy_Thunder_Sound_ID_0", "Weather_Cloudy_Thunder_Sound_ID_1", "Weather_Cloudy_Thunder_Sound_ID_2", "Weather_Cloudy_Thunder_Sound_ID_3", "Weather_Foggy_Thunder_Sound_ID_0", @@ -218,7 +218,7 @@ static const std::set allowedKeysUnused = { "Inventory_Uniform "Map_Travel_Boat_Blue", "Map_Travel_Boat_Green", "Map_Travel_Boat_Red", "Map_Travel_Magic_Blue", "Map_Travel_Magic_Green", "Map_Travel_Magic_Red", "Map_Travel_Siltstrider_Blue", "Map_Travel_Siltstrider_Green", "Map_Travel_Siltstrider_Red", "Movies_Game_Logo", "PixelWater_Resolution", "PixelWater_SurfaceFPS", - "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_MaxNumberRipples", + "PixelWater_TileCount", "Movies_Loading", "Movies_Options_Menu", "Movies_Project_Logo", "Water_NearWaterUnderwaterFreq", "Water_NearWaterUnderwaterVolume", "Water_PSWaterReflectTerrain", "Water_PSWaterReflectUpdate", "Water_RippleAlphas", "Water_RippleScale", "Water_SurfaceTextureSize", "Water_TileTextureDivisor", "Weather_AlphaReduce", "Weather_Ashstorm_Storm_Threshold", From 2ea4013382e43fa24c733689a06165617c481626 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 15:32:14 +0300 Subject: [PATCH 0880/2167] Correct base cloud speed, support Timescale Clouds fallback setting (#7792) --- CHANGELOG.md | 1 + apps/openmw/mwrender/sky.cpp | 15 ++++++++++++--- apps/openmw/mwrender/sky.hpp | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..4e162ded2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -173,6 +173,7 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7792: Support Timescale Clouds Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 060b6ee5de..3efddc4ba3 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -240,6 +240,7 @@ namespace MWRender , mIsStorm(false) , mDay(0) , mMonth(0) + , mTimescaleClouds(Fallback::Map::getBool("Weather_Timescale_Clouds")) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) , mStormParticleDirection(MWWorld::Weather::defaultDirection()) @@ -557,8 +558,17 @@ namespace MWRender mParticleNode->setAttitude(quat); } + const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale(); + // UV Scroll the clouds - mCloudAnimationTimer += duration * mCloudSpeed * 0.003; + float cloudDelta = duration * mCloudSpeed / 400.f; + if (mTimescaleClouds) + cloudDelta *= timeScale / 60.f; + + mCloudAnimationTimer += cloudDelta; + if (mCloudAnimationTimer >= 4.f) + mCloudAnimationTimer -= 4.f; + mNextCloudUpdater->setTextureCoord(mCloudAnimationTimer); mCloudUpdater->setTextureCoord(mCloudAnimationTimer); @@ -574,8 +584,7 @@ namespace MWRender } // rotate the stars by 360 degrees every 4 days - mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration - * osg::DegreesToRadians(360.f) / (3600 * 96.f); + mAtmosphereNightRoll += timeScale * duration * osg::DegreesToRadians(360.f) / (3600 * 96.f); if (mAtmosphereNightNode->getNodeMask() != 0) mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); mPrecipitationOccluder->update(); diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 96af2e6ec9..e0cd26f98a 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -165,6 +165,7 @@ namespace MWRender int mDay; int mMonth; + bool mTimescaleClouds; float mCloudAnimationTimer; float mRainTimer; From 23e30eaaa5d9932188e35ea3a4720f29e5160d9d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 16:39:50 +0300 Subject: [PATCH 0881/2167] Support MaxNumberRipples setting (#7795) --- CHANGELOG.md | 1 + apps/openmw/mwrender/ripplesimulation.cpp | 35 +++++++++++++++++++++-- apps/openmw/mwrender/ripplesimulation.hpp | 2 ++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e162ded2d..3f4106770f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,6 +174,7 @@ Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Feature #7792: Support Timescale Clouds + Feature #7795: Support MaxNumberRipples INI setting Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index abfb7ba9cd..b7661ca664 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -80,6 +80,25 @@ namespace node->setStateSet(stateset); } + + int findOldestParticleAlive(const osgParticle::ParticleSystem* partsys) + { + int oldest = -1; + float oldestAge = 0.f; + for (int i = 0; i < partsys->numParticles(); ++i) + { + const osgParticle::Particle* particle = partsys->getParticle(i); + if (!particle->isAlive()) + continue; + const float age = particle->getAge(); + if (oldest == -1 || age > oldestAge) + { + oldest = i; + oldestAge = age; + } + } + return oldest; + } } namespace MWRender @@ -87,6 +106,7 @@ namespace MWRender RippleSimulation::RippleSimulation(osg::Group* parent, Resource::ResourceSystem* resourceSystem) : mParent(parent) + , mMaxNumberRipples(Fallback::Map::getInt("Water_MaxNumberRipples")) { mParticleSystem = new osgParticle::ParticleSystem; @@ -159,9 +179,6 @@ namespace MWRender currentPos.z() = mParticleNode->getPosition().z(); - if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > 500) - continue; // TODO: remove the oldest particle to make room? - emitRipple(currentPos); } } @@ -226,7 +243,19 @@ namespace MWRender } else { + if (mMaxNumberRipples == 0) + return; + osgParticle::ParticleSystem::ScopedWriteLock lock(*mParticleSystem->getReadWriteMutex()); + if (mParticleSystem->numParticles() - mParticleSystem->numDeadParticles() > mMaxNumberRipples) + { + // osgParticle::ParticleSystem design requires this to be O(N) + // However, the number of particles we'll have to go through is not large + // If the user makes the limit absurd and manages to actually hit it this could be a problem + const int oldest = findOldestParticleAlive(mParticleSystem); + if (oldest != -1) + mParticleSystem->reuseParticle(oldest); + } osgParticle::Particle* p = mParticleSystem->createParticle(nullptr); p->setPosition(osg::Vec3f(pos.x(), pos.y(), 0.f)); p->setAngle(osg::Vec3f(0, 0, Misc::Rng::rollProbability() * osg::PI * 2 - osg::PI)); diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index 1f09797bf4..10b47f5679 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -74,6 +74,8 @@ namespace MWRender std::vector mEmitters; Ripples* mRipples = nullptr; + + int mMaxNumberRipples; }; } From 48bbf0b637dc5ad001ee9e989a03e06cbc6a7200 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 19:09:52 +0300 Subject: [PATCH 0882/2167] Editor: Don't complain about body part references in Verify --- apps/opencs/model/tools/referencecheck.cpp | 8 ++++++-- apps/opencs/model/tools/referencecheck.hpp | 5 ++++- apps/opencs/model/tools/tools.cpp | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 511458b946..33c2168ef3 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -18,16 +18,18 @@ #include #include +#include #include CSMTools::ReferenceCheckStage::ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, const CSMWorld::IdCollection& cells, - const CSMWorld::IdCollection& factions) + const CSMWorld::IdCollection& factions, const CSMWorld::IdCollection& bodyparts) : mReferences(references) , mObjects(referencables) , mDataSet(referencables.getDataSet()) , mCells(cells) , mFactions(factions) + , mBodyParts(bodyparts) { mIgnoreBaseRecords = false; } @@ -49,9 +51,11 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message else { // Check for non existing referenced object - if (mObjects.searchId(cellRef.mRefID) == -1) + if (mObjects.searchId(cellRef.mRefID) == -1 && mBodyParts.searchId(cellRef.mRefID) == -1) + { messages.add(id, "Instance of a non-existent object '" + cellRef.mRefID.getRefIdString() + "'", "", CSMDoc::Message::Severity_Error); + } else { // Check if reference charge is valid for it's proper referenced type diff --git a/apps/opencs/model/tools/referencecheck.hpp b/apps/opencs/model/tools/referencecheck.hpp index 04cca2b803..5f5004b912 100644 --- a/apps/opencs/model/tools/referencecheck.hpp +++ b/apps/opencs/model/tools/referencecheck.hpp @@ -8,6 +8,7 @@ namespace ESM { + struct BodyPart; struct Faction; } @@ -29,7 +30,8 @@ namespace CSMTools { public: ReferenceCheckStage(const CSMWorld::RefCollection& references, const CSMWorld::RefIdCollection& referencables, - const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions); + const CSMWorld::IdCollection& cells, const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& bodyparts); void perform(int stage, CSMDoc::Messages& messages) override; int setup() override; @@ -40,6 +42,7 @@ namespace CSMTools const CSMWorld::RefIdData& mDataSet; const CSMWorld::IdCollection& mCells; const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mBodyParts; bool mIgnoreBaseRecords; }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e1e7a0dcd8..04548ca4cf 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -105,8 +105,8 @@ CSMDoc::OperationHolder* CSMTools::Tools::getVerifier() mData.getFactions(), mData.getScripts(), mData.getResources(CSMWorld::UniversalId::Type_Meshes), mData.getResources(CSMWorld::UniversalId::Type_Icons), mData.getBodyParts())); - mVerifierOperation->appendStage(new ReferenceCheckStage( - mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); + mVerifierOperation->appendStage(new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), + mData.getCells(), mData.getFactions(), mData.getBodyParts())); mVerifierOperation->appendStage(new ScriptCheckStage(mDocument)); From 70a0b7ea9c5543bf81ddb7d51c892e90c77f91d6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 26 Jan 2024 19:23:03 +0300 Subject: [PATCH 0883/2167] Editor: Drop zero attribute warnings --- apps/opencs/model/tools/referenceablecheck.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index d25568fd0a..a692bc10d1 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -691,15 +691,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck( return; } } - else if (npc.mNpdt.mHealth != 0) - { - for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i) - { - if (npc.mNpdt.mAttributes[i] == 0) - messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {}, - CSMDoc::Message::Severity_Warning); - } - } if (level <= 0) messages.add(id, "Level is non-positive", "", CSMDoc::Message::Severity_Warning); From daa9c5f0e111f9938e9b2066ac38b7d8f73e8bac Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 26 Jan 2024 20:37:04 +0000 Subject: [PATCH 0884/2167] Lua: Add water level to Core.Cell --- apps/openmw/mwlua/cellbindings.cpp | 7 +++++++ files/lua_api/openmw/core.lua | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 081df13a0e..8eb08dbbd0 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -115,6 +115,13 @@ namespace MWLua return cell == c.mStore || (cell->getCell()->getWorldSpace() == c.mStore->getCell()->getWorldSpace()); }; + cellT["waterLevel"] = sol::readonly_property([](const CellT& c) -> sol::optional { + if (c.mStore->getCell()->hasWater()) + return c.mStore->getWaterLevel(); + else + return sol::nullopt; + }); + if constexpr (std::is_same_v) { // only for global scripts cellT["getAll"] = [ids = getPackageToTypeTable(context.mLua->sol())]( diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 7ea3c75f1c..dae5fc0594 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -297,6 +297,7 @@ -- @field #number gridY Index of the cell by Y (only for exteriors). -- @field #string worldSpaceId Id of the world space. -- @field #boolean hasWater True if the cell contains water. +-- @field #number waterLevel The water level of the cell. (nil if cell has no water). -- @field #boolean hasSky True if in this cell sky should be rendered. --- From a94add741e416feb7ddbde9348fca405f57939b3 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 26 Jan 2024 21:39:33 +0000 Subject: [PATCH 0885/2167] Lua: Animation bindings --- apps/openmw/CMakeLists.txt | 6 +- apps/openmw/mwbase/luamanager.hpp | 7 +- apps/openmw/mwbase/mechanicsmanager.hpp | 29 +- apps/openmw/mwlua/animationbindings.cpp | 365 ++++++++++++++++++ apps/openmw/mwlua/animationbindings.hpp | 12 + apps/openmw/mwlua/engineevents.cpp | 9 + apps/openmw/mwlua/engineevents.hpp | 9 +- apps/openmw/mwlua/localscripts.cpp | 8 +- apps/openmw/mwlua/localscripts.hpp | 10 + apps/openmw/mwlua/luabindings.cpp | 3 + apps/openmw/mwlua/luamanagerimp.cpp | 44 +++ apps/openmw/mwlua/luamanagerimp.hpp | 4 + apps/openmw/mwlua/magicbindings.cpp | 11 + apps/openmw/mwmechanics/activespells.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 25 ++ apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/character.cpp | 229 ++++++----- apps/openmw/mwmechanics/character.hpp | 15 +- .../mwmechanics/mechanicsmanagerimp.cpp | 23 ++ .../mwmechanics/mechanicsmanagerimp.hpp | 4 + apps/openmw/mwmechanics/objects.cpp | 24 ++ apps/openmw/mwmechanics/objects.hpp | 4 + apps/openmw/mwmechanics/spellcasting.cpp | 8 +- apps/openmw/mwmechanics/spelleffects.cpp | 14 +- apps/openmw/mwmechanics/summoning.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 95 +++-- apps/openmw/mwrender/animation.hpp | 78 +--- apps/openmw/mwrender/animationpriority.hpp | 42 ++ apps/openmw/mwrender/blendmask.hpp | 22 ++ apps/openmw/mwrender/bonegroup.hpp | 16 + apps/openmw/mwrender/characterpreview.cpp | 6 +- apps/openmw/mwrender/sky.cpp | 11 +- components/esm3/loadmgef.cpp | 2 +- components/resource/scenemanager.cpp | 6 +- components/resource/scenemanager.hpp | 6 +- docs/source/luadoc_data_paths.sh | 1 + docs/source/reference/lua-scripting/api.rst | 2 + .../lua-scripting/interface_animation.rst | 8 + .../lua-scripting/openmw_animation.rst | 7 + .../lua-scripting/tables/interfaces.rst | 3 + .../lua-scripting/tables/packages.rst | 2 + files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + .../omw/mechanics/animationcontroller.lua | 145 +++++++ files/lua_api/openmw/animation.lua | 255 ++++++++++++ files/lua_api/openmw/core.lua | 25 ++ files/lua_api/openmw/interfaces.lua | 3 + 47 files changed, 1380 insertions(+), 228 deletions(-) create mode 100644 apps/openmw/mwlua/animationbindings.cpp create mode 100644 apps/openmw/mwlua/animationbindings.hpp create mode 100644 apps/openmw/mwrender/animationpriority.hpp create mode 100644 apps/openmw/mwrender/blendmask.hpp create mode 100644 apps/openmw/mwrender/bonegroup.hpp create mode 100644 docs/source/reference/lua-scripting/interface_animation.rst create mode 100644 docs/source/reference/lua-scripting/openmw_animation.rst create mode 100644 files/data/scripts/omw/mechanics/animationcontroller.lua create mode 100644 files/lua_api/openmw/animation.lua diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 373de3683d..566aedfff0 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -24,7 +24,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples - actorutil distortion + actorutil distortion animationpriority bonegroup blendmask ) add_openmw_dir (mwinput @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings itemdata types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal - worker magicbindings factionbindings classbindings + worker magicbindings factionbindings classbindings animationbindings ) add_openmw_dir (mwsound @@ -109,7 +109,7 @@ add_openmw_dir (mwstate add_openmw_dir (mwbase environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager - inputmanager windowmanager statemanager + inputmanager windowmanager statemanager luamanager ) # Main executable diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index f3cea83224..0503fcec9e 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -8,6 +8,7 @@ #include #include "../mwgui/mode.hpp" +#include "../mwrender/animationpriority.hpp" #include namespace MWWorld @@ -60,10 +61,14 @@ namespace MWBase virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; + virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; + virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, + const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, + std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; - // `arg` is either forwarded from MWGui::pushGuiMode or empty virtual void uiModeChanged(const MWWorld::Ptr& arg) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 9e99a37ec7..532100af7a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -9,6 +9,7 @@ #include #include "../mwmechanics/greetingstate.hpp" +#include "../mwrender/animationpriority.hpp" #include "../mwworld/ptr.hpp" @@ -170,16 +171,33 @@ namespace MWBase ///< Forces an object to refresh its animation state. virtual bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool persist = false) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. /// /// \param mode 0 normal, 1 immediate start, 2 immediate loop - /// \param count How many times the animation should be run - /// \param persist Whether the animation state should be stored in saved games - /// and persist after cell unload. + /// \param number How many times the animation should be run + /// \param scripted Whether the animation should be treated as a scripted animation. /// \return Success or error + virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop) + = 0; + ///< Lua variant of playAnimationGroup. The mode parameter is omitted + /// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and + /// setting the startKey. + /// + /// \param number How many times the animation should be run + /// \param speed How fast to play the animation, where 1.f = normal speed + /// \param startKey Which textkey to start the animation from + /// \param stopKey Which textkey to stop the animation on + /// \param forceLoop Force the animation to be looping, even if it's normally not looping. + /// \param blendMask See MWRender::Animation::BlendMask + /// \param scripted Whether the animation should be treated as as scripted animation + /// \return Success or error + /// + + virtual void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) = 0; virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; ///< Skip the animation for the given MW-reference for one frame. Calls to this function for @@ -192,6 +210,9 @@ namespace MWBase /// Save the current animation state of managed references to their RefData. virtual void persistAnimationStates() = 0; + /// Clear out the animation queue, and cancel any animation currently playing from the queue + virtual void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) = 0; + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp new file mode 100644 index 0000000000..272685dc11 --- /dev/null +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/character.hpp" + +#include "../mwworld/esmstore.hpp" + +#include "context.hpp" +#include "luamanagerimp.hpp" +#include "objectvariant.hpp" + +#include "animationbindings.hpp" +#include + +namespace MWLua +{ + struct AnimationGroup; + struct TextKeyCallback; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + using BlendMask = MWRender::Animation::BlendMask; + using BoneGroup = MWRender::Animation::BoneGroup; + using Priority = MWMechanics::Priority; + using AnimationPriorities = MWRender::Animation::AnimPriority; + + MWWorld::Ptr getMutablePtrOrThrow(const ObjectVariant& variant) + { + if (variant.isLObject()) + throw std::runtime_error("Local scripts can only modify animations of the object they are attached to."); + + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + if (!ptr.getRefData().isEnabled()) + throw std::runtime_error("Can't use a disabled object"); + + return ptr; + } + + MWWorld::Ptr getPtrOrThrow(const ObjectVariant& variant) + { + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + + MWRender::Animation* getMutableAnimationOrThrow(const ObjectVariant& variant) + { + MWWorld::Ptr ptr = getMutablePtrOrThrow(variant); + auto world = MWBase::Environment::get().getWorld(); + MWRender::Animation* anim = world->getAnimation(ptr); + if (!anim) + throw std::runtime_error("Object has no animation"); + return anim; + } + + const MWRender::Animation* getConstAnimationOrThrow(const ObjectVariant& variant) + { + MWWorld::Ptr ptr = getPtrOrThrow(variant); + auto world = MWBase::Environment::get().getWorld(); + const MWRender::Animation* anim = world->getAnimation(ptr); + if (!anim) + throw std::runtime_error("Object has no animation"); + return anim; + } + + const ESM::Static* getStatic(const sol::object& staticOrID) + { + if (staticOrID.is()) + return staticOrID.as(); + else + { + ESM::RefId id = ESM::RefId::deserializeText(LuaUtil::cast(staticOrID)); + return MWBase::Environment::get().getWorld()->getStore().get().find(id); + } + } + + std::string getStaticModelOrThrow(const sol::object& staticOrID) + { + const ESM::Static* static_ = getStatic(staticOrID); + if (!static_) + throw std::runtime_error("Invalid static"); + + return Misc::ResourceHelpers::correctMeshPath(static_->mModel); + } + + static AnimationPriorities getPriorityArgument(const sol::table& args) + { + auto asPriorityEnum = args.get>("priority"); + if (asPriorityEnum) + return asPriorityEnum.value(); + + auto asTable = args.get>("priority"); + if (asTable) + { + AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default); + for (auto entry : asTable.value()) + { + if (!entry.first.is() || !entry.second.is()) + throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only"); + auto group = entry.first.as(); + auto priority = entry.second.as(); + if (group < 0 || group >= BoneGroup::Num_BoneGroups) + throw std::runtime_error("Invalid bonegroup: " + std::to_string(group)); + priorities[group] = priority; + } + + return priorities; + } + + return Priority::Priority_Default; + } + + sol::table initAnimationPackage(const Context& context) + { + auto* lua = context.mLua; + auto mechanics = MWBase::Environment::get().getMechanicsManager(); + auto world = MWBase::Environment::get().getWorld(); + + sol::table api(lua->sol(), sol::create); + + api["PRIORITY"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Default", MWMechanics::Priority::Priority_Default }, + { "WeaponLowerBody", MWMechanics::Priority::Priority_WeaponLowerBody }, + { "SneakIdleLowerBody", MWMechanics::Priority::Priority_SneakIdleLowerBody }, + { "SwimIdle", MWMechanics::Priority::Priority_SwimIdle }, + { "Jump", MWMechanics::Priority::Priority_Jump }, + { "Movement", MWMechanics::Priority::Priority_Movement }, + { "Hit", MWMechanics::Priority::Priority_Hit }, + { "Weapon", MWMechanics::Priority::Priority_Weapon }, + { "Block", MWMechanics::Priority::Priority_Block }, + { "Knockdown", MWMechanics::Priority::Priority_Knockdown }, + { "Torch", MWMechanics::Priority::Priority_Torch }, + { "Storm", MWMechanics::Priority::Priority_Storm }, + { "Death", MWMechanics::Priority::Priority_Death }, + { "Scripted", MWMechanics::Priority::Priority_Scripted }, + })); + + api["BLEND_MASK"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "LowerBody", BlendMask::BlendMask_LowerBody }, + { "Torso", BlendMask::BlendMask_Torso }, + { "LeftArm", BlendMask::BlendMask_LeftArm }, + { "RightArm", BlendMask::BlendMask_RightArm }, + { "UpperBody", BlendMask::BlendMask_UpperBody }, + { "All", BlendMask::BlendMask_All }, + })); + + api["BONE_GROUP"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "LowerBody", BoneGroup::BoneGroup_LowerBody }, + { "Torso", BoneGroup::BoneGroup_Torso }, + { "LeftArm", BoneGroup::BoneGroup_LeftArm }, + { "RightArm", BoneGroup::BoneGroup_RightArm }, + })); + + api["hasAnimation"] = [world](const sol::object& object) -> bool { + return world->getAnimation(getPtrOrThrow(ObjectVariant(object))) != nullptr; + }; + + // equivalent to MWScript's SkipAnim + api["skipAnimationThisFrame"] = [mechanics](const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + // This sets a flag that is only used during the update pass, so + // there's no need to queue + mechanics->skipAnimation(ptr); + }; + + api["getTextKeyTime"] = [](const sol::object& object, std::string_view key) -> sol::optional { + float time = getConstAnimationOrThrow(ObjectVariant(object))->getTextKeyTime(key); + if (time >= 0.f) + return time; + return sol::nullopt; + }; + api["isPlaying"] = [](const sol::object& object, std::string_view groupname) { + return getConstAnimationOrThrow(ObjectVariant(object))->isPlaying(groupname); + }; + api["getCurrentTime"] = [](const sol::object& object, std::string_view groupname) -> sol::optional { + float time = getConstAnimationOrThrow(ObjectVariant(object))->getCurrentTime(groupname); + if (time >= 0.f) + return time; + return sol::nullopt; + }; + api["isLoopingAnimation"] = [](const sol::object& object, std::string_view groupname) { + return getConstAnimationOrThrow(ObjectVariant(object))->isLoopingAnimation(groupname); + }; + api["cancel"] = [](const sol::object& object, std::string_view groupname) { + return getMutableAnimationOrThrow(ObjectVariant(object))->disable(groupname); + }; + api["setLoopingEnabled"] = [](const sol::object& object, std::string_view groupname, bool enabled) { + return getMutableAnimationOrThrow(ObjectVariant(object))->setLoopingEnabled(groupname, enabled); + }; + // MWRender::Animation::getInfo can also return the current speed multiplier, but this is never used. + api["getCompletion"] = [](const sol::object& object, std::string_view groupname) -> sol::optional { + float completion = 0.f; + if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, &completion)) + return completion; + return sol::nullopt; + }; + api["getLoopCount"] = [](const sol::object& object, std::string groupname) -> sol::optional { + size_t loops = 0; + if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, nullptr, &loops)) + return loops; + return sol::nullopt; + }; + api["getSpeed"] = [](const sol::object& object, std::string groupname) -> sol::optional { + float speed = 0.f; + if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, &speed, nullptr)) + return speed; + return sol::nullopt; + }; + api["setSpeed"] = [](const sol::object& object, std::string groupname, float speed) { + getMutableAnimationOrThrow(ObjectVariant(object))->adjustSpeedMult(groupname, speed); + }; + api["getActiveGroup"] = [](const sol::object& object, MWRender::BoneGroup boneGroup) -> std::string_view { + return getConstAnimationOrThrow(ObjectVariant(object))->getActiveGroup(boneGroup); + }; + + // Clears out the animation queue, and cancel any animation currently playing from the queue + api["clearAnimationQueue"] = [mechanics](const sol::object& object, bool clearScripted) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + mechanics->clearAnimationQueue(ptr, clearScripted); + }; + + // Extended variant of MWScript's PlayGroup and LoopGroup + api["playQueued"] = sol::overload( + [mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) { + int numberOfLoops = options.get_or("loops", std::numeric_limits::max()); + float speed = options.get_or("speed", 1.f); + std::string startKey = options.get_or("startkey", "start"); + std::string stopKey = options.get_or("stopkey", "stop"); + bool forceLoop = options.get_or("forceloop", false); + + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + mechanics->playAnimationGroupLua(ptr, groupname, numberOfLoops, speed, startKey, stopKey, forceLoop); + }, + [mechanics](const sol::object& object, const std::string& groupname) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + mechanics->playAnimationGroupLua( + ptr, groupname, std::numeric_limits::max(), 1, "start", "stop", false); + }); + + api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) { + int loops = options.get_or("loops", 0); + MWRender::Animation::AnimPriority priority = getPriorityArgument(options); + BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All); + bool autoDisable = options.get_or("autodisable", true); + float speed = options.get_or("speed", 1.0f); + std::string start = options.get_or("startkey", "start"); + std::string stop = options.get_or("stopkey", "stop"); + float startpoint = options.get_or("startpoint", 0.0f); + bool forceLoop = options.get_or("forceloop", false); + + auto animation = getMutableAnimationOrThrow(ObjectVariant(object)); + animation->play(groupname, priority, blendMask, autoDisable, speed, start, stop, startpoint, loops, + forceLoop || animation->isLoopingAnimation(groupname)); + }; + + api["hasGroup"] = [](const sol::object& object, std::string_view groupname) -> bool { + const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); + return anim->hasAnimation(groupname); + }; + + // Note: This checks the nodemap, and does not read the scene graph itself, and so should be thread safe. + api["hasBone"] = [](const sol::object& object, std::string_view bonename) -> bool { + const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); + return anim->getNode(bonename) != nullptr; + }; + + api["addVfx"] = sol::overload( + [context](const sol::object& object, const sol::object& staticOrID) { + context.mLuaManager->addAction( + [object = ObjectVariant(object), model = getStaticModelOrThrow(staticOrID)] { + MWRender::Animation* anim = getMutableAnimationOrThrow(object); + anim->addEffect(model, ""); + }, + "addVfxAction"); + }, + [context](const sol::object& object, const sol::object& staticOrID, const sol::table& options) { + context.mLuaManager->addAction( + [object = ObjectVariant(object), model = getStaticModelOrThrow(staticOrID), + effectId = options.get_or("vfxId", ""), loop = options.get_or("loop", false), + bonename = options.get_or("bonename", ""), + particleTexture = options.get_or("particleTextureOverride", "")] { + MWRender::Animation* anim = getMutableAnimationOrThrow(ObjectVariant(object)); + + anim->addEffect(model, effectId, loop, bonename, particleTexture); + }, + "addVfxAction"); + }); + + api["removeVfx"] = [context](const sol::object& object, std::string_view effectId) { + context.mLuaManager->addAction( + [object = ObjectVariant(object), effectId = std::string(effectId)] { + MWRender::Animation* anim = getMutableAnimationOrThrow(object); + anim->removeEffect(effectId); + }, + "removeVfxAction"); + }; + + api["removeAllVfx"] = [context](const sol::object& object) { + context.mLuaManager->addAction( + [object = ObjectVariant(object)] { + MWRender::Animation* anim = getMutableAnimationOrThrow(object); + anim->removeEffects(); + }, + "removeVfxAction"); + }; + + return LuaUtil::makeReadOnly(api); + } + + sol::table initCoreVfxBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table api(lua, sol::create); + auto world = MWBase::Environment::get().getWorld(); + + api["spawn"] = sol::overload( + [world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos) { + auto model = getStaticModelOrThrow(staticOrID); + context.mLuaManager->addAction( + [world, model, worldPos]() { world->spawnEffect(model, "", worldPos); }, "openmw.vfx.spawn"); + }, + [world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos, const sol::table& options) { + auto model = getStaticModelOrThrow(staticOrID); + + bool magicVfx = options.get_or("mwMagicVfx", true); + std::string textureOverride = options.get_or("particleTextureOverride", ""); + float scale = options.get_or("scale", 1.f); + + context.mLuaManager->addAction( + [world, model, textureOverride, worldPos, scale, magicVfx]() { + world->spawnEffect(model, textureOverride, worldPos, scale, magicVfx); + }, + "openmw.vfx.spawn"); + }); + + return api; + } +} diff --git a/apps/openmw/mwlua/animationbindings.hpp b/apps/openmw/mwlua/animationbindings.hpp new file mode 100644 index 0000000000..d28dda9208 --- /dev/null +++ b/apps/openmw/mwlua/animationbindings.hpp @@ -0,0 +1,12 @@ +#ifndef MWLUA_ANIMATIONBINDINGS_H +#define MWLUA_ANIMATIONBINDINGS_H + +#include + +namespace MWLua +{ + sol::table initAnimationPackage(const Context& context); + sol::table initCoreVfxBindings(const Context& context); +} + +#endif // MWLUA_ANIMATIONBINDINGS_H diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 0fbb13f1cf..43507ff1a5 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -86,6 +86,15 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } + void operator()(const OnAnimationTextKey& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onAnimationTextKey(event.mGroupname, event.mKey); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 7c706edcd0..bf8d219fd5 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -51,7 +51,14 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - using Event = std::variant; + struct OnAnimationTextKey + { + ESM::RefNum mActor; + std::string mGroupname; + std::string mKey; + }; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8cf383e985..1d5e710869 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -3,6 +3,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/aicombat.hpp" #include "../mwmechanics/aiescort.hpp" #include "../mwmechanics/aifollow.hpp" @@ -162,6 +164,10 @@ namespace MWLua MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr, cancelOther); }; + selfAPI["_enableLuaAnimations"] = [](SelfObject& self, bool enable) { + const MWWorld::Ptr& ptr = self.ptr(); + MWBase::Environment::get().getMechanicsManager()->enableLuaAnimations(ptr, enable); + }; } LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) @@ -170,7 +176,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index b87b628a89..230ec93d3c 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -71,6 +71,14 @@ namespace MWLua void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); } + void onAnimationTextKey(std::string_view groupname, std::string_view key) + { + callEngineHandlers(mOnAnimationTextKeyHandlers, groupname, key); + } + void onPlayAnimation(std::string_view groupname, const sol::table& options) + { + callEngineHandlers(mOnPlayAnimationHandlers, groupname, options); + } void applyStatsCache(); @@ -83,6 +91,8 @@ namespace MWLua EngineHandlerList mOnConsumeHandlers{ "onConsume" }; EngineHandlerList mOnActivatedHandlers{ "onActivated" }; EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; + EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; + EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; }; } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 48f2f3e35d..4da34bf9d4 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -35,6 +35,7 @@ #include "mwscriptbindings.hpp" #include "objectlists.hpp" +#include "animationbindings.hpp" #include "camerabindings.hpp" #include "cellbindings.hpp" #include "debugbindings.hpp" @@ -147,6 +148,7 @@ namespace MWLua }; api["contentFiles"] = initContentFilesBindings(lua->sol()); api["sound"] = initCoreSoundBindings(context); + api["vfx"] = initCoreVfxBindings(context); api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); for (size_t i = 0; i < contentList.size(); ++i) @@ -330,6 +332,7 @@ namespace MWLua sol::state_view lua = context.mLua->sol(); MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager(); return { + { "openmw.animation", initAnimationPackage(context) }, { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2417e9e340..89402a6008 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -23,6 +23,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwrender/bonegroup.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwworld/datetimemanager.hpp" @@ -362,6 +363,49 @@ namespace MWLua mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); } + void LuaManager::animationTextKey(const MWWorld::Ptr& actor, const std::string& key) + { + auto pos = key.find(": "); + if (pos != std::string::npos) + mEngineEvents.addToQueue( + EngineEvents::OnAnimationTextKey{ getId(actor), key.substr(0, pos), key.substr(pos + 2) }); + } + + void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, + const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, + std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + { + sol::table options = mLua.newTable(); + options["blendmask"] = blendMask; + options["autodisable"] = autodisable; + options["speed"] = speedmult; + options["startkey"] = start; + options["stopkey"] = stop; + options["startpoint"] = startpoint; + options["loops"] = loops; + options["forceloop"] = loopfallback; + + bool priorityAsTable = false; + for (uint32_t i = 1; i < MWRender::sNumBlendMasks; i++) + if (priority[static_cast(i)] != priority[static_cast(0)]) + priorityAsTable = true; + if (priorityAsTable) + { + sol::table priorityTable = mLua.newTable(); + for (uint32_t i = 0; i < MWRender::sNumBlendMasks; i++) + priorityTable[static_cast(i)] = priority[static_cast(i)]; + options["priority"] = priorityTable; + } + else + options["priority"] = priority[MWRender::BoneGroup_LowerBody]; + + // mEngineEvents.addToQueue(event); + // Has to be called immediately, otherwise engine details that depend on animations playing immediately + // break. + if (auto* scripts = actor.getRefData().getLuaScripts()) + scripts->onPlayAnimation(groupname, options); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 8bd189d8e9..7556abad5d 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -79,6 +79,10 @@ namespace MWLua mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override; + void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override; + void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, + const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, + std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 6e35776cba..1e3cb2ab69 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -389,6 +389,17 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); + magicEffectT["particle"] + = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return rec.mParticle; }); + magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool { + return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + }); + magicEffectT["castingStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); }); + magicEffectT["hitStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); }); + magicEffectT["areaStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return MWBase::Environment::get() .getWorld() diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d8e409d9e2..a9c669fce5 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -324,7 +324,7 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation && !reflectStatic->mModel.empty()) animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel), - ESM::MagicEffect::Reflect, false); + ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false); caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); } if (removedSpell) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index bc3cc3bb6a..92f8a212c9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2019,6 +2019,24 @@ namespace MWMechanics return false; } } + + bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->getCharacterController().playGroupLua( + groupName, speed, startKey, stopKey, loops, forceLoop); + return false; + } + + void Actors::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->getCharacterController().enableLuaAnimations(enable); + } + void Actors::skipAnimation(const MWWorld::Ptr& ptr) const { const auto iter = mIndex.find(ptr.mRef); @@ -2048,6 +2066,13 @@ namespace MWMechanics actor.getCharacterController().persistAnimationState(); } + void Actors::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->getCharacterController().clearAnimQueue(clearScripted); + } + void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const { for (const Actor& actor : mActors) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7c676ca018..3ead5f069a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -114,10 +114,14 @@ namespace MWMechanics bool playAnimationGroup( const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop); + void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const; void persistAnimationStates() const; + void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f1bec8ce9d..5533cb578b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -36,6 +36,7 @@ #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -270,7 +271,7 @@ namespace case CharState_IdleSwim: return Priority_SwimIdle; case CharState_IdleSneak: - priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + priority[MWRender::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; [[fallthrough]]; default: return priority; @@ -444,8 +445,8 @@ namespace MWMechanics { mHitState = CharState_Block; priority = Priority_Hit; - priority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + priority[MWRender::BoneGroup_LeftArm] = Priority_Block; + priority[MWRender::BoneGroup_LowerBody] = Priority_WeaponLowerBody; startKey = "block start"; stopKey = "block stop"; } @@ -482,8 +483,7 @@ namespace MWMechanics return; } - mAnimation->play( - mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); + playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); } void CharacterController::refreshJumpAnims(JumpingState jump, bool force) @@ -502,7 +502,7 @@ namespace MWMechanics std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); std::string jumpAnimName = "jump"; jumpAnimName += weapShortGroup; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + MWRender::Animation::BlendMask jumpmask = MWRender::BlendMask_All; if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName)) jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); @@ -520,10 +520,10 @@ namespace MWMechanics mCurrentJump = jumpAnimName; if (mJumpState == JumpState_InAir) - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start", - "stop", 0.f, ~0ul); + playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, + startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); else if (mJumpState == JumpState_Landing) - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); + playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } bool CharacterController::onOpen() const @@ -539,8 +539,8 @@ namespace MWMechanics if (mAnimation->isPlaying("containerclose")) return false; - mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", 0.f, 0); + mAnimation->play( + "containeropen", Priority_Scripted, MWRender::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; } @@ -560,8 +560,8 @@ namespace MWMechanics if (animPlaying) startPoint = 1.f - complete; - mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", startPoint, 0); + mAnimation->play("containerclose", Priority_Scripted, MWRender::BlendMask_All, false, 1.0f, "start", "stop", + startPoint, 0); } } @@ -600,7 +600,7 @@ namespace MWMechanics if (!isRealWeapon(mWeaponType)) { if (blendMask != nullptr) - *blendMask = MWRender::Animation::BlendMask_LowerBody; + *blendMask = MWRender::BlendMask_LowerBody; return baseGroupName; } @@ -619,13 +619,13 @@ namespace MWMechanics // Special case for crossbows - we should apply 1h animations a fallback only for lower body if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) - *blendMask = MWRender::Animation::BlendMask_LowerBody; + *blendMask = MWRender::BlendMask_LowerBody; if (!mAnimation->hasAnimation(groupName)) { groupName = baseGroupName; if (blendMask != nullptr) - *blendMask = MWRender::Animation::BlendMask_LowerBody; + *blendMask = MWRender::BlendMask_LowerBody; } return groupName; @@ -658,7 +658,7 @@ namespace MWMechanics } } - MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + MWRender::Animation::BlendMask movemask = MWRender::BlendMask_All; std::string_view weapShortGroup = getWeaponShortGroup(mWeaponType); @@ -749,7 +749,7 @@ namespace MWMechanics } } - mAnimation->play( + playBlendedAnimation( mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); } @@ -821,8 +821,8 @@ namespace MWMechanics clearStateAnimation(mCurrentIdle); mCurrentIdle = std::move(idleGroup); - mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", - startPoint, numLoops, true); + playBlendedAnimation( + mCurrentIdle, priority, MWRender::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); } void CharacterController::refreshCurrentAnims( @@ -855,8 +855,8 @@ namespace MWMechanics resetCurrentIdleState(); resetCurrentJumpState(); - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, false, 1.0f, "start", - "stop", startpoint, 0); + playBlendedAnimation( + mCurrentDeath, Priority_Death, MWRender::BlendMask_All, false, 1.0f, "start", "stop", startpoint, 0); } CharacterState CharacterController::chooseRandomDeathState() const @@ -998,6 +998,8 @@ namespace MWMechanics { std::string_view evt = key->second; + MWBase::Environment::get().getLuaManager()->animationTextKey(mPtr, key->second); + if (evt.substr(0, 7) == "sound: ") { MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1189,8 +1191,9 @@ namespace MWMechanics { if (!animPlaying) { - int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; - mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); + int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm; + playBlendedAnimation( + "idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else { @@ -1247,41 +1250,6 @@ namespace MWMechanics } } - bool CharacterController::isLoopingAnimation(std::string_view group) const - { - // In Morrowind, a some animation groups are always considered looping, regardless - // of loop start/stop keys. - // To be match vanilla behavior we probably only need to check this list, but we don't - // want to prevent modded animations with custom group names from looping either. - static const std::unordered_set loopingAnimations = { "walkforward", "walkback", "walkleft", - "walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback", - "runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward", - "sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright", - "spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7", - "idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand", - "inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" }; - static const std::vector shortGroups = getAllWeaponTypeShortGroups(); - - if (mAnimation && mAnimation->getTextKeyTime(std::string(group) + ": loop start") >= 0) - return true; - - // Most looping animations have variants for each weapon type shortgroup. - // Just remove the shortgroup instead of enumerating all of the possible animation groupnames. - // Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow" - // when the shortgroup is crossbow. - std::size_t suffixLength = 0; - for (std::string_view suffix : shortGroups) - { - if (suffix.length() > suffixLength && group.ends_with(suffix)) - { - suffixLength = suffix.length(); - } - } - group.remove_suffix(suffixLength); - - return loopingAnimations.count(group) > 0; - } - bool CharacterController::updateWeaponState() { // If the current animation is scripted, we can't do anything here. @@ -1357,8 +1325,8 @@ namespace MWMechanics if (mAnimation->isPlaying("shield")) mAnimation->disable("shield"); - mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, false, 1.0f, "start", - "stop", 0.0f, std::numeric_limits::max(), true); + playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop", + 0.0f, std::numeric_limits::max(), true); } else if (mAnimation->isPlaying("torch")) { @@ -1369,7 +1337,7 @@ namespace MWMechanics // For biped actors, blend weapon animations with lower body animations with higher priority MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); if (cls.isBipedal(mPtr)) - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + priorityWeapon[MWRender::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; @@ -1400,19 +1368,19 @@ namespace MWMechanics { // Note: we do not disable unequipping animation automatically to avoid body desync weapgroup = getWeaponAnimation(mWeaponType); - int unequipMask = MWRender::Animation::BlendMask_All; + int unequipMask = MWRender::BlendMask_All; bool useShieldAnims = mAnimation->useShieldAnimations(); if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) { - unequipMask = unequipMask | ~MWRender::Animation::BlendMask_LeftArm; - mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, 1.0f, + unequipMask = unequipMask | ~MWRender::BlendMask_LeftArm; + playBlendedAnimation("shield", Priority_Block, MWRender::BlendMask_LeftArm, true, 1.0f, "unequip start", "unequip stop", 0.0f, 0); } else if (mWeaponType == ESM::Weapon::HandToHand) mAnimation->showCarriedLeft(false); - mAnimation->play( + playBlendedAnimation( weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperBodyState::Unequipping; @@ -1458,15 +1426,15 @@ namespace MWMechanics if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); - int equipMask = MWRender::Animation::BlendMask_All; + int equipMask = MWRender::BlendMask_All; if (useShieldAnims && weaptype != ESM::Weapon::Spell) { - equipMask = equipMask | ~MWRender::Animation::BlendMask_LeftArm; - mAnimation->play("shield", Priority_Block, MWRender::Animation::BlendMask_LeftArm, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); + equipMask = equipMask | ~MWRender::BlendMask_LeftArm; + playBlendedAnimation("shield", Priority_Block, MWRender::BlendMask_LeftArm, true, 1.0f, + "equip start", "equip stop", 0.0f, 0); } - mAnimation->play( + playBlendedAnimation( weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperBodyState::Equipping; @@ -1617,11 +1585,11 @@ namespace MWMechanics if (mAnimation->getNode("Bip01 L Hand")) mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), - -1, false, "Bip01 L Hand", effect->mParticle); + "", false, "Bip01 L Hand", effect->mParticle); if (mAnimation->getNode("Bip01 R Hand")) mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), - -1, false, "Bip01 R Hand", effect->mParticle); + "", false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation const ESM::ENAMstruct& firstEffect = effects->front(); @@ -1656,9 +1624,8 @@ namespace MWMechanics startKey = mAttackType + " start"; stopKey = mAttackType + " stop"; } - - mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, - 1, startKey, stopKey, 0.0f, 0); + playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, 1, + startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperBodyState::Casting; } } @@ -1709,8 +1676,8 @@ namespace MWMechanics mAttackVictim = MWWorld::Ptr(); mAttackHitPos = osg::Vec3f(); - mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, - weapSpeed, startKey, stopKey, 0.0f, 0); + playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, weapSpeed, + startKey, stopKey, 0.0f, 0); } } @@ -1783,7 +1750,7 @@ namespace MWMechanics } mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, + playBlendedAnimation(mCurrentWeapon, priorityWeapon, MWRender::BlendMask_All, false, weapSpeed, mAttackType + " max attack", mAttackType + ' ' + hit, startPoint, 0); } @@ -1813,7 +1780,7 @@ namespace MWMechanics // Follow animations have lower priority than movement for non-biped creatures, logic be damned if (!cls.isBipedal(mPtr)) priorityFollow = Priority_Default; - mAnimation->play(mCurrentWeapon, priorityFollow, MWRender::Animation::BlendMask_All, false, weapSpeed, + playBlendedAnimation(mCurrentWeapon, priorityFollow, MWRender::BlendMask_All, false, weapSpeed, mAttackType + ' ' + start, mAttackType + ' ' + stop, 0.0f, 0); mUpperBodyState = UpperBodyState::AttackEnd; @@ -1935,9 +1902,16 @@ namespace MWMechanics mIdleState = CharState_SpecialIdle; auto priority = mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default; mAnimation->setPlayScriptedOnly(mAnimQueue.front().mScripted); - mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::Animation::BlendMask_All, false, 1.0f, - (loopStart ? "loop start" : "start"), "stop", mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount, - mAnimQueue.front().mLooping); + if (mAnimQueue.front().mScripted) + mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::BlendMask_All, false, + mAnimQueue.front().mSpeed, (loopStart ? "loop start" : mAnimQueue.front().mStartKey), + mAnimQueue.front().mStopKey, mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount, + mAnimQueue.front().mLooping); + else + playBlendedAnimation(mAnimQueue.front().mGroup, priority, MWRender::BlendMask_All, false, + mAnimQueue.front().mSpeed, (loopStart ? "loop start" : mAnimQueue.front().mStartKey), + mAnimQueue.front().mStopKey, mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount, + mAnimQueue.front().mLooping); } } @@ -2504,6 +2478,7 @@ namespace MWMechanics state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { + // TODO: Probably want to presist lua animations too if (!iter->mScripted) continue; @@ -2541,8 +2516,10 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; - entry.mScripted = true; - entry.mLooping = isLoopingAnimation(entry.mGroup); + entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup); + entry.mStartKey = "start"; + entry.mStopKey = "stop"; + entry.mSpeed = 1.f; entry.mTime = iter->mTime; if (iter->mAbsolute) { @@ -2559,6 +2536,18 @@ namespace MWMechanics } } + void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, + int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, + float startpoint, size_t loops, bool loopfallback) const + { + if (mLuaAnimations) + MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable, + speedmult, start, stop, startpoint, loops, loopfallback); + else + mAnimation->play( + groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback); + } + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) @@ -2568,7 +2557,7 @@ namespace MWMechanics if (isScriptedAnimPlaying() && !scripted) return true; - bool looping = isLoopingAnimation(groupname); + bool looping = mAnimation->isLoopingAnimation(groupname); // If this animation is a looped animation that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count @@ -2602,8 +2591,12 @@ namespace MWMechanics entry.mGroup = groupname; entry.mLoopCount = count; entry.mTime = 0.f; - entry.mScripted = scripted; + // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + entry.mScripted = (scripted && groupname != "idle"); entry.mLooping = looping; + entry.mSpeed = 1.f; + entry.mStartKey = ((mode == 2) ? "loop start" : "start"); + entry.mStopKey = "stop"; bool playImmediately = false; @@ -2618,10 +2611,6 @@ namespace MWMechanics mAnimQueue.resize(1); } - // "PlayGroup idle" is a special case, used to stop and remove scripted animations playing - if (groupname == "idle") - entry.mScripted = false; - mAnimQueue.push_back(entry); if (playImmediately) @@ -2630,6 +2619,42 @@ namespace MWMechanics return true; } + bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey, + std::string_view stopKey, int loops, bool forceLoop) + { + // Note: In mwscript, "idle" is a special case used to clear the anim queue. + // In lua we offer an explicit clear method instead so this method does not treat "idle" special. + + if (!mAnimation || !mAnimation->hasAnimation(groupname)) + return false; + + AnimationQueueEntry entry; + entry.mGroup = groupname; + // Note: MWScript gives one less loop to actors than non-actors. + // But this is the Lua version. We don't need to reproduce this weirdness here. + entry.mLoopCount = std::max(loops, 0); + entry.mStartKey = startKey; + entry.mStopKey = stopKey; + entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; + entry.mScripted = true; + entry.mSpeed = speed; + entry.mTime = 0; + + if (mAnimQueue.size() > 1) + mAnimQueue.resize(1); + mAnimQueue.push_back(entry); + + if (mAnimQueue.size() == 1) + playAnimQueue(); + + return true; + } + + void CharacterController::enableLuaAnimations(bool enable) + { + mLuaAnimations = enable; + } + void CharacterController::skipAnim() { mSkipAnim = true; @@ -2745,18 +2770,20 @@ namespace MWMechanics // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. // Stop any effects that are no longer active - std::vector effects; - mAnimation->getLoopingEffects(effects); + std::vector effects = mAnimation->getLoopingEffects(); - for (int effectId : effects) + for (std::string_view effectId : effects) { - if (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() - || mPtr.getClass() - .getCreatureStats(mPtr) - .getMagicEffects() - .getOrDefault(MWMechanics::EffectKey(effectId)) - .getMagnitude() - <= 0) + auto index = ESM::MagicEffect::indexNameToIndex(effectId); + + if (index >= 0 + && (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() + || mPtr.getClass() + .getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(MWMechanics::EffectKey(index)) + .getMagnitude() + <= 0)) mAnimation->removeEffect(effectId); } } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index dc551900b5..a507c73743 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -138,9 +138,13 @@ namespace MWMechanics float mTime; bool mLooping; bool mScripted; + std::string mStartKey; + std::string mStopKey; + float mSpeed; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; + bool mLuaAnimations{ false }; CharacterState mIdleState{ CharState_None }; std::string mCurrentIdle; @@ -209,8 +213,6 @@ namespace MWMechanics void refreshMovementAnims(CharacterState movement, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false); - void clearAnimQueue(bool clearScriptedAnims = false); - bool updateWeaponState(); void updateIdleStormState(bool inwater) const; @@ -247,8 +249,6 @@ namespace MWMechanics void prepareHit(); - bool isLoopingAnimation(std::string_view group) const; - public: CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); virtual ~CharacterController(); @@ -274,10 +274,17 @@ namespace MWMechanics void persistAnimationState() const; void unpersistAnimationState(); + void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, + bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, + size_t loops, bool loopfallback = false) const; bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); + bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, + int loops, bool forceLoop); + void enableLuaAnimations(bool enable); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; bool isScriptedAnimPlaying() const; + void clearAnimQueue(bool clearScriptedAnims = false); enum KillResult { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index c9e8e8f322..5323f7e65c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -756,6 +756,21 @@ namespace MWMechanics else return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } + bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, + float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) + { + if (ptr.getClass().isActor()) + return mActors.playAnimationGroupLua(ptr, groupName, loops, speed, startKey, stopKey, forceLoop); + else + return mObjects.playAnimationGroupLua(ptr, groupName, loops, speed, startKey, stopKey, forceLoop); + } + void MechanicsManager::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) + { + if (ptr.getClass().isActor()) + mActors.enableLuaAnimations(ptr, enable); + else + mObjects.enableLuaAnimations(ptr, enable); + } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { if (ptr.getClass().isActor()) @@ -799,6 +814,14 @@ namespace MWMechanics mObjects.persistAnimationStates(); } + void MechanicsManager::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) + { + if (ptr.getClass().isActor()) + mActors.clearAnimationQueue(ptr, clearScripted); + else + mObjects.clearAnimationQueue(ptr, clearScripted); + } + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr& ptr) { mActors.updateMagicEffects(ptr); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index f2483396fe..93c1fa3dc2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -143,10 +143,14 @@ namespace MWMechanics /// @return Success or error bool playAnimationGroup( const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop) override; + void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; bool checkScriptedAnimationPlaying(const MWWorld::Ptr& ptr) const override; void persistAnimationStates() override; + void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) override; /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 5bdfc91ac7..32d484df2f 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -113,6 +113,23 @@ namespace MWMechanics return false; } } + + bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + return iter->second->playGroupLua(groupName, speed, startKey, stopKey, loops, forceLoop); + return false; + } + + void Objects::enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->enableLuaAnimations(enable); + } + void Objects::skipAnimation(const MWWorld::Ptr& ptr) { const auto iter = mIndex.find(ptr.mRef); @@ -126,6 +143,13 @@ namespace MWMechanics object.persistAnimationState(); } + void Objects::clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted) + { + const auto iter = mIndex.find(ptr.mRef); + if (iter != mIndex.end()) + iter->second->clearAnimQueue(clearScripted); + } + void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const { for (const CharacterController& object : mObjects) diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 296f454e4f..1fe43530b0 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -47,8 +47,12 @@ namespace MWMechanics bool playAnimationGroup( const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + std::string_view startKey, std::string_view stopKey, bool forceLoop); + void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); + void clearAnimationQueue(const MWWorld::Ptr& ptr, bool clearScripted); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) const; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index e4e07b162f..0496033c70 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -552,8 +552,8 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mIndex, false, - {}, effect->mParticle); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + ESM::MagicEffect::indexToName(effect->mIndex), false, {}, effect->mParticle); } else { @@ -626,8 +626,8 @@ namespace MWMechanics { // Don't play particle VFX unless the effect is new or it should be looping. if (playNonLooping || loop) - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), magicEffect.mIndex, loop, - {}, magicEffect.mParticle); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + ESM::MagicEffect::indexToName(magicEffect.mIndex), loop, {}, magicEffect.mParticle); } } } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index e7146f3e7a..ebf9933cfc 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -285,8 +285,8 @@ namespace const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation && !absorbStatic->mModel.empty()) - animation->addEffect( - Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::SpellAbsorption, false); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), + ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; if (spell) @@ -455,11 +455,11 @@ namespace MWMechanics if (!caster.isEmpty()) { MWRender::Animation* anim = world->getAnimation(caster); - anim->removeEffect(effect.mEffectId); + anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); if (fx) - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), ""); } } else if (caster == getPlayer()) @@ -490,7 +490,7 @@ namespace MWMechanics if (!caster.isEmpty()) { MWRender::Animation* anim = world->getAnimation(caster); - anim->removeEffect(effect.mEffectId); + anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); } } } @@ -1045,7 +1045,7 @@ namespace MWMechanics effect.mFlags |= ESM::ActiveEffect::Flag_Remove; auto anim = world->getAnimation(target); if (anim) - anim->removeEffect(effect.mEffectId); + anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); } else effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; @@ -1287,7 +1287,7 @@ namespace MWMechanics { auto anim = MWBase::Environment::get().getWorld()->getAnimation(target); if (anim) - anim->removeEffect(effect.mEffectId); + anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); } } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 85a8d971a9..e4b9e953aa 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -105,7 +105,7 @@ namespace MWMechanics const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_Start")); if (fx) - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1, false); + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", false); } } catch (std::exception& e) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index feed9719b6..581d2843ab 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -46,6 +46,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" @@ -53,6 +54,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority +#include "../mwmechanics/weapontype.hpp" #include "actorutil.hpp" #include "rotatecontroller.hpp" @@ -301,11 +303,10 @@ namespace RemoveCallbackVisitor() : RemoveVisitor() , mHasMagicEffects(false) - , mEffectId(-1) { } - RemoveCallbackVisitor(int effectId) + RemoveCallbackVisitor(std::string_view effectId) : RemoveVisitor() , mHasMagicEffects(false) , mEffectId(effectId) @@ -324,7 +325,7 @@ namespace MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { - bool toRemove = mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId; + bool toRemove = mEffectId == "" || vfxCallback->mParams.mEffectId == mEffectId; if (toRemove) mToRemove.emplace_back(group.asNode(), group.getParent(0)); else @@ -338,7 +339,7 @@ namespace void apply(osg::Geometry&) override {} private: - int mEffectId; + std::string_view mEffectId; }; class FindVfxCallbacksVisitor : public osg::NodeVisitor @@ -348,11 +349,10 @@ namespace FindVfxCallbacksVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mEffectId(-1) { } - FindVfxCallbacksVisitor(int effectId) + FindVfxCallbacksVisitor(std::string_view effectId) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mEffectId(effectId) { @@ -368,7 +368,7 @@ namespace MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); if (vfxCallback) { - if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId) + if (mEffectId == "" || vfxCallback->mParams.mEffectId == mEffectId) { mCallbacks.push_back(vfxCallback); } @@ -382,7 +382,7 @@ namespace void apply(osg::Geometry&) override {} private: - int mEffectId; + std::string_view mEffectId; }; osg::ref_ptr getVFXLightModelInstance() @@ -447,7 +447,7 @@ namespace MWRender typedef std::map> ControllerMap; - ControllerMap mControllerMap[Animation::sNumBlendMasks]; + ControllerMap mControllerMap[sNumBlendMasks]; const SceneUtil::TextKeyMap& getTextKeys() const; }; @@ -715,6 +715,41 @@ namespace MWRender return mSupportedAnimations.find(anim) != mSupportedAnimations.end(); } + bool Animation::isLoopingAnimation(std::string_view group) const + { + // In Morrowind, a some animation groups are always considered looping, regardless + // of loop start/stop keys. + // To be match vanilla behavior we probably only need to check this list, but we don't + // want to prevent modded animations with custom group names from looping either. + static const std::unordered_set loopingAnimations = { "walkforward", "walkback", "walkleft", + "walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback", + "runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward", + "sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright", + "spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7", + "idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand", + "inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" }; + static const std::vector shortGroups = MWMechanics::getAllWeaponTypeShortGroups(); + + if (getTextKeyTime(std::string(group) + ": loop start") >= 0) + return true; + + // Most looping animations have variants for each weapon type shortgroup. + // Just remove the shortgroup instead of enumerating all of the possible animation groupnames. + // Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow" + // when the shortgroup is crossbow. + std::size_t suffixLength = 0; + for (std::string_view suffix : shortGroups) + { + if (suffix.length() > suffixLength && group.ends_with(suffix)) + { + suffixLength = suffix.length(); + } + } + group.remove_suffix(suffixLength); + + return loopingAnimations.count(group) > 0; + } + float Animation::getStartTime(const std::string& groupname) const { for (AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter) @@ -758,16 +793,14 @@ namespace MWRender state.mLoopStopTime = key->first; } - if (mTextKeyListener) + try { - try - { + if (mTextKeyListener != nullptr) mTextKeyListener->handleTextKey(groupname, key, map); - } - catch (std::exception& e) - { - Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what(); - } + } + catch (std::exception& e) + { + Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what(); } } @@ -923,7 +956,7 @@ namespace MWRender return true; } - void Animation::setTextKeyListener(Animation::TextKeyListener* listener) + void Animation::setTextKeyListener(TextKeyListener* listener) { mTextKeyListener = listener; } @@ -1052,7 +1085,16 @@ namespace MWRender return true; } - float Animation::getCurrentTime(const std::string& groupname) const + std::string_view Animation::getActiveGroup(BoneGroup boneGroup) const + { + if (auto timePtr = mAnimationTimePtr[boneGroup]->getTimePtr()) + for (auto& state : mStates) + if (state.second.mTime == timePtr) + return state.first; + return ""; + } + + float Animation::getCurrentTime(std::string_view groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if (iter == mStates.end()) @@ -1496,8 +1538,8 @@ namespace MWRender mExtraLightSource->setActorFade(mAlpha); } - void Animation::addEffect( - const std::string& model, int effectId, bool loop, std::string_view bonename, std::string_view texture) + void Animation::addEffect(std::string_view model, std::string_view effectId, bool loop, std::string_view bonename, + std::string_view texture) { if (!mObjectRoot.get()) return; @@ -1579,7 +1621,7 @@ namespace MWRender overrideFirstRootTexture(texture, mResourceSystem, *node); } - void Animation::removeEffect(int effectId) + void Animation::removeEffect(std::string_view effectId) { RemoveCallbackVisitor visitor(effectId); mInsert->accept(visitor); @@ -1589,17 +1631,19 @@ namespace MWRender void Animation::removeEffects() { - removeEffect(-1); + removeEffect(""); } - void Animation::getLoopingEffects(std::vector& out) const + std::vector Animation::getLoopingEffects() const { if (!mHasMagicEffects) - return; + return {}; FindVfxCallbacksVisitor visitor; mInsert->accept(visitor); + std::vector out; + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) { @@ -1608,6 +1652,7 @@ namespace MWRender if (callback->mParams.mLoop && !callback->mFinished) out.push_back(callback->mParams.mEffectId); } + return out; } void Animation::updateEffects() diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 24366889c4..dae81592b3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,6 +1,10 @@ #ifndef GAME_RENDER_ANIMATION_H #define GAME_RENDER_ANIMATION_H +#include "animationpriority.hpp" +#include "blendmask.hpp" +#include "bonegroup.hpp" + #include "../mwworld/movementdirection.hpp" #include "../mwworld/ptr.hpp" @@ -84,7 +88,7 @@ namespace MWRender std::string mModelName; // Just here so we don't add the same effect twice std::shared_ptr mAnimTime; float mMaxControllerLength; - int mEffectId; + std::string mEffectId; bool mLoop; std::string mBoneName; }; @@ -92,60 +96,9 @@ namespace MWRender class Animation : public osg::Referenced { public: - enum BoneGroup - { - BoneGroup_LowerBody = 0, - BoneGroup_Torso, - BoneGroup_LeftArm, - BoneGroup_RightArm - }; - - enum BlendMask - { - BlendMask_LowerBody = 1 << 0, - BlendMask_Torso = 1 << 1, - BlendMask_LeftArm = 1 << 2, - BlendMask_RightArm = 1 << 3, - - BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, - - BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody - }; - /* This is the number of *discrete* blend masks. */ - static constexpr size_t sNumBlendMasks = 4; - - /// Holds an animation priority value for each BoneGroup. - struct AnimPriority - { - /// Convenience constructor, initialises all priorities to the same value. - AnimPriority(int priority) - { - for (unsigned int i = 0; i < sNumBlendMasks; ++i) - mPriority[i] = priority; - } - - bool operator==(const AnimPriority& other) const - { - for (unsigned int i = 0; i < sNumBlendMasks; ++i) - if (other.mPriority[i] != mPriority[i]) - return false; - return true; - } - - int& operator[](BoneGroup n) { return mPriority[n]; } - - const int& operator[](BoneGroup n) const { return mPriority[n]; } - - bool contains(int priority) const - { - for (unsigned int i = 0; i < sNumBlendMasks; ++i) - if (priority == mPriority[i]) - return true; - return false; - } - - int mPriority[sNumBlendMasks]; - }; + using BlendMask = MWRender::BlendMask; + using BoneGroup = MWRender::BoneGroup; + using AnimPriority = MWRender::AnimPriority; class TextKeyListener { @@ -384,11 +337,11 @@ namespace MWRender * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect(const std::string& model, int effectId, bool loop = false, std::string_view bonename = {}, - std::string_view texture = {}); - void removeEffect(int effectId); + void addEffect(std::string_view model, std::string_view effectId, bool loop = false, + std::string_view bonename = {}, std::string_view texture = {}); + void removeEffect(std::string_view effectId); void removeEffects(); - void getLoopingEffects(std::vector& out) const; + std::vector getLoopingEffects() const; // Add a spell casting glow to an object. From measuring video taken from the original engine, // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. @@ -398,6 +351,8 @@ namespace MWRender bool hasAnimation(std::string_view anim) const; + bool isLoopingAnimation(std::string_view group) const; + // Specifies the axis' to accumulate on. Non-accumulated axis will just // move visually, but not affect the actual movement. Each x/y/z value // should be on the scale of 0 to 1. @@ -446,6 +401,9 @@ namespace MWRender bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr, size_t* loopcount = nullptr) const; + /// Returns the group name of the animation currently active on that bone group. + std::string_view getActiveGroup(BoneGroup boneGroup) const; + /// Get the absolute position in the animation track of the first text key with the given group. float getStartTime(const std::string& groupname) const; @@ -454,7 +412,7 @@ namespace MWRender /// Get the current absolute position in the animation track for the animation that is currently playing from /// the given group. - float getCurrentTime(const std::string& groupname) const; + float getCurrentTime(std::string_view groupname) const; /** Disables the specified animation group; * \param groupname Animation group to disable. diff --git a/apps/openmw/mwrender/animationpriority.hpp b/apps/openmw/mwrender/animationpriority.hpp new file mode 100644 index 0000000000..048d29901e --- /dev/null +++ b/apps/openmw/mwrender/animationpriority.hpp @@ -0,0 +1,42 @@ +#ifndef GAME_RENDER_ANIMATIONPRIORITY_H +#define GAME_RENDER_ANIMATIONPRIORITY_H + +#include "blendmask.hpp" +#include "bonegroup.hpp" + +namespace MWRender +{ + /// Holds an animation priority value for each BoneGroup. + struct AnimPriority + { + /// Convenience constructor, initialises all priorities to the same value. + AnimPriority(int priority) + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + mPriority[i] = priority; + } + + bool operator==(const AnimPriority& other) const + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + if (other.mPriority[i] != mPriority[i]) + return false; + return true; + } + + int& operator[](BoneGroup n) { return mPriority[n]; } + + const int& operator[](BoneGroup n) const { return mPriority[n]; } + + bool contains(int priority) const + { + for (unsigned int i = 0; i < sNumBlendMasks; ++i) + if (priority == mPriority[i]) + return true; + return false; + } + + int mPriority[sNumBlendMasks]; + }; +} +#endif diff --git a/apps/openmw/mwrender/blendmask.hpp b/apps/openmw/mwrender/blendmask.hpp new file mode 100644 index 0000000000..f140814d8d --- /dev/null +++ b/apps/openmw/mwrender/blendmask.hpp @@ -0,0 +1,22 @@ +#ifndef GAME_RENDER_BLENDMASK_H +#define GAME_RENDER_BLENDMASK_H + +#include + +namespace MWRender +{ + enum BlendMask + { + BlendMask_LowerBody = 1 << 0, + BlendMask_Torso = 1 << 1, + BlendMask_LeftArm = 1 << 2, + BlendMask_RightArm = 1 << 3, + + BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm, + + BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody + }; + /* This is the number of *discrete* blend masks. */ + static constexpr size_t sNumBlendMasks = 4; +} +#endif diff --git a/apps/openmw/mwrender/bonegroup.hpp b/apps/openmw/mwrender/bonegroup.hpp new file mode 100644 index 0000000000..2afedade86 --- /dev/null +++ b/apps/openmw/mwrender/bonegroup.hpp @@ -0,0 +1,16 @@ +#ifndef GAME_RENDER_BONEGROUP_H +#define GAME_RENDER_BONEGROUP_H + +namespace MWRender +{ + enum BoneGroup + { + BoneGroup_LowerBody = 0, + BoneGroup_Torso, + BoneGroup_LeftArm, + BoneGroup_RightArm, + + Num_BoneGroups + }; +} +#endif diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 269f7cab75..aa6b5eb4dd 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -457,14 +457,14 @@ namespace MWRender mAnimation->showCarriedLeft(showCarriedLeft); mCurrentAnimGroup = std::move(groupname); - mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentAnimGroup, 1, BlendMask::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { if (!mAnimation->getInfo("torch")) mAnimation->play( - "torch", 2, Animation::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); + "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else if (mAnimation->getInfo("torch")) mAnimation->disable("torch"); @@ -591,7 +591,7 @@ namespace MWRender void RaceSelectionPreview::onSetup() { CharacterPreview::onSetup(); - mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); + mAnimation->play("idle", 1, BlendMask::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->runAnimation(0.f); // attach camera to follow the head node diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 060b6ee5de..57c3e902d8 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -306,7 +306,7 @@ namespace MWRender bool forceShaders = mSceneManager->getForceShaders(); - mAtmosphereDay = mSceneManager->getInstance(Settings::models().mSkyatmosphere, mEarlyRenderBinRoot); + mAtmosphereDay = mSceneManager->getInstance(Settings::models().mSkyatmosphere.get(), mEarlyRenderBinRoot); ModVertexAlphaVisitor modAtmosphere(ModVertexAlphaVisitor::Atmosphere); mAtmosphereDay->accept(modAtmosphere); @@ -319,9 +319,9 @@ namespace MWRender osg::ref_ptr atmosphereNight; if (mSceneManager->getVFS()->exists(Settings::models().mSkynight02.get())) - atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight02, mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight02.get(), mAtmosphereNightNode); else - atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight01, mAtmosphereNightNode); + atmosphereNight = mSceneManager->getInstance(Settings::models().mSkynight01.get(), mAtmosphereNightNode); atmosphereNight->getOrCreateStateSet()->setAttributeAndModes( createAlphaTrackingUnlitMaterial(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); @@ -341,7 +341,8 @@ namespace MWRender mEarlyRenderBinRoot->addChild(mCloudNode); mCloudMesh = new osg::PositionAttitudeTransform; - osg::ref_ptr cloudMeshChild = mSceneManager->getInstance(Settings::models().mSkyclouds, mCloudMesh); + osg::ref_ptr cloudMeshChild + = mSceneManager->getInstance(Settings::models().mSkyclouds.get(), mCloudMesh); mCloudUpdater = new CloudUpdater(forceShaders); mCloudUpdater->setOpacity(1.f); cloudMeshChild->addUpdateCallback(mCloudUpdater); @@ -349,7 +350,7 @@ namespace MWRender mNextCloudMesh = new osg::PositionAttitudeTransform; osg::ref_ptr nextCloudMeshChild - = mSceneManager->getInstance(Settings::models().mSkyclouds, mNextCloudMesh); + = mSceneManager->getInstance(Settings::models().mSkyclouds.get(), mNextCloudMesh); mNextCloudUpdater = new CloudUpdater(forceShaders); mNextCloudUpdater->setOpacity(0.f); nextCloudMeshChild->addUpdateCallback(mNextCloudUpdater); diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 686afbc34a..8d5b99b0c3 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -631,7 +631,7 @@ namespace ESM { auto name = sIndexNameToIndexMap.find(effect); if (name == sIndexNameToIndexMap.end()) - throw std::runtime_error("Unimplemented effect " + std::string(effect)); + return -1; return name->second; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 25abcfd0d8..787f2e8441 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -867,7 +867,7 @@ namespace Resource return static_cast(mErrorMarker->clone(osg::CopyOp::DEEP_COPY_ALL)); } - osg::ref_ptr SceneManager::getTemplate(const std::string& name, bool compile) + osg::ref_ptr SceneManager::getTemplate(std::string_view name, bool compile) { std::string normalized = VFS::Path::normalizeFilename(name); @@ -927,7 +927,7 @@ namespace Resource } } - osg::ref_ptr SceneManager::getInstance(const std::string& name) + osg::ref_ptr SceneManager::getInstance(std::string_view name) { osg::ref_ptr scene = getTemplate(name); return getInstance(scene); @@ -968,7 +968,7 @@ namespace Resource return cloned; } - osg::ref_ptr SceneManager::getInstance(const std::string& name, osg::Group* parentNode) + osg::ref_ptr SceneManager::getInstance(std::string_view name, osg::Group* parentNode) { osg::ref_ptr cloned = getInstance(name); attachTo(cloned, parentNode); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index c7663a4d91..12900441de 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -157,7 +157,7 @@ namespace Resource /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. /// @note Thread safe. - osg::ref_ptr getTemplate(const std::string& name, bool compile = true); + osg::ref_ptr getTemplate(std::string_view name, bool compile = true); /// Clone osg::Node safely. /// @note Thread safe. @@ -172,12 +172,12 @@ namespace Resource /// Instance the given scene template. /// @see getTemplate /// @note Thread safe. - osg::ref_ptr getInstance(const std::string& name); + osg::ref_ptr getInstance(std::string_view name); /// Instance the given scene template and immediately attach it to a parent node /// @see getTemplate /// @note Not thread safe, unless parentNode is not part of the main scene graph yet. - osg::ref_ptr getInstance(const std::string& name, osg::Group* parentNode); + osg::ref_ptr getInstance(std::string_view name, osg::Group* parentNode); /// Attach the given scene instance to the given parent node /// @note You should have the parentNode in its intended position before calling this method, diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 7bcda5110c..02b03cbd69 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -2,6 +2,7 @@ paths=( openmw_aux/*lua scripts/omw/activationhandlers.lua scripts/omw/ai.lua + scripts/omw/mechanics/animationcontroller.lua scripts/omw/playercontrols.lua scripts/omw/camera/camera.lua scripts/omw/mwui/init.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 6d27db0515..1bb7e0b6e9 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -16,6 +16,7 @@ Lua API reference openmw_storage openmw_core openmw_types + openmw_animation openmw_async openmw_vfs openmw_world @@ -33,6 +34,7 @@ Lua API reference openmw_aux_ui interface_activation interface_ai + interface_animation interface_camera interface_controls interface_item_usage diff --git a/docs/source/reference/lua-scripting/interface_animation.rst b/docs/source/reference/lua-scripting/interface_animation.rst new file mode 100644 index 0000000000..5bde11775e --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_animation.rst @@ -0,0 +1,8 @@ +Interface AnimationController +============================= + +.. include:: version.rst + +.. raw:: html + :file: generated_html/scripts_omw_mechanics_animationcontroller.html + diff --git a/docs/source/reference/lua-scripting/openmw_animation.rst b/docs/source/reference/lua-scripting/openmw_animation.rst new file mode 100644 index 0000000000..35ac26ecec --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_animation.rst @@ -0,0 +1,7 @@ +Package openmw.animation +======================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_animation.html diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index e05eb642f0..5029baf0a3 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -10,6 +10,9 @@ * - :ref:`AI ` - by local scripts - Control basic AI of NPCs and creatures. + * - :ref:`AnimationController ` + - by local scripts + - Control animations of NPCs and creatures. * - :ref:`Camera ` - by player scripts - | Allows to alter behavior of the built-in camera script diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 67709bbf7b..9a73334b84 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -13,6 +13,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.types ` | everywhere | | Functions for specific types of game objects. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.animation ` | everywhere | | Animation controls | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers and callbacks. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 0e91a0b495..3ab30c87ff 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -75,6 +75,7 @@ set(BUILTIN_DATA_FILES scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua + scripts/omw/mechanics/animationcontroller.lua scripts/omw/mechanics/playercontroller.lua scripts/omw/playercontrols.lua scripts/omw/settings/player.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index e4338df533..a6f4ca5f33 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -10,6 +10,7 @@ GLOBAL: scripts/omw/activationhandlers.lua GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua +CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua diff --git a/files/data/scripts/omw/mechanics/animationcontroller.lua b/files/data/scripts/omw/mechanics/animationcontroller.lua new file mode 100644 index 0000000000..3293668387 --- /dev/null +++ b/files/data/scripts/omw/mechanics/animationcontroller.lua @@ -0,0 +1,145 @@ +local anim = require('openmw.animation') +local self = require('openmw.self') + +local playBlendedHandlers = {} +local function onPlayBlendedAnimation(groupname, options) + for i = #playBlendedHandlers, 1, -1 do + if playBlendedHandlers[i](groupname, options) == false then + return + end + end +end + +local function playBlendedAnimation(groupname, options) + onPlayBlendedAnimation(groupname, options) + if options.skip then + return + end + anim.playBlended(self, groupname, options) +end + +local textKeyHandlers = {} +local function onAnimationTextKey(groupname, key) + local handlers = textKeyHandlers[groupname] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](groupname, key) == false then + return + end + end + end + handlers = textKeyHandlers[''] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](groupname, key) == false then + return + end + end + end +end + +local initialized = false + +local function onUpdate(dt) + -- The script is loaded before the actor's CharacterController object is initialized, therefore + -- we have to delay this initialization step or the call won't have any effect. + if not initialized then + self:_enableLuaAnimations(true) + initialized = true + end +end + +return { + engineHandlers = { + _onPlayAnimation = playBlendedAnimation, + _onAnimationTextKey = onAnimationTextKey, + onUpdate = onUpdate, + }, + + interfaceName = 'AnimationController', + --- + -- Animation controller interface + -- @module AnimationController + -- @usage local anim = require('openmw.animation') + -- local I = require('openmw.interfaces') + -- + -- -- play spellcast animation + -- I.AnimationController.playBlendedAnimation('spellcast', { startkey = 'self start', stopkey = 'self stop', priority = { + -- [anim.BONE_GROUP.RightArm] = anim.PRIORITY.Weapon, + -- [anim.BONE_GROUP.LeftArm] = anim.PRIORITY.Weapon, + -- [anim.BONE_GROUP.Torso] = anim.PRIORITY.Weapon, + -- [anim.BONE_GROUP.LowerBody] = anim.PRIORITY.WeaponLowerBody + -- } }) + -- + -- @usage -- react to the spellcast release textkey + -- I.AnimationController.addTextKeyHandler('spellcast', function(groupname, key) + -- -- Note, Lua is 1-indexed so have to subtract 1 less than the length of 'release' + -- if key.sub(key, #key - 6) == 'release' then + -- print('Abra kadabra!') + -- end + -- end) + -- + -- @usage -- Add a text key handler that will react to all keys + -- I.AnimationController.addTextKeyHandler('', function(groupname, key) + -- if key.sub(key, #key - 2) == 'hit' and not key.sub(key, #key - 7) == ' min hit' then + -- print('Hit!') + -- end + -- end) + -- + -- @usage -- Make a handler that changes player attack speed based on current fatigue + -- I.AnimationController.addPlayBlendedAnimationHandler(function (groupname, options) + -- local stop = options.stopkey + -- if #stop > 10 and stop.sub(stop, #stop - 10) == ' max attack' then + -- -- This is an attack wind up animation, scale its speed by attack + -- local fatigue = Actor.stats.dynamic.fatigue(self) + -- local factor = 1 - fatigue.current / fatigue.base + -- speed = 1 - factor * 0.8 + -- options.speed = speed + -- end + -- end) + -- + + interface = { + --- Interface version + -- @field [parent=#AnimationController] #number version + version = 0, + + --- AnimationController Package + -- @type Package + + --- Make this actor play an animation. Makes a call to @{openmw.animation#playBlended}, after invoking handlers added through addPlayBlendedAnimationHandler + -- @function [parent=#AnimationController] playBlendedAnimation + -- @param #string groupname The animation group to be played + -- @param #table options The table of play options that will be passed to @{openmw.animation#playBlended} + playBlendedAnimation = playBlendedAnimation, + + --- Add new playBlendedAnimation handler for this actor + -- If `handler(groupname, options)` returns false, other handlers for + -- the call will be skipped. + -- @function [parent=#AnimationController] addPlayBlendedAnimationHandler + -- @param #function handler The handler. + addPlayBlendedAnimationHandler = function(handler) + playBlendedHandlers[#playBlendedHandlers + 1] = handler + end, + + --- Add new text key handler for this actor + -- While playing, some animations emit text key events. Register a handle to listen for all + -- text key events associated with this actor's animations. + -- If `handler(groupname, key)` returns false, other handlers for + -- the call will be skipped. + -- @function [parent=#AnimationController] addTextKeyHandler + -- @param #string groupname Name of the animation group to listen to keys for. If the empty string or nil, all keys will be received + -- @param #function handler The handler. + addTextKeyHandler = function(groupname, handler) + if not groupname then + groupname = "" + end + local handlers = textKeyHandlers[groupname] + if handlers == nil then + handlers = {} + textKeyHandlers[groupname] = handlers + end + handlers[#handlers + 1] = handler + end, + } +} \ No newline at end of file diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua new file mode 100644 index 0000000000..bb5a0594df --- /dev/null +++ b/files/lua_api/openmw/animation.lua @@ -0,0 +1,255 @@ +--- +-- `openmw.animation` defines functions that allow control of character animations +-- Note that for some methods, such as @{openmw.animation#playBlended} you should use the associated methods on the +-- [AnimationController](interface_animation.html) interface rather than invoking this API directly. +-- @module animation +-- @usage local anim = require('openmw.animation') + +--- Possible @{#Priority} values +-- @field [parent=#animation] #Priority PRIORITY + +--- `animation.PRIORITY` +-- @type Priority +-- @field #number Default "0" +-- @field #number WeaponLowerBody "1" +-- @field #number SneakIdleLowerBody "2" +-- @field #number SwimIdle "3" +-- @field #number Jump "4" +-- @field #number Movement "5" +-- @field #number Hit "6" +-- @field #number Weapon "7" +-- @field #number Block "8" +-- @field #number Knockdown "9" +-- @field #number Torch "10" +-- @field #number Storm "11" +-- @field #number Death "12" +-- @field #number Scripted "13" Special priority used by scripted animations. When any animation with this priority is present, all animations without this priority are paused. + +--- Possible @{#BlendMask} values +-- @field [parent=#animation] #BlendMask BLEND_MASK + +--- `animation.BLEND_MASK` +-- @type BlendMask +-- @field #number LowerBody "1" All bones from 'Bip01 pelvis' and below +-- @field #number Torso "2" All bones from 'Bip01 Spine1' and up, excluding arms +-- @field #number LeftArm "4" All bones from 'Bip01 L Clavicle' and out +-- @field #number RightArm "8" All bones from 'Bip01 R Clavicle' and out +-- @field #number UpperBody "14" All bones from 'Bip01 Spine1' and up, including arms +-- @field #number All "15" All bones + +--- Possible @{#BoneGroup} values +-- @field [parent=#animation] #BoneGroup BONE_GROUP + +--- `animation.BONE_GROUP` +-- @type BoneGroup +-- @field #number LowerBody "1" All bones from 'Bip01 pelvis' and below +-- @field #number Torso "2" All bones from 'Bip01 Spine1' and up, excluding arms +-- @field #number LeftArm "3" All bones from 'Bip01 L Clavicle' and out +-- @field #number RightArm "4" All bones from 'Bip01 R Clavicle' and out + + +--- +-- Check if the object has an animation object or not +-- @function [parent=#animation] hasAnimation +-- @param openmw.core#GameObject actor +-- @return #boolean + +--- +-- Skips animations for one frame, equivalent to mwscript's SkipAnim +-- Can be used only in local scripts on self. +-- @function [parent=#animation] skipAnimationThisFrame +-- @param openmw.core#GameObject actor + +--- +-- Get the absolute position within the animation track of the given text key +-- @function [parent=#animation] getTextKeyTime +-- @param openmw.core#GameObject actor +-- @param #string text key +-- @return #number + +--- +-- Check if the given animation group is currently playing +-- @function [parent=#animation] isPlaying +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #boolean + +--- +-- Get the current absolute time of the given animation group if it is playing, or -1 if it is not playing. +-- @function [parent=#animation] getCurrentTime +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #number + +--- +-- Check whether the animation is a looping animation or not. This is determined by a combination +-- of groupname, some of which are hardcoded to be looping, and the presence of loop start/stop keys. +-- The groupnames that are hardcoded as looping are the following, as well as per-weapon-type suffixed variants of each. +-- "walkforward", "walkback", "walkleft", "walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", +-- "runforward", "runback", "runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", +-- "sneakforward", "sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright", +-- "spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7", "idle8", +-- "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand", "inventoryweapononehand", +-- "inventoryweapontwohand", "inventoryweapontwowide" +-- @function [parent=#animation] isLoopingAnimation +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #boolean + + +--- +-- Cancels and removes the animation group from the list of active animations +-- Can be used only in local scripts on self. +-- @function [parent=#animation] cancel +-- @param openmw.core#GameObject actor +-- @param #string groupname + +--- +-- Enables or disables looping for the given animation group. Looping is enabled by default. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] setLoopingEnabled +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @param #boolean enabled + +--- +-- Returns the completion of the animation, or nil if the animation group is not active. +-- @function [parent=#animation] getCompletion +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #number, #nil + +--- +-- Returns the remaining number of loops, not counting the current loop, or nil if the animation group is not active. +-- @function [parent=#animation] getLoopCount +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #number, #nil + +--- +-- Get the current playback speed of an animation group, or nil if the animation group is not active. +-- @function [parent=#animation] getSpeed +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #number, #nil + +--- +-- Modifies the playback speed of an animation group. +-- Note that this is not sticky and only affects the speed until the currently playing sequence ends. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] setSpeed +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @param #number speed The new animation speed, where speed=1 is normal speed. + +--- +-- Clears all animations currently in the animation queue. This affects animations played by mwscript, @{openmw.animation#playQueued}, and ai packages, but does not affect animations played using @{openmw.animation#playBlended}. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] clearAnimationQueue +-- @param openmw.core#GameObject actor +-- @param #boolean clearScripted whether to keep animation with priority Scripted or not. + +--- +-- Acts as a slightly extended version of MWScript's LoopGroup. Plays this animation exclusively +-- until it ends, or the queue is cleared using #clearAnimationQueue. Use #clearAnimationQueue and the `startkey` option +-- to imitate the behavior of LoopGroup's play modes. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] playQueued +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @param #table options A table of play options. Can contain: +-- +-- * `loops` - a number >= 0, the number of times the animation should loop after the first play (default: infinite). +-- * `speed` - a floating point number >= 0, the speed at which the animation should play (default: 1); +-- * `startkey` - the animation key at which the animation should start (default: "start") +-- * `stopkey` - the animation key at which the animation should end (default: "stop") +-- * `forceloop` - a boolean, to set if the animation should loop even if it's not a looping animation (default: false) +-- +-- @usage -- Play death1 without waiting. Equivalent to playgroup, death1, 1 +-- anim.clearAnimationQueue(self, false) +-- anim.playQueued(self, 'death1') +-- +-- @usage -- Play an animation group with custom start/stop keys +-- anim.clearAnimationQueue(self, false) +-- anim.playQueued(self, 'spellcast', { startkey = 'self start', stopkey = 'self stop' }) +-- + +--- +-- Play an animation directly. You probably want to use the [AnimationController](interface_animation.html) interface, which will trigger relevant handlers, +-- instead of calling this directly. Note that the still hardcoded character controller may at any time and for any reason alter +-- or cancel currently playing animations, so making your own calls to this function either directly or through the [AnimationController](interface_animation.html) +-- interface may be of limited utility. For now, use openmw.animation#playQueued to script your own animations. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] playBlended +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @param #table options A table of play options. Can contain: +-- +-- * `loops` - a number >= 0, the number of times the animation should loop after the first play (default: 0). +-- * `priority` - Either a single #Priority value that will be assigned to all bone groups. Or a table mapping bone groups to its priority (default: PRIORITY.Default). +-- * `blendMask` - A mask of which bone groups to include in the animation (Default: BLEND_MASK.All. +-- * `autodisable` - If true, the animation will be immediately removed upon finishing, which means information will not be possible to query once completed. (Default: true) +-- * `speed` - a floating point number >= 0, the speed at which the animation should play (default: 1) +-- * `startkey` - the animation key at which the animation should start (default: "start") +-- * `stopkey` - the animation key at which the animation should end (default: "stop") +-- * `startpoint` - a floating point number 0 <= value <= 1, starting completion of the animation (default: 0) +-- * `forceloop` - a boolean, to set if the animation should loop even if it's not a looping animation (default: false) + +--- +-- Check if the actor's animation has the given animation group or not. +-- @function [parent=#animation] hasGroup +-- @param openmw.core#GameObject actor +-- @param #string groupname +-- @return #boolean + +--- +-- Check if the actor's skeleton has the given bone or not +-- @function [parent=#animation] hasBone +-- @param openmw.core#GameObject actor +-- @param #string bonename +-- @return #boolean + +--- +-- Get the current active animation for a bone group +-- @function [parent=#animation] getActiveGroup +-- @param openmw.core#GameObject actor +-- @param #number bonegroup Bone group enum, see @{openmw.animation#BONE_GROUP} +-- @return #string + +--- +-- Plays a VFX on the actor. +-- Can be used only in local scripts on self. +-- @function [parent=#animation] addVfx +-- @param openmw.core#GameObject actor +-- @param #any static @{openmw.core#StaticRecord} or #string ID +-- @param #table options optional table of parameters. Can contain: +-- +-- * `loop` - boolean, if true the effect will loop until removed (default: 0). +-- * `bonename` - name of the bone to attach the vfx to. (default: "") +-- * `particle` - name of the particle texture to use. (default: "") +-- * `vfxId` - a string ID that can be used to remove the effect later, using #removeVfx, and to avoid duplicate effects. The default value of "" can have duplicates. To avoid interaction with the engine, use unique identifiers unrelated to magic effect IDs. The engine uses this identifier to add and remove magic effects based on what effects are active on the actor. If this is set equal to the @{openmw.core#MagicEffectId} identifier of the magic effect being added, for example core.magic.EFFECT_TYPE.FireDamage, then the engine will remove it once the fire damage effect on the actor reaches 0. (Default: ""). +-- +-- @usage local mgef = core.magic.effects[myEffectName] +-- anim.addVfx(self, 'VFX_Hands', {bonename = 'Bip01 L Hand', particle = mgef.particle, loop = mgef.continuousVfx, vfxId = mgef.id..'_myuniquenamehere'}) +-- -- later: +-- anim.removeVfx(self, mgef.id..'_myuniquenamehere') +-- + +--- +-- Removes a specific VFX +-- Can be used only in local scripts on self. +-- @function [parent=#animation] removeVfx +-- @param openmw.core#GameObject actor +-- @param #number vfxId an integer ID that uniquely identifies the VFX to remove + +--- +-- Removes all vfx from the actor +-- Can be used only in local scripts on self. +-- @function [parent=#animation] removeAllVfx +-- @param openmw.core#GameObject actor + + + + +return nil + diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index dae5fc0594..94d63312f1 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -668,6 +668,11 @@ -- @field #number baseCost -- @field openmw.util#Color color -- @field #boolean harmful +-- @field #boolean continuousVfx Whether the magic effect's vfx should loop or not +-- @field #string particle Identifier of the particle texture +-- @field #string castingStatic Identifier of the vfx static used for casting +-- @field #string hitStatic Identifier of the vfx static used on hit +-- @field #string areaStatic Identifier of the vfx static used for AOE spells --- -- @type MagicEffectWithParams @@ -899,4 +904,24 @@ -- @field #number favouredSkillValue Secondary skill value required to get this rank. -- @field #number factionReaction Reaction of faction members if player is in this faction. +--- @{#VFX}: Visual effects +-- @field [parent=#core] #VFX vfx + +--- +-- Spawn a VFX at the given location in the world +-- @function [parent=#VFX] spawn +-- @param #any static openmw.core#StaticRecord or #string ID +-- @param openmw.util#Vector3 location +-- @param #table options optional table of parameters. Can contain: +-- +-- * `mwMagicVfx` - Boolean that if true causes the textureOverride parameter to only affect nodes with the Nif::RC_NiTexturingProperty property set. (default: true). +-- * `particleTextureOverride` - Name of a particle texture that should override this effect's default texture. (default: "") +-- * `scale` - A number that scales the size of the vfx (Default: 1) +-- +-- @usage -- Spawn a sanctuary effect near the player +-- local effect = core.magic.effects[core.magic.EFFECT_TYPE.Sanctuary] +-- pos = self.position + util.vector3(0, 100, 0) +-- core.vfx.spawn(effect.castingStatic, pos) +-- + return nil diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index d4a290aa47..57103768d2 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -5,6 +5,9 @@ --- -- @field [parent=#interfaces] scripts.omw.activationhandlers#scripts.omw.activationhandlers Activation +--- +-- @field [parent=#interfaces] scripts.omw.mechanics.animationcontroller#scripts.omw.mechanics.animationcontroller AnimationController + --- -- @field [parent=#interfaces] scripts.omw.ai#scripts.omw.ai AI From 1d1ce2de7b332c25de68eb6e4847063ed91ee6eb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 26 Jan 2024 17:15:41 +0100 Subject: [PATCH 0886/2167] Use the correct id to absorb enchantments --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/activespells.cpp | 34 ++++++++++++++---------- apps/openmw/mwmechanics/activespells.hpp | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 10 +++---- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..a2fbdc6eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work + Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index a9c669fce5..937e7a6658 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -174,6 +174,25 @@ namespace MWMechanics mWorsenings = -1; } + ESM::RefId ActiveSpells::ActiveSpellParams::getEnchantment() const + { + // Enchantment id is not stored directly. Instead the enchanted item is stored. + const auto& store = MWBase::Environment::get().getESMStore(); + switch (store->find(mId)) + { + case ESM::REC_ARMO: + return store->get().find(mId)->mEnchant; + case ESM::REC_BOOK: + return store->get().find(mId)->mEnchant; + case ESM::REC_CLOT: + return store->get().find(mId)->mEnchant; + case ESM::REC_WEAP: + return store->get().find(mId)->mEnchant; + default: + return {}; + } + } + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) @@ -438,21 +457,8 @@ namespace MWMechanics if (store->get().search(id) == nullptr) return false; - // Enchantment id is not stored directly. Instead the enchanted item is stored. return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { - switch (store->find(spell.mId)) - { - case ESM::REC_ARMO: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_BOOK: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_CLOT: - return store->get().find(spell.mId)->mEnchant == id; - case ESM::REC_WEAP: - return store->get().find(spell.mId)->mEnchant == id; - default: - return false; - } + return spell.getEnchantment() == id; }) != mSpells.end(); } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 87497d9d7a..a505b8990a 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -73,6 +73,7 @@ namespace MWMechanics const std::string& getDisplayName() const { return mDisplayName; } ESM::RefNum getItem() const { return mItem; } + ESM::RefId getEnchantment() const; // Increments worsenings count and sets the next timestamp void worsen(); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index ebf9933cfc..8c415949f5 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -279,7 +279,8 @@ namespace return false; } - void absorbSpell(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + void absorbSpell(const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, const MWWorld::Ptr& caster, + const MWWorld::Ptr& target) { const auto& esmStore = *MWBase::Environment::get().getESMStore(); const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); @@ -287,15 +288,14 @@ namespace if (animation && !absorbStatic->mModel.empty()) animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); - const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; - if (spell) + if (const ESM::Spell* spell = esmStore.get().search(spellParams.getId())) { spellCost = MWMechanics::calcSpellCost(*spell); } else { - const ESM::Enchantment* enchantment = esmStore.get().search(spellId); + const ESM::Enchantment* enchantment = esmStore.get().search(spellParams.getEnchantment()); if (enchantment) spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster); } @@ -342,7 +342,7 @@ namespace { if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude) { - absorbSpell(spellParams.getId(), caster, target); + absorbSpell(spellParams, caster, target); return MWMechanics::MagicApplicationResult::Type::REMOVED; } } From ad5d594c28ad4dd4cdbebaefd662f1c3285523da Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 27 Jan 2024 14:47:22 +0100 Subject: [PATCH 0887/2167] Let menu scripts clean up before loading a game --- apps/openmw/mwstate/statemanagerimp.cpp | 4 ++++ files/data/scripts/omw/input/settings.lua | 1 + files/data/scripts/omw/settings/common.lua | 1 - files/data/scripts/omw/settings/menu.lua | 13 +++++++------ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 4c4d0de7a1..e80debb998 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -421,6 +421,10 @@ void MWState::StateManager::loadGame(const Character* character, const std::file { try { + // let menu scripts do cleanup + mState = State_Ended; + MWBase::Environment::get().getLuaManager()->gameEnded(); + cleanup(); Log(Debug::Info) << "Reading save file " << filepath.filename(); diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 42fec91f82..6c1b857131 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -47,6 +47,7 @@ local recording = nil I.Settings.registerRenderer('inputBinding', function(id, set, arg) if type(id) ~= 'string' then error('inputBinding: must have a string default value') end + if not arg then error('inputBinding: argument with "key" and "type" is required') end if not arg.type then error('inputBinding: type argument is required') end if not arg.key then error('inputBinding: key argument is required') end local info = input.actions[arg.key] or input.triggers[arg.key] diff --git a/files/data/scripts/omw/settings/common.lua b/files/data/scripts/omw/settings/common.lua index 1d62a54dc4..7ca6628b08 100644 --- a/files/data/scripts/omw/settings/common.lua +++ b/files/data/scripts/omw/settings/common.lua @@ -90,7 +90,6 @@ local function registerGroup(options) } local valueSection = contextSection(options.key) local argumentSection = contextSection(options.key .. argumentSectionPostfix) - argumentSection:removeOnExit() for i, opt in ipairs(options.settings) do local setting = registerSetting(opt) setting.order = i diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 59d15ee2cd..017a48deb2 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -354,6 +354,8 @@ end local function onGroupRegistered(global, key) local group = common.getSection(global, common.groupSectionKey):get(key) + if not group then return end + groups[group.page] = groups[group.page] or {} local pageGroup = { key = group.key, @@ -364,6 +366,8 @@ local function onGroupRegistered(global, key) if not groups[group.page][pageGroup.key] then common.getSection(global, group.key):subscribe(onSettingChanged(global)) common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey) + if settingKey == nil then return end + local group = common.getSection(global, common.groupSectionKey):get(group.key) if not group or not pageOptions[group.page] then return end @@ -431,10 +435,12 @@ local menuGroups = {} local menuPages = {} local function resetPlayerGroups() + local settingGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do for groupKey, group in pairs(page) do if not menuGroups[groupKey] and not group.global then page[groupKey] = nil + settingGroupsSection:set(groupKey, nil) end end if pageOptions[pageKey] then @@ -487,8 +493,6 @@ local function registerPage(options) ui.registerSettingsPage(pageOptions[page.key]) end -local lastState - return { interfaceName = 'Settings', interface = { @@ -509,14 +513,11 @@ return { }, engineHandlers = { onStateChanged = function() - if lastState == menu.STATE.Running then - resetPlayerGroups() - end + resetPlayerGroups() updatePlayerGroups() if menu.getState() == menu.STATE.Running then updateGlobalGroups() end - lastState = menu.getState() end, }, eventHandlers = { From cfd67f3ce7577a48092bba98634a54dbceb7310c Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 27 Jan 2024 13:57:26 +0000 Subject: [PATCH 0888/2167] #7791: Require local variables to exist for lua mwscript local variables --- apps/openmw/mwlua/mwscriptbindings.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index af88249d3e..a41ef30a44 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -104,8 +104,12 @@ namespace MWLua }); mwscript["player"] = sol::readonly_property( [](const MWScriptRef&) { return GObject(MWBase::Environment::get().getWorld()->getPlayerPtr()); }); - mwscriptVars[sol::meta_function::index] = [](MWScriptVariables& s, std::string_view var) { - return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var)); + mwscriptVars[sol::meta_function::index] + = [](MWScriptVariables& s, std::string_view var) -> sol::optional { + if (s.mRef.getLocals().hasVar(s.mRef.mId, var)) + return s.mRef.getLocals().getVarAsDouble(s.mRef.mId, Misc::StringUtils::lowerCase(var)); + else + return sol::nullopt; }; mwscriptVars[sol::meta_function::new_index] = [](MWScriptVariables& s, std::string_view var, double val) { MWScript::Locals& locals = s.mRef.getLocals(); From d9f8c5c3e8b0bf852bdb2e4cb9bf7a6ce029e7a9 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 27 Jan 2024 15:03:34 +0100 Subject: [PATCH 0889/2167] Fix menu setting page key set --- files/data/scripts/omw/settings/menu.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 017a48deb2..9b161df588 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -499,7 +499,7 @@ return { version = 1, registerPage = function(options) registerPage(options) - menuPages[options] = true + menuPages[options.key] = true end, registerRenderer = registerRenderer, registerGroup = function(options) @@ -514,8 +514,8 @@ return { engineHandlers = { onStateChanged = function() resetPlayerGroups() - updatePlayerGroups() if menu.getState() == menu.STATE.Running then + updatePlayerGroups() updateGlobalGroups() end end, From 4ef68a8938293fdeda8cc67f7c4f4a92329bfc85 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 27 Jan 2024 16:26:31 +0100 Subject: [PATCH 0890/2167] Split Class::getModel into Class::getCorrectedModel and Class::getModel so preloading can use string_view --- apps/openmw/mwclass/activator.cpp | 9 ++--- apps/openmw/mwclass/activator.hpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/apparatus.hpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/armor.hpp | 2 +- apps/openmw/mwclass/bodypart.cpp | 2 +- apps/openmw/mwclass/bodypart.hpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/book.hpp | 2 +- apps/openmw/mwclass/classmodel.hpp | 12 ++---- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/clothing.hpp | 2 +- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/container.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 10 ++--- apps/openmw/mwclass/creature.hpp | 4 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwclass/door.hpp | 2 +- apps/openmw/mwclass/esm4base.hpp | 4 +- apps/openmw/mwclass/esm4npc.cpp | 9 ++--- apps/openmw/mwclass/esm4npc.hpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/ingredient.hpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/light.hpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/lockpick.hpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/misc.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 45 +++++++++++++--------- apps/openmw/mwclass/npc.hpp | 6 ++- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/potion.hpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/probe.hpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/repair.hpp | 2 +- apps/openmw/mwclass/static.cpp | 2 +- apps/openmw/mwclass/static.hpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwclass/weapon.hpp | 2 +- apps/openmw/mwrender/actoranimation.cpp | 8 ++-- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/esm4npcanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- apps/openmw/mwscript/miscextensions.cpp | 4 +- apps/openmw/mwworld/cellpreloader.cpp | 26 ++++++------- apps/openmw/mwworld/class.cpp | 15 ++++++-- apps/openmw/mwworld/class.hpp | 6 ++- apps/openmw/mwworld/projectilemanager.cpp | 8 ++-- apps/openmw/mwworld/scene.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +- 55 files changed, 132 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 01437b2abd..678c4e054b 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -62,7 +62,7 @@ namespace MWClass physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } - std::string Activator::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Activator::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } @@ -141,15 +141,14 @@ namespace MWClass ESM::RefId Activator::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { - const std::string model - = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise + // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise + const std::string_view model = getModel(ptr); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const ESM::RefId* creatureId = nullptr; for (const ESM::Creature& iter : store.get()) { - if (!iter.mModel.empty() - && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel))) + if (!iter.mModel.empty() && Misc::StringUtils::ciEqual(model, iter.mModel)) { creatureId = !iter.mOriginal.empty() ? &iter.mOriginal : &iter.mId; break; diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 309e163abe..ec0f1bf282 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -41,7 +41,7 @@ namespace MWClass std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool useAnim() const override; ///< Whether or not to use animated variant of model (default false) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 1ff7ef5bd6..1bf6f9c845 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -35,7 +35,7 @@ namespace MWClass } } - std::string Apparatus::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Apparatus::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index ce0916c079..c6bd45858a 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -49,7 +49,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; }; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 28bb1ff35c..ffa5b36a4d 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -44,7 +44,7 @@ namespace MWClass } } - std::string Armor::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Armor::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index d464360623..808bc078f4 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -74,7 +74,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/bodypart.cpp b/apps/openmw/mwclass/bodypart.cpp index 431fb69652..81e42ac725 100644 --- a/apps/openmw/mwclass/bodypart.cpp +++ b/apps/openmw/mwclass/bodypart.cpp @@ -42,7 +42,7 @@ namespace MWClass return false; } - std::string BodyPart::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view BodyPart::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/bodypart.hpp b/apps/openmw/mwclass/bodypart.hpp index 4a2d9d3620..4268c1ecf5 100644 --- a/apps/openmw/mwclass/bodypart.hpp +++ b/apps/openmw/mwclass/bodypart.hpp @@ -25,7 +25,7 @@ namespace MWClass bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index d731f56394..2c375547d0 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -41,7 +41,7 @@ namespace MWClass } } - std::string Book::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Book::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index a30e85c107..ca804a32e6 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -54,7 +54,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/classmodel.hpp b/apps/openmw/mwclass/classmodel.hpp index 65c2f87a14..f063ae7292 100644 --- a/apps/openmw/mwclass/classmodel.hpp +++ b/apps/openmw/mwclass/classmodel.hpp @@ -4,22 +4,16 @@ #include "../mwworld/livecellref.hpp" #include "../mwworld/ptr.hpp" -#include -#include - #include +#include namespace MWClass { template - std::string getClassModel(const MWWorld::ConstPtr& ptr) + std::string_view getClassModel(const MWWorld::ConstPtr& ptr) { const MWWorld::LiveCellRef* ref = ptr.get(); - - if (!ref->mBase->mModel.empty()) - return Misc::ResourceHelpers::correctMeshPath(ref->mBase->mModel); - - return {}; + return ref->mBase->mModel; } } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 32a0b62729..17519405de 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -39,7 +39,7 @@ namespace MWClass } } - std::string Clothing::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Clothing::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a1e8348713..f95559f9c0 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -66,7 +66,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; int getEnchantmentPoints(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 28779f971f..e2023ef8c3 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -126,7 +126,7 @@ namespace MWClass physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } - std::string Container::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Container::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 5f8b962e4a..88d8148fdc 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -85,7 +85,7 @@ namespace MWClass void respawn(const MWWorld::Ptr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 66a195489e..d19e5d5c43 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -178,14 +178,14 @@ namespace MWClass objects.insertCreature(ptr, model, hasInventoryStore(ptr)); } - std::string Creature::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Creature::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } - void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const + void Creature::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { - std::string model = getModel(ptr); + std::string_view model = getModel(ptr); if (!model.empty()) models.push_back(model); @@ -651,13 +651,13 @@ namespace MWClass if (sounds.empty()) { - const std::string model = getModel(ptr); + const std::string_view model = getModel(ptr); if (!model.empty()) { for (const ESM::Creature& creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() - && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(creature.mModel))) + && Misc::StringUtils::ciEqual(model, creature.mModel)) { const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 38b7bb0ec1..b8619128c2 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -105,9 +105,9 @@ namespace MWClass float getMaxSpeed(const MWWorld::Ptr& ptr) const override; - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; - void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 99acfcf4df..015c454915 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -94,7 +94,7 @@ namespace MWClass return true; } - std::string Door::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Door::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 17ada40c6f..18dd2348ab 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -51,7 +51,7 @@ namespace MWClass ESM::RefId getScript(const MWWorld::ConstPtr& ptr) const override; ///< Return name of the script attached to ptr - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; MWWorld::DoorState getDoorState(const MWWorld::ConstPtr& ptr) const override; /// This does not actually cause the door to move. Use World::activateDoor instead. diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 7059ae07cb..f5fd346637 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -96,9 +96,9 @@ namespace MWClass std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return {}; } - std::string getModel(const MWWorld::ConstPtr& ptr) const override + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override { - std::string model = getClassModel(ptr); + std::string_view model = getClassModel(ptr); // Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack. // Needed because otherwise LOD meshes are rendered on top of normal meshes. diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 2da7342de4..a7fb0a3544 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -175,17 +175,14 @@ namespace MWClass return getCustomData(ptr).mIsFemale; } - std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const { const ESM4NpcCustomData& data = getCustomData(ptr); if (data.mTraits == nullptr) return {}; - std::string_view model; if (data.mTraits->mIsTES4) - model = data.mTraits->mModel; - else - model = data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale; - return Misc::ResourceHelpers::correctMeshPath(model); + return data.mTraits->mModel; + return data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale; } std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/esm4npc.hpp b/apps/openmw/mwclass/esm4npc.hpp index 7830f20f32..39116586f1 100644 --- a/apps/openmw/mwclass/esm4npc.hpp +++ b/apps/openmw/mwclass/esm4npc.hpp @@ -54,7 +54,7 @@ namespace MWClass return ESM4Impl::getToolTipInfo(getName(ptr), count); } - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; std::string_view getName(const MWWorld::ConstPtr& ptr) const override; static const ESM4::Npc* getTraitsRecord(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 6bd28103f8..9af9a5703b 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -39,7 +39,7 @@ namespace MWClass } } - std::string Ingredient::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Ingredient::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index b7a36d300f..2e7632cd5e 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -47,7 +47,7 @@ namespace MWClass const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; float getWeight(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 6e34e3c2bd..dc37b8d154 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -70,7 +70,7 @@ namespace MWClass return true; } - std::string Light::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Light::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 70d6852ff8..97625ee5f8 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -69,7 +69,7 @@ namespace MWClass float getRemainingUsageTime(const MWWorld::ConstPtr& ptr) const override; ///< Returns the remaining duration of the object. - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; float getWeight(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index d3c3d479e4..42b5634b64 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -38,7 +38,7 @@ namespace MWClass } } - std::string Lockpick::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Lockpick::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index fc65a038a6..48c18411a6 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -59,7 +59,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 0f26dfd2df..0470a89a16 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -49,7 +49,7 @@ namespace MWClass } } - std::string Miscellaneous::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Miscellaneous::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index dafeb0c764..6b7b838953 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -45,7 +45,7 @@ namespace MWClass const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index de587954b8..b8cd4cd23d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" @@ -424,45 +425,51 @@ namespace MWClass return (ref->mBase->mRecordFlags & ESM::FLAG_Persistent) != 0; } - std::string Npc::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Npc::getModel(const MWWorld::ConstPtr& ptr) const + { + const MWWorld::LiveCellRef* ref = ptr.get(); + std::string_view model = Settings::models().mBaseanim.get(); + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); + if (race->mData.mFlags & ESM::Race::Beast) + model = Settings::models().mBaseanimkna.get(); + // Base animations should be in the meshes dir + constexpr std::string_view prefix = "meshes/"; + assert(VFS::Path::pathEqual(prefix, model.substr(0, prefix.size()))); + return model.substr(prefix.size()); + } + + std::string Npc::getCorrectedModel(const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef* ref = ptr.get(); - std::string model = Settings::models().mBaseanim; + const std::string& model = Settings::models().mBaseanim; const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mRace); if (race->mData.mFlags & ESM::Race::Beast) - model = Settings::models().mBaseanimkna; + return Settings::models().mBaseanimkna; return model; } - void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const + void Npc::getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const { const MWWorld::LiveCellRef* npc = ptr.get(); const auto& esmStore = MWBase::Environment::get().getESMStore(); - const ESM::Race* race = esmStore->get().search(npc->mBase->mRace); - if (race && race->mData.mFlags & ESM::Race::Beast) - models.push_back(Settings::models().mBaseanimkna); - - // keep these always loaded just in case - models.push_back(Settings::models().mXargonianswimkna); - models.push_back(Settings::models().mXbaseanimfemale); - models.push_back(Settings::models().mXbaseanim); + models.push_back(getModel(ptr)); if (!npc->mBase->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel)); + models.push_back(npc->mBase->mModel); if (!npc->mBase->mHead.empty()) { const ESM::BodyPart* head = esmStore->get().search(npc->mBase->mHead); if (head) - models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); + models.push_back(head->mModel); } if (!npc->mBase->mHair.empty()) { const ESM::BodyPart* hair = esmStore->get().search(npc->mBase->mHair); if (hair) - models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); + models.push_back(hair->mModel); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); @@ -486,7 +493,7 @@ namespace MWClass const ESM::BodyPart* part = esmStore->get().search(partname); if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + models.push_back(part->mModel); } }; if (equipped->getType() == ESM::Clothing::sRecordId) @@ -501,7 +508,7 @@ namespace MWClass } else { - std::string model = equipped->getClass().getModel(*equipped); + std::string_view model = equipped->getClass().getModel(*equipped); if (!model.empty()) models.push_back(model); } @@ -510,14 +517,14 @@ namespace MWClass } // preload body parts - if (race) + if (const ESM::Race* race = esmStore->get().search(npc->mBase->mRace)) { const std::vector& parts = MWRender::NpcAnimation::getBodyParts(race->mId, female, false, false); for (const ESM::BodyPart* part : parts) { if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); + models.push_back(part->mModel); } } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 95245bb994..1d70c5e1ba 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -85,7 +85,7 @@ namespace MWClass const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const override; - void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; + void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). @@ -131,7 +131,9 @@ namespace MWClass ESM::RefId getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const override; - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; + + std::string getCorrectedModel(const MWWorld::ConstPtr& ptr) const override; float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override; diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 7cf0c54f5c..e5da876d06 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -38,7 +38,7 @@ namespace MWClass } } - std::string Potion::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Potion::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 66b11b8ef4..057874ca1e 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -47,7 +47,7 @@ namespace MWClass const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; float getWeight(const MWWorld::ConstPtr& ptr) const override; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 96c94339bb..4f5e7be5cb 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -38,7 +38,7 @@ namespace MWClass } } - std::string Probe::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Probe::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 9a66d7a4bf..fc1092546e 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -54,7 +54,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index cf4a42be70..3000ea4087 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -36,7 +36,7 @@ namespace MWClass } } - std::string Repair::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Repair::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index ee96c83eeb..50c58231ce 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -44,7 +44,7 @@ namespace MWClass const std::string& getInventoryIcon(const MWWorld::ConstPtr& ptr) const override; ///< Return name of inventory icon. - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu (default implementation: return a diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index fe1226f19b..502a4fcb66 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -43,7 +43,7 @@ namespace MWClass physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } - std::string Static::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Static::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 65f60f7653..a3ea68a86f 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -32,7 +32,7 @@ namespace MWClass bool hasToolTip(const MWWorld::ConstPtr& ptr) const override; ///< @return true if this object has a tooltip when focused (default implementation: true) - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 2c9a9b5c7a..b5a3415717 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -43,7 +43,7 @@ namespace MWClass } } - std::string Weapon::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Weapon::getModel(const MWWorld::ConstPtr& ptr) const { return getClassModel(ptr); } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 110069341d..9e79532bc0 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -72,7 +72,7 @@ namespace MWClass std::unique_ptr use(const MWWorld::Ptr& ptr, bool force = false) const override; ///< Generate action for using via inventory menu - std::string getModel(const MWWorld::ConstPtr& ptr) const override; + std::string_view getModel(const MWWorld::ConstPtr& ptr) const override; bool canSell(const MWWorld::ConstPtr& item, int npcServices) const override; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 2c70cd0436..7da29a1cf0 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -134,7 +134,7 @@ namespace MWRender } } } - return shield.getClass().getModel(shield); + return shield.getClass().getCorrectedModel(shield); } std::string ActorAnimation::getSheathedShieldMesh(const MWWorld::ConstPtr& shield) const @@ -339,7 +339,7 @@ namespace MWRender if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) showHolsteredWeapons = false; - std::string mesh = weapon->getClass().getModel(*weapon); + std::string mesh = weapon->getClass().getCorrectedModel(*weapon); std::string scabbardName = mesh; std::string_view boneName = getHolsteredWeaponBoneName(*weapon); @@ -409,7 +409,7 @@ namespace MWRender if (weapon == inv.end() || weapon->getType() != ESM::Weapon::sRecordId) return; - std::string mesh = weapon->getClass().getModel(*weapon); + std::string_view mesh = weapon->getClass().getModel(*weapon); std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; @@ -466,7 +466,7 @@ namespace MWRender // Add new ones osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo); - std::string model = ammo->getClass().getModel(*ammo); + std::string model = ammo->getClass().getCorrectedModel(*ammo); for (unsigned int i = 0; i < ammoCount; ++i) { osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 58e03aa0a2..b56035eb5f 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -110,7 +110,7 @@ namespace MWRender MWWorld::ConstPtr item = *it; std::string_view bonename; - std::string itemModel = item.getClass().getModel(item); + std::string itemModel = item.getClass().getCorrectedModel(item); if (slot == MWWorld::InventoryStore::Slot_CarriedRight) { if (item.getType() == ESM::Weapon::sRecordId) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 7771735920..4193fb60b4 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -22,7 +22,7 @@ namespace MWRender const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem) : Animation(ptr, std::move(parentNode), resourceSystem) { - setObjectRoot(mPtr.getClass().getModel(mPtr), true, true, false); + setObjectRoot(mPtr.getClass().getCorrectedModel(mPtr), true, true, false); updateParts(); } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b9ea257c9f..1fb0b6c7df 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -952,7 +952,7 @@ namespace MWRender if (weapon != inv.end()) { osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - std::string mesh = weapon->getClass().getModel(*weapon); + std::string mesh = weapon->getClass().getCorrectedModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); @@ -1012,7 +1012,7 @@ namespace MWRender if (show && iter != inv.end()) { osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); - std::string mesh = iter->getClass().getModel(*iter); + std::string mesh = iter->getClass().getCorrectedModel(*iter); // For shields we must try to use the body part model if (iter->getType() == ESM::Armor::sRecordId) { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c2c6abd1bc..15faabb6df 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1497,7 +1497,7 @@ namespace MWRender osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const { osg::Vec3f halfExtents(0, 0, 0); - std::string modelName = object.getClass().getModel(object); + std::string modelName = object.getClass().getCorrectedModel(object); if (modelName.empty()) return halfExtents; @@ -1519,7 +1519,7 @@ namespace MWRender osg::BoundingBox RenderingManager::getCullSafeBoundingBox(const MWWorld::Ptr& ptr) const { - const std::string model = ptr.getClass().getModel(ptr); + const std::string model = ptr.getClass().getCorrectedModel(ptr); if (model.empty()) return {}; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index c19062168e..5db2a5e196 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -86,7 +86,7 @@ namespace MWRender MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; - std::string model = ammo->getClass().getModel(*ammo); + std::string model = ammo->getClass().getCorrectedModel(*ammo); osg::ref_ptr arrow = getResourceSystem()->getSceneManager()->getInstance(model, parent); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 476cacafd0..481a0e2ec1 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1435,7 +1435,7 @@ namespace MWScript msg << "Coordinates: " << pos.x() << " " << pos.y() << " " << pos.z() << std::endl; auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::string model - = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr), vfs); + = ::Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getCorrectedModel(ptr), vfs); msg << "Model: " << model << std::endl; if (!model.empty()) { @@ -1711,7 +1711,7 @@ namespace MWScript for (const T& record : store.get()) { MWWorld::ManualRef ref(store, record.mId); - std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + std::string model = ref.getPtr().getClass().getCorrectedModel(ref.getPtr()); if (!model.empty()) { sceneManager->getTemplate(model); diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index fe14856364..364f3e169e 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -59,7 +60,7 @@ namespace MWWorld return true; } - std::vector& mOut; + std::vector& mOut; }; /// Worker thread item: preload models in a cell. @@ -105,28 +106,28 @@ namespace MWWorld } } - for (std::string& mesh : mMeshes) + std::string mesh; + std::string kfname; + for (std::string_view path : mMeshes) { if (mAbort) break; try { + mesh = Misc::ResourceHelpers::correctMeshPath(path); mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size() - 1) { - Misc::StringUtils::lowerCaseInPlace(mesh); - if (mesh[slashpos + 1] == 'x') + if (Misc::StringUtils::toLower(mesh[slashpos + 1]) == 'x' + && Misc::StringUtils::ciEndsWith(mesh, ".nif")) { - if (mesh.size() > 4 && mesh.ends_with(".nif")) - { - std::string kfname = mesh; - kfname.replace(kfname.size() - 4, 4, ".kf"); - if (mSceneManager->getVFS()->exists(kfname)) - mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - } + kfname = mesh; + kfname.replace(kfname.size() - 4, 4, ".kf"); + if (mSceneManager->getVFS()->exists(kfname)) + mPreloadedObjects.insert(mKeyframeManager->get(kfname)); } } mPreloadedObjects.insert(mSceneManager->getTemplate(mesh)); @@ -144,11 +145,10 @@ namespace MWWorld } private: - typedef std::vector MeshList; bool mIsExterior; int mX; int mY; - MeshList mMeshes; + std::vector mMeshes; Resource::SceneManager* mSceneManager; Resource::BulletShapeManager* mBulletShapeManager; Resource::KeyframeManager* mKeyframeManager; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 82aa83e7c6..932c290aaa 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -305,19 +306,27 @@ namespace MWWorld void Class::adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const {} - std::string Class::getModel(const MWWorld::ConstPtr& ptr) const + std::string_view Class::getModel(const MWWorld::ConstPtr& ptr) const { return {}; } + std::string Class::getCorrectedModel(const MWWorld::ConstPtr& ptr) const + { + std::string_view model = getModel(ptr); + if (!model.empty()) + return Misc::ResourceHelpers::correctMeshPath(model); + return {}; + } + bool Class::useAnim() const { return false; } - void Class::getModelsToPreload(const ConstPtr& ptr, std::vector& models) const + void Class::getModelsToPreload(const ConstPtr& ptr, std::vector& models) const { - std::string model = getModel(ptr); + std::string_view model = getModel(ptr); if (!model.empty()) models.push_back(model); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c787505238..0c6d55692f 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -275,12 +275,14 @@ namespace MWWorld virtual int getServices(const MWWorld::ConstPtr& actor) const; - virtual std::string getModel(const MWWorld::ConstPtr& ptr) const; + virtual std::string_view getModel(const MWWorld::ConstPtr& ptr) const; + + virtual std::string getCorrectedModel(const MWWorld::ConstPtr& ptr) const; virtual bool useAnim() const; ///< Whether or not to use animated variant of model (default false) - virtual void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const; + virtual void getModelsToPreload(const MWWorld::ConstPtr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: ///< list getModel(). diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 0584a9fe94..0b2ba46de0 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -311,7 +311,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); - auto model = ptr.getClass().getModel(ptr); + auto model = ptr.getClass().getCorrectedModel(ptr); createModel(state, model, pos, orient, true, true, lightDiffuseColor, texture); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); @@ -351,7 +351,7 @@ namespace MWWorld MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); - const auto model = ptr.getClass().getModel(ptr); + const auto model = ptr.getClass().getCorrectedModel(ptr); createModel(state, model, pos, orient, false, false, osg::Vec4(0, 0, 0, 0)); if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); @@ -696,7 +696,7 @@ namespace MWWorld { MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); - model = ptr.getClass().getModel(ptr); + model = ptr.getClass().getCorrectedModel(ptr); int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; @@ -749,7 +749,7 @@ namespace MWWorld { MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); - model = ptr.getClass().getModel(ptr); + model = ptr.getClass().getCorrectedModel(ptr); } catch (...) { diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 80f52f2375..8424076758 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -96,7 +96,7 @@ namespace { if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) return {}; - return ptr.getClass().getModel(ptr); + return ptr.getClass().getCorrectedModel(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f20cbd208f..3ac618b56b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2308,7 +2308,7 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr()); mPhysics->remove(getPlayerPtr()); - mPhysics->addActor(getPlayerPtr(), getPlayerPtr().getClass().getModel(getPlayerPtr())); + mPhysics->addActor(getPlayerPtr(), getPlayerPtr().getClass().getCorrectedModel(getPlayerPtr())); applyLoopingParticles(player); @@ -3701,7 +3701,7 @@ namespace MWWorld try { MWWorld::ManualRef ref(store, obj); - std::string model = ref.getPtr().getClass().getModel(ref.getPtr()); + std::string model = ref.getPtr().getClass().getCorrectedModel(ref.getPtr()); if (!model.empty()) scene->preload(model, ref.getPtr().getClass().useAnim()); } From 1288ec5cea93c201dfe0252d3c1a3db117d8b8a1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 27 Jan 2024 16:49:20 +0100 Subject: [PATCH 0891/2167] Use deserializeText for find and countOf --- apps/openmw/mwlua/objectbindings.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 47c55e86f0..2a30e31948 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -215,7 +215,7 @@ namespace MWLua objectT["recordId"] = sol::readonly_property( [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); objectT["globalVariable"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { - std::string globalVariable = o.ptr().getCellRef().getGlobalVariable(); + std::string_view globalVariable = o.ptr().getCellRef().getGlobalVariable(); if (globalVariable.empty()) return sol::nullopt; else @@ -619,7 +619,7 @@ namespace MWLua inventoryT["countOf"] = [](const InventoryT& inventory, std::string_view recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - return store.count(ESM::RefId::stringRefId(recordId)); + return store.count(ESM::RefId::deserializeText(recordId)); }; if constexpr (std::is_same_v) { @@ -637,7 +637,7 @@ namespace MWLua inventoryT["find"] = [](const InventoryT& inventory, std::string_view recordId) -> sol::optional { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - auto itemId = ESM::RefId::stringRefId(recordId); + auto itemId = ESM::RefId::deserializeText(recordId); for (const MWWorld::Ptr& item : store) { if (item.getCellRef().getRefId() == itemId) @@ -651,7 +651,7 @@ namespace MWLua inventoryT["findAll"] = [](const InventoryT& inventory, std::string_view recordId) { const MWWorld::Ptr& ptr = inventory.mObj.ptr(); MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); - auto itemId = ESM::RefId::stringRefId(recordId); + auto itemId = ESM::RefId::deserializeText(recordId); ObjectIdList list = std::make_shared>(); for (const MWWorld::Ptr& item : store) { From 4fcacd59aa2e703e0cac0ef5cbd3538657fd962f Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 27 Jan 2024 12:02:56 -0600 Subject: [PATCH 0892/2167] Add model to NPC lua --- apps/openmw/mwlua/types/npc.cpp | 3 +++ files/lua_api/openmw/types.lua | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 30c8fd60d9..b1ac3d994a 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/mechanicsmanager.hpp" @@ -78,6 +79,8 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> int { return (int)rec.mNpdt.mDisposition; }); record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.serializeText(); }); + record["model"] = sol::readonly_property( + [](const ESM::NPC& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["isMale"] = sol::readonly_property([](const ESM::NPC& rec) -> bool { return rec.isMale(); }); record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index ba0b2dd58b..734288bfb7 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -939,6 +939,7 @@ -- @field #string name -- @field #string race -- @field #string class Name of the NPC's class (e. g. Acrobat) +-- @field #string model Path to the model associated with this NPC, used for animations. -- @field #string mwscript MWScript that is attached to this NPC -- @field #string hair Path to the hair body part model -- @field #string head Path to the head body part model From 2008f35e5794781aa2a2885e0f454c912edcf8c3 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 27 Jan 2024 19:09:26 +0100 Subject: [PATCH 0893/2167] Don't reset player setting groups right after game load, refactor update group functions --- files/data/scripts/omw/settings/menu.lua | 42 +++++++++--------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 9b161df588..6d6d07a20b 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -396,51 +396,38 @@ local function onGroupRegistered(global, key) end end - -local function updatePlayerGroups() - local playerGroups = storage.playerSection(common.groupSectionKey) - for groupKey in pairs(playerGroups:asTable()) do - onGroupRegistered(false, groupKey) +local function updateGroups(global) + local groupSection = common.getSection(global, common.groupSectionKey) + for groupKey in pairs(groupSection:asTable()) do + onGroupRegistered(global, groupKey) end - playerGroups:subscribe(async:callback(function(_, key) + groupSection:subscribe(async:callback(function(_, key) if key then - onGroupRegistered(false, key) + onGroupRegistered(global, key) else - for groupKey in pairs(playerGroups:asTable()) do - onGroupRegistered(false, groupKey) + for groupKey in pairs(groupSection:asTable()) do + onGroupRegistered(global, groupKey) end end end)) end +local updatePlayerGroups = function() updateGroups(false) end updatePlayerGroups() -local function updateGlobalGroups() - local globalGroups = storage.globalSection(common.groupSectionKey) - for groupKey in pairs(globalGroups:asTable()) do - onGroupRegistered(true, groupKey) - end - globalGroups:subscribe(async:callback(function(_, key) - if key then - onGroupRegistered(true, key) - else - for groupKey in pairs(globalGroups:asTable()) do - onGroupRegistered(true, groupKey) - end - end - end)) -end +local updateGlobalGroups = function() updateGroups(true) end local menuGroups = {} local menuPages = {} local function resetPlayerGroups() - local settingGroupsSection = storage.playerSection(common.groupSectionKey) + print('MENU reset player groups') + local playerGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do for groupKey, group in pairs(page) do if not menuGroups[groupKey] and not group.global then page[groupKey] = nil - settingGroupsSection:set(groupKey, nil) + playerGroupsSection:set(groupKey, nil) end end if pageOptions[pageKey] then @@ -513,10 +500,11 @@ return { }, engineHandlers = { onStateChanged = function() - resetPlayerGroups() if menu.getState() == menu.STATE.Running then updatePlayerGroups() updateGlobalGroups() + else + resetPlayerGroups() end end, }, From 422e4551572569d0e26c04f6c09c9a941b018616 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 28 Jan 2024 05:38:12 +0300 Subject: [PATCH 0894/2167] Actually normalize the sun position exposed to post-processing --- components/fx/stateupdater.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/fx/stateupdater.hpp b/components/fx/stateupdater.hpp index f1a4cd89f3..9b7a25286a 100644 --- a/components/fx/stateupdater.hpp +++ b/components/fx/stateupdater.hpp @@ -43,6 +43,7 @@ namespace fx void setSunPos(const osg::Vec4f& pos, bool night) { mData.get() = pos; + mData.get().normalize(); if (night) mData.get().z() *= -1.f; From 43307bee28da92c55d46856e9cd2f4be6bbf24d1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 28 Jan 2024 11:25:46 +0400 Subject: [PATCH 0895/2167] Make ContentSelector errors localizable --- components/contentselector/model/contentmodel.cpp | 4 +++- components/contentselector/model/contentmodel.hpp | 4 ++++ components/contentselector/model/loadordererror.cpp | 12 ------------ components/contentselector/model/loadordererror.hpp | 1 - 4 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 components/contentselector/model/loadordererror.cpp diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0aab06ac90..d800112712 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -723,8 +723,10 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile* file) const int index = indexFromItem(item(file->filePath())).row(); for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) { + assert(error.errorCode() != LoadOrderError::ErrorCode::ErrorCode_None); + text += "

"; - text += error.toolTip(); + text += mErrorToolTips[error.errorCode() - 1].arg(error.fileName()); text += "

"; } text += (""); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 1ba3090a32..f754b9ea30 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -93,6 +93,10 @@ namespace ContentSelectorModel QIcon mWarningIcon; bool mShowOMWScripts; + QString mErrorToolTips[ContentSelectorModel::LoadOrderError::ErrorCode_LoadOrder] + = { tr("Unable to find dependent file: %1"), tr("Dependent file needs to be active: %1"), + tr("This file needs to load after %1") }; + public: QString mMimeType; QStringList mMimeTypes; diff --git a/components/contentselector/model/loadordererror.cpp b/components/contentselector/model/loadordererror.cpp deleted file mode 100644 index c1b2025588..0000000000 --- a/components/contentselector/model/loadordererror.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "loadordererror.hpp" -#include - -QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder] - = { QString("Unable to find dependent file: %1"), QString("Dependent file needs to be active: %1"), - QString("This file needs to load after %1") }; - -QString ContentSelectorModel::LoadOrderError::toolTip() const -{ - assert(mErrorCode); - return sErrorToolTips[mErrorCode - 1].arg(mFileName); -} diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp index 8f47b6ed6a..b066ce4d4e 100644 --- a/components/contentselector/model/loadordererror.hpp +++ b/components/contentselector/model/loadordererror.hpp @@ -28,7 +28,6 @@ namespace ContentSelectorModel } inline ErrorCode errorCode() const { return mErrorCode; } inline QString fileName() const { return mFileName; } - QString toolTip() const; private: ErrorCode mErrorCode; From b83b30f0dc3b2827feea09841239455267dd37b8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 22 Jan 2024 19:06:51 +0300 Subject: [PATCH 0896/2167] Editor: Reset effect arguments when the effect ID changes (#7785) --- CHANGELOG.md | 1 + .../model/world/nestedcoladapterimp.hpp | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa220717d..7f92c39052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work + Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 235396c650..46928973fe 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -385,6 +385,26 @@ namespace CSMWorld case 0: { effect.mEffectID = static_cast(value.toInt()); + switch (effect.mEffectID) + { + case ESM::MagicEffect::DrainSkill: + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + case ESM::MagicEffect::FortifySkill: + case ESM::MagicEffect::AbsorbSkill: + effect.mAttribute = -1; + break; + case ESM::MagicEffect::DrainAttribute: + case ESM::MagicEffect::DamageAttribute: + case ESM::MagicEffect::RestoreAttribute: + case ESM::MagicEffect::FortifyAttribute: + case ESM::MagicEffect::AbsorbAttribute: + effect.mSkill = -1; + break; + default: + effect.mSkill = -1; + effect.mAttribute = -1; + } break; } case 1: From 36e1bdab1053bd2dd3ca8a46c98795afe0091600 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 27 Jan 2024 18:57:51 +0100 Subject: [PATCH 0897/2167] Use a smaller integer type instead of dealing with casting issues. --- apps/openmw/mwbase/luamanager.hpp | 2 +- apps/openmw/mwbase/mechanicsmanager.hpp | 6 ++--- apps/openmw/mwlua/animationbindings.cpp | 4 +-- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.hpp | 3 ++- apps/openmw/mwmechanics/actors.cpp | 4 +-- apps/openmw/mwmechanics/actors.hpp | 6 ++--- apps/openmw/mwmechanics/character.cpp | 25 ++++++++++--------- apps/openmw/mwmechanics/character.hpp | 6 ++--- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +-- .../mwmechanics/mechanicsmanagerimp.hpp | 6 ++--- apps/openmw/mwmechanics/objects.cpp | 6 ++--- apps/openmw/mwmechanics/objects.hpp | 4 +-- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/animation.hpp | 4 +-- apps/openmw/mwscript/animationextensions.cpp | 2 +- 16 files changed, 44 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 0503fcec9e..57886d5399 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -64,7 +64,7 @@ namespace MWBase virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 532100af7a..d7e377a0eb 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -171,7 +171,7 @@ namespace MWBase ///< Forces an object to refresh its animation state. virtual bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number = 1, bool scripted = false) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number = 1, bool scripted = false) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. @@ -180,8 +180,8 @@ namespace MWBase /// \param number How many times the animation should be run /// \param scripted Whether the animation should be treated as a scripted animation. /// \return Success or error - virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, - std::string_view startKey, std::string_view stopKey, bool forceLoop) + virtual bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, + float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) = 0; ///< Lua variant of playAnimationGroup. The mode parameter is omitted /// and forced to 0. modes 1 and 2 can be emulated by doing clearAnimationQueue() and diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 272685dc11..ecd1e96dbb 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -249,7 +249,7 @@ namespace MWLua // Extended variant of MWScript's PlayGroup and LoopGroup api["playQueued"] = sol::overload( [mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) { - int numberOfLoops = options.get_or("loops", std::numeric_limits::max()); + uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits::max()); float speed = options.get_or("speed", 1.f); std::string startKey = options.get_or("startkey", "start"); std::string stopKey = options.get_or("stopkey", "stop"); @@ -265,7 +265,7 @@ namespace MWLua }); api["playBlended"] = [](const sol::object& object, std::string_view groupname, const sol::table& options) { - int loops = options.get_or("loops", 0); + uint32_t loops = options.get_or("loops", 0u); MWRender::Animation::AnimPriority priority = getPriorityArgument(options); BlendMask blendMask = options.get_or("blendmask", BlendMask::BlendMask_All); bool autoDisable = options.get_or("autodisable", true); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 89402a6008..ac5ae60f7d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -373,7 +373,7 @@ namespace MWLua void LuaManager::playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) { sol::table options = mLua.newTable(); options["blendmask"] = blendMask; diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 7556abad5d..a539d04348 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,7 +82,8 @@ namespace MWLua void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) override; void playAnimation(const MWWorld::Ptr& actor, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, - std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback) override; + std::string_view start, std::string_view stop, float startpoint, uint32_t loops, + bool loopfallback) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92f8a212c9..a4e7168b0f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2005,7 +2005,7 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) @@ -2020,7 +2020,7 @@ namespace MWMechanics } } - bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool Actors::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { const auto iter = mIndex.find(ptr.mRef); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3ead5f069a..fe9f37511a 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -112,9 +112,9 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; - bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, + bool scripted = false) const; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5533cb578b..08a476631c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -483,7 +483,8 @@ namespace MWMechanics return; } - playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); + playBlendedAnimation(mCurrentHit, priority, MWRender::BlendMask_All, true, 1, startKey, stopKey, 0.0f, + std::numeric_limits::max()); } void CharacterController::refreshJumpAnims(JumpingState jump, bool force) @@ -521,7 +522,7 @@ namespace MWMechanics mCurrentJump = jumpAnimName; if (mJumpState == JumpState_InAir) playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, - startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); + startAtLoop ? "loop start" : "start", "stop", 0.f, std::numeric_limits::max()); else if (mJumpState == JumpState_Landing) playBlendedAnimation(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } @@ -749,8 +750,8 @@ namespace MWMechanics } } - playBlendedAnimation( - mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); + playBlendedAnimation(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, + std::numeric_limits::max(), true); } void CharacterController::refreshIdleAnims(CharacterState idle, bool force) @@ -778,7 +779,7 @@ namespace MWMechanics } MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); - size_t numLoops = std::numeric_limits::max(); + size_t numLoops = std::numeric_limits::max(); // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to // "idle"+weapon or "idle". @@ -1192,8 +1193,8 @@ namespace MWMechanics if (!animPlaying) { int mask = MWRender::BlendMask_Torso | MWRender::BlendMask_RightArm; - playBlendedAnimation( - "idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); + playBlendedAnimation("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, + std::numeric_limits::max(), true); } else { @@ -1326,7 +1327,7 @@ namespace MWMechanics mAnimation->disable("shield"); playBlendedAnimation("torch", Priority_Torch, MWRender::BlendMask_LeftArm, false, 1.0f, "start", "stop", - 0.0f, std::numeric_limits::max(), true); + 0.0f, std::numeric_limits::max(), true); } else if (mAnimation->isPlaying("torch")) { @@ -2548,7 +2549,7 @@ namespace MWMechanics groupname, priority, blendMask, autodisable, speedmult, start, stop, startpoint, loops, loopfallback); } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) + bool CharacterController::playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; @@ -2585,7 +2586,7 @@ namespace MWMechanics // exactly x times, while non-actors will loop x+1 instead. if (mPtr.getClass().isActor()) count--; - count = std::max(count, 0); + count = std::max(count, 0u); AnimationQueueEntry entry; entry.mGroup = groupname; @@ -2620,7 +2621,7 @@ namespace MWMechanics } bool CharacterController::playGroupLua(std::string_view groupname, float speed, std::string_view startKey, - std::string_view stopKey, int loops, bool forceLoop) + std::string_view stopKey, uint32_t loops, bool forceLoop) { // Note: In mwscript, "idle" is a special case used to clear the anim queue. // In lua we offer an explicit clear method instead so this method does not treat "idle" special. @@ -2632,7 +2633,7 @@ namespace MWMechanics entry.mGroup = groupname; // Note: MWScript gives one less loop to actors than non-actors. // But this is the Lua version. We don't need to reproduce this weirdness here. - entry.mLoopCount = std::max(loops, 0); + entry.mLoopCount = std::max(loops, 0u); entry.mStartKey = startKey; entry.mStopKey = stopKey; entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index a507c73743..7146baeb0e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -134,7 +134,7 @@ namespace MWMechanics struct AnimationQueueEntry { std::string mGroup; - size_t mLoopCount; + uint32_t mLoopCount; float mTime; bool mLooping; bool mScripted; @@ -277,9 +277,9 @@ namespace MWMechanics void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, bool loopfallback = false) const; - bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); + bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false); bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, - int loops, bool forceLoop); + uint32_t loops, bool forceLoop); void enableLuaAnimations(bool enable); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 5323f7e65c..9fd1b3ff8d 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -749,14 +749,14 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) { if (ptr.getClass().isActor()) return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } - bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, + bool MechanicsManager::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { if (ptr.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 93c1fa3dc2..029d974ac2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -141,9 +141,9 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error - bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + bool playAnimationGroup(const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, + bool scripted = false) override; + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) override; void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable) override; void skipAnimation(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 32d484df2f..12d342666b 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,7 +99,7 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) @@ -114,8 +114,8 @@ namespace MWMechanics } } - bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, - std::string_view startKey, std::string_view stopKey, bool forceLoop) + bool Objects::playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, + float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 1fe43530b0..31c2768b1b 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,8 +46,8 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); - bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, int loops, float speed, + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, uint32_t number, bool scripted = false); + bool playAnimationGroupLua(const MWWorld::Ptr& ptr, std::string_view groupName, uint32_t loops, float speed, std::string_view startKey, std::string_view stopKey, bool forceLoop); void enableLuaAnimations(const MWWorld::Ptr& ptr, bool enable); void skipAnimation(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 581d2843ab..9cdbb19a98 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -805,7 +805,7 @@ namespace MWRender } void Animation::play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, - float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) { if (!mObjectRoot || mAnimSources.empty()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index dae81592b3..a2226a3054 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -153,7 +153,7 @@ namespace MWRender bool mPlaying; bool mLoopingEnabled; - size_t mLoopCount; + uint32_t mLoopCount; AnimPriority mPriority; int mBlendMask; @@ -379,7 +379,7 @@ namespace MWRender * the "start" and "stop" keys for looping? */ void play(std::string_view groupname, const AnimPriority& priority, int blendMask, bool autodisable, - float speedmult, std::string_view start, std::string_view stop, float startpoint, size_t loops, + float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback = false); /** Adjust the speed multiplier of an already playing animation. diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 8d439ec82b..16c1f5a134 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -56,7 +56,7 @@ namespace MWScript } MWBase::Environment::get().getMechanicsManager()->playAnimationGroup( - ptr, group, mode, std::numeric_limits::max(), true); + ptr, group, mode, std::numeric_limits::max(), true); } }; From 993cea7d657db6b16eb65a08f1870ba394c87858 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 16:24:15 +0100 Subject: [PATCH 0898/2167] MR Comments --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08a476631c..2b980ebd41 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2516,7 +2516,8 @@ namespace MWMechanics { AnimationQueueEntry entry; entry.mGroup = iter->mGroup; - entry.mLoopCount = iter->mLoopCount; + entry.mLoopCount + = static_cast(std::min(iter->mLoopCount, std::numeric_limits::max())); entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup); entry.mStartKey = "start"; entry.mStopKey = "stop"; @@ -2586,7 +2587,6 @@ namespace MWMechanics // exactly x times, while non-actors will loop x+1 instead. if (mPtr.getClass().isActor()) count--; - count = std::max(count, 0u); AnimationQueueEntry entry; entry.mGroup = groupname; @@ -2633,7 +2633,7 @@ namespace MWMechanics entry.mGroup = groupname; // Note: MWScript gives one less loop to actors than non-actors. // But this is the Lua version. We don't need to reproduce this weirdness here. - entry.mLoopCount = std::max(loops, 0u); + entry.mLoopCount = loops; entry.mStartKey = startKey; entry.mStopKey = stopKey; entry.mLooping = mAnimation->isLoopingAnimation(groupname) || forceLoop; From 24a0a0c3bf8c311992187d4f95841a4cc0c928fb Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 16:34:44 +0100 Subject: [PATCH 0899/2167] size_t -> uint32_t --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2b980ebd41..5c01c5c355 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2540,7 +2540,7 @@ namespace MWMechanics void CharacterController::playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, - float startpoint, size_t loops, bool loopfallback) const + float startpoint, uint32_t loops, bool loopfallback) const { if (mLuaAnimations) MWBase::Environment::get().getLuaManager()->playAnimation(mPtr, groupname, priority, blendMask, autodisable, diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 7146baeb0e..430635fff5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -276,7 +276,7 @@ namespace MWMechanics void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, - size_t loops, bool loopfallback = false) const; + uint32_t loops, bool loopfallback = false) const; bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false); bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, uint32_t loops, bool forceLoop); From 0ed94ead4ea257a6b7e826e6117ca9bc06928ebb Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 17:34:22 +0100 Subject: [PATCH 0900/2167] Check that count is non-zero before decrementing it. --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5c01c5c355..8d69da2c43 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2585,7 +2585,7 @@ namespace MWMechanics // if played with a count of 0, all objects play exactly once from start to stop. // But if the count is x > 0, actors and non-actors behave differently. actors will loop // exactly x times, while non-actors will loop x+1 instead. - if (mPtr.getClass().isActor()) + if (mPtr.getClass().isActor() && count > 0) count--; AnimationQueueEntry entry; From fbffecfd13ed858feb1294ae28bbc2d6aa0d78c6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 28 Jan 2024 21:02:06 +0100 Subject: [PATCH 0901/2167] ~0ul -> std::numeric_limits::max() --- apps/openmw/mwrender/characterpreview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index aa6b5eb4dd..59844ee9ae 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -464,7 +464,7 @@ namespace MWRender { if (!mAnimation->getInfo("torch")) mAnimation->play( - "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, ~0ul, true); + "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, std::numeric_limits::max(), true); } else if (mAnimation->getInfo("torch")) mAnimation->disable("torch"); From c90ebcc86b948780f0b26e4883c2c37e72b5d2d6 Mon Sep 17 00:00:00 2001 From: Yury Stepovikov Date: Sun, 28 Jan 2024 21:33:10 +0000 Subject: [PATCH 0902/2167] Allow multiselect in the archives tab (#7606) --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 80 ++++++- apps/launcher/datafilespage.hpp | 11 +- apps/launcher/ui/datafilespage.ui | 333 ++++++++++++++++-------------- 4 files changed, 261 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..0b49b0ae9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,7 @@ Feature #7546: Start the game on Fredas Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music + Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 114221ce92..70ebe29625 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #include @@ -162,8 +164,8 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); - connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchive(-1); }); - connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchive(1); }); + connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); + connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); connect( ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); @@ -218,6 +220,18 @@ void Launcher::DataFilesPage::buildView() &DataFilesPage::readNavMeshToolStderr); connect(mNavMeshToolInvoker->getProcess(), qOverload(&QProcess::finished), this, &DataFilesPage::navMeshToolFinished); + + buildArchiveContextMenu(); +} + +void Launcher::DataFilesPage::buildArchiveContextMenu() +{ + connect(ui.archiveListWidget, &QListWidget::customContextMenuRequested, this, + &DataFilesPage::slotShowArchiveContextMenu); + + mArchiveContextMenu = new QMenu(ui.archiveListWidget); + mArchiveContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); + mArchiveContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); } bool Launcher::DataFilesPage::loadSettings() @@ -707,17 +721,71 @@ void Launcher::DataFilesPage::removeDirectory() refreshDataFilesView(); } -void Launcher::DataFilesPage::moveArchive(int step) +void Launcher::DataFilesPage::slotShowArchiveContextMenu(const QPoint& pos) { - int selectedRow = ui.archiveListWidget->currentRow(); + QPoint globalPos = ui.archiveListWidget->viewport()->mapToGlobal(pos); + mArchiveContextMenu->exec(globalPos); +} + +void Launcher::DataFilesPage::setCheckStateForMultiSelectedItems(bool checked) +{ + Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; + + for (QListWidgetItem* selectedItem : ui.archiveListWidget->selectedItems()) + { + selectedItem->setCheckState(checkState); + } +} + +void Launcher::DataFilesPage::slotUncheckMultiSelectedItems() +{ + setCheckStateForMultiSelectedItems(false); +} + +void Launcher::DataFilesPage::slotCheckMultiSelectedItems() +{ + setCheckStateForMultiSelectedItems(true); +} + +void Launcher::DataFilesPage::moveArchives(int step) +{ + QList selectedItems = ui.archiveListWidget->selectedItems(); + QList> sortedItems; + + for (QListWidgetItem* selectedItem : selectedItems) + { + int selectedRow = ui.archiveListWidget->row(selectedItem); + sortedItems.append(qMakePair(selectedRow, selectedItem)); + } + + if (step > 0) + { + std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first > b.first; }); + } + else + { + std::sort(sortedItems.begin(), sortedItems.end(), [](auto a, auto b) { return a.first < b.first; }); + } + + for (auto i : sortedItems) + { + if (!moveArchive(i.second, step)) + break; + } +} + +bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step) +{ + int selectedRow = ui.archiveListWidget->row(listItem); int newRow = selectedRow + step; if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) - return; + return false; - const auto* item = ui.archiveListWidget->takeItem(selectedRow); + const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); addArchive(item->text(), item->checkState(), newRow); ui.archiveListWidget->setCurrentRow(newRow); + return true; } void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState selected, int row) diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index dc3aeaef6f..e568137e8f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ namespace Launcher ContentSelectorView::ContentSelector* mSelector; Ui::DataFilesPage ui; + QMenu* mArchiveContextMenu; public: explicit DataFilesPage(const Files::ConfigurationManager& cfg, Config::GameSettings& gameSettings, @@ -72,9 +74,13 @@ namespace Launcher void addSubdirectories(bool append); void sortDirectories(); void removeDirectory(); - void moveArchive(int step); + void moveArchives(int step); void moveDirectory(int step); + void slotShowArchiveContextMenu(const QPoint& pos); + void slotCheckMultiSelectedItems(); + void slotUncheckMultiSelectedItems(); + void on_newProfileAction_triggered(); void on_cloneProfileAction_triggered(); void on_deleteProfileAction_triggered(); @@ -120,7 +126,10 @@ namespace Launcher void addArchive(const QString& name, Qt::CheckState selected, int row = -1); void addArchivesFromDir(const QString& dir); + bool moveArchive(QListWidgetItem* listItem, int step); void buildView(); + void buildArchiveContextMenu(); + void setCheckStateForMultiSelectedItems(bool checked); void setProfile(int index, bool savePrevious); void setProfile(const QString& previous, const QString& current, bool savePrevious); void removeProfile(const QString& profile); diff --git a/apps/launcher/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui index 249207123e..2b54307838 100644 --- a/apps/launcher/ui/datafilespage.ui +++ b/apps/launcher/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 573 - 384 + 557
@@ -29,6 +29,12 @@
+ + + 0 + 0 + + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -41,14 +47,111 @@ Data Directories - + QAbstractItemView::InternalMove - + + + + + + + 0 + 33 + + + + Scan directories for likely data directories and append them at the end of the list. + + + Append + + + + + + + + 0 + 33 + + + + Scan directories for likely data directories and insert them above the selected position + + + Insert Above + + + + + + + + 0 + 33 + + + + Move selected directory one position up + + + Move Up + + + + + + + + 0 + 33 + + + + Move selected directory one position down + + + Move Down + + + + + + + + 0 + 33 + + + + Remove selected directory + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + @@ -61,116 +164,6 @@ - - - - - 0 - 33 - - - - - 0 - 33 - - - - Scan directories for likely data directories and append them at the end of the list. - - - Append - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Scan directories for likely data directories and insert them above the selected position - - - Insert Above - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected directory one position up - - - Move Up - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected directory one position down - - - Move Down - - - - - - - - 0 - 33 - - - - - 0 - 33 - - - - Remove selected directory - - - Remove - - - @@ -178,64 +171,90 @@ Archive Files - + + + Qt::CustomContextMenu + QAbstractItemView::InternalMove + + Qt::CopyAction + + + QAbstractItemView::ExtendedSelection + - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected archive one position up - - - Move Up - - + + + + + + 0 + 33 + + + + + 0 + 33 + + + + Move selected archive one position up + + + Move Up + + + + + + + + 0 + 33 + + + + + 0 + 33 + + + + Move selected archive one position down + + + Move Down + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + - + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - - - - 0 - 33 - - - - - 0 - 33 - - - - Move selected archive one position down - - - Move Down - - - From f9b69623d35a23d7eb9a00487e2645a2585c842a Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 29 Jan 2024 11:01:52 +0100 Subject: [PATCH 0903/2167] Remove stateless encoder from ReadersCache It was added by https://gitlab.com/OpenMW/openmw/-/merge_requests/2804 without a good reason. There is already encoder available in the used context. --- apps/openmw/mwworld/esmloader.cpp | 11 ++++++----- apps/openmw/mwworld/worldimp.cpp | 2 -- components/esm3/readerscache.hpp | 14 +------------- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index e586a4c204..0be90c65f0 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -64,11 +64,12 @@ namespace MWWorld } case ESM::Format::Tes4: { - ESM4::Reader readerESM4(std::move(stream), filepath, - MWBase::Environment::get().getResourceSystem()->getVFS(), mReaders.getStatelessEncoder()); - readerESM4.setModIndex(index); - readerESM4.updateModIndices(mNameToIndex); - mStore.loadESM4(readerESM4); + ESM4::Reader reader(std::move(stream), filepath, + MWBase::Environment::get().getResourceSystem()->getVFS(), + mEncoder != nullptr ? &mEncoder->getStatelessEncoder() : nullptr); + reader.setModIndex(index); + reader.updateModIndices(mNameToIndex); + mStore.loadESM4(reader); break; } } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c6282c6f5a..a265ff0a76 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -274,8 +274,6 @@ namespace MWWorld const std::vector& groundcoverFiles, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { mContentFiles = contentFiles; - if (encoder) - mReaders.setStatelessEncoder(encoder->getStatelessEncoder()); mESMVersions.resize(mContentFiles.size(), -1); loadContentFiles(fileCollections, contentFiles, encoder, listener); diff --git a/components/esm3/readerscache.hpp b/components/esm3/readerscache.hpp index 94bee206ec..5d8c2afcbd 100644 --- a/components/esm3/readerscache.hpp +++ b/components/esm3/readerscache.hpp @@ -9,8 +9,6 @@ #include #include -#include - namespace ESM { class ReadersCache @@ -57,23 +55,13 @@ namespace ESM BusyItem get(std::size_t index); - void setStatelessEncoder(const ToUTF8::StatelessUtf8Encoder& statelessEncoderPtr) - { - mStatelessEncoder.emplace(statelessEncoderPtr); - } - - const ToUTF8::StatelessUtf8Encoder* getStatelessEncoder() - { - return mStatelessEncoder.has_value() ? &mStatelessEncoder.value() : nullptr; - } - private: const std::size_t mCapacity; std::map::iterator> mIndex; std::list mBusyItems; std::list mFreeItems; std::list mClosedItems; - std::optional mStatelessEncoder; + inline void closeExtraReaders(); inline void releaseItem(std::list::iterator it) noexcept; From 8ed7a5319d47a283227da1002e8b259a097fb593 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 29 Jan 2024 22:08:00 +0100 Subject: [PATCH 0904/2167] Exclude deleted actors, prevent copies, and try to avoid a second getActorsSidingWith call --- apps/openmw/mwmechanics/actors.cpp | 8 ++++---- apps/openmw/mwscript/aiextensions.cpp | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5650ad9d2a..f9239d32d4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -606,8 +606,7 @@ namespace MWMechanics // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting // those actors, (recursive) and any actor currently being followed or escorted by actor1 - const std::set allies1 = cachedAllies.getActorsSidingWith(actor1); - const std::set allies2 = cachedAllies.getActorsSidingWith(actor2); + const std::set& allies1 = cachedAllies.getActorsSidingWith(actor1); const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and @@ -619,7 +618,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2, &allies2); + mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2)); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -644,6 +643,7 @@ namespace MWMechanics // Check that actor2 is in combat with actor1 if (creatureStats2.getAiSequence().isInCombat(actor1)) { + const std::set& allies2 = cachedAllies.getActorsSidingWith(actor2); // Check that an ally of actor2 is also in combat with actor1 for (const MWWorld::Ptr& ally2 : allies2) { @@ -741,7 +741,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2, &allies2); + mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2)); } } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 3493c98f41..a91a585367 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -507,7 +507,8 @@ namespace MWScript runtime.pop(); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); - if (!target.isEmpty() && !target.getClass().getCreatureStats(target).isDead()) + if (!target.isEmpty() && !target.getBase()->isDeleted() + && !target.getClass().getCreatureStats(target).isDead()) MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target, nullptr); } }; From 340d1423c65d91db44b7a3fe6a852fb49909039d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 29 Jan 2024 22:25:39 +0100 Subject: [PATCH 0905/2167] Optimize AI package target comparisons --- apps/openmw/mwmechanics/actors.cpp | 10 +++++----- apps/openmw/mwmechanics/aipackage.cpp | 20 ++++++++++++++++++++ apps/openmw/mwmechanics/aipackage.hpp | 2 ++ apps/openmw/mwmechanics/aisequence.cpp | 6 +++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f9239d32d4..5db2e6ca29 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2083,7 +2083,7 @@ namespace MWMechanics for (const auto& package : stats.getAiSequence()) { if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat - && package->getTarget() == actorPtr) + && package->targetIs(actorPtr)) break; if (package->sideWithTarget() && !package->getTarget().isEmpty()) { @@ -2103,7 +2103,7 @@ namespace MWMechanics } list.push_back(package->getTarget()); } - else if (package->getTarget() == actorPtr) + else if (package->targetIs(actorPtr)) { list.push_back(iteratedActor); } @@ -2122,7 +2122,7 @@ namespace MWMechanics std::vector list; forEachFollowingPackage( mActors, actorPtr, getPlayer(), [&](const Actor& actor, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actorPtr) + if (package->followTargetThroughDoors() && package->targetIs(actorPtr)) list.push_back(actor.getPtr()); else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) @@ -2154,7 +2154,7 @@ namespace MWMechanics std::vector list; forEachFollowingPackage( mActors, actor, getPlayer(), [&](const Actor&, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) + if (package->followTargetThroughDoors() && package->targetIs(actor)) { list.push_back(static_cast(package.get())->getFollowIndex()); return false; @@ -2172,7 +2172,7 @@ namespace MWMechanics std::map map; forEachFollowingPackage( mActors, actor, getPlayer(), [&](const Actor& otherActor, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) + if (package->followTargetThroughDoors() && package->targetIs(actor)) { const int index = static_cast(package.get())->getFollowIndex(); map[index] = otherActor.getPtr(); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index fe83ce11ab..4bcfc7dedd 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -98,6 +98,26 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const return mCachedTarget; } +bool MWMechanics::AiPackage::targetIs(const MWWorld::Ptr& ptr) const +{ + if (mTargetActorId == -2) + return ptr.isEmpty(); + else if (mTargetActorId == -1) + { + if (mTargetActorRefId.empty()) + { + mTargetActorId = -2; + return ptr.isEmpty(); + } + if (!ptr.isEmpty() && ptr.getCellRef().getRefId() == mTargetActorRefId) + return getTarget() == ptr; + return false; + } + if (ptr.isEmpty() || !ptr.getClass().isActor()) + return false; + return ptr.getClass().getCreatureStats(ptr).getActorId() == mTargetActorId; +} + void MWMechanics::AiPackage::reset() { // reset all members diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 9e13ee9cd5..29a9f9c9ad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -87,6 +87,8 @@ namespace MWMechanics /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) virtual MWWorld::Ptr getTarget() const; + /// Optimized version of getTarget() == ptr + virtual bool targetIs(const MWWorld::Ptr& ptr) const; /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5d6f25ecb8..82c4b1c0bd 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -176,11 +176,11 @@ namespace MWMechanics if (!isInCombat()) return false; - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + for (const auto& package : mPackages) { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) + if (package->getTypeId() == AiPackageTypeId::Combat) { - if ((*it)->getTarget() == actor) + if (package->targetIs(actor)) return true; } } From ad8a05e2a1f1856f6d5bc97026dc030dbf432e66 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 30 Jan 2024 18:58:15 +0100 Subject: [PATCH 0906/2167] Trigger a game ended state handler before loading to allow menu scripts to do cleanup --- apps/openmw/mwstate/statemanagerimp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 9f0f8d2928..63190f72c3 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -449,9 +449,12 @@ void MWState::StateManager::loadGame(const Character* character, const std::file { try { - // let menu scripts do cleanup - mState = State_Ended; - MWBase::Environment::get().getLuaManager()->gameEnded(); + if (mState != State_Ended) + { + // let menu scripts do cleanup + mState = State_Ended; + MWBase::Environment::get().getLuaManager()->gameEnded(); + } cleanup(); From 76915ce6e9a9c73ee71a11b47ea304df2a4ec428 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 30 Jan 2024 18:58:34 +0100 Subject: [PATCH 0907/2167] Queue auto started scripts until next update --- apps/openmw/mwlua/luamanagerimp.cpp | 8 ++++++-- apps/openmw/mwlua/luamanagerimp.hpp | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index f695bd294e..15a172388d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -166,6 +166,10 @@ namespace MWLua mObjectLists.update(); + for (auto scripts : mQueuedAutoStartedScripts) + scripts->addAutoStartedScripts(); + mQueuedAutoStartedScripts.clear(); + std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) { return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().mRef->isDeleted(); }); @@ -343,7 +347,7 @@ namespace MWLua if (!localScripts) { localScripts = createLocalScripts(ptr); - localScripts->addAutoStartedScripts(); + mQueuedAutoStartedScripts.push_back(localScripts); } mActiveLocalScripts.insert(localScripts); mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); @@ -459,7 +463,7 @@ namespace MWLua if (!autoStartConf.empty()) { localScripts = createLocalScripts(ptr, std::move(autoStartConf)); - localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()` + mQueuedAutoStartedScripts.push_back(localScripts); } } if (localScripts) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index e82c503c3a..22745fb90a 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -176,6 +176,7 @@ namespace MWLua MenuScripts mMenuScripts{ &mLua }; GlobalScripts mGlobalScripts{ &mLua }; std::set mActiveLocalScripts; + std::vector mQueuedAutoStartedScripts; ObjectLists mObjectLists; MWWorld::Ptr mPlayer; From 011d9d6493099065e6943b371cb7c7253a7a5e94 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 14 Jan 2024 20:33:23 +0100 Subject: [PATCH 0908/2167] Dehardcode skill and level progression --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwclass/npc.cpp | 18 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 3 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 2 +- apps/openmw/mwlua/engineevents.cpp | 9 + apps/openmw/mwlua/engineevents.hpp | 15 +- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 5 + apps/openmw/mwlua/luamanagerimp.cpp | 5 + apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/stats.cpp | 171 ++++++++++- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 6 +- apps/openmw/mwmechanics/combat.cpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 3 +- apps/openmw/mwmechanics/npcstats.cpp | 27 ++ apps/openmw/mwmechanics/npcstats.hpp | 4 + apps/openmw/mwmechanics/recharge.cpp | 2 +- apps/openmw/mwmechanics/repair.cpp | 2 +- apps/openmw/mwmechanics/security.cpp | 4 +- apps/openmw/mwmechanics/spellcasting.cpp | 6 +- apps/openmw/mwmechanics/trading.cpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwworld/actioneat.cpp | 2 +- components/esm3/loadskil.hpp | 34 ++- docs/source/luadoc_data_paths.sh | 1 + docs/source/reference/lua-scripting/api.rst | 1 + .../interface_skill_progression.rst | 6 + .../lua-scripting/tables/interfaces.rst | 4 + files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + .../omw/mechanics/playercontroller.lua | 75 +++++ files/data/scripts/omw/skillhandlers.lua | 283 ++++++++++++++++++ files/lua_api/openmw/core.lua | 4 + files/lua_api/openmw/interfaces.lua | 3 + files/lua_api/openmw/types.lua | 21 +- 37 files changed, 687 insertions(+), 48 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_skill_progression.rst create mode 100644 files/data/scripts/omw/skillhandlers.lua diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 57886d5399..115416cd8c 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -62,6 +62,7 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index de587954b8..7eda9cd2cf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -22,6 +22,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -655,7 +656,7 @@ namespace MWClass ESM::RefId weapskill = ESM::Skill::HandToHand; if (!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); - skillUsageSucceeded(ptr, weapskill, 0); + skillUsageSucceeded(ptr, weapskill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); @@ -845,7 +846,7 @@ namespace MWClass ESM::RefId skill = armor.getClass().getEquipmentSkill(armor); if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, skill, 0); + skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent); if (skill == ESM::Skill::LightArmor) sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); @@ -855,7 +856,7 @@ namespace MWClass sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); } else if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); + skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent); } } @@ -1131,16 +1132,7 @@ namespace MWClass void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { - MWMechanics::NpcStats& stats = getNpcStats(ptr); - - if (stats.isWerewolf()) - return; - - MWWorld::LiveCellRef* ref = ptr.get(); - - const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mClass); - - stats.useSkill(skill, *class_, usageType, extraFactor); + MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor); } float Npc::getArmorRating(const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 2b7774d8c9..3b0ba47250 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -543,7 +543,8 @@ namespace MWDialogue mPermanentDispositionChange += perm; MWWorld::Ptr player = MWMechanics::getPlayer(); - player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Speechcraft, success ? ESM::Skill::Speechcraft_Success : ESM::Skill::Speechcraft_Fail); if (success) { diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index bfaf011835..fa7bce449b 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -134,7 +134,7 @@ namespace MWGui return false; } else - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_PickPocket); return true; } diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 43507ff1a5..7cc2b3db48 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -94,6 +94,15 @@ namespace MWLua if (auto* scripts = getLocalScripts(actor)) scripts->onAnimationTextKey(event.mGroupname, event.mKey); } + + void operator()(const OnSkillUse& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillUse(event.mSkill, event.useType, event.scale); + } private: MWWorld::Ptr getPtr(ESM::RefNum id) const diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index bf8d219fd5..b9f4c25b39 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -57,8 +57,21 @@ namespace MWLua std::string mGroupname; std::string mKey; }; + struct OnAnimationTextKey + { + ESM::RefNum mActor; + std::string mGroupname; + std::string mKey; + }; + struct OnSkillUse + { + ESM::RefNum mActor; + std::string mSkill; + int useType; + float scale; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1d5e710869..0c0af5b902 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 230ec93d3c..f5f740b1c6 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -79,6 +79,10 @@ namespace MWLua { callEngineHandlers(mOnPlayAnimationHandlers, groupname, options); } + void onSkillUse(std::string_view skillId, int useType, float scale) + { + callEngineHandlers(mOnSkillUse, skillId, useType, scale); + } void applyStatsCache(); @@ -93,6 +97,7 @@ namespace MWLua EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; + EngineHandlerList mOnSkillUse{ "_onSkillUse" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index ac5ae60f7d..0974856d25 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -406,6 +406,11 @@ namespace MWLua scripts->onPlayAnimation(groupname, options); } + void LuaManager::skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) + { + mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a539d04348..2e29ff272a 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -84,6 +84,7 @@ namespace MWLua const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) override; + void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 02bed00bf5..224fdba0f2 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -73,6 +73,94 @@ namespace MWLua "StatUpdateAction"); } + static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (prop == "current") + stats.setLevel(LuaUtil::cast(value)); + } + + static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getNpcStats(ptr); + if (prop == "progress") + stats.setLevelProgress(LuaUtil::cast(value)); + else if (prop == "skillIncreasesForAttribute") + stats.setSkillIncreasesForAttribute( + *std::get(index).getIf(), LuaUtil::cast(value)); + else if (prop == "skillIncreasesForSpecialization") + stats.setSkillIncreasesForSpecialization(std::get(index), LuaUtil::cast(value)); + } + + class SkillIncreasesForAttributeStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForAttributeStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, ESM::StringRefId attributeId) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute", + [attributeId](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId); + }); + } + + void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value; + } + }; + + class SkillIncreasesForSpecializationStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForSpecializationStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, int specialization) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", + [specialization](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization(specialization); + }); + } + + void set(const Context& context, int specialization, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] + = value; + } + }; + class LevelStat { ObjectVariant mObject; @@ -85,7 +173,7 @@ namespace MWLua public: sol::object getCurrent(const Context& context) const { - return getValue(context, mObject, &LevelStat::setValue, std::monostate{}, "current", + return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current", [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } @@ -93,7 +181,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, std::monostate{}, "current" }] = value; + obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value; } sol::object getProgress(const Context& context) const @@ -101,7 +189,30 @@ namespace MWLua const auto& ptr = mObject.ptr(); if (!ptr.getClass().isNpc()) return sol::nil; - return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); + + return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); }); + } + + void setProgress(const Context& context, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value; + } + + SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const + { + return SkillIncreasesForAttributeStats{ mObject }; + } + + SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const + { + return SkillIncreasesForSpecializationStats{ mObject }; } static std::optional create(ObjectVariant object, Index) @@ -110,13 +221,6 @@ namespace MWLua return {}; return LevelStat{ std::move(object) }; } - - static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - auto& stats = ptr.getClass().getCreatureStats(ptr); - if (prop == "current") - stats.setLevel(LuaUtil::cast(value)); - } }; class DynamicStat @@ -323,6 +427,14 @@ namespace MWLua namespace sol { + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; template <> struct is_automagical : std::false_type { @@ -360,10 +472,39 @@ namespace MWLua sol::table stats(context.mLua->sol(), sol::create); actor["stats"] = LuaUtil::makeReadOnly(stats); + auto skillIncreasesForAttributeStatsT + = context.mLua->sol().new_usertype("SkillIncreasesForAttributeStats"); + for (auto attribute : MWBase::Environment::get().getESMStore()->get()) + { + skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property( + [=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); }, + [=](const SkillIncreasesForAttributeStats& stat, const sol::object& value) { + stat.set(context, attribute.mId, value); + }); + } + // ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization) + auto skillIncreasesForSpecializationStatsT + = context.mLua->sol().new_usertype( + "skillIncreasesForSpecializationStats"); + for (int i = 0; i < 3; i++) + { + std::string_view index = ESM::Class::specializationIndexToLuaId.at(i); + skillIncreasesForSpecializationStatsT[index] + = sol::property([=](const SkillIncreasesForSpecializationStats& stat) { return stat.get(context, i); }, + [=](const SkillIncreasesForSpecializationStats& stat, const sol::object& value) { + stat.set(context, i, value); + }); + } + auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); levelStatT["current"] = sol::property([context](const LevelStat& stat) { return stat.getCurrent(context); }, [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); - levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); + levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }, + [context](const LevelStat& stat, const sol::object& value) { stat.setProgress(context, value); }); + levelStatT["skillIncreasesForAttribute"] + = sol::readonly_property([](const LevelStat& stat) { return stat.getSkillIncreasesForAttributeStats(); }); + levelStatT["skillIncreasesForSpecialization"] = sol::readonly_property( + [](const LevelStat& stat) { return stat.getSkillIncreasesForSpecializationStats(); }); stats["level"] = addIndexedAccessor(0); auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); @@ -461,6 +602,14 @@ namespace MWLua skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); }); + skillT["skillGain1"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[0]; }); + skillT["skillGain2"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[1]; }); + skillT["skillGain3"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[2]; }); + skillT["skillGain4"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[3]; }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a4e7168b0f..1f8265aab5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1962,7 +1962,7 @@ namespace MWMechanics mSneakSkillTimer = 0.f; if (avoidedNotice && mSneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_AvoidNotice); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index aea3e36632..5a995e7f06 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -335,7 +335,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) void MWMechanics::Alchemy::increaseSkill() { - mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, 0); + mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, ESM::Skill::Alchemy_CreatePotion); } float MWMechanics::Alchemy::getAlchemyFactor() const diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8d69da2c43..7330b72255 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2088,7 +2088,7 @@ namespace MWMechanics mSecondsOfSwimming += duration; while (mSecondsOfSwimming > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_SwimOneSecond); mSecondsOfSwimming -= 1; } } @@ -2097,7 +2097,7 @@ namespace MWMechanics mSecondsOfRunning += duration; while (mSecondsOfRunning > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_RunOneSecond); mSecondsOfRunning -= 1; } } @@ -2215,7 +2215,7 @@ namespace MWMechanics { // report acrobatics progression if (isPlayer) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Fall); } } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3208ea2293..06a1c3b9cc 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -167,7 +167,7 @@ namespace MWMechanics blockerStats.setBlock(true); if (blocker == getPlayer()) - blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); + blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, ESM::Skill::Block_Success); return true; } @@ -267,7 +267,7 @@ namespace MWMechanics applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) - attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); + attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); bool unaware = attacker == getPlayer() && !sequence.isInCombat() diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 13894146b3..1de55b0c8d 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -84,7 +84,8 @@ namespace MWMechanics if (getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; - mEnchanter.getClass().skillUsageSucceeded(mEnchanter, ESM::Skill::Enchant, 2); + mEnchanter.getClass().skillUsageSucceeded( + mEnchanter, ESM::Skill::Enchant, ESM::Skill::Enchant_CreateMagicItem); } enchantment.mEffects = mEffectList; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 808059fccd..046c94fc8f 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -298,6 +298,11 @@ int MWMechanics::NpcStats::getLevelProgress() const return mLevelProgress; } +void MWMechanics::NpcStats::setLevelProgress(int progress) +{ + mLevelProgress = progress; +} + void MWMechanics::NpcStats::levelUp() { const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); @@ -344,11 +349,33 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::Attribu return MWBase::Environment::get().getESMStore()->get().find(gmst.str())->mValue.getInteger(); } +int MWMechanics::NpcStats::getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const +{ + auto it = mSkillIncreases.find(attribute); + if (it == mSkillIncreases.end()) + return 0; + return it->second; +} + +void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute, int increases) +{ + if (increases == 0) + mSkillIncreases.erase(attribute); + else + mSkillIncreases[attribute] = increases; +} + int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const { return mSpecIncreases[spec]; } +void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(int spec, int increases) +{ + assert(spec >= 0 && spec < 3); + mSpecIncreases[spec] = increases; +} + void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id) { mUsedIds.insert(id); diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 7113ee6207..9695d45ac4 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -92,10 +92,14 @@ namespace MWMechanics void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); int getLevelProgress() const; + void setLevelProgress(int progress); int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const; + int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const; + void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases); int getSkillIncreasesForSpecialization(int spec) const; + void setSkillIncreasesForSpecialization(int spec, int increases); void levelUp(); diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 6e16436bcc..7b0ad75d3c 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -84,7 +84,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Fail")); } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, ESM::Skill::Enchant_Recharge); gem.getContainerStore()->remove(gem, 1); if (gem.getCellRef().getCount() == 0) diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 3011004244..914fa0b542 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -70,7 +70,7 @@ namespace MWMechanics stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill - player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, ESM::Skill::Armorer_Repair); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index a13131cae6..0fb8a95699 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -64,7 +64,7 @@ namespace MWMechanics lock.getCellRef().unlock(); resultMessage = "#{sLockSuccess}"; resultSound = "Open Lock"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_PickLock); } else resultMessage = "#{sLockFail}"; @@ -115,7 +115,7 @@ namespace MWMechanics resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_DisarmTrap); } else resultMessage = "#{sTrapFail}"; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0496033c70..721131dcb0 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -354,7 +354,7 @@ namespace MWMechanics if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_UseMagicItem); } else if (type == ESM::Enchantment::CastOnce) { @@ -364,7 +364,7 @@ namespace MWMechanics else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_CastOnStrike); } if (isProjectile) @@ -439,7 +439,7 @@ namespace MWMechanics } if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) - mCaster.getClass().skillUsageSucceeded(mCaster, school, 0); + mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index 9500897f25..b7e361c0b9 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -79,7 +79,8 @@ namespace MWMechanics { skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); return true; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 149113dfb1..bd2d43dd5a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -69,7 +69,7 @@ namespace // Advance acrobatics and set flag for GetPCJumping if (isPlayer) { - ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Jump); MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index b77fe146ef..c67502bdee 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -11,7 +11,7 @@ namespace MWWorld void ActionEat::executeImp(const Ptr& actor) { if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer()) - actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, 1); + actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, ESM::Skill::Alchemy_UseIngredient); } ActionEat::ActionEat(const MWWorld::Ptr& object) diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index 978e3d5dc4..9cae87903c 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -47,13 +47,45 @@ namespace ESM uint32_t mRecordFlags; SkillId mId; + //! Enum that defines the index into SKDTstruct::mUseValue for all vanilla skill uses + enum UseType + { + // These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + // Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, //!< \Note This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 0, + + }; + struct SKDTstruct { int32_t mAttribute; // see defs.hpp int32_t mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth float mUseValue[4]; // How much skill improves through use. Meaning // of each field depends on what skill this - // is. We should document this better later. + // is. See UseType above }; // Total size: 24 bytes SKDTstruct mData; diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 02b03cbd69..a9f7c71067 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -9,5 +9,6 @@ paths=( scripts/omw/settings/player.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua + scripts/omw/skillhandlers.lua ) printf '%s\n' "${paths[@]}" \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 1bb7e0b6e9..2f4708810b 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -40,6 +40,7 @@ Lua API reference interface_item_usage interface_mwui interface_settings + interface_skill_progression interface_ui iterables diff --git a/docs/source/reference/lua-scripting/interface_skill_progression.rst b/docs/source/reference/lua-scripting/interface_skill_progression.rst new file mode 100644 index 0000000000..5cd2e87812 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_skill_progression.rst @@ -0,0 +1,6 @@ +Interface SkillProgression +=================== + +.. raw:: html + :file: generated_html/scripts_omw_skillhandlers.html + diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 5029baf0a3..0692e60b1c 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -25,6 +25,10 @@ - by global scripts - | Allows to extend or override built-in item usage | mechanics. + * - :ref:`SkillProgression ` + - by local scripts + - | Control, extend, and override skill progression of the + - | player. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 3ab30c87ff..a0c97ad308 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/textEdit.lua scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua + scripts/omw/skillhandlers.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index a6f4ca5f33..a07b62c5fb 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -16,6 +16,7 @@ PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua +PLAYER: scripts/omw/skillhandlers.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 19e62d02c7..29baf32020 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -1,7 +1,13 @@ +local ambient = require('openmw.ambient') local core = require('openmw.core') +local Skill = core.stats.Skill +local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') +local NPC = types.NPC +local Actor = types.Actor +local ui = require('openmw.ui') local cell = nil local autodoors = {} @@ -31,6 +37,69 @@ local function processAutomaticDoors() end end +local function skillLevelUpHandler(skillid, source, params) + local skillStat = NPC.stats.skills[skillid](self) + if skillStat.base >= 100 then + return false + end + + if params.skillIncreaseValue then + skillStat.base = skillStat.base + params.skillIncreaseValue + end + + local levelStat = Actor.stats.level(self) + if params.levelUpProgress then + levelStat.progress = levelStat.progress + params.levelUpProgress + end + + if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then + levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + = levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue + end + + if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then + levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + ambient.playSound("skillraise") + + local message = core.getGMST('sNotifyMessage39') + message = message:gsub("%%s", skillRecord.name) + message = message:gsub("%%d", tostring(skillStat.base)) + + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}') + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end +end + +local function skillUsedHandler(skillid, useType, params) + if NPC.isWerewolf(self) then + return false + end + + if params.skillGain then + local skillStat = NPC.stats.skills[skillid](self) + skillStat.progress = skillStat.progress + params.skillGain + + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) + end + end +end + local function onUpdate() if self.cell ~= cell then cell = self.cell @@ -39,8 +108,14 @@ local function onUpdate() processAutomaticDoors() end +local function onActive() + I.SkillProgression.addSkillUsedHandler(skillUsedHandler) + I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) +end + return { engineHandlers = { onUpdate = onUpdate, + onActive = onActive, }, } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua new file mode 100644 index 0000000000..a4fb42ebde --- /dev/null +++ b/files/data/scripts/omw/skillhandlers.lua @@ -0,0 +1,283 @@ +local self = require('openmw.self') +local types = require('openmw.types') +local core = require('openmw.core') +local NPC = require('openmw.types').NPC +local Skill = core.stats.Skill + +--- +-- Table of skill use types defined by morrowind. +-- Each entry corresponds to an index into the available skill gain values +-- of a @{openmw.types#SkillRecord} +-- @type SkillUseType +-- @field #number Armor_HitByOpponent 0 +-- @field #number Block_Success 0 +-- @field #number Spellcast_Success 0 +-- @field #number Weapon_SuccessfulHit 0 +-- @field #number Alchemy_CreatePotion 0 +-- @field #number Alchemy_UseIngredient 1 +-- @field #number Enchant_Recharge 0 +-- @field #number Enchant_UseMagicItem 1 +-- @field #number Enchant_CreateMagicItem 2 +-- @field #number Enchant_CastOnStrike 3 +-- @field #number Acrobatics_Jump 0 +-- @field #number Acrobatics_Fall 1 +-- @field #number Mercantile_Success 0 +-- @field #number Mercantile_Bribe 1 +-- @field #number Security_DisarmTrap 0 +-- @field #number Security_PickLock 1 +-- @field #number Sneak_AvoidNotice 0 +-- @field #number Sneak_PickPocket 1 +-- @field #number Speechcraft_Success 0 +-- @field #number Speechcraft_Fail 1 +-- @field #number Armorer_Repair 0 +-- @field #number Athletics_RunOneSecond 0 +-- @field #number Athletics_SwimOneSecond 0 + +--- +-- Table of valid sources for skill increases +-- @type SkillLevelUpSource +-- @field #string Book book +-- @field #string Trainer trainer +-- @field #string Usage usage + +--- +-- Table of valid handler signatures +-- @type HandlerSignatures +-- + +--- Signature of the skillLevelUp handler +-- @function [parent=#HandlerSignatures] skillLevelUpHandler +-- @param #string skillid The ID of the skill being leveled up +-- @param #SkillLevelUpSource source The source of the skill level up +-- @param #table params Modifiable skill level up values as a table. These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: +-- +-- * `skillIncreaseValue` - The numeric amount of skill levels gained. +-- * `levelUpProgress` - The numeric amount of level up progress gained. +-- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. +-- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. +-- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. +-- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. + +--- Signature of the skillUsed handler +-- @function [parent=#HandlerSignatures] skillUsedHandler +-- @param #string skillid The ID of the skill being progressed +-- @param #SkillUseType useType The use type the skill progression +-- @param #table params Modifiable skill progression value. By default contains the single value +-- +-- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. + + +local skillUsedHandlers = {} +local skillLevelUpHandlers = {} + +local function tableHasValue(table, value) + for _, v in pairs(table) do + if v == value then return true end + end + return false +end + +local function getSkillGainValue(skillid, useType, scale) + local skillRecord = Skill.record(skillid) + + local skillGain = 0 + if useType == 0 then + skillGain = skillRecord.skillGain1 + elseif useType == 1 then + skillGain = skillRecord.skillGain2 + elseif useType == 2 then + skillGain = skillRecord.skillGain3 + elseif useType == 3 then + skillGain = skillRecord.skillGain4 + end + + if scale ~= nil then skillGain = skillGain * scale end + return skillGain +end + +local function getSkillProgressRequirementUnorm(npc, skillid) + local npcRecord = NPC.record(npc) + local class = NPC.classes.record(npcRecord.class) + local skillStat = NPC.stats.skills[skillid](npc) + local skillRecord = Skill.record(skillid) + + local factor = core.getGMST('fMiscSkillBonus') + if tableHasValue(class.majorSkills, skillid) then + factor = core.getGMST('fMajorSkillBonus') + elseif tableHasValue(class.minorSkills, skillid) then + factor = core.getGMST('fMinorSkillBonus') + end + + if skillRecord.specialization == class.specialization then + factor = factor * core.getGMST('fSpecialSkillBonus') + end + + return (skillStat.base + 1) * factor +end + +local function skillUsed(skillid, useType, scale) + if #skillUsedHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + if useType > 3 or useType < 0 then + print('Error: Unknown useType: '..tostring(useType)) + return + end + + -- Compute skill gain + local skillStat = NPC.stats.skills[skillid](self) + local skillGainUnorm = getSkillGainValue(skillid, useType, scale) + local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) + local skillGain = skillGainUnorm / skillProgressRequirementUnorm + + -- Put skill gain in a table so that handlers can modify it + local parameters = { + skillGain = skillGain, + } + + for i = #skillUsedHandlers, 1, -1 do + if skillUsedHandlers[i](skillid, useType, parameters) == false then + return + end + end +end + +local function skillLevelUp(skillid, source) + if #skillLevelUpHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + local levelUpProgress = 0 + local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte') + + if tableHasValue(class.minorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMinorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute') + elseif tableHasValue(class.majorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMajorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') + end + + local parameters = + { + skillIncreaseValue = 1, + levelUpProgress = levelUpProgress, + levelUpAttribute = skillRecord.attribute, + levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, + levelUpSpecialization = skillRecord.specialization, + levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), + } + + for i = #skillLevelUpHandlers, 1, -1 do + if skillLevelUpHandlers[i](skillid, source, parameters) == false then + return + end + end +end + +return { + interfaceName = 'SkillProgression', + --- + -- Allows to extend or override built-in skill progression mechanics. + -- @module SkillProgression + -- @usage local I = require('openmw.interfaces') + -- + -- -- Forbid increasing destruction skill past 50 + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) + -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then + -- return false + -- end + -- end) + -- + -- -- Scale sneak skill progression based on active invisibility effects + -- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params) + -- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then + -- local activeEffects = Actor.activeEffects(self) + -- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100 + -- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude + -- visibility = 1 - math.min(1, math.max(0, visibility)) + -- local oldSkillGain = params.skillGain + -- params.skillGain = oldSkillGain * visibility + -- end + -- end + -- + interface = { + --- Interface version + -- @field [parent=#SkillProgression] #number version + version = 0, + + --- Add new skill level up handler for this actor + -- @function [parent=#SkillProgression] addSkillLevelUpHandler + -- @param #function handler The handler, see #skillLevelUpHandler. + addSkillLevelUpHandler = function(handler) + skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler + end, + + --- Add new skillUsed handler for this actor + -- @function [parent=#SkillProgression] addSkillUsedHandler + -- @param #function handler The handler. + addSkillUsedHandler = function(handler) + skillUsedHandlers[#skillUsedHandlers + 1] = handler + end, + + --- Register a skill use, activating relevant handlers + -- @function [parent=#SkillProgression] skillUsed + -- @param #string skillid The if of the skill that was used + -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} + -- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1. + skillUsed = skillUsed, + + --- @{#SkillUseType} + -- @field [parent=#SkillProgression] #SkillUseType SKILL_USE_TYPES Available skill usage types + SKILL_USE_TYPES = { + -- These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + -- Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, -- Note: This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 0, + }, + + --- Register a skill increase, activating relevant handlers + -- @function [parent=#SkillProgression] skillUsed + -- @param #string skillid The id of the skill to be increased. + -- @param #SkillLevelUpSource source The source of the skill increase. + skillLevelUp = skillLevelUp, + + --- @{#SkillLevelUpSource} + -- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES + SKILL_INCREASE_SOURCES = { + Book = 'book', + Usage = 'usage', + Trainer = 'trainer', + }, + }, + engineHandlers = { _onSkillUse = skillUsed }, +} diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 94d63312f1..6babfa6c85 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -875,6 +875,10 @@ -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute +-- @field #string skillGain1 1 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain2 2 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain3 3 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain4 4 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- -- @type MagicSchoolData diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 57103768d2..5ed98bd8be 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -26,6 +26,9 @@ --- -- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage +--- +-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression + --- -- @function [parent=#interfaces] __index -- @param #interfaces self diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 734288bfb7..c74b599597 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -352,10 +352,29 @@ -- @function [parent=#ActorSpells] clear -- @param self +--- Values affect how much each attribute can be increased at level up, and are all reset to 0 upon level up. +-- @type SkillIncreasesForAttributeStats +-- @field #number agility Number of contributions to agility for the next level up. +-- @field #number endurance Number of contributions to endurance for the next level up. +-- @field #number intelligence Number of contributions to intelligence for the next level up. +-- @field #number luck Number of contributions to luck for the next level up. +-- @field #number personality Number of contributions to personality for the next level up. +-- @field #number speed Number of contributions to speed for the next level up. +-- @field #number strength Number of contributions to strength for the next level up. +-- @field #number willpower Number of contributions to willpower for the next level up. + +--- Values affect the graphic used on the level up screen, and are all reset to 0 upon level up. +-- @type SkillIncreasesForSpecializationStats +-- @field #number combat Number of contributions to combat specialization for the next level up. +-- @field #number magic Number of contributions to magic specialization for the next level up. +-- @field #number stealth Number of contributions to stealth specialization for the next level up. + --- -- @type LevelStat -- @field #number current The actor's current level. --- @field #number progress The NPC's level progress (read-only.) +-- @field #number progress The NPC's level progress. +-- @field #SkillIncreasesForAttributeStats skillIncreasesForAttribute The NPC's attribute contributions towards the next level up. Values affect how much each attribute can be increased at level up. +-- @field #SkillIncreasesForSpecializationStats skillIncreasesForSpecialization The NPC's attribute contributions towards the next level up. Values affect the graphic used on the level up screen. --- -- @type DynamicStat From 80e9631abd303ee4f5ae6f92ca5f96dfd05d0179 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 20:29:35 +0100 Subject: [PATCH 0909/2167] Use ESM::Class::Specialization for parameters to npcstats --- apps/openmw/mwgui/levelupdialog.cpp | 6 ++++-- apps/openmw/mwlua/stats.cpp | 6 ++++-- apps/openmw/mwmechanics/npcstats.cpp | 4 ++-- apps/openmw/mwmechanics/npcstats.hpp | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 2160a04b1b..4950e3edf4 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -164,8 +164,10 @@ namespace MWGui const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); setClassImage(mClassImage, - ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), - pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2)))); + ESM::RefId::stringRefId( + getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Combat), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Magic), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Stealth)))); int level = creatureStats.getLevel() + 1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 224fdba0f2..4257a90e84 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -89,7 +89,8 @@ namespace MWLua stats.setSkillIncreasesForAttribute( *std::get(index).getIf(), LuaUtil::cast(value)); else if (prop == "skillIncreasesForSpecialization") - stats.setSkillIncreasesForSpecialization(std::get(index), LuaUtil::cast(value)); + stats.setSkillIncreasesForSpecialization( + static_cast(std::get(index)), LuaUtil::cast(value)); } class SkillIncreasesForAttributeStats @@ -144,7 +145,8 @@ namespace MWLua return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", [specialization](const MWWorld::Ptr& ptr) { - return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization(specialization); + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization( + static_cast(specialization)); }); } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 046c94fc8f..2c7a710355 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -365,12 +365,12 @@ void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::Attrib mSkillIncreases[attribute] = increases; } -int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const +int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const { return mSpecIncreases[spec]; } -void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(int spec, int increases) +void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases) { assert(spec >= 0 && spec < 3); mSpecIncreases[spec] = increases; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 9695d45ac4..0104153a38 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -3,6 +3,7 @@ #include "creaturestats.hpp" #include +#include #include #include #include @@ -98,8 +99,8 @@ namespace MWMechanics int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const; void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases); - int getSkillIncreasesForSpecialization(int spec) const; - void setSkillIncreasesForSpecialization(int spec, int increases); + int getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const; + void setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases); void levelUp(); From e1a22242d9460a1bf39f19967b5ae421fade7022 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 20:40:38 +0100 Subject: [PATCH 0910/2167] skillGain as a table --- apps/openmw/mwlua/stats.cpp | 15 +++++++-------- files/data/scripts/omw/skillhandlers.lua | 22 +++------------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 4257a90e84..c6492c1ec2 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -604,14 +604,13 @@ namespace MWLua skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); }); - skillT["skillGain1"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[0]; }); - skillT["skillGain2"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[1]; }); - skillT["skillGain3"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[2]; }); - skillT["skillGain4"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[3]; }); + skillT["skillGain"] = sol::readonly_property([lua](const ESM::Skill& rec) -> sol::table { + sol::table res(lua, sol::create); + int index = 1; + for (auto skillGain : rec.mData.mUseValue) + res[index++] = skillGain; + return res; + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index a4fb42ebde..e27925c750 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -77,24 +77,6 @@ local function tableHasValue(table, value) return false end -local function getSkillGainValue(skillid, useType, scale) - local skillRecord = Skill.record(skillid) - - local skillGain = 0 - if useType == 0 then - skillGain = skillRecord.skillGain1 - elseif useType == 1 then - skillGain = skillRecord.skillGain2 - elseif useType == 2 then - skillGain = skillRecord.skillGain3 - elseif useType == 3 then - skillGain = skillRecord.skillGain4 - end - - if scale ~= nil then skillGain = skillGain * scale end - return skillGain -end - local function getSkillProgressRequirementUnorm(npc, skillid) local npcRecord = NPC.record(npc) local class = NPC.classes.record(npcRecord.class) @@ -128,7 +110,9 @@ local function skillUsed(skillid, useType, scale) -- Compute skill gain local skillStat = NPC.stats.skills[skillid](self) - local skillGainUnorm = getSkillGainValue(skillid, useType, scale) + local skillRecord = Skill.record(skillid) + local skillGainUnorm = skillRecord.skillGain[useType + 1] + if scale then skillGainUnorm = skillGainUnorm * scale end local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) local skillGain = skillGainUnorm / skillProgressRequirementUnorm From 7755c97fdf6233f603cdcfc14812647374664715 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:03:46 +0100 Subject: [PATCH 0911/2167] update docs --- files/lua_api/openmw/core.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 6babfa6c85..e15c2224fb 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -875,10 +875,7 @@ -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute --- @field #string skillGain1 1 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain2 2 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain3 3 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain4 4 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #table skillGain Table of the 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType). --- -- @type MagicSchoolData From 2cfa819ccca07c3f6fe9fdc7b6b6d3ef7889b94f Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:24:40 +0100 Subject: [PATCH 0912/2167] Use interface in _onSkillUse engine handler. --- files/data/scripts/omw/skillhandlers.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index e27925c750..975a8b984e 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -1,4 +1,5 @@ local self = require('openmw.self') +local I = require('openmw.interfaces') local types = require('openmw.types') local core = require('openmw.core') local NPC = require('openmw.types').NPC @@ -263,5 +264,10 @@ return { Trainer = 'trainer', }, }, - engineHandlers = { _onSkillUse = skillUsed }, + engineHandlers = { + _onSkillUse = function (skillid, useType, scale) + -- Use the interface here so any overrides of skillUsed will receive the call. + I.SkillProgression.skillUsed(skillid, useType, scale) + end, + }, } From 9f15f3b431e057232ada7fb39748a4a4e37a6d2e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:48:19 +0100 Subject: [PATCH 0913/2167] Add engine handler for skill levelup, to dehardcode the book/trainer case. --- apps/openmw/mwbase/luamanager.hpp | 3 ++- apps/openmw/mwgui/trainingwindow.cpp | 6 ++---- apps/openmw/mwlua/engineevents.cpp | 9 +++++++++ apps/openmw/mwlua/engineevents.hpp | 8 +++++++- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.cpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwworld/actionread.cpp | 8 ++------ files/data/scripts/omw/skillhandlers.lua | 5 ++++- 10 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 115416cd8c..7f7ee85127 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -62,8 +62,9 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; - virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, + virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 5395f6db1c..fa4fd266b5 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -174,10 +175,7 @@ namespace MWGui } // increase skill - MWWorld::LiveCellRef* playerRef = player.get(); - - const ESM::Class* class_ = store.get().find(playerRef->mBase->mClass); - pcStats.increaseSkill(skill->mId, *class_, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(player, skill->mId, "trainer"); // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 7cc2b3db48..f9b9d461cc 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -104,6 +104,15 @@ namespace MWLua scripts->onSkillUse(event.mSkill, event.useType, event.scale); } + void operator()(const OnSkillLevelUp& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillLevelUp(event.mSkill, event.mSource); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index b9f4c25b39..862c09ef2a 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -70,8 +70,14 @@ namespace MWLua int useType; float scale; }; + struct OnSkillLevelUp + { + ESM::RefNum mActor; + std::string mSkill; + std::string mSource; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 0c0af5b902..8bd0d7c047 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, &mOnSkillLevelUp }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index f5f740b1c6..2ec78860d1 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -83,6 +83,10 @@ namespace MWLua { callEngineHandlers(mOnSkillUse, skillId, useType, scale); } + void onSkillLevelUp(std::string_view skillId, std::string_view source) + { + callEngineHandlers(mOnSkillLevelUp, skillId, source); + } void applyStatsCache(); @@ -98,6 +102,7 @@ namespace MWLua EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; EngineHandlerList mOnSkillUse{ "_onSkillUse" }; + EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 0974856d25..5a743f41e3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -411,6 +411,11 @@ namespace MWLua mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale }); } + void LuaManager::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) + { + mEngineEvents.addToQueue(EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 2e29ff272a..685fbbde4c 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -85,6 +85,7 @@ namespace MWLua std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) override; void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; + void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 3f48920dc0..477c92d2dd 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -49,12 +50,7 @@ namespace MWWorld ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId); if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId)) { - MWWorld::LiveCellRef* playerRef = actor.get(); - - const ESM::Class* class_ - = MWBase::Environment::get().getESMStore()->get().find(playerRef->mBase->mClass); - - npcStats.increaseSkill(skill, *class_, true, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(actor, skill, "book"); npcStats.flagAsUsed(ref->mBase->mId); } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 975a8b984e..1f3b856344 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -265,9 +265,12 @@ return { }, }, engineHandlers = { + -- Use the interface in these handlers so any overrides will receive the calls. _onSkillUse = function (skillid, useType, scale) - -- Use the interface here so any overrides of skillUsed will receive the call. I.SkillProgression.skillUsed(skillid, useType, scale) end, + _onSkillLevelUp = function (skillid, source) + I.SkillProgression.skillLevelUp(skillid, source) + end, }, } From 264a8c06690e2039a6d8afee6a81e9e17c708527 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:48:46 +0100 Subject: [PATCH 0914/2167] Remove the now unused npcstats methods useSkill and increaseSkill --- apps/openmw/mwmechanics/npcstats.cpp | 84 ---------------------------- apps/openmw/mwmechanics/npcstats.hpp | 5 -- 2 files changed, 89 deletions(-) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 2c7a710355..eceaf8b482 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -209,90 +209,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ES return progressRequirement; } -void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor) -{ - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float skillGain = 1; - if (usageType >= 4) - throw std::runtime_error("skill usage type out of range"); - if (usageType >= 0) - { - skillGain = skill->mData.mUseValue[usageType]; - if (skillGain < 0) - throw std::runtime_error("invalid skill gain factor"); - } - skillGain *= extraFactor; - - MWMechanics::SkillValue& value = getSkill(skill->mId); - - value.setProgress(value.getProgress() + skillGain); - - if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_))) - { - // skill levelled up - increaseSkill(skill->mId, class_, false); - } -} - -void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook) -{ - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float base = getSkill(skill->mId).getBase(); - - if (base >= 100.f) - return; - - base += 1; - - const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); - - // is this a minor or major skill? - int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo - int index = ESM::Skill::refIdToIndex(skill->mId); - for (const auto& skills : class_.mData.mSkills) - { - if (skills[0] == index) - { - mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); - break; - } - else if (skills[1] == index) - { - mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); - break; - } - } - - mSkillIncreases[ESM::Attribute::indexToRefId(skill->mData.mAttribute)] += increase; - - mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); - - // Play sound & skill progress notification - /// \todo check if character is the player, if levelling is ever implemented for NPCs - MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("skillraise")); - - std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sNotifyMessage39", {}) }; - message = Misc::StringUtils::format( - message, MyGUI::TextIterator::toTagsString(skill->mName).asUTF8(), static_cast(base)); - - if (readBook) - message = "#{sBookSkillMessage}\n" + message; - - MWBase::Environment::get().getWindowManager()->messageBox(message, MWGui::ShowInDialogueMode_Never); - - if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) - { - // levelup is possible now - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); - } - - getSkill(skill->mId).setBase(base); - if (!preserveProgress) - getSkill(skill->mId).setProgress(0); -} - int MWMechanics::NpcStats::getLevelProgress() const { return mLevelProgress; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 0104153a38..f94744cb71 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,11 +87,6 @@ namespace MWMechanics float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const; - void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f); - ///< Increase skill by usage. - - void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); - int getLevelProgress() const; void setLevelProgress(int progress); From 055e9a5055407aed00f1741130365cc222afdc69 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 21 Jan 2024 15:12:07 +0100 Subject: [PATCH 0915/2167] clang'd --- apps/openmw/mwlua/luamanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5a743f41e3..07ad612ec3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -413,7 +413,8 @@ namespace MWLua void LuaManager::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) { - mEngineEvents.addToQueue(EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); + mEngineEvents.addToQueue( + EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) From e0e968a082ffb97f93866ac9c81a5161c9ea9d77 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 30 Jan 2024 00:05:57 +0100 Subject: [PATCH 0916/2167] rebase errors --- apps/openmw/mwbase/luamanager.hpp | 4 ++-- apps/openmw/mwlua/engineevents.cpp | 2 +- apps/openmw/mwlua/engineevents.hpp | 6 ------ apps/openmw/mwlua/localscripts.cpp | 3 ++- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 7f7ee85127..db5b51165d 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -63,11 +63,11 @@ namespace MWBase virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, - virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; - virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; + virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index f9b9d461cc..6c652bccba 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -94,7 +94,7 @@ namespace MWLua if (auto* scripts = getLocalScripts(actor)) scripts->onAnimationTextKey(event.mGroupname, event.mKey); } - + void operator()(const OnSkillUse& event) const { MWWorld::Ptr actor = getPtr(event.mActor); diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 862c09ef2a..fb9183eb7c 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -57,12 +57,6 @@ namespace MWLua std::string mGroupname; std::string mKey; }; - struct OnAnimationTextKey - { - ESM::RefNum mActor; - std::string mGroupname; - std::string mKey; - }; struct OnSkillUse { ESM::RefNum mActor; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8bd0d7c047..8fa0571afc 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,8 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, &mOnSkillLevelUp }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, + &mOnSkillLevelUp }); } void LocalScripts::setActive(bool active) From 47d5868e2ccd208287a3e560c9fa7bb021db00f5 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 30 Jan 2024 22:05:41 +0100 Subject: [PATCH 0917/2167] creationTime field in save info --- apps/openmw/mwlua/menuscripts.cpp | 7 +++++++ files/lua_api/openmw/menu.lua | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/openmw/mwlua/menuscripts.cpp b/apps/openmw/mwlua/menuscripts.cpp index e4a3962912..033e56ac97 100644 --- a/apps/openmw/mwlua/menuscripts.cpp +++ b/apps/openmw/mwlua/menuscripts.cpp @@ -89,6 +89,13 @@ namespace MWLua sol::table contentFiles(lua, sol::create); for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i) contentFiles[i + 1] = Misc::StringUtils::lowerCase(slot.mProfile.mContentFiles[i]); + + { + auto system_time = std::chrono::system_clock::now() + - (std::filesystem::file_time_type::clock::now() - slot.mTimeStamp); + slotInfo["creationTime"] = std::chrono::duration(system_time.time_since_epoch()).count(); + } + slotInfo["contentFiles"] = contentFiles; saves[slot.mPath.filename().string()] = slotInfo; } diff --git a/files/lua_api/openmw/menu.lua b/files/lua_api/openmw/menu.lua index 4deb5d00a3..b443a983c9 100644 --- a/files/lua_api/openmw/menu.lua +++ b/files/lua_api/openmw/menu.lua @@ -51,6 +51,7 @@ -- @field #string playerName -- @field #string playerLevel -- @field #number timePlayed Gameplay time for this saved game. Note: available even with [time played](../modding/settings/saves.html#timeplayed) turned off +-- @field #number creationTime Time at which the game was saved, as a timestamp in seconds. Can be passed as the second argument to `os.data`. -- @field #list<#string> contentFiles --- From 4f8476efd4cf69b13eb769ef47a92159b7f626fc Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 31 Jan 2024 23:01:16 +0100 Subject: [PATCH 0918/2167] Comments --- .../lua-scripting/interface_skill_progression.rst | 2 +- docs/source/reference/lua-scripting/tables/interfaces.rst | 2 +- files/data/scripts/omw/mechanics/playercontroller.lua | 4 +--- files/data/scripts/omw/skillhandlers.lua | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/source/reference/lua-scripting/interface_skill_progression.rst b/docs/source/reference/lua-scripting/interface_skill_progression.rst index 5cd2e87812..f27f03d556 100644 --- a/docs/source/reference/lua-scripting/interface_skill_progression.rst +++ b/docs/source/reference/lua-scripting/interface_skill_progression.rst @@ -1,5 +1,5 @@ Interface SkillProgression -=================== +========================== .. raw:: html :file: generated_html/scripts_omw_skillhandlers.html diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 0692e60b1c..f2e5921b02 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -28,7 +28,7 @@ * - :ref:`SkillProgression ` - by local scripts - | Control, extend, and override skill progression of the - - | player. + | player. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 29baf32020..935bf5029f 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -68,9 +68,7 @@ local function skillLevelUpHandler(skillid, source, params) ambient.playSound("skillraise") - local message = core.getGMST('sNotifyMessage39') - message = message:gsub("%%s", skillRecord.name) - message = message:gsub("%%d", tostring(skillStat.base)) + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then message = '#{sBookSkillMessage}\n'..message diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 1f3b856344..30188f2341 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -212,7 +212,7 @@ return { skillUsedHandlers[#skillUsedHandlers + 1] = handler end, - --- Register a skill use, activating relevant handlers + --- Trigger a skill use, activating relevant handlers -- @function [parent=#SkillProgression] skillUsed -- @param #string skillid The if of the skill that was used -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} @@ -250,9 +250,9 @@ return { Athletics_SwimOneSecond = 0, }, - --- Register a skill increase, activating relevant handlers - -- @function [parent=#SkillProgression] skillUsed - -- @param #string skillid The id of the skill to be increased. + --- Trigger a skill level up, activating relevant handlers + -- @function [parent=#SkillProgression] skillLevelUp + -- @param #string skillid The id of the skill to level up. -- @param #SkillLevelUpSource source The source of the skill increase. skillLevelUp = skillLevelUp, From c7fcd1c31b644c67d44f75024f6845c549012ad2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 31 Jan 2024 17:00:33 -0600 Subject: [PATCH 0919/2167] Fix formatting --- apps/openmw/mwrender/characterpreview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 59844ee9ae..9914aec7ca 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -463,8 +463,8 @@ namespace MWRender if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && showCarriedLeft) { if (!mAnimation->getInfo("torch")) - mAnimation->play( - "torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, std::numeric_limits::max(), true); + mAnimation->play("torch", 2, BlendMask::BlendMask_LeftArm, false, 1.0f, "start", "stop", 0.0f, + std::numeric_limits::max(), true); } else if (mAnimation->getInfo("torch")) mAnimation->disable("torch"); From 7e345436f4104676a6ade618f1720b42aaeaa5bd Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2023 18:44:50 +0400 Subject: [PATCH 0920/2167] Localize Launcher and Wizard --- CHANGELOG.md | 1 + CI/install_debian_deps.sh | 7 +- CMakeLists.txt | 58 +- apps/bulletobjecttool/main.cpp | 4 - apps/launcher/CMakeLists.txt | 2 + apps/launcher/datafilespage.cpp | 7 +- apps/launcher/main.cpp | 13 +- apps/launcher/ui/mainwindow.ui | 6 +- apps/launcher/ui/settingspage.ui | 4 +- apps/navmeshtool/main.cpp | 4 - apps/opencs/editor.cpp | 2 - apps/openmw/options.cpp | 4 - apps/wizard/CMakeLists.txt | 2 + apps/wizard/conclusionpage.cpp | 6 +- apps/wizard/main.cpp | 28 + components/CMakeLists.txt | 4 + components/files/configurationmanager.cpp | 4 + files/lang/components_de.ts | 85 ++ files/lang/components_fr.ts | 85 ++ files/lang/components_ru.ts | 87 ++ files/lang/launcher_de.ts | 1451 ++++++++++++++++++++ files/lang/launcher_fr.ts | 1451 ++++++++++++++++++++ files/lang/launcher_ru.ts | 1466 +++++++++++++++++++++ files/lang/wizard_de.ts | 858 ++++++++++++ files/lang/wizard_fr.ts | 858 ++++++++++++ files/lang/wizard_ru.ts | 860 ++++++++++++ 26 files changed, 7327 insertions(+), 30 deletions(-) create mode 100644 files/lang/components_de.ts create mode 100644 files/lang/components_fr.ts create mode 100644 files/lang/components_ru.ts create mode 100644 files/lang/launcher_de.ts create mode 100644 files/lang/launcher_fr.ts create mode 100644 files/lang/launcher_ru.ts create mode 100644 files/lang/wizard_de.ts create mode 100644 files/lang/wizard_fr.ts create mode 100644 files/lang/wizard_ru.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c42bc552..cdf765029b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources + Feature #6411: Support translations in openmw-launcher Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 8d7c0a493f..4420db364d 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -33,9 +33,10 @@ declare -rA GROUPED_DEPS=( libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - libsdl2-dev libqt5opengl5-dev libopenal-dev libunshield-dev libtinyxml-dev - libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev - librecast-dev libsqlite3-dev ca-certificates libicu-dev libyaml-cpp-dev + libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev + libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev + libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev + libyaml-cpp-dev " # These dependencies can alternatively be built and linked statically. diff --git a/CMakeLists.txt b/CMakeLists.txt index 28109bd01b..4dca42e8ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,9 +224,9 @@ find_package(LZ4 REQUIRED) if (USE_QT) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) if (QT_VERSION_MAJOR VERSION_EQUAL 5) - find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL REQUIRED) + find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED) else() - find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets REQUIRED) + find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED) endif() message(STATUS "Using Qt${QT_VERSION}") endif() @@ -1074,3 +1074,57 @@ if (DOXYGEN_FOUND) WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) endif () + +# Qt localization +if (USE_QT) + file(GLOB LAUNCHER_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/launcher_*.ts) + file(GLOB WIZARD_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/wizard_*.ts) + file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) + get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) + add_custom_target(translations + COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components + VERBATIM + COMMAND_EXPAND_LISTS + + COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard + VERBATIM + COMMAND_EXPAND_LISTS + + COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher + VERBATIM + COMMAND_EXPAND_LISTS) + + if (BUILD_LAUNCHER OR BUILD_WIZARD) + if (APPLE) + set(QT_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") + else () + get_generator_is_multi_config(multi_config) + if (multi_config) + set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") + else () + set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") + endif () + endif () + + file(GLOB TS_FILES ${COMPONENTS_TS_FILES}) + + if (BUILD_LAUNCHER) + set(TS_FILES ${TS_FILES} ${LAUNCHER_TS_FILES}) + endif () + + if (BUILD_WIZARD) + set(TS_FILES ${TS_FILES} ${WIZARD_TS_FILES}) + endif () + + qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent) + + add_custom_target(qm-files + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_TRANSLATIONS_PATH} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH} + DEPENDS ${QM_FILES} + COMMENT "Copy *.qm files to resources folder") + endif () +endif() diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 504aef7e67..884c196e53 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -76,10 +76,6 @@ namespace bpo::value()->default_value(StringsVector(), "fallback-archive")->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)"); - addOption("resources", - bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), - "set resources directory"); - addOption("content", bpo::value()->default_value(StringsVector(), "")->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts"); diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index aa3970efdb..0c888afe9d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -71,6 +71,8 @@ openmw_add_executable(openmw-launcher ${UI_HDRS} ) +add_dependencies(openmw-launcher qm-files) + if (WIN32) INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".") endif (WIN32) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 70ebe29625..9b14a91934 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -308,7 +308,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) { - tooltip += "Will be added to the current profile\n"; + tooltip += tr("Will be added to the current profile"); QFont font = item->font(); font.setBold(true); font.setItalic(true); @@ -326,7 +326,10 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (mSelector->containsDataFiles(currentDir)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); - tooltip += "Contains content file(s)"; + if (!tooltip.isEmpty()) + tooltip += "\n"; + + tooltip += tr("Contains content file(s)"); } else { diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 78323458ce..3cbd1f5092 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED @@ -34,13 +35,23 @@ int runLauncher(int argc, char* argv[]) { QApplication app(argc, argv); + QString resourcesPath("."); + if (!variables["resources"].empty()) + { + resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); + } + // Internationalization QString locale = QLocale::system().name().section('_', 0, 0); QTranslator appTranslator; - appTranslator.load(":/translations/" + locale + ".qm"); + appTranslator.load(resourcesPath + "/translations/launcher_" + locale + ".qm"); app.installTranslator(&appTranslator); + QTranslator componentsAppTranslator; + componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); + app.installTranslator(&componentsAppTranslator); + Launcher::MainDialog mainWin(configurationManager); Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); diff --git a/apps/launcher/ui/mainwindow.ui b/apps/launcher/ui/mainwindow.ui index b05a9d2c3b..9a7352654a 100644 --- a/apps/launcher/ui/mainwindow.ui +++ b/apps/launcher/ui/mainwindow.ui @@ -6,13 +6,13 @@ 0 0 - 720 + 750 635 - 720 + 750 635 @@ -148,7 +148,7 @@ QToolButton { Display - Allows to change graphics settings + Allows to change display settings diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 7006238e71..2d06c1802e 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -36,7 +36,7 @@ - Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled. + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> Always allow actors to follow over water @@ -206,7 +206,7 @@ - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored -- like in Morrowind.</p></body></html> + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> Classic reflected Absorb spells behavior diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 9ed7fb4c2e..3ec34114af 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -88,10 +88,6 @@ namespace NavMeshTool ->composing(), "set fallback BSA archives (later archives have higher priority)"); - addOption("resources", - bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), - "set resources directory"); - addOption("content", bpo::value()->default_value(StringsVector(), "")->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon/omwscripts"); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 05f90b96f3..42fac11bf1 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -119,8 +119,6 @@ boost::program_options::variables_map CS::Editor::readConfiguration() boost::program_options::value()->default_value( Files::MaybeQuotedPathContainer::value_type(), "")); addOption("encoding", boost::program_options::value()->default_value("win1252")); - addOption("resources", - boost::program_options::value()->default_value(Files::MaybeQuotedPath(), "resources")); addOption("fallback-archive", boost::program_options::value>() ->default_value(std::vector(), "fallback-archive") diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp index 1554e252d0..9465175daa 100644 --- a/apps/openmw/options.cpp +++ b/apps/openmw/options.cpp @@ -35,10 +35,6 @@ namespace OpenMW bpo::value()->default_value(StringsVector(), "fallback-archive")->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)"); - addOption("resources", - bpo::value()->default_value(Files::MaybeQuotedPath(), "resources"), - "set resources directory"); - addOption("start", bpo::value()->default_value(""), "set initial cell"); addOption("content", bpo::value()->default_value(StringsVector(), "")->multitoken()->composing(), diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 8c459f4f9c..3a17210cd3 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -79,6 +79,8 @@ openmw_add_executable(openmw-wizard ${UI_HDRS} ) +add_dependencies(openmw-wizard qm-files) + target_link_libraries(openmw-wizard components_qt ) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 4a4a4ef689..548d55f298 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -37,14 +37,14 @@ void Wizard::ConclusionPage::initializePage() if (field(QLatin1String("installation.retailDisc")).toBool() == true) { textLabel->setText( - tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

" - "

Click Finish to close the Wizard.

")); + tr("

The OpenMW Wizard successfully installed Morrowind on your " + "computer.

")); } else { textLabel->setText( tr("

The OpenMW Wizard successfully modified your existing Morrowind " - "installation.

Click Finish to close the Wizard.

")); + "installation.")); } } else diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index 03ac24c8c0..a911ac7350 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -1,5 +1,11 @@ #include #include +#include + +#include +#include + +#include #include "mainwizard.hpp" @@ -11,6 +17,11 @@ int main(int argc, char* argv[]) { + boost::program_options::variables_map variables; + boost::program_options::options_description description; + Files::ConfigurationManager configurationManager; + configurationManager.addCommonOptions(description); + configurationManager.readConfiguration(variables, description, true); QApplication app(argc, argv); @@ -28,6 +39,23 @@ int main(int argc, char* argv[]) app.setLibraryPaths(libraryPaths); #endif + QString resourcesPath("."); + if (!variables["resources"].empty()) + { + resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); + } + + // Internationalization + QString locale = QLocale::system().name().section('_', 0, 0); + + QTranslator appTranslator; + appTranslator.load(resourcesPath + "/translations/wizard_" + locale + ".qm"); + app.installTranslator(&appTranslator); + + QTranslator componentsAppTranslator; + componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); + app.installTranslator(&componentsAppTranslator); + Wizard::MainWizard wizard; wizard.show(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f25a4cc621..bb6fd1dd37 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -595,6 +595,10 @@ if (USE_QT) target_link_libraries(components_qt components Qt::Widgets Qt::Core) target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") + if (BUILD_LAUNCHER OR BUILD_WIZARD) + add_dependencies(components_qt qm-files) + endif() + if (BUILD_WITH_CODE_COVERAGE) target_compile_options(components_qt PRIVATE --coverage) target_link_libraries(components_qt gcov) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 943f514676..210261cdf4 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -194,6 +194,10 @@ namespace Files "instead of being appended"); addOption("user-data", bpo::value()->default_value(Files::MaybeQuotedPath(), ""), "set user data directory (used for saves, screenshots, etc)"); + addOption("resources", + boost::program_options::value()->default_value( + Files::MaybeQuotedPath(), "resources"), + "set resources directory"); } bpo::variables_map separateComposingVariables( diff --git a/files/lang/components_de.ts b/files/lang/components_de.ts new file mode 100644 index 0000000000..59ba558328 --- /dev/null +++ b/files/lang/components_de.ts @@ -0,0 +1,85 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + + + + Dependent file needs to be active: %1 + + + + This file needs to load after %1 + + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + + + + ContentSelectorView::ContentSelector + + &Check Selected + + + + &Uncheck Selected + + + + &Copy Path(s) to Clipboard + + + + <No game file> + + + + + Process::ProcessInvoker + + Error starting executable + + + + Error running executable + + + + +Arguments: + + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts new file mode 100644 index 0000000000..c1c70ba277 --- /dev/null +++ b/files/lang/components_fr.ts @@ -0,0 +1,85 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + + + + Dependent file needs to be active: %1 + + + + This file needs to load after %1 + + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + + + + ContentSelectorView::ContentSelector + + &Check Selected + + + + &Uncheck Selected + + + + &Copy Path(s) to Clipboard + + + + <No game file> + + + + + Process::ProcessInvoker + + Error starting executable + + + + Error running executable + + + + +Arguments: + + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + + + + diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts new file mode 100644 index 0000000000..cca6591afe --- /dev/null +++ b/files/lang/components_ru.ts @@ -0,0 +1,87 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + Выберите язык, используемый вашими файлами данных ESM/ESP, чтобы позволить OpenMW определить их кодировку. + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + Зависимый файл не найден: %1 + + + Dependent file needs to be active: %1 + Зависимый файл должен быть включен: %1 + + + This file needs to load after %1 + Этот файл должен быть загружен после %1 + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + <b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/> + + + + ContentSelectorView::ContentSelector + + &Check Selected + &Включить выбранное + + + &Uncheck Selected + &Отключить выбранное + + + &Copy Path(s) to Clipboard + &Скопировать пути в буфер обмена + + + <No game file> + <Без файла игры> + + + + Process::ProcessInvoker + + Error starting executable + Не удалось запустить исполняемый файл + + + Error running executable + При исполнении приложения произошла ошибка + + + +Arguments: + + +Параметры: + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + <html><head/><body><p><b>Не удалось найти %1</b></p><p>Приложение не найдено.</p><p>Пожалуйста, убедитесь, что OpenMW установлен правильно, и повторите попытку.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Не удалось запустить %1</b></p><p>У приложения нет прав на исполнение.</p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Не удалось запустить %1</b></p><p>Возникла ошибка при запуске %1.</p><p>Нажмите "Показать детали..." для получения дополнительной информации.</p></body></html> + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Исполнение файла %1 завершилось с ошибкой</b></p><p>Возникла ошибка при выполнении %1.</p><p>Нажмите "Показать детали..." для получения дополнительной информации.</p></body></html> + + + diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts new file mode 100644 index 0000000000..1c23f90425 --- /dev/null +++ b/files/lang/launcher_de.ts @@ -0,0 +1,1451 @@ + + + + + DataFilesPage + + Content Files + + + + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Data Directories + + + + <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Scan directories for likely data directories and append them at the end of the list. + + + + Append + + + + Scan directories for likely data directories and insert them above the selected position + + + + Insert Above + + + + Move selected directory one position up + + + + Move Up + + + + Move selected directory one position down + + + + Move Down + + + + Remove selected directory + + + + Remove + + + + Archive Files + + + + Move selected archive one position up + + + + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Move selected archive one position down + + + + Navigation Mesh Cache + + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + + + + Update + + + + Cancel navigation mesh generation. Already processed data will be saved. + + + + Cancel + + + + Remove unused tiles + + + + Max size + + + + MiB + + + + Content List + + + + Select a content list + + + + New Content List + + + + &New Content List + + + + Clone Content List + + + + Delete Content List + + + + Ctrl+N + + + + Ctrl+G + + + + Ctrl+D + + + + Check Selection + + + + Uncheck Selection + + + + Refresh Data Files + + + + Ctrl+R + + + + + GraphicsPage + + 0 + + + + 2 + + + + 4 + + + + 8 + + + + 16 + + + + Custom: + + + + Standard: + + + + Fullscreen + + + + Windowed Fullscreen + + + + Windowed + + + + Disabled + + + + Enabled + + + + Adaptive + + + + FPS + + + + × + + + + Screen + + + + Window mode + + + + Framerate limit + + + + Window border + + + + Resolution + + + + Anti-aliasing + + + + Vertical synchronization + + + + + ImportPage + + Form + + + + Morrowind Installation Wizard + + + + Run &Installation Wizard + + + + Morrowind Settings Importer + + + + File to import settings from: + + + + Browse... + + + + Import add-on and plugin selection (creates a new Content List) + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + Import bitmap fonts setup + + + + Run &Settings Importer + + + + + Launcher::DataFilesPage + + English + + + + French + + + + German + + + + Italian + + + + Polish + + + + Russian + + + + Spanish + + + + New Content List + + + + Content List name: + + + + Clone Content List + + + + Select Directory + + + + Delete Content List + + + + Are you sure you want to delete <b>%1</b>? + + + + Delete + + + + Contains content file(s) + + + + Will be added to the current profile + + + + &Check Selected + + + + &Uncheck Selected + + + + + Launcher::GraphicsPage + + Error receiving number of screens + + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + + + + Screen + + + + Error receiving resolutions + + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + + + + Morrowind configuration file (*.ini) + + + + Importer finished + + + + Failed to import settings from INI file. + + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + Launcher::MainDialog + + Close + + + + Launch OpenMW + + + + Help + + + + Error opening OpenMW configuration file + + + + First run + + + + Run &Installation Wizard + + + + Skip + + + + OpenMW %1 release + + + + OpenMW development (%1) + + + + Compiled on %1 %2 + + + + Error detecting Morrowind installation + + + + Run &Installation Wizard... + + + + Error reading OpenMW configuration files + + + + Error writing OpenMW configuration file + + + + Error writing user settings file + + + + Error writing Launcher configuration file + + + + No game file selected + + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + + + + Error creating OpenMW configuration directory: code %0 + + + + <br><b>Could not create directory %0</b><br><br>%1<br> + + + + + Launcher::SettingsPage + + Text file (*.txt) + + + + + MainWindow + + OpenMW Launcher + + + + OpenMW version + + + + toolBar + + + + Data Files + + + + Allows to setup data files and directories + + + + Settings + + + + Allows to tweak engine settings + + + + Import + + + + Allows to import data from original engine + + + + Display + + + + Allows to change display settings + + + + + QObject + + Select configuration file + + + + Select script file + + + + + SelectSubdirs + + Select directories you wish to add + + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + + Permanent barter disposition changes + + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + + + + Followers defend immediately + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + + Uncapped Damage Fatigue + + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + + + + Classic Calm spells behavior + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + + + + Soulgem values rebalance + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + + Swim upward correction + + + + Enchanted weapons are magical + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + + Classic reflected Absorb spells behavior + + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + + + + Use navigation mesh for pathfinding + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + + + + NPCs avoid collisions + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + + Racial variation in speed fix + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + + + + Can loot during death animation + + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + + + + Unarmed creature attacks damage armor + + + + Off + + + + Affect werewolves + + + + Do not affect werewolves + + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + + + + Axis-aligned bounding box + + + + Rotating box + + + + Cylinder + + + + Visuals + + + + Animations + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + + Use magic item animation + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + + Smooth movement + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + + Use additional animation sources + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + + Turn to movement direction + + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + + Weapon sheathing + + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + + Shield sheathing + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + + Player movement ignores animation + + + + Shaders + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + + + + Auto use object normal maps + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + + Auto use terrain normal maps + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + + + + Auto use object specular maps + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + + Auto use terrain specular maps + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + + + + Bump/reflect map local lighting + + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + + + + Use anti-alias alpha testing + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + + + + Adjust coverage for alpha test + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + + Fog + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + + + + Radial fog + + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + + + + Exponential fog + + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + + + + Sky blending + + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + + + + Sky blending start + + + + Terrain + + + + Viewing distance + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + + Object paging min size + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + + Distant land + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + + Active grid object paging + + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + + + + Day night switch nodes + + + + Post Processing + + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + + + + Enable post processing + + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + + + + Transparent postpass + + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + + + Auto exposure speed + + + + Audio + + + + Select your preferred audio device. + + + + Default + + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + + HRTF + + + + Automatic + + + + On + + + + Select your preferred HRTF profile. + + + + Interface + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + + GUI scaling factor + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + + Show effect duration + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + + Change dialogue topic color + + + + Size of characters in game texts. + + + + Font size + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + + Can zoom on maps + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show projectile damage + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show melee info + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + + Stretch menu background + + + + Show owned objects + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + + Show enchant chance + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + + Merchant equipping fix + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + + Miscellaneous + + + + Saves + + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + + + + Add "Time Played" to saves + + + + JPG + + + + PNG + + + + TGA + + + + Notify on saved screenshot + + + + Testing + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + + Grab cursor + + + + Skip menu and generate default character + + + + Start default character at + + + + default cell + + + + Run script after startup: + + + + Browse… + + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + + + + Gameplay + + + + Always allow actors to follow over water + + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + + + + Only magical ammo bypass resistance + + + + Graphic herbalism + + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + + Trainers choose offered skills by base value + + + + Steal from knocked out actors in combat + + + + Factor strength into hand-to-hand combat + + + + Background physics threads + + + + Actor collision shape type + + + + Soft particles + + + + Weather particle occlusion + + + + cells + + + + Shadows + + + + bounds + + + + primitives + + + + none + + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + + + + Shadow planes computation method + + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + + unit(s) + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + + + Enable actor shadows + + + + 512 + + + + 1024 + + + + 2048 + + + + 4096 + + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + + + + Fade start multiplier + + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + + + + Enable player shadows + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + + Shadow map resolution + + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + + + + Shadow distance limit: + + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + + + Enable object shadows + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + + Enable indoor shadows + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + + + + Enable terrain shadows + + + + Lighting + + + + Lighting method + + + + Audio device + + + + HRTF profile + + + + Tooltip + + + + Crosshair + + + + Tooltip and crosshair + + + + Maximum quicksaves + + + + Screenshots + + + + Screenshot format + + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + + + + Lights maximum distance + + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + + + + Max light sources + + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + + + + Lights fade multiplier + + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + + + + Legacy + + + + Shaders (compatibility) + + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + + + + Lights bounding sphere multiplier + + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + + + + Lights minimum interior brightness + + + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts new file mode 100644 index 0000000000..f604044368 --- /dev/null +++ b/files/lang/launcher_fr.ts @@ -0,0 +1,1451 @@ + + + + + DataFilesPage + + Content Files + + + + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Data Directories + + + + <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Scan directories for likely data directories and append them at the end of the list. + + + + Append + + + + Scan directories for likely data directories and insert them above the selected position + + + + Insert Above + + + + Move selected directory one position up + + + + Move Up + + + + Move selected directory one position down + + + + Move Down + + + + Remove selected directory + + + + Remove + + + + Archive Files + + + + Move selected archive one position up + + + + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Move selected archive one position down + + + + Navigation Mesh Cache + + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + + + + Update + + + + Cancel navigation mesh generation. Already processed data will be saved. + + + + Cancel + + + + Remove unused tiles + + + + Max size + + + + MiB + + + + Content List + + + + Select a content list + + + + New Content List + + + + &New Content List + + + + Clone Content List + + + + Delete Content List + + + + Ctrl+N + + + + Ctrl+G + + + + Ctrl+D + + + + Check Selection + + + + Uncheck Selection + + + + Refresh Data Files + + + + Ctrl+R + + + + + GraphicsPage + + 0 + + + + 2 + + + + 4 + + + + 8 + + + + 16 + + + + Custom: + + + + Standard: + + + + Fullscreen + + + + Windowed Fullscreen + + + + Windowed + + + + Disabled + + + + Enabled + + + + Adaptive + + + + FPS + + + + × + + + + Screen + + + + Window mode + + + + Framerate limit + + + + Window border + + + + Resolution + + + + Anti-aliasing + + + + Vertical synchronization + + + + + ImportPage + + Form + + + + Morrowind Installation Wizard + + + + Run &Installation Wizard + + + + Morrowind Settings Importer + + + + File to import settings from: + + + + Browse... + + + + Import add-on and plugin selection (creates a new Content List) + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + Import bitmap fonts setup + + + + Run &Settings Importer + + + + + Launcher::DataFilesPage + + English + + + + French + + + + German + + + + Italian + + + + Polish + + + + Russian + + + + Spanish + + + + New Content List + + + + Content List name: + + + + Clone Content List + + + + Select Directory + + + + Delete Content List + + + + Are you sure you want to delete <b>%1</b>? + + + + Delete + + + + Contains content file(s) + + + + Will be added to the current profile + + + + &Check Selected + + + + &Uncheck Selected + + + + + Launcher::GraphicsPage + + Error receiving number of screens + + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + + + + Screen + + + + Error receiving resolutions + + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + + + + Morrowind configuration file (*.ini) + + + + Importer finished + + + + Failed to import settings from INI file. + + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + Launcher::MainDialog + + Close + + + + Launch OpenMW + + + + Help + + + + Error opening OpenMW configuration file + + + + First run + + + + Run &Installation Wizard + + + + Skip + + + + OpenMW %1 release + + + + OpenMW development (%1) + + + + Compiled on %1 %2 + + + + Error detecting Morrowind installation + + + + Run &Installation Wizard... + + + + Error reading OpenMW configuration files + + + + Error writing OpenMW configuration file + + + + Error writing user settings file + + + + Error writing Launcher configuration file + + + + No game file selected + + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + + + + Error creating OpenMW configuration directory: code %0 + + + + <br><b>Could not create directory %0</b><br><br>%1<br> + + + + + Launcher::SettingsPage + + Text file (*.txt) + + + + + MainWindow + + OpenMW Launcher + + + + OpenMW version + + + + toolBar + + + + Data Files + + + + Allows to setup data files and directories + + + + Settings + + + + Allows to tweak engine settings + + + + Import + + + + Allows to import data from original engine + + + + Display + + + + Allows to change display settings + + + + + QObject + + Select configuration file + + + + Select script file + + + + + SelectSubdirs + + Select directories you wish to add + + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + + Permanent barter disposition changes + + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + + + + Followers defend immediately + + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + + + + Uncapped Damage Fatigue + + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + + + + Classic Calm spells behavior + + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + + + + Soulgem values rebalance + + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + + + + Swim upward correction + + + + Enchanted weapons are magical + + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + + + + Classic reflected Absorb spells behavior + + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + + + + Use navigation mesh for pathfinding + + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + + + + NPCs avoid collisions + + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + + + + Racial variation in speed fix + + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + + + + Can loot during death animation + + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + + + + Unarmed creature attacks damage armor + + + + Off + + + + Affect werewolves + + + + Do not affect werewolves + + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + + + + Axis-aligned bounding box + + + + Rotating box + + + + Cylinder + + + + Visuals + + + + Animations + + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + + + Use magic item animation + + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + + + + Smooth movement + + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + + + + Use additional animation sources + + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + + + + Turn to movement direction + + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + + + + Weapon sheathing + + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + + + + Shield sheathing + + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + + Player movement ignores animation + + + + Shaders + + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + + + + Auto use object normal maps + + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + + + + Auto use terrain normal maps + + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + + + + Auto use object specular maps + + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + + + + Auto use terrain specular maps + + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + + + + Bump/reflect map local lighting + + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + + + + Use anti-alias alpha testing + + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + + + + Adjust coverage for alpha test + + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + + + + Fog + + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + + + + Radial fog + + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + + + + Exponential fog + + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + + + + Sky blending + + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + + + + Sky blending start + + + + Terrain + + + + Viewing distance + + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + + + + Object paging min size + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + + Distant land + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + + + + Active grid object paging + + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + + + + Day night switch nodes + + + + Post Processing + + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + + + + Enable post processing + + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + + + + Transparent postpass + + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + + + Auto exposure speed + + + + Audio + + + + Select your preferred audio device. + + + + Default + + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + + + + HRTF + + + + Automatic + + + + On + + + + Select your preferred HRTF profile. + + + + Interface + + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + + + + GUI scaling factor + + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + + + + Show effect duration + + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + + + + Change dialogue topic color + + + + Size of characters in game texts. + + + + Font size + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + + Can zoom on maps + + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show projectile damage + + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + + + + Show melee info + + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + + + + Stretch menu background + + + + Show owned objects + + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + + + + Show enchant chance + + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + + + + Merchant equipping fix + + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + + + + Miscellaneous + + + + Saves + + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + + + + Add "Time Played" to saves + + + + JPG + + + + PNG + + + + TGA + + + + Notify on saved screenshot + + + + Testing + + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + + + + Grab cursor + + + + Skip menu and generate default character + + + + Start default character at + + + + default cell + + + + Run script after startup: + + + + Browse… + + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + + + + Gameplay + + + + Always allow actors to follow over water + + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + + + + Only magical ammo bypass resistance + + + + Graphic herbalism + + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + + Trainers choose offered skills by base value + + + + Steal from knocked out actors in combat + + + + Factor strength into hand-to-hand combat + + + + Background physics threads + + + + Actor collision shape type + + + + Soft particles + + + + Weather particle occlusion + + + + cells + + + + Shadows + + + + bounds + + + + primitives + + + + none + + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + + + + Shadow planes computation method + + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + + + + unit(s) + + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + + + + Enable actor shadows + + + + 512 + + + + 1024 + + + + 2048 + + + + 4096 + + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + + + + Fade start multiplier + + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + + + + Enable player shadows + + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + + + + Shadow map resolution + + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + + + + Shadow distance limit: + + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + + + + Enable object shadows + + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + + + + Enable indoor shadows + + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + + + + Enable terrain shadows + + + + Lighting + + + + Lighting method + + + + Audio device + + + + HRTF profile + + + + Tooltip + + + + Crosshair + + + + Tooltip and crosshair + + + + Maximum quicksaves + + + + Screenshots + + + + Screenshot format + + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + + + + Lights maximum distance + + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + + + + Max light sources + + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + + + + Lights fade multiplier + + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + + + + Legacy + + + + Shaders (compatibility) + + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + + + + Lights bounding sphere multiplier + + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + + + + Lights minimum interior brightness + + + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts new file mode 100644 index 0000000000..844e36898c --- /dev/null +++ b/files/lang/launcher_ru.ts @@ -0,0 +1,1466 @@ + + + + + DataFilesPage + + Content Files + Файлы данных + + + Data Directories + Директории данных + + + Scan directories for likely data directories and append them at the end of the list. + Автоматически определить в директории потенциальные директории данных и добавить их в конец списка. + + + Append + Добавить + + + Scan directories for likely data directories and insert them above the selected position + Автоматически определить в директории потенциальные директории данных и вставить их выше выбранной позиции + + + Insert Above + Вставить выше + + + Move selected directory one position up + Переместить выбранную директорию на одну позицию вверх + + + Move Up + Переместить выше + + + Move selected directory one position down + Переместить выбранную директорию на одну позицию вниз + + + Move Down + Переместить ниже + + + Remove selected directory + Удалить выбранную директорию + + + Remove + Удалить + + + Archive Files + Архивы + + + Move selected archive one position up + Переместить выбранный архив на одну позицию вверх + + + Move selected archive one position down + Переместить выбранный архив на одну позицию вниз + + + Navigation Mesh Cache + Кэш навигационной сетки + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + Создать кэш навигационной сетки для всего текущего контента. Кэш будет использован движком для ускорения загрузки локаций. + + + Update + Обновить + + + Cancel navigation mesh generation. Already processed data will be saved. + Отменить создание кэша навигационной сетки. Уже обработанные данные будут сохранены. + + + Cancel + Отменить + + + Remove unused tiles + Удалить неиспользуемые тайлы + + + Max size + Максимальный размер + + + MiB + МБ + + + Content List + Список плагинов + + + Select a content list + Выбрать список плагинов + + + New Content List + Новый список плагинов + + + &New Content List + Новый список плагинов + + + Clone Content List + Создать копию списка плагинов + + + Delete Content List + Удалить список плагинов + + + Ctrl+N + Ctrl+N + + + Ctrl+G + Ctrl+G + + + Ctrl+D + Ctrl+D + + + Check Selection + Включить выбранное + + + Uncheck Selection + Отключить выбранное + + + Refresh Data Files + Обновить файлы данных + + + Ctrl+R + Ctrl+R + + + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + + + <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + + + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + + + + GraphicsPage + + 0 + 0 + + + 2 + 2 + + + 4 + 4 + + + 8 + 8 + + + 16 + 16 + + + Custom: + Особое: + + + Standard: + Стандартное: + + + Fullscreen + Полный экран + + + Windowed Fullscreen + Оконный без полей + + + Windowed + Оконный + + + Disabled + Отключена + + + Enabled + Включена + + + Adaptive + Адаптивная + + + FPS + кадров в секунду + + + × + × + + + Screen + Экран + + + Window mode + Режим окна + + + Framerate limit + Максимум кадров в секунду + + + Window border + Рамка окна + + + Resolution + Разрешение экрана + + + Anti-aliasing + Сглаживание + + + Vertical synchronization + Вертикальная синхронизация + + + + ImportPage + + Form + Form + + + Morrowind Installation Wizard + Мастер установки Morrowind + + + Run &Installation Wizard + Запустить &Мастер установки Morrowind + + + Morrowind Settings Importer + Импорт настроек из Morrowind + + + File to import settings from: + Импортировать настройки из файла: + + + Browse... + Выбрать... + + + Import add-on and plugin selection (creates a new Content List) + Импортировать подключенные плагины (создает новый список плагинов) + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Шрифты, поставляемые с оригинальной игрой, становятся размытыми при масштабировании интерфейса и поддерживают ограниченное количество символов, +поэтому в комплекте с OpenMW идет набор шрифтов, не имеющих этих проблем. Они используют технологию TrueType и весьма похожи на шрифты Morrowind. +Включите эту опцию, если вы все равно хотите использовать оригинальные шрифты вместо шрифтов OpenMW, или если вы используете сторонние растровые шрифты. + + + Import bitmap fonts setup + Импортировать растровые шрифты + + + Run &Settings Importer + Запустить &импорт настроек + + + + Launcher::DataFilesPage + + Select Directory + Выбор директории + + + English + Английский + + + French + Французский + + + German + Немецкий + + + Italian + Итальянский + + + Polish + Польский + + + Russian + Русский + + + Spanish + Испанский + + + New Content List + Новый список плагинов + + + Content List name: + Название списка плагинов: + + + Clone Content List + Создать копию списка плагинов + + + Delete Content List + Удалить список плагинов + + + Are you sure you want to delete <b>%1</b>? + Вы уверены, что хотите удалить <b>%1</b>? + + + Delete + Удалить + + + Will be added to the current profile + Будет добавлена в текущий профиль + + + Contains content file(s) + Содержит файл(ы) данных + + + &Check Selected + &Включить выбранные + + + &Uncheck Selected + &Отключить выбранные + + + + Launcher::GraphicsPage + + Error receiving number of screens + Не удалось получить количество экранов + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + <br><b>Вызов SDL_GetNumVideoDisplays завершился с ошибкой:</b><br><br> + + + Screen + Экран + + + Error receiving resolutions + Не удалось получить доступные разрешения экрана + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + <br><b>Вызов SDL_GetDisplayMode завершился с ошибкой:</b><br><br> + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + Не удалось записать данные в файл с настройками OpenMW + + + Morrowind configuration file (*.ini) + Файл с настройками Morrowind (*.ini) + + + Importer finished + Импорт завершен + + + Failed to import settings from INI file. + Не удалось импортировать настройки из INI-файла. + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Не удалось открыть %1 для записи </b></p> <p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> + + + + Launcher::MainDialog + + Close + Закрыть + + + Launch OpenMW + Запустить OpenMW + + + Help + Справка + + + Error opening OpenMW configuration file + Не удалось открыть файл с настройками OpenMW + + + First run + Первый запуск + + + Run &Installation Wizard + Запустить &Мастер установки + + + Skip + Пропустить + + + OpenMW %1 release + OpenMW версии %1 + + + OpenMW development (%1) + OpenMW ревизии %1 + + + Compiled on %1 %2 + Дата сборки: %1 %2 + + + Error detecting Morrowind installation + Не удалось найти установленный Morrowind + + + Run &Installation Wizard... + Запустить &Мастер установки... + + + Error reading OpenMW configuration files + Не удалось открыть файлы с настройками OpenMW + + + Error writing OpenMW configuration file + Не удалось записать данные в файл с настройками OpenMW + + + Error writing user settings file + Не удалось сохранить данные в файл с пользовательскими настройками + + + Error writing Launcher configuration file + Не удалось сохранить данные в файл с настройками лаунчера + + + No game file selected + Файл игры не выбран + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + <html><head/><body><p><b>Добро пожаловать в OpenMW!</b></p><p>Рекомендуется запустить Мастер установки.</p><p>Мастер позволит вам выбрать существующую установку Morrowind или же установить Morrowind для использования его OpenMW.</p></body></html> + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Не удалось открыть %0 для чтения:</b><br><br>%1<br><br>Пожалуйста, проверьте права доступа и повторите попытку.<br> + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Не удалось открыть %0 для чтения</b><br><br>Пожалуйста, проверьте права доступа и повторите попытку.<br> + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + <br><b>Не удалось определить местоположение Data Files</b><br><br>Не удалось найти директорию с файлами данных. + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + <br>Проблема могла возникнуть из-за неполной установки OpenMW.<br>Возможно, переустановка OpenMW поможет решить проблему.<br> + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Не удалось создать %0 или открыть его для записи</b><br><br>Пожалуйста, проверьте права доступа и повторите попытку.<br> + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + <br><b>У вас не выбрана игра.</b><br><br>OpenMW не может быть запущен без файла игры.<br> + + + Error creating OpenMW configuration directory: code %0 + Не удалось создать директорию для настроек OpenMW: код ошибки %0 + + + <br><b>Could not create directory %0</b><br><br>%1<br> + <br><b>Не удалось создать директорию %0</b><br><br> + + + + Launcher::SettingsPage + + Text file (*.txt) + Текстовый файл (*.txt) + + + + MainWindow + + OpenMW Launcher + Лаунчер OpenMW + + + OpenMW version + Версия OpenMW + + + toolBar + toolBar + + + Data Files + Файлы + + + Allows to setup data files and directories + Позволяет выбрать файлы и директории с данными + + + Allows to change display settings + Позволяет настроить экран + + + Settings + Настройки + + + Allows to tweak engine settings + Позволяет настроить движок + + + Import + Импорт + + + Allows to import data from original engine + Позволяет импортировать данные из оригинального движка + + + Display + Экран + + + + QObject + + Select configuration file + Выбор файла с настройками + + + Select script file + Выбор файла скрипта + + + + SelectSubdirs + + Select directories you wish to add + Выберите директории, которые хотите добавить + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + <html><head/><body><p>Делает изменение отношения торговцев к персонажу игрока из-за торговли постоянным.</p></body></html> + + + Permanent barter disposition changes + Постоянная смена отношения торговцев + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Немедленно вводить спутников персонажа игрока в бой с противниками, начавшими бой с ними или персонажем игрока. Если настройка отключена, они не вступят в бой до первой атаки от противников или игрока.</p></body></html> + + + Followers defend immediately + Спутники защищаются сразу + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Позволяет эффекту Отнять запас сил снижать запас сил до отрицательных значений, по аналогии с эффектом Уменьшить запас сил.</p><p>Это означает, что в отличие от Morrowind с помощью этого эффекта можно заставить персонажа потерять сознание.</p></body></html> + + + Uncapped Damage Fatigue + Отнять запас сил без ограничений + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Предотвращает вступление в бой персонажей, подвергнутых эффектам Усмирения, каждый кадр - как в Morrowind без MCP.</p></body></html> + + + Classic Calm spells behavior + Классическое поведение Усмирения + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p>Делает стоимость заполненных камней душ зависимой только от силы души.</p></body></html> + + + Soulgem values rebalance + Ребаланс стоимости камней душ + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Делает так, чтобы персонаж плыл немного вверх относительно камеры в режиме от третьего лица. Предназначено для того, чтобы было проще плыть по поверхности воды без погружения в нее.</p></body></html> + + + Swim upward correction + Коррекция при плавании вверх + + + Enchanted weapons are magical + Зачарованное оружие - магическое + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + <html><head/><body><p>Разрешает карманные кражи у персонажей без сознания в бою.</p></body></html> + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + <html><head/><body><p>Эффекты отраженных заклинаний Поглощения не отзеркаливаются - как в Morrowind.</p></body></html> + + + Classic reflected Absorb spells behavior + Классическое поведение Поглощения + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>Когда включено, NPC пытаются избегать столкновения с другими персонажами.</p></body></html> + + + NPCs avoid collisions + Персонажи избегают столкновения + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Не учитывать множитель веса расы при вычислении скорости перемещения.</p></body></html> + + + Racial variation in speed fix + Фикс влияния веса рас на Скорость + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, игрок может обыскивать тела персонажей (например, призванных существ) во время анимации их смерти, если они не находятся в бою. Если при этом игрок убрал тело, то нам приходится сразу увеличивать счетчик количества погибших и запускать скрипт удаленного персонажа.</p><p>Когда эта настройка отключена, игроку всегда придется ждать завершения анимации смерти. В основном предотвращает эксплойт с призванными существами (сбор дорогого оружия с дремор и золотых святых). Конфликтует с модами на манекены, которые используют команду SkipAnim для предотвращения завершения анимации смерти.</p></body></html> + + + Can loot during death animation + Обыск тел во время анимации смерти + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + <html><head/><body><p>Позволяет любым персонажам плыть возле поверхности воды, чтобы следовать за другим персонажем, вне зависимости от того, могут они плыть, или нет. Работает только с навигационной сеткой.</p></body></html> + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Позволяет атакам существ без оружия повреждать броню цели, по аналогии с атаками оружием.</p></body></html> + + + Unarmed creature attacks damage armor + Атаки существ повреждают броню + + + Off + Отключено + + + Affect werewolves + Включая оборотней + + + Do not affect werewolves + Не включая оборотней + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>Определяет, сколько фоновых потоков будет создано для физических вычислений. При значении 0 все вычисления будут происходить в основном потоке.</p><p>Для значений больше 1 нужно, чтобы библиотека Bullet была скомпилирована с поддержкой многопоточности.</p></body></html> + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Форма объектов столкновений, используемых для физики и поиска путей с помощью навигационной сетки. Цилиндры дают лучшую совместимость между поиском путей и возможностью двигаться по ним. Изменение значения этой настройки влияет на создание навигационной сетки, поэтому сетка, сгенерированная с одним значением, не может быть использована для другого. + + + Axis-aligned bounding box + Параллелепипед + + + Rotating box + Вращающийся параллелепипед + + + Cylinder + Цилиндр + + + Visuals + Графика + + + Animations + Анимации + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>Использовать анимации сотворения заклинаний для магических предметов, по аналогии с заклинаниями.</p></body></html> + + + Use magic item animation + Анимации магических предметов + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>Делает перемещение персонажей более плавным. Рекомендуется использовать совместно с настройкой "Поворот в направлении движения".</p></body></html> + + + Smooth movement + Плавное перемещение + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Загружать KF-файлы и файлы скелетов из директории Animations</p></body></html> + + + Use additional animation sources + Использовать источники анимаций + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + <html><head/><body><p>Влияет на перемещение по диагонали и в сторону. Включение этой настройки делает перемещение более реалистичным.</p><p>При ее выключении тело персонажа всегда целиком направлено в направлении движения. Так как в игре нет анимаций для движения по диагонали, из-за этого получается скольжение.</p><p>Когда настройка включена, только ноги персонажа поворачиваются в направлении движения. Верхняя часть тела поворачивается частично. Голова всегда смотрит в одном направлении с камерой. В бою этот режим работает только для движения по диагонали, вне боя он также влияет на движение влево и вправо. Также во время плавания тело персонажа поворачивается в направлении движения.</p></body></html> + + + Turn to movement direction + Поворот в направлении движения + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>Отображать зачехленное оружие (с колчанами и ножнами). Требует ресурсы из модов.</p></body></html> + + + Weapon sheathing + Зачехление оружия + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>Отображать щиты на спине. Требует ресурсы из модов.</p></body></html> + + + Shield sheathing + Зачехление щитов + + + Shaders + Шейдеры + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, карты нормалей автоматически распознаются и используются, если у них правильное название + (см. настройку 'normal map pattern', например, для базовой текстуры foo.dds, карта нормалей должна иметь название foo_n.dds). + Когда настройка отключена, карты используются, только если они явно заданы в модели (.nif или .osg). Влияет на объекты.</p></body></html> + + + Auto use object normal maps + Карты нормалей объектов + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + <html><head/><body><p>См. 'Карты нормалей объектов'. Влияет на ландшафт.</p></body></html> + + + Auto use terrain normal maps + Карты нормалей ландшафта + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, спекулярные карты автоматически распознаются и используются, если у них правильное название + (см. настройку 'specular map pattern', например, для базовой текстуры foo.dds, + спекулярная карта должна иметь название foo_spec.dds). + Когда настройка отключена, карты используются, только если они явно заданы в модели + (.osg-моделях, они не поддерживаются в .nif-моделях). Влияет на объекты.</p></body></html> + + + Auto use object specular maps + Спекулярные карты объектов + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + <html><head/><body><p>Если существует файл с маской, заданной с помощью настройки 'terrain specular map pattern' то он будет использоваться в качестве спекулярной карты. Он должен представлять собой текстуру, содержащую цвет слоя в RGB-канале и мощность спекулярного освещения в альфа-канале.</p></body></html> + + + Auto use terrain specular maps + Спекулярные карты ландшафта + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + <html><head/><body><p>Обычно освещение не должно влиять на карты окружения, из-за чего объекты с картами окружения (в том числе объекты с рельефными текстурами) светятся в темноте. + Morrowind Code Patch предоставляет способ обойти эту проблему, применяя карты окружения перед применением освещения, аналогично этой опции. + Объекты с картами окружения будут использовать шейдеры. + </p></body></html> + + + Bump/reflect map local lighting + Локальное освещение карт отражений + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> + + + Use anti-alias alpha testing + Сглаживание альфа-тестирования + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + <html><head/><body><p>Смягчение систем частиц. Эта техника смягчает пересечения между отдельными частицами и непрозрачными объектами с помощью их смешения.</p></body></html> + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Эмуляция сохранения покрытия в MIP-картах, чтобы модели с альфа-тестированием не усыхали по мере удаления от камеры. Однако если MIP-карты на уровне самих текстур уже созданы с сохранением покрытия, покрытие будет расти по мере удаления от камеры, так что читайте инструкции к вашим модам.</p></body></html> + + + Adjust coverage for alpha test + Покрытие альфа-тестирования + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>ЭКСПЕРИМЕНТАЛЬНАЯ НАСТРОЙКА: Предотвращает отображение падающих капель дождя и снежинок под крышами.</p></body></html> + + + Fog + Туман + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>По умолчанию туман становится плотнее по мере удаления от плоскости отсечения (которая находится на расстоянии дистанции обзора от камеры), из-за чего могут быть графические артефакты возле края экрана. + С этой настройкой плотность тумана для объекта зависит от расстояния от камеры до объекта (т.н. евклидово расстояние), благодаря чему туман выглядит не так искусственно, особенно при большом поле зрения.</p></body></html> + + + Radial fog + Радиальный туман + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Использование экспоненциальной формулы для тумана. По умолчанию используется линейный туман.</p></body></html> + + + Exponential fog + Экспоненциальный туман + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + <html><head/><body><p>Снизить видимость плоскости отсечения с помощью смешения объектов с небом.</p></body></html> + + + Sky blending + Смешивать с небом + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + Множитель расстояния обзора, определяющий начало смешения.</p></body></html> + + + Sky blending start + Минимальное расстояние смешения + + + Terrain + Ландшафт + + + Viewing distance + Расстояние обзора + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + <html><head/><body><p>Определяет, насколько большим должен быть объект, чтобы отображаться на удаленном ландшафте. Для этого размер объекта делится на его расстояние до камеры, и результат сравнивается со значением этой настройки. Чем меньше значение, тем больше объектов будет отображаться в сцене.</p></body></html> + + + Object paging min size + Минимальный размер склеиваемых объектов + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Когда включено, на удаленном ландшафте будут отображаться объекты, склеенные вместе с помощью специального алгоритма. Когда выключено, будет отображаться только сам ландшафт.</p></body></html> + + + Distant land + Удаленный ландшафт + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + <html><head/><body><p>Склеивать объекты в активных ячейках.</p></body></html> + + + Active grid object paging + Склеивание в активных ячейках + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>Если эта настройка включена, модели с поддержкой переключения в зависимости от времени суток будут ее использовать.</p></body></html> + + + Day night switch nodes + Переключение узлов дня и ночи + + + Post Processing + Постобработка + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + <html><head/><body><p>Если эта настройка включена, то будет включена постобработка графики.</p></body></html> + + + Enable post processing + Включить постобработку + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>Отрисовывать прозрачные объекты заново - со строгой границей прозрачности.</p></body></html> + + + Transparent postpass + Дополнительный проход для прозрачных объектов + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Определяет, насколько быстро может меняться эффект аккомодации глаза от кадра к кадру. Более низкие значения означают более медленные преобразования.</p></body></html> + + + Auto exposure speed + Скорость автоматической экспозиции + + + Audio + Звук + + + Select your preferred audio device. + Выберите предпочитаемое звуковое устройство. + + + Default + По умолчанию + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + Эта настройка отвечает за HRTF, симулирующий объемный звук в стереосистемах. + + + HRTF + HRTF + + + Automatic + Автоматически + + + On + Включено + + + Select your preferred HRTF profile. + Выберите предпочитаемый вами профиль HRTF. + + + Interface + Интерфейс + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> + + + GUI scaling factor + Масштаб интерфейса + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> + + + Show effect duration + Показывать длительность эффектов + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> + + + Change dialogue topic color + Смена цвета тем для диалогов + + + Size of characters in game texts. + Размер символов в текстах + + + Font size + Размер шрифтов + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> + + + Can zoom on maps + Включить масштабирование карты + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Если эта настройка включена, контейнеры, поддерживающие графический гербализм, будут использовать его вместо открытия меню контейнера.</p></body></html> + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> + + + Show projectile damage + Показывать урон снарядов + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> + + + Show melee info + Показывать информацию об оружии + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> + + + Stretch menu background + Растягивать фон меню + + + Show owned objects + Выделять объекты с владельцами + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> + + + Show enchant chance + Показывать шанс успеха зачарования + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + <html><head/><body><p>Предотвращает экипировку торговцами предметов, которые им проданы.</p></body></html> + + + Merchant equipping fix + Фикс экипировки предметов торговцами + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + <html><head/><body><p>Учителя выбирают навыки, которым учат, по базовым значениям навыка, благодаря чему моды могут использовать эффекты на усиление навыка торговли, не делая его предлагаемым навыком.</p></body></html> + + + Miscellaneous + Разное + + + Saves + Сохранения + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + <html><head/><body><p>Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.</p></body></html> + + + Add "Time Played" to saves + Выводить "Время в игре" в сохранениях + + + JPG + JPG + + + PNG + PNG + + + TGA + TGA + + + Notify on saved screenshot + Уведомление при сохранении снимка + + + Testing + Отладка + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + Эти настройки предназначены для отладки модов, и при их использовании для нормального игрового процесса возникнут проблемы. + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> + + + Grab cursor + Захватывать курсор + + + Skip menu and generate default character + Пропустить меню и создать персонажа по умолчанию + + + Start default character at + Запустить с персонажем по умолчанию в локации + + + default cell + по умолчанию + + + Run script after startup: + Запустить скрипт после запуска: + + + Browse… + Выбрать… + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Когда настройка включена, в фоне создаются навигационные меши для геометрии игрового мира. Когда она отключена, то используются только путевые точки из игровых файлов. На однопоточных системах включенный навигатор может значительно снизить скорость смены локаций. На многопоточных системах влияние на производительность незначительное. Также на многопоточных системах задержки могут зависеть от других настроек или производительности системы. Передвижение по открытому миру, а также вход в локации и выход из них могут привести к обновлению кэша. Персонажи могут не иметь возможности найти путь вокруг них, пока обновление не будет завершено. Можете попробовать отключить навигатор, если вы предпочитаете старомодный ИИ, который не знает, как до вас добраться, пока вы стоите за камнем и колдуете огненные стрелы.</p></body></html> + + + Use navigation mesh for pathfinding + Использовать навигационную сетку + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>В режиме вида от третьего лица камера перемещается согласно анимациям движения персонажа игрока. Включение этой настройки отключает это поведение, убирая зависимость движения персонажа игрока от состояния его анимаций. Такое поведение было в OpenMW версии 0.48 и ранее.</p></body></html> + + + Player movement ignores animation + Движение игрока обходит анимации + + + Gameplay + Игровой процесс + + + Always allow actors to follow over water + Позволить всем следовать по воде + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + <html><head/><body><p>Если настройка включена, требуются магические метательные снаряды, чтобы обойти сопротивление обычному оружию или уязвимость к нему. Если отключена, то требуются магические снаряды или магическое оружие дальнего боя.</p></body></html> + + + Only magical ammo bypass resistance + Только снаряды обходят сопротивление + + + Graphic herbalism + Графический гербализм + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Разрешает зачарованному оружию без флага Магическое обходить сопротивление обычному оружию, как в Morrowind.</p></body></html> + + + Trainers choose offered skills by base value + Учителя выбирают базовые навыки + + + Steal from knocked out actors in combat + Кража у персонажей без сознания в бою + + + Factor strength into hand-to-hand combat + Учет Силы в рукопашном бою + + + Background physics threads + Количество фоновых потоков для физики + + + Actor collision shape type + Форма объекта столкновений для персонажей + + + Soft particles + Мягкие частицы + + + Weather particle occlusion + Окклюзия погодных частиц + + + cells + ячеек + + + Shadows + Тени + + + bounds + по границам объектов + + + primitives + по примитивам + + + none + отключено + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + <html><head/><body><p>Определяет используемый тип вычисления границ сцены. Вычисление по границам объектов (по умолчанию) дает хороший баланс между производительностью и качеством теней, вычисление по примитивам улучшает качество теней, а еще можно полностью отключить вычисление.</p></body></html> + + + Shadow planes computation method + Метод определения границ для теней + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><head/><body><p>64 игровых единицы соответствуют одному ярду (примерно 0.9 м)</p></body></html> + + + unit(s) + единиц(ы) + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + <html><head/><body><p>Включить тени от NPC и существ, кроме персонажа игрока. Может незначительно снизить производительность.</p></body></html> + + + Enable actor shadows + Включить тени от персонажей + + + 512 + 512 + + + 1024 + 1024 + + + 2048 + 2048 + + + 4096 + 4096 + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><head/><body><p>Множитель от расстояния выше, при котором тени начнут плавно угасать.</p></body></html> + + + Fade start multiplier + Множитель начала угасания теней + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><head/><body><p>Включить тени от персонажа игрока. Может очень незначительно снизить производительность.</p></body></html> + + + Enable player shadows + Включить тени от персонажа игрока + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + <html><head/><body><p>Разрешение отдельных теневых карт. Увеличение значения улучшает качество теней, но может незначительно снизить производительность.</p></body></html> + + + Shadow map resolution + Разрешение теневой карты + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><head/><body><p>Расстояние от камеры, при достижении которого тени полностью исчезают.</p></body></html> + + + Shadow distance limit: + Максимальная дистанция теней: + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + <html><head/><body><p>Включить тени от объектов. Может значительно снизить производительность.</p></body></html> + + + Enable object shadows + Включить тени от объектов + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + <html><head/><body><p>Из-за ограничений в файлах Morrowind, только персонажи могут отбрасывать тени в интерьерах, что может раздражать.</p><p>Не имеет эффекта, если тени от персонажей отключены.</p></body></html> + + + Enable indoor shadows + Включить тени в интерьерах + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>Включить тени от ландшафта (включая удаленный ландшафт). Может значительно снизить производительность и качество теней.</p></body></html> + + + Enable terrain shadows + Включить тени от ландшафта + + + Lighting + Освещение + + + Lighting method + Способ освещения + + + Audio device + Звуковое устройство + + + HRTF profile + Профиль HRTF + + + Tooltip + Всплывающая подсказка + + + Crosshair + Прицел + + + Tooltip and crosshair + Всплывающая подсказка и прицел + + + Maximum quicksaves + Количество быстрых сохранений + + + Screenshots + Снимки экрана + + + Screenshot format + Формат снимков экрана + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> + + + Lights maximum distance + Дальность отображения источников света + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> + + + Max light sources + Макс. кол-во источников света + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + <html><head/><body><p>Доля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.</p><p>Низкие значения ведут к плавному затуханию, высокие - к резкому.</p></body></html> + + + Lights fade multiplier + Множитель начала затухания + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + <html><head/><body><p>Задает способ обработки источников света.</p> +<p> "Устаревший" всегда использует 8 источников света на объект и выдает освещение, наиболее близкое к таковому в оригинальной игре.</p> +<p>"Шейдеры (режим совместимости)" убирает ограничение в 8 источников света. Этот режим также позволяет освещению влиять на анимированную траву и позволяет настроить угасание света на расстоянии. Рекомендуется использовать этот режим на устаревшем аппаратном обеспечении и с количеством источников света на объект около 8.</p> +<p> "Шейдеры" работает аналогично режиму "Шейдеры (режим совместимости)" но использует более современный подход, позволяющий использовать большее количество источников света с минимальным влиянием на производительность на современном аппаратном обеспечении.</p></body></html> + + + Legacy + Устаревший + + + Shaders (compatibility) + Шейдеры (режим совместимости) + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> + + + Lights bounding sphere multiplier + Множитель размера ограничивающей сферы + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + <html><head/><body><p>Минимальный уровень фонового освещения в помещениях.</p><p>Увеличьте значение, если помещения в игре кажутся слишком темными.</p></body></html> + + + Lights minimum interior brightness + Минимальный уровень освещения в помещениях + + + diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts new file mode 100644 index 0000000000..7bf54e90b1 --- /dev/null +++ b/files/lang/wizard_de.ts @@ -0,0 +1,858 @@ + + + + + ComponentSelectionPage + + + WizardPage + + + + + Select Components + + + + + Which components should be installed? + + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + + + + + Selected components: + + + + + ConclusionPage + + + WizardPage + + + + + Completing the OpenMW Wizard + + + + + Placeholder + + + + + ExistingInstallationPage + + + WizardPage + + + + + Select Existing Installation + + + + + Select an existing installation for OpenMW to use or modify. + + + + + Detected installations: + + + + + Browse... + + + + + ImportPage + + + WizardPage + + + + + Import Settings + + + + + Import settings from the Morrowind installation. + + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + + + + + Import settings from Morrowind.ini + + + + + Import add-on and plugin selection + + + + + Import bitmap fonts setup from Morrowind.ini + + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + + InstallationPage + + + WizardPage + + + + + Installing + + + + + Please wait while Morrowind is installed on your computer. + + + + + InstallationTargetPage + + + WizardPage + + + + + Select Installation Destination + + + + + Where should Morrowind be installed? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + + Morrowind will be installed to the following location. + + + + + Browse... + + + + + IntroPage + + + WizardPage + + + + + Welcome to the OpenMW Wizard + + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + + + + + LanguageSelectionPage + + + WizardPage + + + + + Select Morrowind Language + + + + + What is the language of the Morrowind installation? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + + + Select the language of the Morrowind installation. + + + + + MethodSelectionPage + + + WizardPage + + + + + Select Installation Method + + + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + + + Retail CD/DVD + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + + + Install from a retail disc to a new location. + + + + + Existing Installation + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + + Select an existing installation. + + + + + Don't have a copy? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + + + Buy the game + + + + + QObject + + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + + + + + B&rowse... + + + + + Select configuration file + + + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + + + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + + + + + Most recent Morrowind not detected + + + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + + + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + + + + + Wizard::ComponentSelectionPage + + + &Install + + + + + &Skip + + + + + Morrowind (installed) + + + + + Morrowind + + + + + Tribunal (installed) + + + + + Tribunal + + + + + Bloodmoon (installed) + + + + + Bloodmoon + + + + + About to install Tribunal after Bloodmoon + + + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + + + + + Re-install &Bloodmoon + + + + + Wizard::ConclusionPage + + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + + + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + + + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + + + + + Wizard::ExistingInstallationPage + + + No existing installations detected + + + + + Error detecting Morrowind configuration + + + + + Morrowind configuration file (*.ini) + + + + + Select Morrowind.esm (located in Data Files) + + + + + Morrowind master file (Morrowind.esm) + + + + + Error detecting Morrowind files + + + + + Wizard::InstallationPage + + + <p>Attempting to install component %1.</p> + + + + + Attempting to install component %1. + + + + + %1 Installation + + + + + Select %1 installation media + + + + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + + + + + <p>Detected old version of component Morrowind.</p> + + + + + Detected old version of component Morrowind. + + + + + Morrowind Installation + + + + + Installation finished + + + + + Installation completed successfully! + + + + + Installation failed! + + + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + + + + + <p><span style="color:red;"><b>%1</b></p> + + + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + + + + + An error occurred + + + + + Wizard::InstallationTargetPage + + + Error creating destination + + + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + + + + + Insufficient permissions + + + + + Destination not empty + + + + + Select where to install Morrowind + + + + + Wizard::LanguageSelectionPage + + + English + + + + + French + + + + + German + + + + + Italian + + + + + Polish + + + + + Russian + + + + + Spanish + + + + + Wizard::MainWizard + + + OpenMW Wizard + + + + + + Error opening Wizard log file + + + + + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + + Error opening OpenMW configuration file + + + + + Quit Wizard + + + + + Are you sure you want to exit the Wizard? + + + + + Error creating OpenMW configuration directory + + + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + Error writing OpenMW configuration file + + + + + Wizard::UnshieldWorker + + + + Failed to open Morrowind configuration file! + + + + + + Opening %1 failed: %2. + + + + + Failed to write Morrowind configuration file! + + + + + Writing to %1 failed: %2. + + + + + Installing: %1 + + + + + + Installing: %1 directory + + + + + Installation finished! + + + + + + Component parameter is invalid! + + + + + + An invalid component parameter was supplied. + + + + + Failed to find a valid archive containing %1.bsa! Retrying. + + + + + Installing %1 + + + + + Installation media path not set! + + + + + The source path for %1 was not set. + + + + + + Cannot create temporary directory! + + + + + + Failed to create %1. + + + + + Cannot move into temporary directory! + + + + + Failed to move into %1. + + + + + Moving installation files + + + + + + + + Could not install directory! + + + + + + + + Installing %1 to %2 failed. + + + + + Could not install translation file! + + + + + Failed to install *%1 files. + + + + + Could not install Morrowind data file! + + + + + + Failed to install %1. + + + + + Could not install Morrowind configuration file! + + + + + Installing: Sound directory + + + + + Could not find Tribunal data file! + + + + + + + Failed to find %1. + + + + + Could not find Tribunal patch file! + + + + + Could not find Bloodmoon data file! + + + + + Updating Morrowind configuration file + + + + + %1 installation finished! + + + + + Extracting: %1 + + + + + + + Failed to open InstallShield Cabinet File. + + + + + + + Opening %1 failed. + + + + + Failed to extract %1. + + + + + Complete path: %1 + + + + diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts new file mode 100644 index 0000000000..7f42087dbf --- /dev/null +++ b/files/lang/wizard_fr.ts @@ -0,0 +1,858 @@ + + + + + ComponentSelectionPage + + + WizardPage + + + + + Select Components + + + + + Which components should be installed? + + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + + + + + Selected components: + + + + + ConclusionPage + + + WizardPage + + + + + Completing the OpenMW Wizard + + + + + Placeholder + + + + + ExistingInstallationPage + + + WizardPage + + + + + Select Existing Installation + + + + + Select an existing installation for OpenMW to use or modify. + + + + + Detected installations: + + + + + Browse... + + + + + ImportPage + + + WizardPage + + + + + Import Settings + + + + + Import settings from the Morrowind installation. + + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + + + + + Import settings from Morrowind.ini + + + + + Import add-on and plugin selection + + + + + Import bitmap fonts setup from Morrowind.ini + + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + + + + + InstallationPage + + + WizardPage + + + + + Installing + + + + + Please wait while Morrowind is installed on your computer. + + + + + InstallationTargetPage + + + WizardPage + + + + + Select Installation Destination + + + + + Where should Morrowind be installed? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + + Morrowind will be installed to the following location. + + + + + Browse... + + + + + IntroPage + + + WizardPage + + + + + Welcome to the OpenMW Wizard + + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + + + + + LanguageSelectionPage + + + WizardPage + + + + + Select Morrowind Language + + + + + What is the language of the Morrowind installation? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + + + Select the language of the Morrowind installation. + + + + + MethodSelectionPage + + + WizardPage + + + + + Select Installation Method + + + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + + + Retail CD/DVD + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + + + Install from a retail disc to a new location. + + + + + Existing Installation + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + + Select an existing installation. + + + + + Don't have a copy? + + + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + + + Buy the game + + + + + QObject + + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + + + + + B&rowse... + + + + + Select configuration file + + + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + + + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + + + + + Most recent Morrowind not detected + + + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + + + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + + + + + Wizard::ComponentSelectionPage + + + &Install + + + + + &Skip + + + + + Morrowind (installed) + + + + + Morrowind + + + + + Tribunal (installed) + + + + + Tribunal + + + + + Bloodmoon (installed) + + + + + Bloodmoon + + + + + About to install Tribunal after Bloodmoon + + + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + + + + + Re-install &Bloodmoon + + + + + Wizard::ConclusionPage + + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + + + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + + + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + + + + + Wizard::ExistingInstallationPage + + + No existing installations detected + + + + + Error detecting Morrowind configuration + + + + + Morrowind configuration file (*.ini) + + + + + Select Morrowind.esm (located in Data Files) + + + + + Morrowind master file (Morrowind.esm) + + + + + Error detecting Morrowind files + + + + + Wizard::InstallationPage + + + <p>Attempting to install component %1.</p> + + + + + Attempting to install component %1. + + + + + %1 Installation + + + + + Select %1 installation media + + + + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + + + + + <p>Detected old version of component Morrowind.</p> + + + + + Detected old version of component Morrowind. + + + + + Morrowind Installation + + + + + Installation finished + + + + + Installation completed successfully! + + + + + Installation failed! + + + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + + + + + <p><span style="color:red;"><b>%1</b></p> + + + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + + + + + An error occurred + + + + + Wizard::InstallationTargetPage + + + Error creating destination + + + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + + + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + + + + + Insufficient permissions + + + + + Destination not empty + + + + + Select where to install Morrowind + + + + + Wizard::LanguageSelectionPage + + + English + + + + + French + + + + + German + + + + + Italian + + + + + Polish + + + + + Russian + + + + + Spanish + + + + + Wizard::MainWizard + + + OpenMW Wizard + + + + + + Error opening Wizard log file + + + + + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + + Error opening OpenMW configuration file + + + + + Quit Wizard + + + + + Are you sure you want to exit the Wizard? + + + + + Error creating OpenMW configuration directory + + + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + + + + + Error writing OpenMW configuration file + + + + + Wizard::UnshieldWorker + + + + Failed to open Morrowind configuration file! + + + + + + Opening %1 failed: %2. + + + + + Failed to write Morrowind configuration file! + + + + + Writing to %1 failed: %2. + + + + + Installing: %1 + + + + + + Installing: %1 directory + + + + + Installation finished! + + + + + + Component parameter is invalid! + + + + + + An invalid component parameter was supplied. + + + + + Failed to find a valid archive containing %1.bsa! Retrying. + + + + + Installing %1 + + + + + Installation media path not set! + + + + + The source path for %1 was not set. + + + + + + Cannot create temporary directory! + + + + + + Failed to create %1. + + + + + Cannot move into temporary directory! + + + + + Failed to move into %1. + + + + + Moving installation files + + + + + + + + Could not install directory! + + + + + + + + Installing %1 to %2 failed. + + + + + Could not install translation file! + + + + + Failed to install *%1 files. + + + + + Could not install Morrowind data file! + + + + + + Failed to install %1. + + + + + Could not install Morrowind configuration file! + + + + + Installing: Sound directory + + + + + Could not find Tribunal data file! + + + + + + + Failed to find %1. + + + + + Could not find Tribunal patch file! + + + + + Could not find Bloodmoon data file! + + + + + Updating Morrowind configuration file + + + + + %1 installation finished! + + + + + Extracting: %1 + + + + + + + Failed to open InstallShield Cabinet File. + + + + + + + Opening %1 failed. + + + + + Failed to extract %1. + + + + + Complete path: %1 + + + + diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts new file mode 100644 index 0000000000..3113774cd3 --- /dev/null +++ b/files/lang/wizard_ru.ts @@ -0,0 +1,860 @@ + + + + + ComponentSelectionPage + + + WizardPage + WizardPage + + + + Select Components + Выбор компонентов + + + + Which components should be installed? + Какие компоненты должны быть установлены? + + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> + + + + Selected components: + Выбранные компоненты: + + + + ConclusionPage + + + WizardPage + WizardPage + + + + Completing the OpenMW Wizard + Завершение работы Мастера установки OpenMW + + + + Placeholder + Placeholder + + + + ExistingInstallationPage + + + WizardPage + WizardPage + + + + Select Existing Installation + Выбрать установленную копию игры + + + + Select an existing installation for OpenMW to use or modify. + Выбрать установленную копию игры для использования или изменения через OpenMW. + + + + Detected installations: + Обнаруженные установленные копии: + + + + Browse... + Выбрать... + + + + ImportPage + + + WizardPage + WizardPage + + + + Import Settings + Импортировать настройки + + + + Import settings from the Morrowind installation. + Импортировать настройки из установленной копии Morrowind. + + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> + + + + Import settings from Morrowind.ini + Импортировать настройки из Morrowind.ini + + + + Import add-on and plugin selection + Импортировать список подключенных плагинов + + + + Import bitmap fonts setup from Morrowind.ini + Импортировать растровые шрифты из Morrowind.ini + + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Шрифты, поставляемые с оригинальной игрой, становятся размытыми при масштабировании интерфейса и поддерживают ограниченное количество символов, +поэтому в комплекте с OpenMW идет набор шрифтов, не имеющих этих проблем. Они используют технологию TrueType и весьма похожи на шрифты Morrowind. +Включите эту опцию, если вы все равно хотите использовать оригинальные шрифты вместо шрифтов OpenMW, или если вы используете сторонние растровые шрифты. + + + + InstallationPage + + + WizardPage + WizardPage + + + + Installing + Установка + + + + Please wait while Morrowind is installed on your computer. + Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. + + + + InstallationTargetPage + + + WizardPage + WizardPage + + + + Select Installation Destination + Выберите путь для установки + + + + Where should Morrowind be installed? + Куда нужно установить Morrowind? + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + Morrowind will be installed to the following location. + Morrowind будет установлен в следующее место. + + + + Browse... + Выбрать... + + + + IntroPage + + + WizardPage + WizardPage + + + + Welcome to the OpenMW Wizard + Добро пожаловать в Мастер установки + + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. + + + + LanguageSelectionPage + + + WizardPage + WizardPage + + + + Select Morrowind Language + Выберите язык вашей копии Morrowind + + + + What is the language of the Morrowind installation? + Какой язык использует ваша копия Morrowind? + + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + + Select the language of the Morrowind installation. + Выберите язык, используемый вашей копией Morrowind. + + + + MethodSelectionPage + + + WizardPage + WizardPage + + + + Select Installation Method + Выберите способ установки + + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + + Retail CD/DVD + CD/DVD-диск + + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + + Install from a retail disc to a new location. + Установить игру с диска + + + + Existing Installation + Установленная копия игры + + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + + Select an existing installation. + Выбрать установленную копию игры. + + + + Don't have a copy? + Нет копии игры? + + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + + Buy the game + Купить игру + + + + QObject + + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> + + + + B&rowse... + &Выбрать... + + + + Select configuration file + Выберите файл с настройками + + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. + + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> + + + + Most recent Morrowind not detected + Актуальная версия Morrowind не найдена + + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. + + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? + + + + Wizard::ComponentSelectionPage + + + &Install + &Установить + + + + &Skip + &Пропустить + + + + Morrowind (installed) + Morrowind (установлен) + + + + Morrowind + Morrowind + + + + Tribunal (installed) + Tribunal (установлен) + + + + Tribunal + Tribunal + + + + Bloodmoon (installed) + Bloodmoon (установлен) + + + + Bloodmoon + Bloodmoon + + + + About to install Tribunal after Bloodmoon + Попытка установить Tribunal после Bloodmoon + + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> + + + + Re-install &Bloodmoon + Переустановить &Bloodmoon + + + + Wizard::ConclusionPage + + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> + + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> + + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> + + + + Wizard::ExistingInstallationPage + + + No existing installations detected + Установленные копии игры не найдены + + + + Error detecting Morrowind configuration + Попытка найти настройки Morrowind завершилась ошибкой + + + + Morrowind configuration file (*.ini) + Файл настроек Morrowind (*.ini) + + + + Select Morrowind.esm (located in Data Files) + Выберите Morrowind.esm (расположен в Data Files) + + + + Morrowind master file (Morrowind.esm) + Мастер-файл Morrowind (Morrowind.esm) + + + + Error detecting Morrowind files + Не удалось обнаружить файлы Morrowind + + + + Wizard::InstallationPage + + + <p>Attempting to install component %1.</p> + <p>Попытка установить компонент %1.</p> + + + + Attempting to install component %1. + Попытка установить компонент %1. + + + + %1 Installation + Установка %1 + + + + Select %1 installation media + Выберите установочный дистрибутив %1 + + + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> + + + + <p>Detected old version of component Morrowind.</p> + lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> + + + + Detected old version of component Morrowind. + Обнаружена устаревшая версия компонента Morrowind. + + + + Morrowind Installation + Установка Morrowind + + + + Installation finished + Установка завершена + + + + Installation completed successfully! + Установка успешно завершена! + + + + Installation failed! + Установка не удалась! + + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> + + + + <p><span style="color:red;"><b>%1</b></p> + <p><span style="color:red;"><b>%1</b></p> + + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> + + + + An error occurred + Произошла ошибка + + + + Wizard::InstallationTargetPage + + + Error creating destination + Не удалось создать директорию назначения + + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> + + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> + + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> + + + + Insufficient permissions + Не хватает прав доступа + + + + Destination not empty + Выбранная директория не пустая + + + + Select where to install Morrowind + Выберите, куда установить Morrowind + + + + Wizard::LanguageSelectionPage + + + English + Английский + + + + French + Французский + + + + German + Немецкий + + + + Italian + Итальянский + + + + Polish + Польский + + + + Russian + Русский + + + + Spanish + Испанский + + + + Wizard::MainWizard + + + OpenMW Wizard + Мастер OpenMW + + + + + Error opening Wizard log file + Не удалось открыть лог-файл Мастера + + + + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> + + + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> + + + + + + Error opening OpenMW configuration file + Не удалось открыть файл с настройками OpenMW + + + + Quit Wizard + Завершить работу Мастера + + + + Are you sure you want to exit the Wizard? + Вы уверены, что хотите завершить работу Мастера? + + + + Error creating OpenMW configuration directory + Не удалось создать директорию для настроек OpenMW + + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> + + + + + Error writing OpenMW configuration file + Не удалось записать данные в файл с настройками OpenMW + + + + Wizard::UnshieldWorker + + + + Failed to open Morrowind configuration file! + Не удалось открыть файл с настройками Morrowind! + + + + + Opening %1 failed: %2. + Попытка открыть %1 не удалась: %2. + + + + Failed to write Morrowind configuration file! + Не удалось записать данные в файл с настройками Morrowind! + + + + Writing to %1 failed: %2. + Запись в %1 завершилась с ошибкой: %2. + + + + Installing: %1 + Установка: %1 + + + + + Installing: %1 directory + Установка: директория %1 + + + + Installation finished! + Установка завершена! + + + + + Component parameter is invalid! + Некорректный параметр для компонента! + + + + + An invalid component parameter was supplied. + Задан некорректный параметр для компонента. + + + + Failed to find a valid archive containing %1.bsa! Retrying. + Не удалось найти архив, содержащий %1.bsa! Повторная попытка. + + + + Installing %1 + Установка %1 + + + + Installation media path not set! + путь к установочному дистрибутиву не задан! + + + + The source path for %1 was not set. + Исходный пусть для %1 не задан. + + + + + Cannot create temporary directory! + Не удалось создать временную директорию! + + + + + Failed to create %1. + Не удалось создать %1. + + + + Cannot move into temporary directory! + Не удалось переместить во временную директорию! + + + + Failed to move into %1. + Не удалось переместить в %1. + + + + Moving installation files + Перемещение файлов установки + + + + + + + Could not install directory! + Не удалось установить директорию! + + + + + + + Installing %1 to %2 failed. + Не удалось установить %1 в %2. + + + + Could not install translation file! + Не удалось установить файл с переводом! + + + + Failed to install *%1 files. + Не удалось установить файлы *%1. + + + + Could not install Morrowind data file! + Не удалось установить файл с данными Morrowind! + + + + + Failed to install %1. + Не удалось установить %1. + + + + Could not install Morrowind configuration file! + Не удалось установить файл с настройками Morrowind! + + + + Installing: Sound directory + Установка: директория Sound + + + + Could not find Tribunal data file! + Не удалось найти файл с данными Tribunal! + + + + + + Failed to find %1. + Не удалось найти %1. + + + + Could not find Tribunal patch file! + Не удалось найти файл с патчем для Tribunal! + + + + Could not find Bloodmoon data file! + Не удалось найти файл с данными Bloodmoon! + + + + Updating Morrowind configuration file + Обновление файла с настройками Morrowind + + + + %1 installation finished! + Установка %1 завершена! + + + + Extracting: %1 + Извлечение: %1 + + + + + + Failed to open InstallShield Cabinet File. + Не удалось открыть файл InstallShield Cabinet. + + + + + + Opening %1 failed. + Не удалось открыть %1. + + + + Failed to extract %1. + Не удалось извлечь %1. + + + + Complete path: %1 + Полный путь: %1 + + + From 41595ee396c428f76a90e1e5a9720fe68f588af7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 2 Jan 2024 11:16:26 +0400 Subject: [PATCH 0921/2167] Implement CI job to check translations --- .gitlab-ci.yml | 14 ++++++++++++++ CI/check_qt_translations.sh | 11 +++++++++++ CI/install_debian_deps.sh | 6 ++++++ 3 files changed, 31 insertions(+) create mode 100755 CI/check_qt_translations.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 63f5bfb45e..7dc69484c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -172,6 +172,20 @@ Clang_Format: - CI/check_file_names.sh - CI/check_clang_format.sh +Lupdate: + extends: .Ubuntu_Image + stage: checks + cache: + key: Ubuntu_lupdate.ubuntu_22.04.v1 + paths: + - apt-cache/ + variables: + LUPDATE: lupdate + before_script: + - CI/install_debian_deps.sh openmw-qt-translations + script: + - CI/check_qt_translations.sh + Teal: stage: checks extends: .Ubuntu_Image diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh new file mode 100755 index 0000000000..f3a82ed2e6 --- /dev/null +++ b/CI/check_qt_translations.sh @@ -0,0 +1,11 @@ +#!/bin/bash -ex + +set -o pipefail + +LUPDATE="${LUPDATE:-lupdate}" + +${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts + +! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 4420db364d..b7784cf3f0 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -100,6 +100,12 @@ declare -rA GROUPED_DEPS=( clang-format-14 git-core " + + [openmw-qt-translations]=" + qttools5-dev + qttools5-dev-tools + git-core + " ) if [[ $# -eq 0 ]]; then From 66d1e036d2a4431d3e9779205bc1df4d69aff8f8 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 30 Jan 2024 09:45:57 +0400 Subject: [PATCH 0922/2167] Pass some arguments by references --- apps/openmw/mwphysics/closestnotmerayresultcallback.cpp | 4 ++-- apps/openmw/mwphysics/closestnotmerayresultcallback.hpp | 2 +- apps/openmw/mwstate/character.cpp | 4 ++-- apps/openmw/mwstate/character.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/projectilemanager.hpp | 2 +- components/widgets/sharedstatebutton.cpp | 2 +- components/widgets/sharedstatebutton.hpp | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 30a42bc3b8..875704d790 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -10,10 +10,10 @@ namespace MWPhysics { ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, - std::vector targets, const btVector3& from, const btVector3& to) + const std::vector& targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) , mMe(me) - , mTargets(std::move(targets)) + , mTargets(targets) { } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index e6f5c45d36..37bda3bd52 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,7 +14,7 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector targets, + ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, const btVector3& from, const btVector3& to); btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 85f5087fe6..9a3bc46742 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -84,8 +84,8 @@ void MWState::Character::addSlot(const ESM::SavedGame& profile) mSlots.push_back(slot); } -MWState::Character::Character(std::filesystem::path saves, const std::string& game) - : mPath(std::move(saves)) +MWState::Character::Character(const std::filesystem::path& saves, const std::string& game) + : mPath(saves) { if (!std::filesystem::is_directory(mPath)) { diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 7b9eba2fee..3c68d9f490 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -32,7 +32,7 @@ namespace MWState void addSlot(const ESM::SavedGame& profile); public: - Character(std::filesystem::path saves, const std::string& game); + Character(const std::filesystem::path& saves, const std::string& game); void cleanup(); ///< Delete the directory we used, if it is empty diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 0584a9fe94..b25d138814 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -188,7 +188,7 @@ namespace MWWorld }; void ProjectileManager::createModel(State& state, const std::string& model, const osg::Vec3f& pos, - const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture) + const osg::Quat& orient, bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, const std::string& texture) { state.mNode = new osg::PositionAttitudeTransform; state.mNode->setNodeMask(MWRender::Mask_Effect); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 65254a9110..012e3b5922 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -136,7 +136,7 @@ namespace MWWorld void moveMagicBolts(float dt); void createModel(State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient, - bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = ""); + bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, const std::string& texture = ""); void update(State& state, float duration); void operator=(const ProjectileManager&); diff --git a/components/widgets/sharedstatebutton.cpp b/components/widgets/sharedstatebutton.cpp index ff24db1cfb..2d8070b0fb 100644 --- a/components/widgets/sharedstatebutton.cpp +++ b/components/widgets/sharedstatebutton.cpp @@ -116,7 +116,7 @@ namespace Gui } } - void SharedStateButton::createButtonGroup(ButtonGroup group) + void SharedStateButton::createButtonGroup(ButtonGroup& group) { for (ButtonGroup::iterator it = group.begin(); it != group.end(); ++it) { diff --git a/components/widgets/sharedstatebutton.hpp b/components/widgets/sharedstatebutton.hpp index 688d949f6e..33dd70c763 100644 --- a/components/widgets/sharedstatebutton.hpp +++ b/components/widgets/sharedstatebutton.hpp @@ -38,7 +38,7 @@ namespace Gui void shareStateWith(const ButtonGroup& shared); /// @note The ButtonGroup connection will be destroyed when any widget in the group gets destroyed. - static void createButtonGroup(ButtonGroup group); + static void createButtonGroup(ButtonGroup& group); //! Set button selected state void setStateSelected(bool _value); From 130b6349311aa67299536fa976e89be24072b10e Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 1 Feb 2024 17:52:41 +0100 Subject: [PATCH 0923/2167] Changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b014ca0389..b680fd84f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -170,9 +170,11 @@ Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb + Feature #7648: Lua Save game API Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7805: Lua Menu context Task #5896: Do not use deprecated MyGUI properties Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char From a59981e3fafc7109ced518ee2a052e5d0eefd262 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Thu, 1 Feb 2024 20:00:10 +0100 Subject: [PATCH 0924/2167] Initialize mScripted again. --- apps/openmw/mwmechanics/character.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8d69da2c43..8c496a9ec6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2519,6 +2519,7 @@ namespace MWMechanics entry.mLoopCount = static_cast(std::min(iter->mLoopCount, std::numeric_limits::max())); entry.mLooping = mAnimation->isLoopingAnimation(entry.mGroup); + entry.mScripted = true; entry.mStartKey = "start"; entry.mStopKey = "stop"; entry.mSpeed = 1.f; From 225e834b88a9f24e0e2f04b11a9a94ea0b508c68 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 2 Feb 2024 09:28:19 +0400 Subject: [PATCH 0925/2167] Fix some Coverity Scan complaints --- apps/mwiniimporter/importer.cpp | 2 +- apps/openmw/mwgui/journalwindow.cpp | 6 +++--- apps/openmw/mwlua/animationbindings.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- apps/openmw/mwworld/globals.cpp | 2 +- apps/openmw/mwworld/magiceffects.cpp | 2 +- components/lua/scriptscontainer.cpp | 10 +++++----- components/shader/shadermanager.cpp | 6 +++--- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 7b3bcc3f1c..8c7c238b4a 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -584,7 +584,7 @@ void MwIniImporter::importGameFiles( reader.close(); } - auto sortedFiles = dependencySort(unsortedFiles); + auto sortedFiles = dependencySort(std::move(unsortedFiles)); // hard-coded dependency Morrowind - Tribunal - Bloodmoon if (findString(sortedFiles, "Morrowind.esm") != sortedFiles.end()) diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 0cc5a00831..574c425d3e 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -126,7 +126,7 @@ namespace MWGui::BookPage::ClickCallback callback = [this](intptr_t linkId) { notifyTopicClicked(linkId); }; getPage(LeftBookPage)->adviseLinkClicked(callback); - getPage(RightBookPage)->adviseLinkClicked(callback); + getPage(RightBookPage)->adviseLinkClicked(std::move(callback)); getPage(LeftBookPage)->eventMouseWheel += MyGUI::newDelegate(this, &JournalWindowImpl::notifyMouseWheel); @@ -140,7 +140,7 @@ namespace getPage(LeftTopicIndex)->adviseLinkClicked(callback); getPage(CenterTopicIndex)->adviseLinkClicked(callback); - getPage(RightTopicIndex)->adviseLinkClicked(callback); + getPage(RightTopicIndex)->adviseLinkClicked(std::move(callback)); } adjustButton(PrevPageBTN); @@ -376,7 +376,7 @@ namespace setVisible(PageTwoNum, relPages > 1); getPage(LeftBookPage)->showPage((relPages > 0) ? book : Book(), page + 0); - getPage(RightBookPage)->showPage((relPages > 0) ? book : Book(), page + 1); + getPage(RightBookPage)->showPage((relPages > 0) ? std::move(book) : Book(), page + 1); setText(PageOneNum, page + 1); setText(PageTwoNum, page + 2); diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index ecd1e96dbb..a1f472908c 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -119,7 +119,7 @@ namespace MWLua if (asTable) { AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default); - for (auto entry : asTable.value()) + for (const auto& entry : asTable.value()) { if (!entry.first.is() || !entry.second.is()) throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only"); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8d69da2c43..7b305ac69a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -711,7 +711,7 @@ namespace MWMechanics mMovementAnimationHasMovement = true; clearStateAnimation(mCurrentMovement); - mCurrentMovement = movementAnimName; + mCurrentMovement = std::move(movementAnimName); // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b9ea257c9f..1467f8e737 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -844,7 +844,7 @@ namespace MWRender } } } - SceneUtil::ForceControllerSourcesVisitor assignVisitor(src); + SceneUtil::ForceControllerSourcesVisitor assignVisitor(std::move(src)); node->accept(assignVisitor); } else diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 264109d17f..4977df56c0 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -99,7 +99,7 @@ namespace MWWorld global.load(reader, isDeleted); if (const auto iter = mVariables.find(global.mId); iter != mVariables.end()) - iter->second = global; + iter->second = std::move(global); return true; } diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 8b7ad79db2..4ff0e60c46 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -133,7 +133,7 @@ namespace MWWorld continue; ESM::ActiveSpells::ActiveSpellParams params; params.mId = id; - params.mDisplayName = name; + params.mDisplayName = std::move(name); params.mCasterActorId = creatureStats.mActorId; params.mType = ESM::ActiveSpells::Type_Enchantment; params.mWorsenings = -1; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 2a1a755422..9b4a119ba4 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -115,20 +115,20 @@ namespace LuaUtil std::string_view handlerName = cast(key); sol::function fn = cast(handler); if (handlerName == HANDLER_INIT) - onInit = fn; + onInit = std::move(fn); else if (handlerName == HANDLER_LOAD) - onLoad = fn; + onLoad = std::move(fn); else if (handlerName == HANDLER_SAVE) - script.mOnSave = fn; + script.mOnSave = std::move(fn); else if (handlerName == HANDLER_INTERFACE_OVERRIDE) - script.mOnOverride = fn; + script.mOnOverride = std::move(fn); else { auto it = mEngineHandlers.find(handlerName); if (it == mEngineHandlers.end()) Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << debugName; else - insertHandler(it->second->mList, scriptId, fn); + insertHandler(it->second->mList, scriptId, std::move(fn)); } } } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 612552dabc..96d6346149 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -503,7 +503,7 @@ namespace Shader { break; } - shaderSource = source; + shaderSource = std::move(source); std::vector linkedShaderNames; if (!Manager.createSourceFromTemplate( @@ -554,7 +554,7 @@ namespace Shader if (!addLineDirectivesAfterConditionalBlocks(source) || !parseIncludes(mPath, source, templateName, fileNumber, {}, insertedPaths)) return nullptr; - mHotReloadManager->templateIncludedFiles[templateName] = insertedPaths; + mHotReloadManager->templateIncludedFiles[templateName] = std::move(insertedPaths); templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; } @@ -597,7 +597,7 @@ namespace Shader if (!vert || !frag) throw std::runtime_error("failed initializing shader: " + templateName); - return getProgram(vert, frag, programTemplate); + return getProgram(std::move(vert), std::move(frag), programTemplate); } osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, From 69936f35376cb326b0590e198d0dab1eb8e41b2c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 2 Feb 2024 09:45:42 +0400 Subject: [PATCH 0926/2167] Move TextureData --- apps/openmw/mwlua/uibindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 30f190ad38..32d6db2539 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -242,7 +242,7 @@ namespace MWLua sol::object size = LuaUtil::getFieldOrNil(options, "size"); if (size.is()) data.mSize = size.as(); - return luaManager->uiResourceManager()->registerTexture(data); + return luaManager->uiResourceManager()->registerTexture(std::move(data)); }; api["screenSize"] = []() { return osg::Vec2f(Settings::video().mResolutionX, Settings::video().mResolutionY); }; From 5bd641d2dd94443bf24654f429fbfcb0f8faad59 Mon Sep 17 00:00:00 2001 From: Anton Uramer Date: Fri, 2 Feb 2024 12:53:03 +0100 Subject: [PATCH 0927/2167] Lua API Revision 52 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28109bd01b..3ea891b56d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 51) +set(OPENMW_LUA_API_REVISION 52) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") From 3882ba15fbc680019a3483a7495afc5f147125ad Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 2 Feb 2024 20:10:25 +0100 Subject: [PATCH 0928/2167] Rework documentation of skill used/level up handlers. --- files/data/scripts/omw/skillhandlers.lua | 55 +++++++++++------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 30188f2341..f6a8ec4248 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -41,32 +41,6 @@ local Skill = core.stats.Skill -- @field #string Trainer trainer -- @field #string Usage usage ---- --- Table of valid handler signatures --- @type HandlerSignatures --- - ---- Signature of the skillLevelUp handler --- @function [parent=#HandlerSignatures] skillLevelUpHandler --- @param #string skillid The ID of the skill being leveled up --- @param #SkillLevelUpSource source The source of the skill level up --- @param #table params Modifiable skill level up values as a table. These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: --- --- * `skillIncreaseValue` - The numeric amount of skill levels gained. --- * `levelUpProgress` - The numeric amount of level up progress gained. --- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. --- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. --- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. --- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. - ---- Signature of the skillUsed handler --- @function [parent=#HandlerSignatures] skillUsedHandler --- @param #string skillid The ID of the skill being progressed --- @param #SkillUseType useType The use type the skill progression --- @param #table params Modifiable skill progression value. By default contains the single value --- --- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. - local skillUsedHandlers = {} local skillLevelUpHandlers = {} @@ -118,12 +92,12 @@ local function skillUsed(skillid, useType, scale) local skillGain = skillGainUnorm / skillProgressRequirementUnorm -- Put skill gain in a table so that handlers can modify it - local parameters = { + local options = { skillGain = skillGain, } for i = #skillUsedHandlers, 1, -1 do - if skillUsedHandlers[i](skillid, useType, parameters) == false then + if skillUsedHandlers[i](skillid, useType, options) == false then return end end @@ -150,7 +124,7 @@ local function skillLevelUp(skillid, source) levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') end - local parameters = + local options = { skillIncreaseValue = 1, levelUpProgress = levelUpProgress, @@ -161,7 +135,7 @@ local function skillLevelUp(skillid, source) } for i = #skillLevelUpHandlers, 1, -1 do - if skillLevelUpHandlers[i](skillid, source, parameters) == false then + if skillLevelUpHandlers[i](skillid, source, options) == false then return end end @@ -199,13 +173,32 @@ return { version = 0, --- Add new skill level up handler for this actor + -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) + -- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is + -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. + -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: + -- + -- * `skillIncreaseValue` - The numeric amount of skill levels gained. + -- * `levelUpProgress` - The numeric amount of level up progress gained. + -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. + -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. + -- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. + -- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. + -- -- @function [parent=#SkillProgression] addSkillLevelUpHandler - -- @param #function handler The handler, see #skillLevelUpHandler. + -- @param #function handler The handler. addSkillLevelUpHandler = function(handler) skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler end, --- Add new skillUsed handler for this actor + -- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler) + -- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed}, + -- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. + -- By default contains the single value: + -- + -- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. + -- -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) From 784459a652842341a91b74e1d386fb1ecdd87da4 Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 2 Feb 2024 22:07:58 +0100 Subject: [PATCH 0929/2167] Clean up the cleanup code --- apps/openmw/mwstate/statemanagerimp.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 63190f72c3..e02b6053ad 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -68,6 +68,8 @@ void MWState::StateManager::cleanup(bool force) mLastSavegame.clear(); MWMechanics::CreatureStats::cleanup(); + + endGame(); } MWBase::Environment::get().getLuaManager()->clear(); } @@ -449,13 +451,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file { try { - if (mState != State_Ended) - { - // let menu scripts do cleanup - mState = State_Ended; - MWBase::Environment::get().getLuaManager()->gameEnded(); - } - cleanup(); Log(Debug::Info) << "Reading save file " << filepath.filename(); From a1970857fd9aec08f6984e0dad4f4fdad16e2ee2 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 15:18:34 +0100 Subject: [PATCH 0930/2167] Queue quick loads --- apps/openmw/mwstate/statemanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index e02b6053ad..c413583c2d 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -693,7 +693,8 @@ void MWState::StateManager::quickLoad() { if (currentCharacter->begin() == currentCharacter->end()) return; - loadGame(currentCharacter, currentCharacter->begin()->mPath); // Get newest save + // use requestLoad, otherwise we can crash by loading during the wrong part of the frame + requestLoad(currentCharacter->begin()->mPath); } } From 55285b5e57d60ecc16fb3f6cbea079f3e260e9dc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 10:43:38 -0600 Subject: [PATCH 0931/2167] Fix Global Iteration --- apps/openmw/mwlua/mwscriptbindings.cpp | 75 ++++++++++++++++++++------ 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index a41ef30a44..b2872ed4fc 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -125,21 +125,39 @@ namespace MWLua return "ESM3_GlobalStore{" + std::to_string(store.getSize()) + " globals}"; }; globalStoreT[sol::meta_function::length] = [](const GlobalStore& store) { return store.getSize(); }; - globalStoreT[sol::meta_function::index] - = sol::overload([](const GlobalStore& store, std::string_view globalId) -> sol::optional { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - return sol::nullopt; - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } - }); + globalStoreT[sol::meta_function::index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId) -> sol::optional { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + return sol::nullopt; + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + }, + [](const GlobalStore& store, int index) -> sol::optional { + if (index < 1 || index >= store.getSize()) + return sol::nullopt; + auto g = store.at(index - 1); + if (g == nullptr) + return sol::nullopt; + std::string globalId = g->mId.serializeText(); + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + }); + globalStoreT[sol::meta_function::new_index] = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { auto g = store.search(ESM::RefId::deserializeText(globalId)); @@ -155,7 +173,32 @@ namespace MWLua MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); } }); - globalStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { + int index = 0; // Start index + return sol::as_function( + [index, &store](sol::this_state ts) mutable -> sol::optional> { + if (index >= store.getSize()) + return sol::nullopt; + + const auto& global = store.at(index++); + if (!global) + return sol::nullopt; + + std::string globalId = global->mId.serializeText(); + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + float value; + if (varType == 's' || varType == 'l') + { + value = static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + value = MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + + return std::make_tuple(globalId, value); + }); + }; globalStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); api["getGlobalVariables"] = [globalStore](sol::optional player) { if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) From 9daf10c305a178cce510aac8548da65fa8305af6 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 10:45:24 -0600 Subject: [PATCH 0932/2167] Remove comment --- apps/openmw/mwlua/mwscriptbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index b2872ed4fc..e234a2be8e 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -174,7 +174,7 @@ namespace MWLua } }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { - int index = 0; // Start index + int index = 0; return sol::as_function( [index, &store](sol::this_state ts) mutable -> sol::optional> { if (index >= store.getSize()) From e6196c782db5e90a31b71d4a5b619e9c8ba04fef Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 23:59:58 +0100 Subject: [PATCH 0933/2167] Limit navmesh vertices coordinates values Float values with more than 22 significant fraction bits may cause out of bounds access in recastnavigation on triangles rasterization. Prevent passing such values there. --- CHANGELOG.md | 1 + .../detournavigator/navigator.cpp | 2 +- components/detournavigator/makenavmesh.cpp | 28 +++++++++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c42bc552..5658fdc904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6402: The sound of a thunderstorm does not stop playing after entering the premises Bug #6427: Enemy health bar disappears before damaging effect ends Bug #6550: Cloned body parts don't inherit texture effects + Bug #6574: Crash at far away from world origin coordinates Bug #6645: Enemy block sounds align with animation instead of blocked hits Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index aba8598f18..b61a88662b 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -871,7 +871,7 @@ namespace TEST_F(DetourNavigatorNavigatorTest, update_for_very_big_object_should_be_limited) { - const float size = static_cast(2 * static_cast(std::numeric_limits::max()) - 1); + const float size = static_cast((1 << 22) - 1); CollisionShapeInstance bigBox(std::make_unique(btVector3(size, size, 1))); const ObjectTransform objectTransform{ .mPosition = ESM::Position{ .pos = { 0, 0, 0 }, .rot{ 0, 0, 0 } }, diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index 4bac85f420..d35ecf499d 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -186,16 +186,35 @@ namespace DetourNavigator &context, solid, width, height, bmin.ptr(), bmax.ptr(), settings.mCellSize, settings.mCellHeight); } + bool isSupportedCoordinate(float value) + { + constexpr float maxVertexCoordinate = static_cast(1 << 22); + return -maxVertexCoordinate < value && value < maxVertexCoordinate; + } + + template + bool isSupportedCoordinates(Iterator begin, Iterator end) + { + return std::all_of(begin, end, isSupportedCoordinate); + } + [[nodiscard]] bool rasterizeTriangles(RecastContext& context, const Mesh& mesh, const RecastSettings& settings, const RecastParams& params, rcHeightfield& solid) { std::vector areas(mesh.getAreaTypes().begin(), mesh.getAreaTypes().end()); std::vector vertices = mesh.getVertices(); - for (std::size_t i = 0; i < vertices.size(); i += 3) + constexpr std::size_t verticesPerTriangle = 3; + + for (std::size_t i = 0; i < vertices.size(); i += verticesPerTriangle) { - for (std::size_t j = 0; j < 3; ++j) - vertices[i + j] = toNavMeshCoordinates(settings, vertices[i + j]); + for (std::size_t j = 0; j < verticesPerTriangle; ++j) + { + const float coordinate = toNavMeshCoordinates(settings, vertices[i + j]); + if (!isSupportedCoordinate(coordinate)) + return false; + vertices[i + j] = coordinate; + } std::swap(vertices[i + 1], vertices[i + 2]); } @@ -217,6 +236,9 @@ namespace DetourNavigator rectangle.mBounds.mMax.x(), rectangle.mHeight, rectangle.mBounds.mMin.y(), // vertex 3 }; + if (!isSupportedCoordinates(vertices.begin(), vertices.end())) + return false; + const std::array indices{ 0, 1, 2, // triangle 0 0, 2, 3, // triangle 1 From 5f9acbd0f0a64030d3bc30a352d46b675bdce778 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 13:03:23 -0600 Subject: [PATCH 0934/2167] Add function to replace duplicated code --- apps/openmw/mwlua/mwscriptbindings.cpp | 57 +++++++++----------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index e234a2be8e..ded00dc2b2 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -53,6 +53,20 @@ namespace sol namespace MWLua { + auto getGlobalVariableValue(const std::string_view globalId) -> float + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + else if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + return 0; + }; + sol::table initMWScriptBindings(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -130,15 +144,7 @@ namespace MWLua auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) return sol::nullopt; - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + return getGlobalVariableValue(globalId); }, [](const GlobalStore& store, int index) -> sol::optional { if (index < 1 || index >= store.getSize()) @@ -147,15 +153,7 @@ namespace MWLua if (g == nullptr) return sol::nullopt; std::string globalId = g->mId.serializeText(); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::new_index] @@ -163,15 +161,7 @@ namespace MWLua auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - MWBase::Environment::get().getWorld()->setGlobalInt(globalId, static_cast(val)); - } - else - { - MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); - } + return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { int index = 0; @@ -182,19 +172,10 @@ namespace MWLua const auto& global = store.at(index++); if (!global) - return sol::nullopt; + return sol::nullopt; std::string globalId = global->mId.serializeText(); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - float value; - if (varType == 's' || varType == 'l') - { - value = static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - value = MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + float value = getGlobalVariableValue(globalId); return std::make_tuple(globalId, value); }); From 292879d0fbfb4761500e3e843f65c39e226e3184 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 4 Feb 2024 08:51:45 +0400 Subject: [PATCH 0935/2167] Address Coverity Scan complaints left --- apps/openmw/mwdialogue/filter.cpp | 10 ++++++++-- apps/openmw/mwlua/animationbindings.cpp | 9 +++++---- apps/openmw/mwlua/itemdata.cpp | 4 ++-- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- apps/openmw/mwlua/postprocessingbindings.cpp | 2 +- apps/openmw/mwlua/types/actor.cpp | 3 ++- apps/openmw/mwlua/types/player.cpp | 2 +- apps/openmw/mwlua/uibindings.cpp | 4 ++-- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 899acf603e..8b67ea28b3 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -375,12 +375,18 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return mChoice; case SelectWrapper::Function_AiSetting: + { + int argument = select.getArgument(); + if (argument < 0 || argument > 3) + { + throw std::runtime_error("AiSetting index is out of range"); + } return mActor.getClass() .getCreatureStats(mActor) - .getAiSetting((MWMechanics::AiSetting)select.getArgument()) + .getAiSetting(static_cast(argument)) .getModified(false); - + } case SelectWrapper::Function_PcAttribute: { ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index a1f472908c..d024c41307 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -344,18 +344,19 @@ namespace MWLua [world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos) { auto model = getStaticModelOrThrow(staticOrID); context.mLuaManager->addAction( - [world, model, worldPos]() { world->spawnEffect(model, "", worldPos); }, "openmw.vfx.spawn"); + [world, model = std::move(model), worldPos]() { world->spawnEffect(model, "", worldPos); }, + "openmw.vfx.spawn"); }, [world, context](const sol::object& staticOrID, const osg::Vec3f& worldPos, const sol::table& options) { auto model = getStaticModelOrThrow(staticOrID); bool magicVfx = options.get_or("mwMagicVfx", true); - std::string textureOverride = options.get_or("particleTextureOverride", ""); + std::string texture = options.get_or("particleTextureOverride", ""); float scale = options.get_or("scale", 1.f); context.mLuaManager->addAction( - [world, model, textureOverride, worldPos, scale, magicVfx]() { - world->spawnEffect(model, textureOverride, worldPos, scale, magicVfx); + [world, model = std::move(model), texture = std::move(texture), worldPos, scale, magicVfx]() { + world->spawnEffect(model, texture, worldPos, scale, magicVfx); }, "openmw.vfx.spawn"); }); diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 6dea2360e7..d7ced755ea 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -42,7 +42,7 @@ namespace MWLua ObjectVariant mObject; public: - ItemData(ObjectVariant object) + ItemData(const ObjectVariant& object) : mObject(object) { } @@ -116,7 +116,7 @@ namespace MWLua item["itemData"] = [](const sol::object& object) -> sol::optional { ObjectVariant o(object); if (o.ptr().getClass().isItem(o.ptr()) || o.ptr().mRef->getType() == ESM::REC_LIGH) - return ItemData(std::move(o)); + return ItemData(o); return {}; }; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d4366cac50..8fde31ef85 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -643,7 +643,7 @@ namespace MWLua scripts->setSavedDataDeserializer(mLocalSerializer.get()); ESM::LuaScripts data; scripts->save(data); - localData[id] = data; + localData[id] = std::move(data); } mMenuScripts.removeAllScripts(); diff --git a/apps/openmw/mwlua/postprocessingbindings.cpp b/apps/openmw/mwlua/postprocessingbindings.cpp index 5ce37d13da..74359e3a4b 100644 --- a/apps/openmw/mwlua/postprocessingbindings.cpp +++ b/apps/openmw/mwlua/postprocessingbindings.cpp @@ -85,7 +85,7 @@ namespace MWLua } context.mLuaManager->addAction( - [=] { + [shader, name, values = std::move(values)] { MWBase::Environment::get().getWorld()->getPostProcessor()->setUniform(shader.mShader, name, values); }, "SetUniformShaderAction"); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 473b0d3301..4fda04e7c5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -274,7 +274,8 @@ namespace MWLua { ei = LuaUtil::cast(item); } - context.mLuaManager->addAction([obj = Object(ptr), ei = ei] { setSelectedEnchantedItem(obj.ptr(), ei); }, + context.mLuaManager->addAction( + [obj = Object(ptr), ei = std::move(ei)] { setSelectedEnchantedItem(obj.ptr(), ei); }, "setSelectedEnchantedItemAction"); }; diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 812296daf8..d2a9c5d920 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -117,7 +117,7 @@ namespace MWLua // The journal mwscript function has a try function here, we will make the lua function throw an // error. However, the addAction will cause it to error outside of this function. context.mLuaManager->addAction( - [actor, q, stage] { + [actor = std::move(actor), q, stage] { MWWorld::Ptr actorPtr; if (actor) actorPtr = actor->ptr(); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 6b1dcfef51..b5bd30fbe6 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -241,7 +241,7 @@ namespace MWLua for (unsigned i = 0; i < newStack.size(); ++i) newStack[i] = nameToMode.at(LuaUtil::cast(modes[i + 1])); luaManager->addAction( - [windowManager, newStack, arg]() { + [windowManager, newStack = std::move(newStack), arg]() { MWWorld::Ptr ptr; if (arg.has_value()) ptr = arg->ptr(); @@ -319,7 +319,7 @@ namespace MWLua }; auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); - uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) { return self.name(); }); + uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); }); uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; From 8c6a1ae8c08831a1c35fea82eca3e21715d2f68b Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 12:02:44 +0100 Subject: [PATCH 0936/2167] Allow menu scripts to send global events while a game is loaded --- apps/openmw/mwlua/corebindings.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 8c881f9f75..8d8e97ed07 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -1,6 +1,7 @@ #include "corebindings.hpp" #include +#include #include #include @@ -133,7 +134,14 @@ namespace MWLua sol::table api(context.mLua->sol(), sol::create); for (auto& [k, v] : LuaUtil::getMutableFromReadOnly(initCorePackage(context))) api[k] = v; - api["sendGlobalEvent"] = sol::nil; + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + { + throw std::logic_error("Can't send global events when no game is loaded"); + } + context.mLuaEvents->addGlobalEvent( + { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; api["sound"] = sol::nil; api["vfx"] = sol::nil; return LuaUtil::makeReadOnly(api); From 02accd7a495723d985f8b239970558414117546f Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 15:12:24 +0100 Subject: [PATCH 0937/2167] Fix statemanager cleanup setting game state to ended by accident --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 6 ++++++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 11 +++++++---- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index c70194a0a1..e865756408 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -56,6 +56,7 @@ namespace MWBase virtual void newGameStarted() = 0; virtual void gameLoaded() = 0; virtual void gameEnded() = 0; + virtual void noGame() = 0; virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0; virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0; virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d4366cac50..9029d7509e 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -379,6 +379,12 @@ namespace MWLua mMenuScripts.stateChanged(); } + void LuaManager::noGame() + { + clear(); + mMenuScripts.stateChanged(); + } + void LuaManager::uiModeChanged(const MWWorld::Ptr& arg) { if (mPlayer.isEmpty()) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 1ed9ed233e..a9657e4d61 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -70,6 +70,7 @@ namespace MWLua void newGameStarted() override; void gameLoaded() override; void gameEnded() override; + void noGame() override; void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void inputEvent(const InputEvent& event) override; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index e02b6053ad..5927544f35 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -62,16 +62,19 @@ void MWState::StateManager::cleanup(bool force) MWBase::Environment::get().getInputManager()->clear(); MWBase::Environment::get().getMechanicsManager()->clear(); - mState = State_NoGame; mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; mLastSavegame.clear(); - MWMechanics::CreatureStats::cleanup(); - endGame(); + mState = State_NoGame; + MWBase::Environment::get().getLuaManager()->noGame(); + } + else + { + // TODO: do we need this cleanup? + MWBase::Environment::get().getLuaManager()->clear(); } - MWBase::Environment::get().getLuaManager()->clear(); } std::map MWState::StateManager::buildContentFileIndexMap(const ESM::ESMReader& reader) const From 5d0537e57c2726ddddc766b967239b54fc41f472 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 15:16:42 +0100 Subject: [PATCH 0938/2167] Document core.sendGlobalEvent behavior in menu scripts --- files/lua_api/openmw/core.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index db60346e7c..33f21a6cce 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -19,7 +19,7 @@ -- @function [parent=#core] quit --- --- Send an event to global scripts. +-- Send an event to global scripts. Note: in menu scripts, errors if the game is not running (check @{openmw.menu#menu.getState}) -- @function [parent=#core] sendGlobalEvent -- @param #string eventName -- @param eventData From 72136e7e921077e5cb9b5d3bba9b0b38edcdf74c Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 15:39:04 +0100 Subject: [PATCH 0939/2167] Remove debug log --- files/data/scripts/omw/settings/menu.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 6d6d07a20b..781d1cdd27 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -421,7 +421,6 @@ local menuGroups = {} local menuPages = {} local function resetPlayerGroups() - print('MENU reset player groups') local playerGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do for groupKey, group in pairs(page) do From 891f3583feece6e49829ca3c2f1c1c06439701c7 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 3 Feb 2024 15:39:28 +0100 Subject: [PATCH 0940/2167] Only reset settings when there is no game, not on game end / player death --- files/data/scripts/omw/settings/menu.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 781d1cdd27..88913143e8 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -502,7 +502,7 @@ return { if menu.getState() == menu.STATE.Running then updatePlayerGroups() updateGlobalGroups() - else + elseif menu.getState() == menu.STATE.NoGame then resetPlayerGroups() end end, From 6c2ddc635d7e25f586210a19edd1cc6756958781 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 4 Feb 2024 21:41:03 +0100 Subject: [PATCH 0941/2167] Reset friendly hits at the end of combat and don't count hits while in combat --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 10 ++++++++++ apps/openmw/mwmechanics/aisequence.hpp | 1 + apps/openmw/mwmechanics/combat.cpp | 2 ++ apps/openmw/mwmechanics/creaturestats.cpp | 5 +++++ apps/openmw/mwmechanics/creaturestats.hpp | 4 +++- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 ++ 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a87cf7a80..7c4efcc2f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X Bug #5714: Touch spells cast using ExplodeSpell don't always explode + Bug #5755: Reset friendly hit counter Bug #5849: Paralysis breaks landing Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla Bug #5883: Immobile creatures don't cause water ripples diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5d6f25ecb8..f5bb0be62f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -82,7 +82,11 @@ namespace MWMechanics void AiSequence::onPackageRemoved(const AiPackage& package) { if (package.getTypeId() == AiPackageTypeId::Combat) + { mNumCombatPackages--; + if (mNumCombatPackages == 0) + mResetFriendlyHits = true; + } else if (package.getTypeId() == AiPackageTypeId::Pursue) mNumPursuitPackages--; @@ -246,6 +250,12 @@ namespace MWMechanics return; } + if (mResetFriendlyHits) + { + actor.getClass().getCreatureStats(actor).resetFriendlyHits(); + mResetFriendlyHits = false; + } + if (mPackages.empty()) { mLastAiPackage = AiPackageTypeId::None; diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 92c1724ea6..dd2f0cbf77 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -39,6 +39,7 @@ namespace MWMechanics /// Finished with top AIPackage, set for one frame bool mDone{}; + bool mResetFriendlyHits{}; int mNumCombatPackages{}; int mNumPursuitPackages{}; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3208ea2293..06de3b64f4 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -674,6 +674,8 @@ namespace MWMechanics return false; MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); + if (statsTarget.getAiSequence().isInCombat()) + return true; statsTarget.friendlyHit(); if (statsTarget.getFriendlyHits() >= 4) return false; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 757bdf7c49..e20045791c 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -319,6 +319,11 @@ namespace MWMechanics ++mFriendlyHits; } + void CreatureStats::resetFriendlyHits() + { + mFriendlyHits = 0; + } + bool CreatureStats::hasTalkedToPlayer() const { return mTalkedTo; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index f0a834bd33..7989357634 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -200,6 +200,8 @@ namespace MWMechanics void friendlyHit(); ///< Increase number of friendly hits by one. + void resetFriendlyHits(); + bool hasTalkedToPlayer() const; ///< Has this creature talked with the player before? @@ -294,7 +296,7 @@ namespace MWMechanics bool wasTeleported() const { return mTeleported; } void setTeleported(bool v) { mTeleported = v; } - const std::map getAttributes() const { return mAttributes; } + const std::map& getAttributes() const { return mAttributes; } }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9fd1b3ff8d..c68a8db43c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1701,6 +1701,8 @@ namespace MWMechanics // We don't care about dialogue filters since the target is invalid. // We still want to play the combat taunt. MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); + if (!stats.getAiSequence().isInCombat()) + stats.resetFriendlyHits(); return; } From 6792cf02b24c151f868df1ef4e6c3a60c7e73887 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 5 Feb 2024 18:46:01 +0100 Subject: [PATCH 0942/2167] Group owner bindings into a table --- apps/openmw/mwlua/object.hpp | 6 ++ apps/openmw/mwlua/objectbindings.cpp | 137 +++++++++++++++------------ files/lua_api/openmw/core.lua | 12 ++- 3 files changed, 90 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index b42b092302..d032515314 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -71,6 +71,12 @@ namespace MWLua { Obj mObj; }; + + template + struct Owner + { + Obj mObj; + }; } #endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 2a30e31948..c594af2944 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -200,6 +200,80 @@ namespace MWLua return Misc::Convert::makeOsgQuat(pos.rot); } + template + void addOwnerbindings(sol::usertype& objectT, const std::string& prefix, const Context& context) + { + using OwnerT = Owner; + sol::usertype ownerT = context.mLua->sol().new_usertype(prefix + "Owner"); + + ownerT[sol::meta_function::to_string] = [](const OwnerT& o) { return "Owner[" + o.mObj.toString() + "]"; }; + + auto getOwnerRecordId = [](const OwnerT& o) -> sol::optional { + ESM::RefId owner = o.mObj.ptr().getCellRef().getOwner(); + if (owner.empty()) + return sol::nullopt; + else + return owner.serializeText(); + }; + auto setOwnerRecordId = [](const OwnerT& o, sol::optional ownerId) { + if (std::is_same_v && !dynamic_cast(&o.mObj)) + throw std::runtime_error("Local scripts can set an owner only on self"); + const MWWorld::Ptr& ptr = o.mObj.ptr(); + + if (!ownerId) + { + ptr.getCellRef().setOwner(ESM::RefId()); + return; + } + ESM::RefId owner = ESM::RefId::deserializeText(*ownerId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (!store.get().search(owner)) + throw std::runtime_error("Invalid owner record id"); + ptr.getCellRef().setOwner(owner); + }; + ownerT["recordId"] = sol::property(getOwnerRecordId, setOwnerRecordId); + + auto getOwnerFactionId = [](const OwnerT& o) -> sol::optional { + ESM::RefId owner = o.mObj.ptr().getCellRef().getFaction(); + if (owner.empty()) + return sol::nullopt; + else + return owner.serializeText(); + }; + auto setOwnerFactionId = [](const OwnerT& o, sol::optional ownerId) { + ESM::RefId ownerFac; + if (std::is_same_v && !dynamic_cast(&o.mObj)) + throw std::runtime_error("Local scripts can set an owner faction only on self"); + if (!ownerId) + { + o.mObj.ptr().getCellRef().setFaction(ESM::RefId()); + return; + } + ownerFac = ESM::RefId::deserializeText(*ownerId); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + if (!store.get().search(ownerFac)) + throw std::runtime_error("Invalid owner faction id"); + o.mObj.ptr().getCellRef().setFaction(ownerFac); + }; + ownerT["factionId"] = sol::property(getOwnerFactionId, setOwnerFactionId); + + auto getOwnerFactionRank = [](const OwnerT& o) -> sol::optional { + int rank = o.mObj.ptr().getCellRef().getFactionRank(); + if (rank < 0) + return sol::nullopt; + else + return rank; + }; + auto setOwnerFactionRank = [](const OwnerT& o, sol::optional factionRank) { + if (std::is_same_v && !dynamic_cast(&o.mObj)) + throw std::runtime_error("Local scripts can set an owner faction rank only on self"); + o.mObj.ptr().getCellRef().setFactionRank(factionRank.value_or(-1)); + }; + ownerT["factionRank"] = sol::property(getOwnerFactionRank, setOwnerFactionRank); + + objectT["owner"] = sol::readonly_property([](const ObjectT& object) { return OwnerT{ object }; }); + } + template void addBasicBindings(sol::usertype& objectT, const Context& context) { @@ -266,68 +340,6 @@ namespace MWLua context.mLuaEvents->addLocalEvent( { dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; - auto getOwnerRecordId = [](const ObjectT& o) -> sol::optional { - ESM::RefId owner = o.ptr().getCellRef().getOwner(); - if (owner.empty()) - return sol::nullopt; - else - return owner.serializeText(); - }; - auto setOwnerRecordId = [](const ObjectT& obj, sol::optional ownerId) { - if (std::is_same_v && !dynamic_cast(&obj)) - throw std::runtime_error("Local scripts can set an owner only on self"); - const MWWorld::Ptr& ptr = obj.ptr(); - - if (!ownerId) - { - ptr.getCellRef().setOwner(ESM::RefId()); - return; - } - ESM::RefId owner = ESM::RefId::deserializeText(*ownerId); - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - if (!store.get().search(owner)) - throw std::runtime_error("Invalid owner record id"); - ptr.getCellRef().setOwner(owner); - }; - objectT["ownerRecordId"] = sol::property(getOwnerRecordId, setOwnerRecordId); - - auto getOwnerFactionId = [](const ObjectT& o) -> sol::optional { - ESM::RefId owner = o.ptr().getCellRef().getFaction(); - if (owner.empty()) - return sol::nullopt; - else - return owner.serializeText(); - }; - auto setOwnerFactionId = [](const ObjectT& object, sol::optional ownerId) { - ESM::RefId ownerFac; - if (std::is_same_v && !dynamic_cast(&object)) - throw std::runtime_error("Local scripts can set an owner faction only on self"); - if (!ownerId) - { - object.ptr().getCellRef().setFaction(ESM::RefId()); - return; - } - ownerFac = ESM::RefId::deserializeText(*ownerId); - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - if (!store.get().search(ownerFac)) - throw std::runtime_error("Invalid owner faction id"); - object.ptr().getCellRef().setFaction(ownerFac); - }; - objectT["ownerFactionId"] = sol::property(getOwnerFactionId, setOwnerFactionId); - - auto getOwnerFactionRank = [](const ObjectT& o) -> sol::optional { - int rank = o.ptr().getCellRef().getFactionRank(); - if (rank < 0) - return sol::nullopt; - else - return rank; - }; - auto setOwnerFactionRank = [](const ObjectT& object, sol::optional factionRank) { - if (std::is_same_v && !dynamic_cast(&object)) - throw std::runtime_error("Local scripts can set an owner faction rank only on self"); - object.ptr().getCellRef().setFactionRank(factionRank.value_or(-1)); - }; - objectT["ownerFactionRank"] = sol::property(getOwnerFactionRank, setOwnerFactionRank); objectT["activateBy"] = [](const ObjectT& object, const ObjectT& actor) { const MWWorld::Ptr& objPtr = object.ptr(); @@ -672,6 +684,7 @@ namespace MWLua = context.mLua->sol().new_usertype(prefix + "Object", sol::base_classes, sol::bases()); addBasicBindings(objectT, context); addInventoryBindings(objectT, prefix, context); + addOwnerbindings(objectT, prefix, context); registerObjectList(prefix, context); } diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 33f21a6cce..ca2da8bb23 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -164,9 +164,7 @@ -- @field openmw.util#Transform rotation Object rotation. -- @field openmw.util#Vector3 startingPosition The object original position -- @field openmw.util#Transform startingRotation The object original rotation --- @field #string ownerRecordId NPC who owns the object (nil if missing). Global and self scripts can set the value. --- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value. --- @field #number ownerFactionRank Rank required to be allowed to pick up the object (`nil` if any rank is allowed). Global and self scripts can set the value. +-- @field #ObjectOwner owner Ownership information -- @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 #GameObject parentContainer Container or actor that contains (or has in inventory) this object. It is nil if the object is in a cell. -- @field #any type Type of the object (one of the tables from the package @{openmw.types#types}). @@ -174,6 +172,14 @@ -- @field #string recordId Returns record ID of the object in lowercase. -- @field #string globalVariable Global Variable associated with this object(read only). + +--- +-- Object owner information +-- @type ObjectOwner +-- @field #string recordId NPC who owns the object (nil if missing). Global and self scripts can set the value. +-- @field #string factionId Faction who owns the object (nil if missing). Global and self scripts can set the value. +-- @field #number factionRank Rank required to be allowed to pick up the object (`nil` if any rank is allowed). Global and self scripts can set the value. + --- -- Does the object still exist and is available. -- Returns true if the object exists and loaded, and false otherwise. If false, then every From a8c219f07cf7bd5e13a671738557288270cabfe3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 6 Feb 2024 14:18:08 +0400 Subject: [PATCH 0943/2167] Move action argument --- apps/openmw/mwlua/uibindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index b5bd30fbe6..f21fdb337a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -241,7 +241,7 @@ namespace MWLua for (unsigned i = 0; i < newStack.size(); ++i) newStack[i] = nameToMode.at(LuaUtil::cast(modes[i + 1])); luaManager->addAction( - [windowManager, newStack = std::move(newStack), arg]() { + [windowManager, newStack = std::move(newStack), arg = std::move(arg)]() { MWWorld::Ptr ptr; if (arg.has_value()) ptr = arg->ptr(); From 04985399f4a4f9f5eadd352f9d497a81501daf9e Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 6 Feb 2024 11:32:42 +0100 Subject: [PATCH 0944/2167] Lua API revision 53 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 313cc46cbe..d62cda4f2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 52) +set(OPENMW_LUA_API_REVISION 53) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") From e7ef49e6dfbee60c5e8d5c9949e4ac8a440e1caf Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 6 Feb 2024 13:40:32 +0100 Subject: [PATCH 0945/2167] Deep update and destroy ui helpers --- files/data/openmw_aux/ui.lua | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/files/data/openmw_aux/ui.lua b/files/data/openmw_aux/ui.lua index 44e1029b4b..0abd3f2f6d 100644 --- a/files/data/openmw_aux/ui.lua +++ b/files/data/openmw_aux/ui.lua @@ -33,4 +33,47 @@ function aux_ui.deepLayoutCopy(layout) return result end +local function isUiElement(v) + return v.__type and v.__type.name == 'LuaUi::Element' +end + +local function deepElementCallback(layout, callback) + if not layout.content then return end + for i = 1, #layout.content do + local child = layout.content[i] + if isUiElement(child) then + callback(child) + deepElementCallback(child.layout, callback) + else + deepElementCallback(child, callback) + end + end +end + +--- +-- Recursively updates all elements in the passed layout or element +-- @function [parent=#ui] deepUpdate +-- @param #any elementOrLayout @{openmw.ui#Layout} or @{openmw.ui#Element} +function aux_ui.deepUpdate(elementOrLayout) + local layout = elementOrLayout + if elementOrLayout.update then + elementOrLayout:update() + layout = elementOrLayout.layout + end + deepElementCallback(layout, function (e) e:update() end) +end + +--- +-- Recursively destroys all elements in the passed layout or element +-- @function [parent=#ui] deepDestroy +-- @param #any elementOrLayout @{openmw.ui#Layout} or @{openmw.ui#Element} +function aux_ui.deepDestroy(elementOrLayout) + local layout = elementOrLayout + if elementOrLayout.destroy then + elementOrLayout:destroy() + layout = elementOrLayout.layout + end + deepElementCallback(layout, function (e) e:destroy() end) +end + return aux_ui From 88ce12ecd8e00f588b53de79ce897ec61b9dcac9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 6 Feb 2024 17:03:29 +0100 Subject: [PATCH 0946/2167] Copy all the luadoc files into resources/lua_api directory --- files/lua_api/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 06c90e4633..0b960ea259 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -10,18 +10,23 @@ set(LUA_API_FILES string.doclua table.doclua openmw/ambient.lua + openmw/animation.lua openmw/async.lua + openmw/camera.lua openmw/core.lua openmw/debug.lua + openmw/input.lua + openmw/interfaces.lua + openmw/menu.lua openmw/nearby.lua openmw/postprocessing.lua openmw/self.lua + openmw/storage.lua openmw/types.lua openmw/ui.lua openmw/util.lua openmw/vfs.lua openmw/world.lua - openmw/menu.lua ) foreach (f ${LUA_API_FILES}) From b1773d7e9fc81e46d698c04be0299def9df65700 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 6 Feb 2024 11:50:50 +0100 Subject: [PATCH 0947/2167] Use nil instead of -1 for default enchantment charge --- apps/openmw/mwlua/types/item.cpp | 15 +++++++++++---- files/lua_api/openmw/types.lua | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 648229a5e5..d1f80e44f4 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,5 +1,6 @@ #include +#include "../../mwmechanics/spellutil.hpp" #include "../../mwworld/class.hpp" #include "../itemdata.hpp" @@ -10,10 +11,16 @@ namespace MWLua { void addItemBindings(sol::table item, const Context& context) { - item["getEnchantmentCharge"] - = [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); }; - item["setEnchantmentCharge"] - = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; + item["getEnchantmentCharge"] = [](const Object& object) -> sol::optional { + float charge = object.ptr().getCellRef().getEnchantmentCharge(); + if (charge == -1) + return sol::nullopt; + else + return charge; + }; + item["setEnchantmentCharge"] = [](const GObject& object, sol::optional charge) { + object.ptr().getCellRef().setEnchantmentCharge(charge.value_or(-1)); + }; item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 4eb8459a6b..44dd578e41 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -652,7 +652,7 @@ -- Get this item's current enchantment charge. -- @function [parent=#Item] getEnchantmentCharge -- @param openmw.core#GameObject item --- @return #number The charge remaining. -1 if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of -1. +-- @return #number The charge remaining. `nil` if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of `nil`. --- -- Checks if the item restocks. @@ -665,7 +665,7 @@ -- Set this item's enchantment charge. -- @function [parent=#Item] setEnchantmentCharge -- @param openmw.core#GameObject item --- @param #number charge +-- @param #number charge Can be `nil` to reset the unused state / full --- -- Whether the object is supposed to be carriable. It is true for all items except From a7da604332b928d0dda54c7a17a59d68d633e171 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 7 Feb 2024 11:14:31 +0100 Subject: [PATCH 0948/2167] Update next tile_id when there is a duplicate in navmeshdb Disable writes on failure to update next tile_id to avoid further errors. --- .../detournavigator/asyncnavmeshupdater.cpp | 37 +++++++++++++++++++ .../detournavigator/asyncnavmeshupdater.cpp | 17 +++++++++ 2 files changed, 54 insertions(+) diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index 9d8a5d85de..bc1288f5f6 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -299,4 +299,41 @@ namespace << " present=" << (present.find(tilePosition) != present.end()); } } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, next_tile_id_should_be_updated_on_duplicate) + { + mRecastMeshManager.setWorldspace(mWorldspace, nullptr); + addHeightFieldPlane(mRecastMeshManager); + addObject(mBox, mRecastMeshManager); + auto db = std::make_unique(":memory:", std::numeric_limits::max()); + NavMeshDb* const dbPtr = db.get(); + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, std::move(db)); + + const TileId nextTileId(dbPtr->getMaxTileId() + 1); + ASSERT_EQ(dbPtr->insertTile(nextTileId, "worldspace", TilePosition{}, TileVersion{ 1 }, {}, {}), 1); + + const auto navMeshCacheItem = std::make_shared(1, mSettings); + const TilePosition tilePosition{ 0, 0 }; + const std::map changedTiles{ { tilePosition, ChangeType::add } }; + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(WaitConditionType::allJobsDone, &mListener); + + const AgentBounds agentBounds{ CollisionShapeType::Cylinder, { 29, 29, 66 } }; + updater.post(agentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + updater.wait(WaitConditionType::allJobsDone, &mListener); + + updater.stop(); + + const auto recastMesh = mRecastMeshManager.getMesh(mWorldspace, tilePosition); + ASSERT_NE(recastMesh, nullptr); + ShapeId nextShapeId{ 1 }; + const std::vector objects = makeDbRefGeometryObjects(recastMesh->getMeshSources(), + [&](const MeshSource& v) { return resolveMeshSource(*dbPtr, v, nextShapeId); }); + const auto tile = dbPtr->findTile( + mWorldspace, tilePosition, serialize(mSettings.mRecast, agentBounds, *recastMesh, objects)); + ASSERT_TRUE(tile.has_value()); + EXPECT_EQ(tile->mTileId, 2); + EXPECT_EQ(tile->mVersion, navMeshFormatVersion); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index ec6313d6f1..03f6062788 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -814,6 +814,23 @@ namespace DetourNavigator Log(Debug::Warning) << "Writes to navmeshdb are disabled to avoid concurrent writes from multiple processes"; } + else if (message.find("UNIQUE constraint failed: tiles.tile_id") != std::string_view::npos) + { + Log(Debug::Warning) << "Found duplicate navmeshdb tile_id, please report the " + "issue to https://gitlab.com/OpenMW/openmw/-/issues, attach openmw.log: " + << mNextTileId; + try + { + mNextTileId = TileId(mDb->getMaxTileId() + 1); + Log(Debug::Info) << "Updated navmeshdb tile_id to: " << mNextTileId; + } + catch (const std::exception& e) + { + mWriteToDb = false; + Log(Debug::Warning) + << "Failed to update next tile_id, writes to navmeshdb are disabled: " << e.what(); + } + } } } }; From f7aa9f8d945a73a3a198a8636d13fa3e4a407d7b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 7 Feb 2024 18:08:06 +0100 Subject: [PATCH 0949/2167] Expose birth signs to Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/birthsignbindings.cpp | 55 +++++++++++++++++++++++++ apps/openmw/mwlua/birthsignbindings.hpp | 13 ++++++ apps/openmw/mwlua/types/player.cpp | 3 ++ files/lua_api/openmw/types.lua | 22 ++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwlua/birthsignbindings.cpp create mode 100644 apps/openmw/mwlua/birthsignbindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cc0cba1a1a..f92a601b82 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings + classbindings itemdata inputprocessor animationbindings birthsignbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp new file mode 100644 index 0000000000..5592179fee --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "birthsignbindings.hpp" +#include "luamanagerimp.hpp" +#include "types/types.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initCoreBirthSignBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table birthSigns(context.mLua->sol(), sol::create); + addRecordFunctionBinding(birthSigns, context); + + auto signT = lua.new_usertype("ESM3_BirthSign"); + signT[sol::meta_function::to_string] = [](const ESM::BirthSign& rec) -> std::string { + return "ESM3_BirthSign[" + rec.mId.toDebugString() + "]"; + }; + signT["id"] = sol::readonly_property([](const ESM::BirthSign& rec) { return rec.mId.serializeText(); }); + signT["name"] = sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mName; }); + signT["description"] + = sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mDescription; }); + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { + return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); + }); + signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mPowers.mList.size(); ++i) + res[i + 1] = rec.mPowers.mList[i].serializeText(); + return res; + }); + + return LuaUtil::makeReadOnly(birthSigns); + } +} diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp new file mode 100644 index 0000000000..7c88b8cccb --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_BIRTHSIGNBINDINGS_H +#define MWLUA_BIRTHSIGNBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreBirthSignBindings(const Context& context); +} + +#endif // MWLUA_BIRTHSIGNBINDINGS_H diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index d2a9c5d920..462bfa888e 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,5 +1,6 @@ #include "types.hpp" +#include "../birthsignbindings.hpp" #include "../luamanagerimp.hpp" #include "apps/openmw/mwbase/inputmanager.hpp" @@ -170,5 +171,7 @@ namespace MWLua player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; + + player["birthSigns"] = initCoreBirthSignBindings(context); } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 4eb8459a6b..a14c44ebdb 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1045,6 +1045,28 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- @{#BirthSigns}: Birth Sign Data +-- @field [parent=#Player] #BirthSigns birthSigns + +--- +-- A read-only list of all @{#BirthSignRecord}s in the world database. +-- @field [parent=#BirthSigns] #list<#BirthSignRecord> records + +--- +-- Returns a read-only @{#BirthSignRecord} +-- @function [parent=#BirthSigns] record +-- @param #string recordId +-- @return #BirthSignRecord + +--- +-- Birth sign data record +-- @type BirthSignRecord +-- @field #string id Birth sign id +-- @field #string name Birth sign name +-- @field #string description Birth sign description +-- @field #string texture Birth sign texture +-- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. + --- -- Send an event to menu scripts. -- @function [parent=#core] sendMenuEvent From 2463633c99fe5044e0b87d7e890619f43db3a355 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 7 Feb 2024 18:10:36 +0100 Subject: [PATCH 0950/2167] Put camera after playercontrols in load order not to override move360 controls --- files/data/builtin.omwscripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 2ec3e2c7ef..f0f988c749 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -14,9 +14,9 @@ GLOBAL: scripts/omw/worldeventhandlers.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua PLAYER: scripts/omw/mechanics/playercontroller.lua MENU: scripts/omw/camera/settings.lua -PLAYER: scripts/omw/camera/camera.lua MENU: scripts/omw/input/settings.lua PLAYER: scripts/omw/input/playercontrols.lua +PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua NPC,CREATURE: scripts/omw/ai.lua From f114d409c8ba855f6c01cbb4850c1f6922ab5a7a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 7 Feb 2024 19:35:26 +0100 Subject: [PATCH 0951/2167] Add get and set birth sign --- apps/openmw/mwlua/types/player.cpp | 28 ++++++++++++++++++++++++++++ files/lua_api/openmw/types.lua | 11 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 462bfa888e..c12f40e832 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -8,7 +8,11 @@ #include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/npcstats.hpp" #include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/globals.hpp" +#include "apps/openmw/mwworld/player.hpp" + +#include namespace MWLua { @@ -35,6 +39,20 @@ namespace sol }; } +namespace +{ + ESM::RefId toBirthSignId(const sol::object& recordOrId) + { + if (recordOrId.is()) + return recordOrId.as()->mId; + std::string_view textId = LuaUtil::cast(recordOrId); + ESM::RefId id = ESM::RefId::deserializeText(textId); + if (!MWBase::Environment::get().getESMStore()->get().search(id)) + throw std::runtime_error("Failed to find birth sign: " + std::string(textId)); + return id; + } +} + namespace MWLua { static void verifyPlayer(const Object& player) @@ -173,5 +191,15 @@ namespace MWLua }; player["birthSigns"] = initCoreBirthSignBindings(context); + player["getBirthSign"] = [](const Object& player) -> std::string { + verifyPlayer(player); + return MWBase::Environment::get().getWorld()->getPlayer().getBirthSign().serializeText(); + }; + player["setBirthSign"] = [](const Object& player, const sol::object& recordOrId) { + verifyPlayer(player); + if (!dynamic_cast(&player)) + throw std::runtime_error("Only global scripts can change birth signs"); + MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(toBirthSignId(recordOrId)); + }; } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a14c44ebdb..edd2c1170f 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1045,6 +1045,17 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- +-- @function [parent=#Player] getBirthSign +-- @param openmw.core#GameObject player +-- @return #string The player's birth sign + +--- +-- Can be used only in global scripts. Note that this does not update the player's spells. +-- @function [parent=#Player] setBirthSign +-- @param openmw.core#GameObject player +-- @param #any recordOrId Record or string ID of the birth sign to assign + --- @{#BirthSigns}: Birth Sign Data -- @field [parent=#Player] #BirthSigns birthSigns From 506824cb9d7e4c9ca64c1bc1c2a0d6627e29e3f3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 2 Feb 2024 22:16:49 +0100 Subject: [PATCH 0952/2167] Cleanup physics callbacks * Do not copy with allocations. * Remove unused DeepestNotMeContactTestResultCallback. * Avoid using pointers which should not be nullptr. * Move constructors implementation to headers. * Move types defined in .cpp are to unnamed namespace. * Comment unused arguments. * Avoid C-style casts. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actorconvexcallback.cpp | 43 ++++----- apps/openmw/mwphysics/actorconvexcallback.hpp | 11 ++- .../closestnotmerayresultcallback.cpp | 9 -- .../closestnotmerayresultcallback.hpp | 13 ++- .../mwphysics/contacttestresultcallback.cpp | 7 +- .../mwphysics/contacttestresultcallback.hpp | 10 +- .../deepestnotmecontacttestresultcallback.cpp | 47 ---------- .../deepestnotmecontacttestresultcallback.hpp | 34 ------- apps/openmw/mwphysics/movementsolver.cpp | 93 ++++++++++--------- apps/openmw/mwphysics/physicssystem.cpp | 1 - .../mwphysics/projectileconvexcallback.cpp | 18 +--- .../mwphysics/projectileconvexcallback.hpp | 12 ++- 13 files changed, 108 insertions(+), 192 deletions(-) delete mode 100644 apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp delete mode 100644 apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 566aedfff0..53989c1797 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -84,7 +84,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback - contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile + contacttestresultcallback stepper movementsolver projectile actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index db077beb31..72bb0eff46 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -9,35 +9,28 @@ namespace MWPhysics { - class ActorOverlapTester : public btCollisionWorld::ContactResultCallback + namespace { - public: - bool overlapping = false; - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override + struct ActorOverlapTester : public btCollisionWorld::ContactResultCallback { - if (cp.getDistance() <= 0.0f) - overlapping = true; - return btScalar(1); - } - }; + bool mOverlapping = false; - ActorConvexCallback::ActorConvexCallback( - const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, const btCollisionWorld* world) - : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - , mMe(me) - , mMotion(motion) - , mMinCollisionDot(minCollisionDot) - , mWorld(world) - { + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* /*colObj0Wrap*/, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* /*colObj1Wrap*/, int /*partId1*/, + int /*index1*/) override + { + if (cp.getDistance() <= 0.0f) + mOverlapping = true; + return 1; + } + }; } btScalar ActorConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) - return btScalar(1); + return 1; // override data for actor-actor collisions // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter @@ -52,7 +45,7 @@ namespace MWPhysics const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); - if (isOverlapping.overlapping) + if (isOverlapping.mOverlapping) { auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); @@ -73,7 +66,7 @@ namespace MWPhysics } else { - return btScalar(1); + return 1; } } } @@ -82,10 +75,10 @@ namespace MWPhysics { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) - return btScalar(1); + return 1; if (projectileHolder->isValidTarget(mMe)) projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); + return 1; } btVector3 hitNormalWorld; @@ -101,7 +94,7 @@ namespace MWPhysics // dot product of the motion vector against the collision contact normal btScalar dotCollision = mMotion.dot(hitNormalWorld); if (dotCollision <= mMinCollisionDot) - return btScalar(1); + return 1; return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); } diff --git a/apps/openmw/mwphysics/actorconvexcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp index 4b9ab1a8a4..8442097a09 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -10,8 +10,15 @@ namespace MWPhysics class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, - const btCollisionWorld* world); + explicit ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, + const btCollisionWorld* world) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , mMe(me) + , mMotion(motion) + , mMinCollisionDot(minCollisionDot) + , mWorld(world) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 875704d790..02c94a6f9d 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,7 +1,6 @@ #include "closestnotmerayresultcallback.hpp" #include -#include #include @@ -9,14 +8,6 @@ namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, - const std::vector& targets, const btVector3& from, const btVector3& to) - : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) - , mTargets(targets) - { - } - btScalar ClosestNotMeRayResultCallback::addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 37bda3bd52..f7f567ed4e 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H #define OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H -#include +#include #include @@ -14,14 +14,19 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, - const btVector3& from, const btVector3& to); + explicit ClosestNotMeRayResultCallback(const btCollisionObject* me, std::span targets, + const btVector3& from, const btVector3& to) + : btCollisionWorld::ClosestRayResultCallback(from, to) + , mMe(me) + , mTargets(targets) + { + } btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; - const std::vector mTargets; + const std::span mTargets; }; } diff --git a/apps/openmw/mwphysics/contacttestresultcallback.cpp b/apps/openmw/mwphysics/contacttestresultcallback.cpp index dae0a65af0..45d1127de4 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.cpp @@ -8,13 +8,8 @@ namespace MWPhysics { - ContactTestResultCallback::ContactTestResultCallback(const btCollisionObject* testedAgainst) - : mTestedAgainst(testedAgainst) - { - } - btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, - int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* col1Wrap, int /*partId1*/, int /*index1*/) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) diff --git a/apps/openmw/mwphysics/contacttestresultcallback.hpp b/apps/openmw/mwphysics/contacttestresultcallback.hpp index ae900e0208..a9ba06368c 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.hpp @@ -14,15 +14,19 @@ namespace MWPhysics { class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback { - const btCollisionObject* mTestedAgainst; - public: - ContactTestResultCallback(const btCollisionObject* testedAgainst); + explicit ContactTestResultCallback(const btCollisionObject* testedAgainst) + : mTestedAgainst(testedAgainst) + { + } btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; std::vector mResult; + + private: + const btCollisionObject* mTestedAgainst; }; } diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp deleted file mode 100644 index 766ca79796..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "deepestnotmecontacttestresultcallback.hpp" - -#include - -#include - -#include "collisiontype.hpp" - -namespace MWPhysics -{ - - DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin) - : mMe(me) - , mTargets(targets) - , mOrigin(origin) - , mLeastDistSqr(std::numeric_limits::max()) - { - } - - btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, - int partId1, int index1) - { - const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; - if (collisionObject != mMe) - { - if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor - && !mTargets.empty()) - { - if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) - return 0.f; - } - - btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if (!mObject || distsqr < mLeastDistSqr) - { - mObject = collisionObject; - mLeastDistSqr = distsqr; - mContactPoint = cp.getPositionWorldOnA(); - mContactNormal = cp.m_normalWorldOnB; - } - } - - return 0.f; - } -} diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp deleted file mode 100644 index d22a79e643..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H - -#include - -#include - -class btCollisionObject; - -namespace MWPhysics -{ - class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback - { - const btCollisionObject* mMe; - const std::vector mTargets; - - // Store the real origin, since the shape's origin is its center - btVector3 mOrigin; - - public: - const btCollisionObject* mObject{ nullptr }; - btVector3 mContactPoint{ 0, 0, 0 }; - btVector3 mContactNormal{ 0, 0, 0 }; - btScalar mLeastDistSqr; - - DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin); - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, - const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; - }; -} - -#endif diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fe5c1a955e..05b9f44654 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -32,53 +32,58 @@ namespace MWPhysics return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } - class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback + namespace { - public: - ContactCollectionCallback(const btCollisionObject* me, osg::Vec3f velocity) - : mMe(me) + class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { - m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; - mVelocity = Misc::Convert::toBullet(velocity); - } - btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override - { - if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) - return 0.0; - // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, - // that would break detection when not moving) - if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) - return 0.0; - auto delta = contact.m_normalWorldOnB * -contact.m_distance1; - mContactSum += delta; - mMaxX = std::max(std::abs(delta.x()), mMaxX); - mMaxY = std::max(std::abs(delta.y()), mMaxY); - mMaxZ = std::max(std::abs(delta.z()), mMaxZ); - if (contact.m_distance1 < mDistance) + public: + explicit ContactCollectionCallback(const btCollisionObject& me, const osg::Vec3f& velocity) + : mVelocity(Misc::Convert::toBullet(velocity)) { - mDistance = contact.m_distance1; - mNormal = contact.m_normalWorldOnB; - mDelta = delta; - return mDistance; + m_collisionFilterGroup = me.getBroadphaseHandle()->m_collisionFilterGroup; + m_collisionFilterMask = me.getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; } - else + + btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* colObj1Wrap, int /*partId1*/, + int /*index1*/) override { - return 0.0; + if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) + return 0.0; + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to + // >=, that would break detection when not moving) + if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) + return 0.0; + auto delta = contact.m_normalWorldOnB * -contact.m_distance1; + mContactSum += delta; + mMaxX = std::max(std::abs(delta.x()), mMaxX); + mMaxY = std::max(std::abs(delta.y()), mMaxY); + mMaxZ = std::max(std::abs(delta.z()), mMaxZ); + if (contact.m_distance1 < mDistance) + { + mDistance = contact.m_distance1; + mNormal = contact.m_normalWorldOnB; + mDelta = delta; + return mDistance; + } + else + { + return 0.0; + } } - } - btScalar mMaxX = 0.0; - btScalar mMaxY = 0.0; - btScalar mMaxZ = 0.0; - btVector3 mContactSum{ 0.0, 0.0, 0.0 }; - btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" - btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" - btScalar mDistance = 0.0; // negative or zero - protected: - btVector3 mVelocity; - const btCollisionObject* mMe; - }; + + btScalar mMaxX = 0.0; + btScalar mMaxY = 0.0; + btScalar mMaxZ = 0.0; + btVector3 mContactSum{ 0.0, 0.0, 0.0 }; + btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" + btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" + btScalar mDistance = 0.0; // negative or zero + + protected: + btVector3 mVelocity; + }; + } osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) @@ -454,8 +459,10 @@ namespace MWPhysics if (btFrom == btTo) return; + assert(projectile.mProjectile != nullptr); + ProjectileConvexCallback resultCallback( - projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); + projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, *projectile.mProjectile); resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; @@ -524,7 +531,7 @@ namespace MWPhysics newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{ actor.mCollisionObject, velocity }; + ContactCollectionCallback callback(*actor.mCollisionObject, velocity); ContactTestWrapper::contactTest( const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 149113dfb1..f84f503bb5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -49,7 +49,6 @@ #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" -#include "deepestnotmecontacttestresultcallback.hpp" #include "hasspherecollisioncallback.hpp" #include "heightfield.hpp" #include "movementsolver.hpp" diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index d7e80b4698..913a3edb0c 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -6,16 +6,6 @@ namespace MWPhysics { - ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, - const btVector3& from, const btVector3& to, Projectile* proj) - : btCollisionWorld::ClosestConvexResultCallback(from, to) - , mCaster(caster) - , mMe(me) - , mProjectile(proj) - { - assert(mProjectile); - } - btScalar ProjectileConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { @@ -33,25 +23,25 @@ namespace MWPhysics { case CollisionType_Actor: { - if (!mProjectile->isValidTarget(hitObject)) + if (!mProjectile.isValidTarget(hitObject)) return 1.f; break; } case CollisionType_Projectile: { auto* target = static_cast(hitObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) + if (!mProjectile.isValidTarget(target->getCasterCollisionObject())) return 1.f; target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: { - mProjectile->setHitWater(); + mProjectile.setHitWater(); break; } } - mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); + mProjectile.hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 3cd304bab0..d75ace22af 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,15 +12,21 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, - const btVector3& to, Projectile* proj); + explicit ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, + const btVector3& from, const btVector3& to, Projectile& projectile) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mCaster(caster) + , mMe(me) + , mProjectile(projectile) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: const btCollisionObject* mCaster; const btCollisionObject* mMe; - Projectile* mProjectile; + Projectile& mProjectile; }; } From c5564323e4431b9632f13847350d398d42644607 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 28 Jan 2024 20:57:46 +0300 Subject: [PATCH 0953/2167] Correct activation behavior for actors in combat (#7794) Stop battle music upon death animation end --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 9 +++----- apps/openmw/mwclass/npc.cpp | 34 ++++++++++++++++++------------ apps/openmw/mwmechanics/actors.cpp | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faea4d0b6c..07d0514f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,6 +138,7 @@ Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells + Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d19e5d5c43..2de58c6127 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -474,20 +474,17 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); - const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); - - const bool isInCombat = aiSequence.isInCombat(); if (stats.isDead()) { - // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) + else if (!stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b8cd4cd23d..ce1df10db3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -924,36 +924,43 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isPursuing = aiSequence.isInPursuit() && actor == MWMechanics::getPlayer(); + const bool inCombatWithActor = aiSequence.isInCombat(actor) || isPursuing; if (stats.isDead()) { - // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat()) + else { - if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) - return std::make_unique(ptr); // stealing + const bool allowStealingFromKO + = Settings::game().mAlwaysAllowStealingFromKnockedOutActors || !inCombatWithActor; + if (stats.getKnockedDown() && allowStealingFromKO) + return std::make_unique(ptr); - // Can't talk to werewolves - if (!getNpcStats(ptr).isWerewolf()) + const bool allowStealingWhileSneaking = !inCombatWithActor; + if (MWBase::Environment::get().getMechanicsManager()->isSneaking(actor) && allowStealingWhileSneaking) + return std::make_unique(ptr); + + const bool allowTalking = !inCombatWithActor && !getNpcStats(ptr).isWerewolf(); + if (allowTalking) return std::make_unique(ptr); } - else // In combat - { - if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && stats.getKnockedDown()) - return std::make_unique(ptr); // stealing - } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique(ptr); + if (inCombatWithActor) + return std::make_unique("#{sActorInCombat}"); + return std::make_unique(); } @@ -1086,7 +1093,8 @@ namespace MWClass if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) return true; - if (!customData.mNpcStats.getAiSequence().isInCombat()) + const MWMechanics::AiSequence& aiSeq = customData.mNpcStats.getAiSequence(); + if (!aiSeq.isInCombat() || aiSeq.isFleeing()) return true; if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown()) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d463fa729b..bd55652e5d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1309,7 +1309,8 @@ namespace MWMechanics if (inProcessingRange) { MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); - if (!stats.isDead() && stats.getAiSequence().isInCombat()) + bool isDead = stats.isDead() && stats.isDeathAnimationFinished(); + if (!isDead && stats.getAiSequence().isInCombat()) { hasHostiles = true; break; From 6760fa4945cc9589606c704f6507d8c311404e65 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 6 Feb 2024 13:46:05 +0400 Subject: [PATCH 0954/2167] Localize screenshot messages --- apps/openmw/engine.cpp | 23 ++++++++++++++++++----- components/sceneutil/screencapture.cpp | 4 ++-- files/data/l10n/OMWEngine/de.yaml | 4 +++- files/data/l10n/OMWEngine/en.yaml | 4 +++- files/data/l10n/OMWEngine/fr.yaml | 4 +++- files/data/l10n/OMWEngine/ru.yaml | 4 +++- files/data/l10n/OMWEngine/sv.yaml | 4 +++- 7 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index fe950a0920..75687ff281 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -109,10 +110,23 @@ namespace profiler.removeUserStatsLine(" -Async"); } - struct ScheduleNonDialogMessageBox + struct ScreenCaptureMessageBox { - void operator()(std::string message) const + void operator()(std::string filePath) const { + if (filePath.empty()) + { + MWBase::Environment::get().getWindowManager()->scheduleMessageBox( + "#{OMWEngine:ScreenshotFailed}", MWGui::ShowInDialogueMode_Never); + + return; + } + + std::string messageFormat + = MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "ScreenshotMade"); + + std::string message = Misc::StringUtils::format(messageFormat, filePath); + MWBase::Environment::get().getWindowManager()->scheduleMessageBox( std::move(message), MWGui::ShowInDialogueMode_Never); } @@ -717,9 +731,8 @@ void OMW::Engine::prepareEngine() mScreenCaptureOperation = new SceneUtil::AsyncScreenCaptureOperation(mWorkQueue, new SceneUtil::WriteScreenshotToFileOperation(mCfgMgr.getScreenshotPath(), Settings::general().mScreenshotFormat, - Settings::general().mNotifyOnSavedScreenshot - ? std::function(ScheduleNonDialogMessageBox{}) - : std::function(IgnoreString{}))); + Settings::general().mNotifyOnSavedScreenshot ? std::function(ScreenCaptureMessageBox{}) + : std::function(IgnoreString{}))); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); diff --git a/components/sceneutil/screencapture.cpp b/components/sceneutil/screencapture.cpp index 8382e75e54..ed90668cfb 100644 --- a/components/sceneutil/screencapture.cpp +++ b/components/sceneutil/screencapture.cpp @@ -122,10 +122,10 @@ namespace SceneUtil << mScreenshotFormat << "\": " << e.what(); } if (fileName.empty()) - mCallback("Failed to save screenshot"); + mCallback(std::string()); else { - mCallback(Files::pathToUnicodeString(fileName) + " has been saved"); + mCallback(Files::pathToUnicodeString(fileName)); Log(Debug::Info) << mScreenshotPath / fileName << " has been saved"; } } diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 2874001309..aab58fb30c 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -29,9 +29,11 @@ BuildingNavigationMesh: "Baue Navigationsgitter" # This save file was created using an older version of OpenMW in a format that is no longer supported. # Load and save this file using {version} to upgrade it. #NewGameConfirmation: "Do you want to start a new game and lose the current one?" +#QuitGameConfirmation: "Quit the game?" #SaveGameDenied: "The game cannot be saved right now." #SavingInProgress: "Saving..." -#QuitGameConfirmation: "Quit the game?" +#ScreenshotFailed: "Failed to save screenshot" +#ScreenshotMade: "%s has been saved" # Save game menu diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 0455d11e07..f6ad237394 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -26,9 +26,11 @@ LoadingRequiresOldVersionError: |- This save file was created using an older version of OpenMW in a format that is no longer supported. Load and save this file using {version} to upgrade it. NewGameConfirmation: "Do you want to start a new game and lose the current one?" +QuitGameConfirmation: "Quit the game?" SaveGameDenied: "The game cannot be saved right now." SavingInProgress: "Saving..." -QuitGameConfirmation: "Quit the game?" +ScreenshotFailed: "Failed to save screenshot" +ScreenshotMade: "%s has been saved" # Save game menu diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 85bac08612..990ecfce9d 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -26,9 +26,11 @@ LoadingRequiresNewVersionError: |- # This save file was created using an older version of OpenMW in a format that is no longer supported. # Load and save this file using {version} to upgrade it. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." +QuitGameConfirmation: "Quitter la partie ?" SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." -QuitGameConfirmation: "Quitter la partie ?" +#ScreenshotFailed: "Failed to save screenshot" +#ScreenshotMade: "%s has been saved" # Save game menu diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 8d221fe33c..a9f396f73c 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -26,9 +26,11 @@ LoadingRequiresOldVersionError: |- Это сохранение создано старой версией OpenMW и использует формат, который больше не поддерживается. Загрузите и сохраните этот файл в {version}, чтобы обновить его. NewGameConfirmation: "Вы хотите начать новую игру? Текущая игра будет потеряна." +QuitGameConfirmation: "Выйти из игры?" SaveGameDenied: "В данный момент игру нельзя сохранить." SavingInProgress: "Сохранение..." -QuitGameConfirmation: "Выйти из игры?" +ScreenshotFailed: "Не удалось создать снимок экрана" +ScreenshotMade: "Создан снимок экрана %s" # Save game menu diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 134fab0e95..bbc6132f55 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -26,9 +26,11 @@ LoadingRequiresNewVersionError: |- # This save file was created using an older version of OpenMW in a format that is no longer supported. # Load and save this file using {version} to upgrade it. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" +QuitGameConfirmation: "Avsluta spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." -QuitGameConfirmation: "Avsluta spelet?" +#ScreenshotFailed: "Failed to save screenshot" +#ScreenshotMade: "%s has been saved" # Save game menu From 0178c5aaef46086333c025c6cd5db76549c7e8bc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 8 Feb 2024 12:15:57 +0300 Subject: [PATCH 0955/2167] Remove Open action fallback for Tribunal NPC companions --- apps/openmw/mwclass/npc.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ce1df10db3..c43a2c1094 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -954,10 +954,6 @@ namespace MWClass return std::make_unique(ptr); } - // Tribunal and some mod companions oddly enough must use open action as fallback - if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::make_unique(ptr); - if (inCombatWithActor) return std::make_unique("#{sActorInCombat}"); From 1689c59546a862cc5b9ee2276f080bd7a71a7015 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Feb 2024 14:09:50 +0100 Subject: [PATCH 0956/2167] Add tests for VFS::Path::Normalized --- apps/openmw_test_suite/CMakeLists.txt | 2 + apps/openmw_test_suite/vfs/testpathutil.cpp | 99 +++++++++++++++++++++ components/vfs/pathutil.hpp | 22 +++-- 3 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 apps/openmw_test_suite/vfs/testpathutil.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 967511953d..71da2de590 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -97,6 +97,8 @@ file(GLOB UNITTEST_SRC_FILES esmterrain/testgridsampling.cpp resource/testobjectcache.cpp + + vfs/testpathutil.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp new file mode 100644 index 0000000000..811b2b3691 --- /dev/null +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -0,0 +1,99 @@ +#include + +#include + +#include + +namespace VFS::Path +{ + namespace + { + using namespace testing; + + TEST(NormalizedTest, shouldSupportDefaultConstructor) + { + const Normalized value; + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, shouldSupportConstructorFromString) + { + const std::string string("Foo\\Bar/baz"); + const Normalized value(string); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromConstCharPtr) + { + const char* const ptr = "Foo\\Bar/baz"; + const Normalized value(ptr); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromStringView) + { + const std::string_view view = "Foo\\Bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + + TEST(NormalizedTest, supportMovingValueOut) + { + Normalized value("Foo\\Bar/baz"); + EXPECT_EQ(std::move(value).value(), "foo/bar/baz"); + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, isNotEqualToNotNormalized) + { + const Normalized value("Foo\\Bar/baz"); + EXPECT_NE(value.value(), "Foo\\Bar/baz"); + } + + TEST(NormalizedTest, shouldSupportOperatorLeftShiftToOStream) + { + const Normalized value("Foo\\Bar/baz"); + std::stringstream stream; + stream << value; + EXPECT_EQ(stream.str(), "foo/bar/baz"); + } + + template + struct NormalizedOperatorsTest : Test + { + }; + + TYPED_TEST_SUITE_P(NormalizedOperatorsTest); + + TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) + { + const Normalized normalized("a/foo/bar/baz"); + const TypeParam otherEqual{ "a/foo/bar/baz" }; + const TypeParam otherNotEqual{ "b/foo/bar/baz" }; + EXPECT_EQ(normalized, otherEqual); + EXPECT_EQ(otherEqual, normalized); + EXPECT_NE(normalized, otherNotEqual); + EXPECT_NE(otherNotEqual, normalized); + } + + TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) + { + const Normalized normalized("b/foo/bar/baz"); + const TypeParam otherEqual{ "b/foo/bar/baz" }; + const TypeParam otherLess{ "a/foo/bar/baz" }; + const TypeParam otherGreater{ "c/foo/bar/baz" }; + EXPECT_FALSE(normalized < otherEqual); + EXPECT_FALSE(otherEqual < normalized); + EXPECT_LT(otherLess, normalized); + EXPECT_FALSE(normalized < otherLess); + EXPECT_LT(normalized, otherGreater); + EXPECT_FALSE(otherGreater < normalized); + } + + REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); + + using StringTypes = Types; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, StringTypes); + } +} diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 0856bfffa2..6ee33f64d2 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -96,22 +96,26 @@ namespace VFS::Path friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; - template - friend bool operator==(const Normalized& lhs, const T& rhs) + friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend bool operator==(const auto& lhs, const Normalized& rhs) { - return lhs.mValue == rhs; + return lhs == rhs.mValue; + } +#endif + + friend bool operator<(const Normalized& lhs, const Normalized& rhs) + { + return lhs.mValue < rhs.mValue; } - friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; } - - template - friend bool operator<(const Normalized& lhs, const T& rhs) + friend bool operator<(const Normalized& lhs, const auto& rhs) { return lhs.mValue < rhs; } - template - friend bool operator<(const T& lhs, const Normalized& rhs) + friend bool operator<(const auto& lhs, const Normalized& rhs) { return lhs < rhs.mValue; } From 062d3e9c0093c375abae285db2749d140fb1280b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Feb 2024 14:34:06 +0100 Subject: [PATCH 0957/2167] Add NormalizedView for normalized paths --- apps/openmw_test_suite/vfs/testpathutil.cpp | 58 ++++++++++++--- components/vfs/pathutil.hpp | 79 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 811b2b3691..23a4d46d12 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -37,6 +37,13 @@ namespace VFS::Path EXPECT_EQ(value.view(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) + { + const NormalizedView view = "foo/bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + TEST(NormalizedTest, supportMovingValueOut) { Normalized value("Foo\\Bar/baz"); @@ -67,9 +74,11 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) { - const Normalized normalized("a/foo/bar/baz"); - const TypeParam otherEqual{ "a/foo/bar/baz" }; - const TypeParam otherNotEqual{ "b/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "a/foo/bar/baz" }; + const Type1 otherEqual{ "a/foo/bar/baz" }; + const Type1 otherNotEqual{ "b/foo/bar/baz" }; EXPECT_EQ(normalized, otherEqual); EXPECT_EQ(otherEqual, normalized); EXPECT_NE(normalized, otherNotEqual); @@ -78,10 +87,12 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) { - const Normalized normalized("b/foo/bar/baz"); - const TypeParam otherEqual{ "b/foo/bar/baz" }; - const TypeParam otherLess{ "a/foo/bar/baz" }; - const TypeParam otherGreater{ "c/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "b/foo/bar/baz" }; + const Type1 otherEqual{ "b/foo/bar/baz" }; + const Type1 otherLess{ "a/foo/bar/baz" }; + const Type1 otherGreater{ "c/foo/bar/baz" }; EXPECT_FALSE(normalized < otherEqual); EXPECT_FALSE(otherEqual < normalized); EXPECT_LT(otherLess, normalized); @@ -92,8 +103,37 @@ namespace VFS::Path REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); - using StringTypes = Types; + template + struct TypePair + { + using Type0 = T0; + using Type1 = T1; + }; - INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, StringTypes); + using TypePairs = Types, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair>; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs); + + TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized) + { + const Normalized value("Foo\\Bar/baz"); + const NormalizedView view(value); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) + { + constexpr NormalizedView view("foo/bar/baz"); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) + { + EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); + } } } diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 6ee33f64d2..aa7cad8524 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -58,6 +59,59 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + class Normalized; + + class NormalizedView + { + public: + constexpr NormalizedView() noexcept = default; + + constexpr NormalizedView(const char* value) + : mValue(value) + { + if (!isNormalized(mValue)) + throw std::invalid_argument("NormalizedView value is not normalized: \"" + std::string(mValue) + "\""); + } + + NormalizedView(const Normalized& value) noexcept; + + constexpr std::string_view value() const noexcept { return mValue; } + + friend constexpr bool operator==(const NormalizedView& lhs, const NormalizedView& rhs) = default; + + friend constexpr bool operator==(const NormalizedView& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend constexpr bool operator==(const auto& lhs, const NormalizedView& rhs) + { + return lhs == rhs.mValue; + } +#endif + + friend constexpr bool operator<(const NormalizedView& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.mValue; + } + + friend constexpr bool operator<(const NormalizedView& lhs, const auto& rhs) + { + return lhs.mValue < rhs; + } + + friend constexpr bool operator<(const auto& lhs, const NormalizedView& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const NormalizedView& value) + { + return stream << value.mValue; + } + + private: + std::string_view mValue; + }; + class Normalized { public: @@ -84,6 +138,11 @@ namespace VFS::Path normalizeFilenameInPlace(mValue); } + explicit Normalized(NormalizedView value) + : mValue(value.value()) + { + } + const std::string& value() const& { return mValue; } std::string value() && { return std::move(mValue); } @@ -105,6 +164,11 @@ namespace VFS::Path } #endif + friend bool operator==(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue == rhs.value(); + } + friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; @@ -120,6 +184,16 @@ namespace VFS::Path return lhs < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.value(); + } + + friend bool operator<(const NormalizedView& lhs, const Normalized& rhs) + { + return lhs.value() < rhs.mValue; + } + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) { return stream << value.mValue; @@ -128,6 +202,11 @@ namespace VFS::Path private: std::string mValue; }; + + inline NormalizedView::NormalizedView(const Normalized& value) noexcept + : mValue(value.view()) + { + } } #endif From a6657c18cc82a5fe1c03fe4175601fcd8029228c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 18 Jan 2024 00:13:34 +0100 Subject: [PATCH 0958/2167] Use normalized path for file archives indices --- apps/openmw_test_suite/testing_util.hpp | 14 +++++--------- components/vfs/archive.hpp | 4 ++-- components/vfs/bsaarchive.hpp | 11 +++-------- components/vfs/filesystemarchive.cpp | 6 +++--- components/vfs/filesystemarchive.hpp | 4 ++-- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index b819848a8f..ad1b0423ef 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -51,25 +51,21 @@ namespace TestingOpenMW struct VFSTestData : public VFS::Archive { - std::map mFiles; + VFS::FileMap mFiles; - VFSTestData(std::map files) + explicit VFSTestData(VFS::FileMap&& files) : mFiles(std::move(files)) { } - void listResources(VFS::FileMap& out) override - { - for (const auto& [key, value] : mFiles) - out.emplace(key, value); - } + void listResources(VFS::FileMap& out) override { out = mFiles; } - bool contains(std::string_view file) const override { return mFiles.contains(file); } + bool contains(VFS::Path::NormalizedView file) const override { return mFiles.contains(file); } std::string getDescription() const override { return "TestData"; } }; - inline std::unique_ptr createTestVFS(std::map files) + inline std::unique_ptr createTestVFS(VFS::FileMap&& files) { auto vfs = std::make_unique(); vfs->addArchive(std::make_unique(std::move(files))); diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 42b88219d7..bd793b8523 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -2,9 +2,9 @@ #define OPENMW_COMPONENTS_VFS_ARCHIVE_H #include -#include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -17,7 +17,7 @@ namespace VFS virtual void listResources(FileMap& out) = 0; /// True if this archive contains the provided normalized file. - virtual bool contains(std::string_view file) const = 0; + virtual bool contains(Path::NormalizedView file) const = 0; virtual std::string getDescription() const = 0; }; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 304fc438ad..847aeca509 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -52,19 +52,14 @@ namespace VFS void listResources(FileMap& out) override { for (auto& resource : mResources) - { - std::string ent = resource.mInfo->name(); - Path::normalizeFilenameInPlace(ent); - - out[ent] = &resource; - } + out[VFS::Path::Normalized(resource.mInfo->name())] = &resource; } - bool contains(std::string_view file) const override + bool contains(Path::NormalizedView file) const override { for (const auto& it : mResources) { - if (Path::pathEqual(file, it.mInfo->name())) + if (Path::pathEqual(file.value(), it.mInfo->name())) return true; } return false; diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 7d88dd9cc0..c72798e7ea 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -37,9 +37,9 @@ namespace VFS FileSystemArchiveFile file(path); - std::string searchable = Path::normalizeFilename(std::string_view{ proper }.substr(prefix)); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - const auto inserted = mIndex.emplace(searchable, file); + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); if (!inserted.second) Log(Debug::Warning) << "Warning: found duplicate file for '" << proper @@ -56,7 +56,7 @@ namespace VFS } } - bool FileSystemArchive::contains(std::string_view file) const + bool FileSystemArchive::contains(Path::NormalizedView file) const { return mIndex.find(file) != mIndex.end(); } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 00fe5ba971..b158ef3472 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -30,12 +30,12 @@ namespace VFS void listResources(FileMap& out) override; - bool contains(std::string_view file) const override; + bool contains(Path::NormalizedView file) const override; std::string getDescription() const override; private: - std::map> mIndex; + std::map> mIndex; bool mBuiltIndex; std::filesystem::path mPath; }; From 525dee00f11edf8500092ef19dde9f8accb26a38 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 6 Feb 2024 10:58:40 +0400 Subject: [PATCH 0959/2167] Refraction fog based on water depth (feature 5926) --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwrender/water.cpp | 5 ++++- files/shaders/compatibility/water.frag | 22 ++++++++++++++++++++-- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index e2903febe4..8873113da2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -187,6 +187,7 @@ Programmers pkubik PLkolek PlutonicOverkill + Qlonever Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) Randy Davin (Kindi) diff --git a/CHANGELOG.md b/CHANGELOG.md index faea4d0b6c..2599db623d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics + Feature #5926: Refraction based on water depth Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index d5fb01242f..9fdb0583a2 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -114,7 +114,10 @@ namespace MWRender } // move the plane back along its normal a little bit to prevent bleeding at the water shore - const float clipFudge = -5; + float fov = Settings::camera().mFieldOfView; + const float clipFudgeMin = 2.5; // minimum offset of clip plane + const float clipFudgeScale = -15000.0; + float clipFudge = abs(abs((*mCullPlane)[3]) - eyePoint.z()) * fov / clipFudgeScale - clipFudgeMin; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 5817b0c5ae..c971f92b99 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -17,6 +17,8 @@ // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- const float VISIBILITY = 2500.0; +const float VISIBILITY_DEPTH = VISIBILITY * 1.5; +const float DEPTH_FADE = 0.15; const float BIG_WAVES_X = 0.1; // strength of big waves const float BIG_WAVES_Y = 0.1; @@ -48,6 +50,7 @@ const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun s const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) +const float REFR_FOG_DISTORT_DISTANCE = 3000.0; // at what distance refraction fog will be calculated using real water depth instead of distorted depth (prevents splotchy shores) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; @@ -160,9 +163,10 @@ void main(void) vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far) * radialise; - float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords-screenCoordsOffset), near, far) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum + float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif // reflection @@ -185,6 +189,16 @@ void main(void) // no alpha here, so make sure raindrop ripple specularity gets properly subdued rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); + // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection + if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) + screenCoordsOffset = vec2(0.0); + + depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); + + // fade to realWaterDepth at a distance to compensate for physically inaccurate depth calculation + waterDepthDistorted = mix(waterDepthDistorted, realWaterDepth, min(surfaceDepth / REFR_FOG_DISTORT_DISTANCE, 1.0)); + // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; vec3 rawRefraction = refraction; @@ -193,7 +207,11 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + { + float depthCorrection = sqrt(1.0 + 4.0 * DEPTH_FADE * DEPTH_FADE); + float factor = DEPTH_FADE * DEPTH_FADE / (-0.5 * depthCorrection + 0.5 - waterDepthDistorted / VISIBILITY) + 0.5 * depthCorrection + 0.5; + refraction = mix(refraction, waterColor, clamp(factor, 0.0, 1.0)); + } // sunlight scattering // normal for sunlight scattering From 7586acc18bf2dbd05b65165c7f9b5118d25fb032 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 8 Feb 2024 16:53:48 +0100 Subject: [PATCH 0960/2167] Remove Core from functions that aren't in openmw.core --- apps/openmw/mwlua/birthsignbindings.cpp | 2 +- apps/openmw/mwlua/birthsignbindings.hpp | 2 +- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/classbindings.hpp | 2 +- apps/openmw/mwlua/types/npc.cpp | 2 +- apps/openmw/mwlua/types/player.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 5592179fee..e569bc1b8f 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -25,7 +25,7 @@ namespace sol namespace MWLua { - sol::table initCoreBirthSignBindings(const Context& context) + sol::table initBirthSignRecordBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); sol::table birthSigns(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp index 7c88b8cccb..bf41707d47 100644 --- a/apps/openmw/mwlua/birthsignbindings.hpp +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - sol::table initCoreBirthSignBindings(const Context& context); + sol::table initBirthSignRecordBindings(const Context& context); } #endif // MWLUA_BIRTHSIGNBINDINGS_H diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 339b724f19..ea1ea8e7ef 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -25,7 +25,7 @@ namespace sol namespace MWLua { - sol::table initCoreClassBindings(const Context& context) + sol::table initClassRecordBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); sol::table classes(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index 9dd9befae4..1acb0a9ad3 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - sol::table initCoreClassBindings(const Context& context); + sol::table initClassRecordBindings(const Context& context); } #endif // MWLUA_CLASSBINDINGS_H diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index b1ac3d994a..d7d459bb81 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -85,7 +85,7 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - npc["classes"] = initCoreClassBindings(context); + npc["classes"] = initClassRecordBindings(context); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index c12f40e832..130d3ded21 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -190,7 +190,7 @@ namespace MWLua return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; - player["birthSigns"] = initCoreBirthSignBindings(context); + player["birthSigns"] = initBirthSignRecordBindings(context); player["getBirthSign"] = [](const Object& player) -> std::string { verifyPlayer(player); return MWBase::Environment::get().getWorld()->getPlayer().getBirthSign().serializeText(); From 86666761a3c037b399ae1b6e522863f444184d06 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 8 Feb 2024 21:51:54 -0600 Subject: [PATCH 0961/2167] Requested changes --- apps/openmw/mwlua/mwscriptbindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index ded00dc2b2..c04339f28a 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -53,7 +53,7 @@ namespace sol namespace MWLua { - auto getGlobalVariableValue(const std::string_view globalId) -> float + float getGlobalVariableValue(const std::string_view globalId) { char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); if (varType == 'f') @@ -147,7 +147,7 @@ namespace MWLua return getGlobalVariableValue(globalId); }, [](const GlobalStore& store, int index) -> sol::optional { - if (index < 1 || index >= store.getSize()) + if (index < 1 || store.getSize() < index) return sol::nullopt; auto g = store.at(index - 1); if (g == nullptr) @@ -170,7 +170,7 @@ namespace MWLua if (index >= store.getSize()) return sol::nullopt; - const auto& global = store.at(index++); + const ESM::Global* global = store.at(index++); if (!global) return sol::nullopt; From 38ab09a52eb8d80c0da0729a916c257e60df6346 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 7 Feb 2024 07:06:24 +0300 Subject: [PATCH 0962/2167] Try to uncursify DebugDrawer scene representation --- apps/openmw/mwrender/renderingmanager.cpp | 5 +- apps/openmw/mwrender/renderingmanager.hpp | 2 +- components/debug/debugdraw.cpp | 79 +++++++++++------------ components/debug/debugdraw.hpp | 18 ++++-- components/sceneutil/serialize.cpp | 25 +++---- 5 files changed, 67 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15faabb6df..978fe47f44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -432,8 +432,9 @@ namespace MWRender mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::cells().mTargetFramerate); } - mDebugDraw - = std::make_unique(mResourceSystem->getSceneManager()->getShaderManager(), mRootNode); + mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); + mRootNode->addChild(mDebugDraw); + mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mEffectManager = std::make_unique(sceneRoot, mResourceSystem); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 22ef987c01..8f85ce6a3f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -335,7 +335,7 @@ namespace MWRender osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; - std::unique_ptr mDebugDraw; + osg::ref_ptr mDebugDraw; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 32f71580a8..b83829facd 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -215,12 +215,12 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in geom.addPrimitiveSet(indices); } -static int getIdexBufferReadFromFrame(const long long int& nFrame) +static int getIndexBufferReadFromFrame(const unsigned int& nFrame) { return nFrame % 2; } -static int getIdexBufferWriteFromFrame(const long long int& nFrame) +static int getIndexBufferWriteFromFrame(const unsigned int& nFrame) { return (nFrame + 1) % 2; } @@ -248,6 +248,16 @@ namespace Debug makeLineInstance(*mLinesToDraw); } + DebugCustomDraw::DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop) + : Drawable(copy, copyop) + , mShapesToDraw(copy.mShapesToDraw) + , mLinesToDraw(copy.mLinesToDraw) + , mCubeGeometry(copy.mCubeGeometry) + , mCylinderGeometry(copy.mCylinderGeometry) + , mWireCubeGeometry(copy.mWireCubeGeometry) + { + } + void DebugCustomDraw::drawImplementation(osg::RenderInfo& renderInfo) const { auto state = renderInfo.getState(); @@ -308,43 +318,23 @@ namespace Debug static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); } - - class DebugDrawCallback : public SceneUtil::NodeCallback - { - public: - DebugDrawCallback(Debug::DebugDrawer& debugDrawer) - : mDebugDrawer(debugDrawer) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - mDebugDrawer.mCurrentFrame = nv->getTraversalNumber(); - int indexRead = getIdexBufferReadFromFrame(mDebugDrawer.mCurrentFrame); - auto& lines = mDebugDrawer.mCustomDebugDrawer[indexRead]->mLinesToDraw; - lines->removePrimitiveSet(0, 1); - lines->addPrimitiveSet(new osg::DrawArrays( - osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); - - nv->pushOntoNodePath(mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->apply(*mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->popFromNodePath(); - } - - Debug::DebugDrawer& mDebugDrawer; - }; } -Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode) - : mParentNode(std::move(parentNode)) +Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop) + : Drawable(copy, copyop) + , mCurrentFrame(copy.mCurrentFrame) + , mCustomDebugDrawer(copy.mCustomDebugDrawer) +{ +} + +Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) { mCurrentFrame = 0; auto program = shaderManager.getProgram("debug"); - mDebugDrawSceneObjects = new osg::Group; - mDebugDrawSceneObjects->setCullingActive(false); - osg::StateSet* stateset = mDebugDrawSceneObjects->getOrCreateStateSet(); + setCullingActive(false); + osg::StateSet* stateset = getOrCreateStateSet(); stateset->addUniform(new osg::Uniform("color", osg::Vec3f(1., 1., 1.))); stateset->addUniform(new osg::Uniform("trans", osg::Vec3f(0., 0., 0.))); stateset->addUniform(new osg::Uniform("scale", osg::Vec3f(1., 1., 1.))); @@ -378,19 +368,28 @@ Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_p mCustomDebugDrawer[i]->mCubeGeometry = cubeGeometry; mCustomDebugDrawer[i]->mCylinderGeometry = cylinderGeom; } - mDebugDrawSceneObjects->addCullCallback(new DebugDrawCallback(*this)); - - mParentNode->addChild(mDebugDrawSceneObjects); } -Debug::DebugDrawer::~DebugDrawer() +void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) { - mParentNode->removeChild(mDebugDrawSceneObjects); + if (!nv.validNodeMask(*this)) + return; + + mCurrentFrame = nv.getTraversalNumber(); + int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); + auto& lines = mCustomDebugDrawer[indexRead]->mLinesToDraw; + lines->removePrimitiveSet(0, 1); + lines->addPrimitiveSet(new osg::DrawArrays( + osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); + + nv.pushOntoNodePath(this); + mCustomDebugDrawer[indexRead]->accept(nv); + nv.popFromNodePath(); } void Debug::DebugDrawer::drawCube(osg::Vec3f mPosition, osg::Vec3f mDims, osg::Vec3f mColor) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( { mPosition, mDims, mColor, DrawShape::Cube }); } @@ -403,12 +402,12 @@ void Debug::DebugDrawer::drawCubeMinMax(osg::Vec3f min, osg::Vec3f max, osg::Vec void Debug::DebugDrawer::addDrawCall(const DrawCall& draw) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); } void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color) { - const int indexWrite = getIdexBufferWriteFromFrame(mCurrentFrame); + const int indexWrite = getIndexBufferWriteFromFrame(mCurrentFrame); const auto& lines = mCustomDebugDrawer[indexWrite]->mLinesToDraw; auto vertices = static_cast(lines->getVertexArray()); auto colors = static_cast(lines->getNormalArray()); diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 659968d35a..610c89d656 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -70,6 +70,9 @@ namespace Debug { public: DebugCustomDraw(); + DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop); + + META_Object(Debug, DebugCustomDraw) mutable std::vector mShapesToDraw; osg::ref_ptr mLinesToDraw; @@ -81,12 +84,15 @@ namespace Debug virtual void drawImplementation(osg::RenderInfo&) const override; }; - struct DebugDrawer + struct DebugDrawer : public osg::Drawable { - friend DebugDrawCallback; + DebugDrawer() = default; + DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop); + DebugDrawer(Shader::ShaderManager& shaderManager); - DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode); - ~DebugDrawer(); + META_Object(Debug, DebugDrawer) + + void accept(osg::NodeVisitor& nv) override; void drawCube( osg::Vec3f mPosition, osg::Vec3f mDims = osg::Vec3(50., 50., 50.), osg::Vec3f mColor = colorWhite); @@ -95,11 +101,9 @@ namespace Debug void addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color = colorWhite); private: - long long int mCurrentFrame; + unsigned int mCurrentFrame; std::array, 2> mCustomDebugDrawer; - osg::ref_ptr mDebugDrawSceneObjects; - osg::ref_ptr mParentNode; }; } #endif // ! diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 8d8acacae4..fa239e692f 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -175,18 +175,19 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", - "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", - "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", - "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", - "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", - "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", - "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", - "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", - "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", - "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; + const char* ignore[] + = { "Debug::DebugDrawer", "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", + "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", + "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", + "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", + "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", + "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", + "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", + "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", + "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", + "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From f9498e6ea4624b65d323c35b9a0d7513b0a98718 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 7 Feb 2024 09:14:17 +0300 Subject: [PATCH 0963/2167] Make DebugDrawer a LightManager child, don't use VAO for lines Fixes terrain lighting but currently breaks non-line primitive rendering in exteriors --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/debug/debugdraw.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 978fe47f44..799e4af24d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -433,7 +433,7 @@ namespace MWRender } mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); - mRootNode->addChild(mDebugDraw); + sceneRoot->addChild(mDebugDraw); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index b83829facd..37584cd950 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -232,7 +232,7 @@ namespace Debug auto vertices = new osg::Vec3Array; auto color = new osg::Vec3Array; lines.setDataVariance(osg::Object::STATIC); - lines.setUseVertexArrayObject(true); + lines.setUseVertexBufferObjects(true); lines.setUseDisplayList(false); lines.setCullingActive(false); From 0d1da08493eb4d9cc2e6c9d5970b7ec1e66db4dc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 20:47:08 +0300 Subject: [PATCH 0964/2167] Set node mask on DebugDrawer Fixes primitive drawing in exteriors/quasiexteriors --- apps/openmw/mwrender/renderingmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 799e4af24d..62e910806f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -433,6 +433,7 @@ namespace MWRender } mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); + mDebugDraw->setNodeMask(Mask_Debug); sceneRoot->addChild(mDebugDraw); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); From 9531b6983a8c3295b0db851bef52dbac6aa80d1f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 21:39:02 +0300 Subject: [PATCH 0965/2167] Don't reallocate debug line primitives --- components/debug/debugdraw.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 37584cd950..c757d4c364 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -317,6 +317,7 @@ namespace Debug mShapesToDraw.clear(); static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); + static_cast(mLinesToDraw->getPrimitiveSet(0))->setCount(0); } } @@ -377,10 +378,6 @@ void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) mCurrentFrame = nv.getTraversalNumber(); int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); - auto& lines = mCustomDebugDrawer[indexRead]->mLinesToDraw; - lines->removePrimitiveSet(0, 1); - lines->addPrimitiveSet(new osg::DrawArrays( - osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); nv.pushOntoNodePath(this); mCustomDebugDrawer[indexRead]->accept(nv); @@ -411,6 +408,7 @@ void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, c const auto& lines = mCustomDebugDrawer[indexWrite]->mLinesToDraw; auto vertices = static_cast(lines->getVertexArray()); auto colors = static_cast(lines->getNormalArray()); + auto primitive = static_cast(lines->getPrimitiveSet(0)); vertices->push_back(start); vertices->push_back(end); @@ -419,4 +417,6 @@ void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, c colors->push_back(color); colors->push_back(color); colors->dirty(); + + primitive->setCount(vertices->size()); } From 6a96cdaa313daa8f58b0f4c313a2a9277c8aaaa9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 21:51:58 +0300 Subject: [PATCH 0966/2167] Make DebugDrawer a Node --- components/debug/debugdraw.cpp | 13 +++---------- components/debug/debugdraw.hpp | 6 +++--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index c757d4c364..bbbf2c238e 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -322,7 +322,7 @@ namespace Debug } Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop) - : Drawable(copy, copyop) + : Node(copy, copyop) , mCurrentFrame(copy.mCurrentFrame) , mCustomDebugDrawer(copy.mCustomDebugDrawer) { @@ -371,17 +371,10 @@ Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) } } -void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) +void Debug::DebugDrawer::traverse(osg::NodeVisitor& nv) { - if (!nv.validNodeMask(*this)) - return; - mCurrentFrame = nv.getTraversalNumber(); - int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); - - nv.pushOntoNodePath(this); - mCustomDebugDrawer[indexRead]->accept(nv); - nv.popFromNodePath(); + mCustomDebugDrawer[getIndexBufferReadFromFrame(mCurrentFrame)]->accept(nv); } void Debug::DebugDrawer::drawCube(osg::Vec3f mPosition, osg::Vec3f mDims, osg::Vec3f mColor) diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 610c89d656..7d7c975749 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -84,15 +84,15 @@ namespace Debug virtual void drawImplementation(osg::RenderInfo&) const override; }; - struct DebugDrawer : public osg::Drawable + struct DebugDrawer : public osg::Node { DebugDrawer() = default; DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop); DebugDrawer(Shader::ShaderManager& shaderManager); - META_Object(Debug, DebugDrawer) + META_Node(Debug, DebugDrawer) - void accept(osg::NodeVisitor& nv) override; + void traverse(osg::NodeVisitor& nv) override; void drawCube( osg::Vec3f mPosition, osg::Vec3f mDims = osg::Vec3(50., 50., 50.), osg::Vec3f mColor = colorWhite); From 4df62d53db442c9770c04a6f1ee634c0a2f2b438 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 10 Feb 2024 01:25:15 +0300 Subject: [PATCH 0967/2167] Fix OSG boilerplate macro for DebugCustomDraw --- components/debug/debugdraw.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 7d7c975749..2518813cad 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -72,7 +72,7 @@ namespace Debug DebugCustomDraw(); DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop); - META_Object(Debug, DebugCustomDraw) + META_Node(Debug, DebugCustomDraw) mutable std::vector mShapesToDraw; osg::ref_ptr mLinesToDraw; From c68dee214ee20b11ad03a7b4a50b15c7dc156246 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 10 Feb 2024 22:53:29 +0100 Subject: [PATCH 0968/2167] Mouse input engine handlers --- apps/openmw/mwbase/luamanager.hpp | 11 ++++++++++- apps/openmw/mwinput/mousemanager.cpp | 11 +++++++++++ apps/openmw/mwlua/inputprocessor.hpp | 15 ++++++++++++++- .../reference/lua-scripting/engine_handlers.rst | 9 +++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e865756408..69693d47a2 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -81,6 +81,12 @@ namespace MWBase struct InputEvent { + struct WheelChange + { + int x; + int y; + }; + enum { KeyPressed, @@ -91,8 +97,11 @@ namespace MWBase TouchPressed, TouchReleased, TouchMoved, + MouseButtonPressed, + MouseButtonReleased, + MouseWheel, } mType; - std::variant mValue; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 55b50b91ae..91ccd4e0a7 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -118,6 +119,8 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } } @@ -125,7 +128,11 @@ namespace MWInput { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) + { mBindingsManager->mouseWheelMoved(arg); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); + } input->setJoystickLastUsed(false); } @@ -161,7 +168,11 @@ namespace MWInput const MWGui::SettingsWindow* settingsWindow = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + { mBindingsManager->mousePressed(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); + } } void MouseManager::updateCursorMode() diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index e005183098..dcd19ae8cd 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -18,7 +18,7 @@ namespace MWLua { mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed, - &mTouchpadReleased, &mTouchpadMoved }); + &mTouchpadReleased, &mTouchpadMoved, &mMouseButtonPress, &mMouseButtonRelease, &mMouseWheel }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -53,6 +53,16 @@ namespace MWLua case InputEvent::TouchMoved: mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); break; + case InputEvent::MouseButtonPressed: + mScriptsContainer->callEngineHandlers(mMouseButtonPress, std::get(event.mValue)); + break; + case InputEvent::MouseButtonReleased: + mScriptsContainer->callEngineHandlers(mMouseButtonRelease, std::get(event.mValue)); + break; + case InputEvent::MouseWheel: + auto wheelEvent = std::get(event.mValue); + mScriptsContainer->callEngineHandlers(mMouseWheel, wheelEvent.y, wheelEvent.x); + break; } } @@ -66,6 +76,9 @@ namespace MWLua typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + typename Container::EngineHandlerList mMouseButtonPress{ "onMouseButtonPress" }; + typename Container::EngineHandlerList mMouseButtonRelease{ "onMouseButtonRelease" }; + typename Container::EngineHandlerList mMouseWheel{ "onMouseWheel" }; }; } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 754a63b314..2b5e99e6ae 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -124,6 +124,15 @@ Engine handler is a function defined by a script, that can be called by the engi * - onTouchMove(touchEvent) - | A finger moved on a touch device. | `Touch event `_. + * - onMouseButtonPress(button) + - | A mouse button was pressed + | Button id + * - onMouseButtonRelease(button) + - | A mouse button was released + | Button id + * - onMouseWheel(vertical, horizontal) + - | Mouse wheel was scrolled + | vertical and horizontal mouse wheel change * - | onConsoleCommand( | mode, command, selectedObject) - | User entered `command` in in-game console. Called if either From 887d09e051cee888d64e774ff56c0d3a9baedb28 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 11 Feb 2024 04:02:01 +0300 Subject: [PATCH 0969/2167] Fix ESM4 marker model hiding hack --- apps/openmw/mwclass/esm4base.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index f5fd346637..f13d6007cd 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -103,7 +103,7 @@ namespace MWClass // Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack. // Needed because otherwise LOD meshes are rendered on top of normal meshes. // TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid. - if (model.empty() || Misc::StringUtils::ciStartsWith(model, "meshes\\marker") + if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker") || Misc::StringUtils::ciEndsWith(model, "lod.nif")) return {}; From 1e079353663d34d4eb8ab4f9640a67f7e5ba95d6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Feb 2024 03:12:52 +0100 Subject: [PATCH 0970/2167] Make crashCatcherInstall no-op for Android The crashcatcher.cpp is not linked on Android because it's not supported but the function need to have some definition. Make it empty to avoid link failures. --- components/crashcatcher/crashcatcher.cpp | 3 --- components/crashcatcher/crashcatcher.hpp | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 9a662c4a92..009029ef19 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -583,8 +583,6 @@ static bool isDebuggerPresent() void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath) { -#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ - || defined(__posix)) if (argc == 2 && strcmp(argv[1], crash_switch) == 0) handleCrash(Files::pathToUnicodeString(crashLogPath).c_str()); @@ -595,5 +593,4 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra Log(Debug::Info) << "Crash handler installed"; else Log(Debug::Warning) << "Installing crash handler failed"; -#endif } diff --git a/components/crashcatcher/crashcatcher.hpp b/components/crashcatcher/crashcatcher.hpp index 9dd1000385..16b416cf98 100644 --- a/components/crashcatcher/crashcatcher.hpp +++ b/components/crashcatcher/crashcatcher.hpp @@ -3,6 +3,11 @@ #include +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ + || defined(__posix)) void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); +#else +inline void crashCatcherInstall(int /*argc*/, char** /*argv*/, const std::filesystem::path& /*crashLogPath*/) {} +#endif #endif From f4fed4ca5f9729c94e98118afb464918e952e490 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 16:27:54 +0100 Subject: [PATCH 0971/2167] Simplify inputBinding renderer, allow binding controller buttons --- .../lua-scripting/setting_renderers.rst | 7 +- .../data/scripts/omw/input/actionbindings.lua | 130 ++++++++++++------ files/data/scripts/omw/input/settings.lua | 92 ++++++++++--- 3 files changed, 166 insertions(+), 63 deletions(-) diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..f817b789bf 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -143,10 +143,9 @@ Table with the following fields: * - name - type (default) - description - * - type - - 'keyboardPress', 'keyboardHold' - - The type of input that's allowed to be bound * - key - #string - Key of the action or trigger to which the input is bound - + * - type + - 'action', 'trigger' + - Type of the key diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua index 35a467fb52..bc871a3934 100644 --- a/files/data/scripts/omw/input/actionbindings.lua +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -1,11 +1,7 @@ -local core = require('openmw.core') local input = require('openmw.input') local util = require('openmw.util') local async = require('openmw.async') local storage = require('openmw.storage') -local ui = require('openmw.ui') - -local I = require('openmw.interfaces') local actionPressHandlers = {} local function onActionPress(id, handler) @@ -89,48 +85,87 @@ end local bindingSection = storage.playerSection('OMWInputBindings') -local keyboardPresses = {} -local keybordHolds = {} -local boundActions = {} +local devices = { + keyboard = true, + mouse = true, + controller = true +} -local function bindAction(action) - if boundActions[action] then return end - boundActions[action] = true - input.bindAction(action, async:callback(function() - if keybordHolds[action] then - for _, binding in pairs(keybordHolds[action]) do - if input.isKeyPressed(binding.code) then return true end +local function invalidBinding(binding) + if not binding.key then + return 'has no key' + elseif binding.type ~= 'action' and binding.type ~= 'trigger' then + return string.format('has invalid type', binding.type) + elseif binding.type == 'action' and not input.actions[binding.key] then + return string.format("action %s doesn't exist", binding.key) + elseif binding.type == 'trigger' and not input.triggers[binding.key] then + return string.format("trigger %s doesn't exist", binding.key) + elseif not binding.device or not devices[binding.device] then + return string.format("invalid device %s", binding.device) + elseif not binding.button then + return 'has no button' + end +end + +local boundActions = {} +local actionBindings = {} + +local function bindAction(binding, id) + local action = binding.key + actionBindings[action] = actionBindings[action] or {} + actionBindings[action][id] = binding + if not boundActions[action] then + boundActions[binding.key] = true + input.bindAction(action, async:callback(function() + for _, binding in pairs(actionBindings[action] or {}) do + if binding.device == 'keyboard' then + if input.isKeyPressed(binding.button) then + return true + end + elseif binding.device == 'mouse' then + if input.isMouseButtonPressed(binding.button) then + return true + end + elseif binding.device == 'controller' then + if input.isControllerButtonPressed(binding.button) then + return true + end + end end - end - return false - end), {}) + return false + end), {}) + end +end + +local triggerBindings = {} +for device in pairs(devices) do triggerBindings[device] = {} end + +local function bindTrigger(binding, id) + local deviceBindings = triggerBindings[binding.device] + deviceBindings[binding.button] = deviceBindings[binding.button] or {} + deviceBindings[binding.button][id] = binding end local function registerBinding(binding, id) - if not input.actions[binding.key] and not input.triggers[binding.key] then - print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) - return - end - if binding.type == 'keyboardPress' then - local bindings = keyboardPresses[binding.code] or {} - bindings[id] = binding - keyboardPresses[binding.code] = bindings - elseif binding.type == 'keyboardHold' then - local bindings = keybordHolds[binding.key] or {} - bindings[id] = binding - keybordHolds[binding.key] = bindings - bindAction(binding.key) - else - error('Unknown binding type "' .. binding.type .. '"') + local invalid = invalidBinding(binding) + if invalid then + print(string.format('Skipping invalid binding %s: %s', id, invalid)) + elseif binding.type == 'action' then + bindAction(binding, id) + elseif binding.type == 'trigger' then + bindTrigger(binding, id) end end function clearBinding(id) - for _, boundTriggers in pairs(keyboardPresses) do - boundTriggers[id] = nil + for _, deviceBindings in pairs(triggerBindings) do + for _, buttonBindings in pairs(deviceBindings) do + buttonBindings[id] = nil + end end - for _, boundKeys in pairs(keybordHolds) do - boundKeys[id] = nil + + for _, bindings in pairs(actionBindings) do + bindings[id] = nil end end @@ -170,11 +205,24 @@ return { end end, onKeyPress = function(e) - local bindings = keyboardPresses[e.code] - if bindings then - for _, binding in pairs(bindings) do - input.activateTrigger(binding.key) - end + local buttonTriggers = triggerBindings.keyboard[e.code] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onMouseButtonPress = function(button) + local buttonTriggers = triggerBindings.mouse[button] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onControllerButtonPress = function(id) + local buttonTriggers = triggerBindings.controller[id] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) end end, } diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 6c1b857131..3c1ba4d6b9 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -44,14 +44,63 @@ local bindingSection = storage.playerSection('OMWInputBindings') local recording = nil +local mouseButtonNames = { + [1] = 'Left', + [2] = 'Middle', + [3] = 'Right', + [4] = '4', + [5] = '5', +} + +-- TODO: support different controllers, use icons to render controller buttons +local controllerButtonNames = { + [-1] = 'Invalid', + [input.CONTROLLER_BUTTON.A] = "A", + [input.CONTROLLER_BUTTON.B] = "B", + [input.CONTROLLER_BUTTON.X] = "X", + [input.CONTROLLER_BUTTON.Y] = "Y", + [input.CONTROLLER_BUTTON.Back] = "Back", + [input.CONTROLLER_BUTTON.Guide] = "Guide", + [input.CONTROLLER_BUTTON.Start] = "Start", + [input.CONTROLLER_BUTTON.LeftStick] = "Left Stick", + [input.CONTROLLER_BUTTON.RightStick] = "Right Stick", + [input.CONTROLLER_BUTTON.LeftShoulder] = "LB", + [input.CONTROLLER_BUTTON.RightShoulder] = "RB", + [input.CONTROLLER_BUTTON.DPadUp] = "D-pad Up", + [input.CONTROLLER_BUTTON.DPadDown] = "D-pad Down", + [input.CONTROLLER_BUTTON.DPadLeft] = "D-pad Left", + [input.CONTROLLER_BUTTON.DPadRight] = "D-pad Right", +} + +local function bindingLabel(recording, binding) + if recording then + return interfaceL10n('N/A') + elseif not binding or not binding.button then + return interfaceL10n('None') + elseif binding.device == 'keyboard' then + return input.getKeyName(binding.button) + elseif binding.device == 'mouse' then + return string.format('Mouse %s', mouseButtonNames[binding.button] or 'Unknown') + elseif binding.device == 'controller' then + return string.format('Controller %s', controllerButtonNames[binding.button] or 'Unknown') + else + return 'Unknown' + end +end + +local inputTypes = { + action = input.actions, + trigger = input.triggers, +} I.Settings.registerRenderer('inputBinding', function(id, set, arg) if type(id) ~= 'string' then error('inputBinding: must have a string default value') end if not arg then error('inputBinding: argument with "key" and "type" is required') end if not arg.type then error('inputBinding: type argument is required') end + if not inputTypes[arg.type] then error('inputBinding: type must be "action" or "trigger"') end if not arg.key then error('inputBinding: key argument is required') end - local info = input.actions[arg.key] or input.triggers[arg.key] - if not info then return {} end + local info = inputTypes[arg.type][arg.key] + if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end local l10n = core.l10n(info.key) @@ -70,9 +119,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) } local binding = bindingSection:get(id) - local label = interfaceL10n('None') - if binding then label = input.getKeyName(binding.code) end - if recording and recording.id == id then label = interfaceL10n('N/A') end + local label = bindingLabel(recording and recording.id == id, binding) local recorder = { template = I.MWUI.templates.textNormal, @@ -115,22 +162,31 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) return column end) +local function bindButton(device, button) + if recording == nil then return end + local binding = { + device = device, + button = button, + type = recording.arg.type, + key = recording.arg.key, + } + bindingSection:set(recording.id, binding) + local refresh = recording.refresh + recording = nil + refresh() +end + return { engineHandlers = { onKeyPress = function(key) - if recording == nil then return end - local binding = { - code = key.code, - type = recording.arg.type, - key = recording.arg.key, - } - if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing - binding.code = nil - end - bindingSection:set(recording.id, binding) - local refresh = recording.refresh - recording = nil - refresh() + bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) + end, + -- TODO: currently never triggers, because mouse events are disabled while inside settings + onMouseButtonPress = function(button) + bindButton('mouse', button) + end, + onControllerButtonPress = function(id) + bindButton('controller', id) end, } } From 75d0b6e3551c4f6ebd89cd1bae08dbbdfac9dc80 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 11 Feb 2024 22:06:58 +0100 Subject: [PATCH 0972/2167] Use decompose to handle AI packages and data --- apps/esmtool/record.cpp | 6 -- apps/openmw_test_suite/esm3/testsaveload.cpp | 44 +++++++++++++- components/esm3/aipackage.cpp | 61 ++++++++++++++------ components/esm3/aipackage.hpp | 16 ++--- components/esm3/loadcrea.cpp | 5 +- components/esm3/loadnpc.cpp | 5 +- 6 files changed, 100 insertions(+), 37 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e3b81daf41..245012ce13 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -722,9 +722,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) @@ -1115,9 +1112,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index f8ef23e887..8010e1d7ef 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -89,8 +90,18 @@ namespace ESM constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); + template > + struct HasSave : std::false_type + { + }; + template - void save(const T& record, ESMWriter& writer) + struct HasSave().save(std::declval()))>> : std::true_type + { + }; + + template + auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> { record.save(writer); } @@ -100,6 +111,12 @@ namespace ESM record.save(writer, true); } + template + auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + { + writer.writeComposite(record); + } + template std::unique_ptr makeEsmStream(const T& record, FormatVersion formatVersion) { @@ -154,6 +171,12 @@ namespace ESM record.load(reader, deleted, true); } + template + auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + { + reader.getComposite(record); + } + template void saveAndLoadRecord(const T& record, FormatVersion formatVersion, T& result) { @@ -490,6 +513,25 @@ namespace ESM EXPECT_EQ(result.mRepeat, record.mRepeat); } + TEST_P(Esm3SaveLoadRecordTest, aiDataShouldNotChange) + { + AIData record; + record.mHello = 1; + record.mFight = 2; + record.mFlee = 3; + record.mAlarm = 4; + record.mServices = 5; + + AIData result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mHello, record.mHello); + EXPECT_EQ(result.mFight, record.mFight); + EXPECT_EQ(result.mFlee, record.mFlee); + EXPECT_EQ(result.mAlarm, record.mAlarm); + EXPECT_EQ(result.mServices, record.mServices); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 4d4c03c349..2cadb9fb22 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -5,9 +5,35 @@ namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mX, v.mY, v.mZ, v.mDuration, v.mId.mData, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mName.mData, v.mShouldRepeat); + } + void AIData::blank() { - mHello = mFight = mFlee = mAlarm = mU1 = mU2 = mU3 = 0; + mHello = mFight = mFlee = mAlarm = 0; mServices = 0; } @@ -28,58 +54,57 @@ namespace ESM else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; - esm.getHExact(&pack.mWander, 14); + esm.getSubHeader(); + esm.getComposite(pack.mWander); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; - esm.getHExact(&pack.mTravel, 16); + esm.getSubHeader(); + esm.getComposite(pack.mTravel); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getHExact(&pack.mTarget, 48); + esm.getSubHeader(); + esm.getComposite(pack.mTarget); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; - esm.getHExact(&pack.mActivate, 33); + esm.getSubHeader(); + esm.getComposite(pack.mActivate); mList.push_back(pack); } - else - { // not AI package related data, so leave - return; - } } void AIPackageList::save(ESMWriter& esm) const { - typedef std::vector::const_iterator PackageIter; - for (PackageIter it = mList.begin(); it != mList.end(); ++it) + for (const AIPackage& package : mList) { - switch (it->mType) + switch (package.mType) { case AI_Wander: - esm.writeHNT("AI_W", it->mWander, sizeof(it->mWander)); + esm.writeNamedComposite("AI_W", package.mWander); break; case AI_Travel: - esm.writeHNT("AI_T", it->mTravel, sizeof(it->mTravel)); + esm.writeNamedComposite("AI_T", package.mTravel); break; case AI_Activate: - esm.writeHNT("AI_A", it->mActivate, sizeof(it->mActivate)); + esm.writeNamedComposite("AI_A", package.mActivate); break; case AI_Escort: case AI_Follow: { - const NAME name = (it->mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); - esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); - esm.writeHNOCString("CNDT", it->mCellName); + const NAME name = (package.mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); + esm.writeNamedComposite(name, package.mTarget); + esm.writeHNOCString("CNDT", package.mCellName); break; } diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 7346a4af36..10e7be8f00 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -5,20 +5,17 @@ #include #include "components/esm/esmcommon.hpp" +#include "components/misc/concepts.hpp" namespace ESM { class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - struct AIData { uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] - char mU1, mU2, mU3; // Unknown values int32_t mServices; // See the Services enum void blank(); @@ -38,7 +35,6 @@ namespace ESM { float mX, mY, mZ; unsigned char mShouldRepeat; - unsigned char mPadding[3]; }; struct AITarget @@ -47,7 +43,6 @@ namespace ESM int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; - unsigned char mPadding; }; struct AIActivate @@ -56,8 +51,6 @@ namespace ESM unsigned char mShouldRepeat; }; -#pragma pack(pop) - enum AiPackageType : std::uint32_t { AI_Wander = 0x575f4941, @@ -98,6 +91,13 @@ namespace ESM void save(ESMWriter& esm) const; }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHello, v.mFight, v.mFlee, v.mAlarm, padding, v.mServices); + } } #endif diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 0c0bad2e7c..1db79e8e76 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -69,7 +69,8 @@ namespace ESM mSpells.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubHeader(); + esm.getComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -130,7 +131,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); mAiPackage.save(esm); } diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 4a30649372..92b16638c2 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -102,7 +102,8 @@ namespace ESM mInventory.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubHeader(); + esm.getComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -186,7 +187,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); From 4cdb76975a6557c8a2ecd9a75f4dc3d00fad3fbc Mon Sep 17 00:00:00 2001 From: BPengu1n Date: Sun, 11 Feb 2024 16:39:16 -0600 Subject: [PATCH 0973/2167] added force PPL checkbox --- apps/launcher/settingspage.cpp | 2 ++ apps/launcher/ui/settingspage.ui | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index c274b75f79..2b02d6b0bb 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -252,6 +252,7 @@ bool Launcher::SettingsPage::loadSettings() lightFadeMultiplierSpinBox->setValue(Settings::shaders().mLightFadeStart); lightsBoundingSphereMultiplierSpinBox->setValue(Settings::shaders().mLightBoundsMultiplier); lightsMinimumInteriorBrightnessSpinBox->setValue(Settings::shaders().mMinimumInteriorBrightness); + loadSettingBool(Settings::shaders().mForcePerPixelLighting, *forcePPLCheckBox); connect(lightingMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this, &SettingsPage::slotLightTypeCurrentIndexChanged); @@ -470,6 +471,7 @@ void Launcher::SettingsPage::saveSettings() Settings::shaders().mLightFadeStart.set(lightFadeMultiplierSpinBox->value()); Settings::shaders().mLightBoundsMultiplier.set(lightsBoundingSphereMultiplierSpinBox->value()); Settings::shaders().mMinimumInteriorBrightness.set(lightsMinimumInteriorBrightnessSpinBox->value()); + saveSettingBool(*forcePPLCheckBox, Settings::shaders().mForcePerPixelLighting); } // Audio diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 2d06c1802e..86039f77ee 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1028,7 +1028,7 @@ - + Qt::Vertical @@ -1103,6 +1103,16 @@ + + + + <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + + + Force per-pixel lighting + + + From 63a1bbb88d7829152f1b3ef17c3cc7c7b007f542 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 23:49:26 +0100 Subject: [PATCH 0974/2167] Enable Lua mouse engine handlers while in UI --- apps/openmw/mwinput/mousemanager.cpp | 13 +++++++------ files/data/scripts/omw/input/settings.lua | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 91ccd4e0a7..ffbe40a2db 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -119,9 +119,10 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } + + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) @@ -130,11 +131,11 @@ namespace MWInput if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) { mBindingsManager->mouseWheelMoved(arg); - MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, - MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } input->setJoystickLastUsed(false); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) @@ -170,9 +171,9 @@ namespace MWInput if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) { mBindingsManager->mousePressed(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } void MouseManager::updateCursorMode() diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 3c1ba4d6b9..5243a86844 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -181,7 +181,6 @@ return { onKeyPress = function(key) bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) end, - -- TODO: currently never triggers, because mouse events are disabled while inside settings onMouseButtonPress = function(button) bindButton('mouse', button) end, From 4c136ed7ee890fe0c2c04a60a2ef861a5c93329b Mon Sep 17 00:00:00 2001 From: BPengu1n Date: Sun, 11 Feb 2024 17:00:45 -0600 Subject: [PATCH 0975/2167] Updated qt localization text for new option --- files/lang/launcher_de.ts | 8 ++++++++ files/lang/launcher_fr.ts | 8 ++++++++ files/lang/launcher_ru.ts | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 1c23f90425..af7bd573a1 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + + + + Force per-pixel lighting + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f604044368..a11e5cbad8 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + + + + Force per-pixel lighting + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 844e36898c..f735883064 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1462,5 +1462,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness Минимальный уровень освещения в помещениях + + <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + + + + Force per-pixel lighting + + From 3149761c853395c86aa057017fdbde5d059b25c9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 11 Feb 2024 23:49:18 +0000 Subject: [PATCH 0976/2167] Fix grammar for A2C checkbox An alternative would be *Anti-alias alpha testing*. The original was wrong because anti-alias is a verb acting on alpha testing, but it treated the whole thing as a noun phrase. --- apps/launcher/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 2d06c1802e..a59891eb54 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -524,7 +524,7 @@ <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Use anti-alias alpha testing + Use anti-aliased alpha testing From 5913ca67d572f8df0ae3f4d026685f697798de97 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 11 Feb 2024 23:53:58 +0000 Subject: [PATCH 0977/2167] .ts files, too --- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 1c23f90425..3b911e2288 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -852,7 +852,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use anti-alias alpha testing + Use anti-aliased alpha testing diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f604044368..757121d1d3 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -852,7 +852,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use anti-alias alpha testing + Use anti-aliased alpha testing diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 844e36898c..dad899aff6 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -859,7 +859,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> - Use anti-alias alpha testing + Use anti-aliased alpha testing Сглаживание альфа-тестирования From 8c591a0b44c5d701c2c60d3325f999aa150bef93 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 12 Feb 2024 01:16:49 +0000 Subject: [PATCH 0978/2167] Groundcover should ignore non-geometry Drawables Fix https://gitlab.com/OpenMW/openmw/-/issues/7633 Untested - the issue didn't link to a mod using the mesh and I couldn't be bothered setting one up manually. --- CHANGELOG.md | 1 + apps/openmw/mwrender/groundcover.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96eb36c9f..b4177f5398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7633: Groundcover should ignore non-geometry Drawables Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 92be726f09..8656af9d2f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -196,6 +196,18 @@ namespace MWRender { } + void apply(osg::Group& group) override + { + for (unsigned int i = 0; i < group.getNumChildren();) + { + if (group.getChild(i)->asDrawable() && !group.getChild(i)->asGeometry()) + group.removeChild(i); + else + ++i; + } + traverse(group); + } + void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) From 567d36240eec88d8b1ae200e0911a94e4da86fc1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 12 Feb 2024 15:02:21 +0000 Subject: [PATCH 0979/2167] Clarify shaders documentation We know people get confused by it. Hopefully this should help. --- .../reference/modding/settings/shaders.rst | 42 ++++++++++--------- files/settings-default.cfg | 13 +++--- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 9ee1cbfaa5..8bd152857f 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -8,11 +8,15 @@ force shaders :Range: True/False :Default: False -Force rendering with shaders. By default, only bump-mapped objects will use shaders. -Enabling this option may cause slightly different visuals if the "clamp lighting" option is set to false. +Force rendering with shaders, even for objects that don't strictly need them. +By default, only objects with certain effects, such as bump or normal maps will use shaders. +Many visual enhancements, such as :ref:`enable shadows` and :ref:`reverse z` require shaders to be used for all objects, and so behave as if this setting is true. +Typically, one or more of these enhancements will be enabled, and shaders will be needed for everything anyway, meaning toggling this setting will have no effect. + +Some settings, such as :ref:`clamp lighting` only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. Otherwise, there should not be a visual difference. -Please note enabling shaders has a significant performance impact on most systems. +Please note enabling shaders may have a significant performance impact on some systems, and a mild impact on many others. force per pixel lighting ------------------------ @@ -21,10 +25,10 @@ force per pixel lighting :Range: True/False :Default: False -Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -Has no effect if the 'force shaders' option is false. +Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. -Note that groundcover shaders ignore this setting. +Note that groundcover shaders and particle effects ignore this setting. clamp lighting -------------- @@ -34,7 +38,7 @@ clamp lighting :Default: True Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -Only affects objects that render with shaders (see 'force shaders' option). +Only affects objects drawn with shaders (see :ref:`force shaders` option). Always affects terrain. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, @@ -49,9 +53,9 @@ auto use object normal maps :Default: False If this option is enabled, normal maps are automatically recognized and used if they are named appropriately -(see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +(see :ref:`normal map pattern`, e.g. for a base texture ``foo.dds``, the normal map texture would have to be named ``foo_n.dds``). If this option is disabled, -normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects. +normal maps are only used if they are explicitly listed within the mesh file (``.nif`` or ``.osg`` file). Affects objects. auto use object specular maps ----------------------------- @@ -61,10 +65,10 @@ auto use object specular maps :Default: False If this option is enabled, specular maps are automatically recognized and used if they are named appropriately -(see 'specular map pattern', e.g. for a base texture foo.dds, -the specular map texture would have to be named foo_spec.dds). +(see :ref:`specular map pattern`, e.g. for a base texture ``foo.dds``, +the specular map texture would have to be named ``foo_spec.dds``). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file -(.osg file, not supported in .nif files). Affects objects. +(``.osg`` file, not supported in ``.nif`` files). Affects objects. auto use terrain normal maps ---------------------------- @@ -73,7 +77,7 @@ auto use terrain normal maps :Range: True/False :Default: False -See 'auto use object normal maps'. Affects terrain. +See :ref:`auto use object normal maps`. Affects terrain. auto use terrain specular maps ------------------------------ @@ -82,7 +86,7 @@ auto use terrain specular maps :Range: True/False :Default: False -If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. +If a file with pattern :ref:`terrain specular map pattern` exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel. normal map pattern @@ -93,7 +97,7 @@ normal map pattern :Default: _n The filename pattern to probe for when detecting normal maps -(see 'auto use object normal maps', 'auto use terrain normal maps') +(see :ref:`auto use object normal maps`, :ref:`auto use terrain normal maps`) normal height map pattern ------------------------- @@ -113,7 +117,7 @@ specular map pattern :Range: :Default: _spec -The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') +The filename pattern to probe for when detecting object specular maps (see :ref:`auto use object specular maps`) terrain specular map pattern ---------------------------- @@ -122,7 +126,7 @@ terrain specular map pattern :Range: :Default: _diffusespec -The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') +The filename pattern to probe for when detecting terrain specular maps (see :ref:`auto use terrain specular maps`) apply lighting to environment maps ---------------------------------- @@ -166,7 +170,7 @@ normal maps are provided. This is due to some groundcover mods using the Z-Up normals technique to avoid some common issues with shading. As a consequence, per pixel lighting would give undesirable results. -Note that the rendering will act as if you have 'force shaders' option enabled +Note that the rendering will act as if you have :ref:`force shaders` option enabled when not set to 'legacy'. This means that shaders will be used to render all objects and the terrain. @@ -283,7 +287,7 @@ between them. Note, this relies on overriding specific properties of particle systems that potentially differ from the source content, this setting may change the look of some particle systems. -Note that the rendering will act as if you have 'force shaders' option enabled. +Note that the rendering will act as if you have :ref:`force shaders` option enabled. This means that shaders will be used to render all objects and the terrain. weather particle occlusion diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a90a46cc5..5f68f055d4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -405,15 +405,18 @@ console history buffer size = 4096 [Shaders] -# Force rendering with shaders. By default, only bump-mapped objects will use shaders. -# Enabling this option may cause slightly different visuals if the "clamp lighting" option -# is set to false. Otherwise, there should not be a visual difference. +# Force rendering with shaders, even for objects that don't strictly need them. +# By default, only objects with certain effects, such as bump or normal maps will use shaders. +# Many visual enhancements, such as "enable shadows" and "reverse z" require shaders to be used for all objects, and so behave as if this setting is true. +# Some settings, such as "clamp lighting" only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. +# Otherwise, there should not be a visual difference. force shaders = false -# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -# Has no effect if the 'force shaders' option is false. +# Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +# Only affects objects drawn with shaders (see "force shaders" option). # Enabling per-pixel lighting can result in visual differences to the original MW engine as # certain lights in Morrowind rely on vertex lighting to look as intended. +# Note that groundcover shaders and particle effects ignore this setting. force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). From 56b31ceaf55b9086e6ab8c07ca1143adbf65a64c Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Mon, 12 Feb 2024 07:52:47 -0800 Subject: [PATCH 0980/2167] add ignore list to raycasts --- CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwlua/nearbybindings.cpp | 69 +++++++-- apps/openmw/mwmechanics/aiwander.cpp | 2 +- .../closestnotmerayresultcallback.cpp | 2 +- .../closestnotmerayresultcallback.hpp | 8 +- apps/openmw/mwphysics/physicssystem.cpp | 26 ++-- apps/openmw/mwphysics/physicssystem.hpp | 9 +- apps/openmw/mwphysics/raycasting.hpp | 11 +- apps/openmw/mwrender/renderingmanager.cpp | 131 +++++++++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 13 +- apps/openmw/mwworld/scene.cpp | 19 +-- apps/openmw/mwworld/scene.hpp | 2 + apps/openmw/mwworld/worldimp.cpp | 11 +- apps/openmw/mwworld/worldimp.hpp | 2 +- files/lua_api/openmw/nearby.lua | 7 + 16 files changed, 237 insertions(+), 79 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d62cda4f2b..7b1b18e41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 53) +set(OPENMW_LUA_API_REVISION 54) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4247ef2e3e..fe8b5cc13a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -304,7 +304,7 @@ namespace MWBase virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}) = 0; virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 7e1845aeac..7eda965e96 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -16,6 +16,31 @@ #include "luamanagerimp.hpp" #include "objectlists.hpp" +namespace +{ + template + std::vector parseIgnoreList(const sol::table& options) + { + std::vector ignore; + + if (const auto& ignoreObj = options.get>("ignore")) + { + ignore.push_back(ignoreObj->ptr()); + } + else if (const auto& ignoreTable = options.get>("ignore")) + { + ignoreTable->for_each([&](const auto& _, const sol::object& value) { + if (value.is()) + { + ignore.push_back(value.as().ptr()); + } + }); + } + + return ignore; + } +} + namespace sol { template <> @@ -71,24 +96,27 @@ namespace MWLua })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { - MWWorld::Ptr ignore; + std::vector ignore; int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { - sol::optional ignoreObj = options->get>("ignore"); - if (ignoreObj) - ignore = ignoreObj->ptr(); + ignore = parseIgnoreList(*options); collisionType = options->get>("collisionType").value_or(collisionType); radius = options->get>("radius").value_or(0); } const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); if (radius <= 0) - return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + { + return rayCasting->castRay(from, to, ignore, {}, collisionType); + } else { - if (!ignore.isEmpty()) - throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + for (const auto& ptr : ignore) + { + if (!ptr.isEmpty()) + throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + } return rayCasting->castSphere(from, to, radius, collisionType); } }; @@ -108,22 +136,37 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ - api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) { + api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to, + const sol::optional& options) { if (!manager->isProcessingInputEvents()) { throw std::logic_error( "castRenderingRay can be used only in player scripts during processing of input events; " "use asyncCastRenderingRay instead."); } + + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); return res; }; - api["asyncCastRenderingRay"] = [context]( - const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) { - context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] { + api["asyncCastRenderingRay"] = [context](const sol::table& callback, const osg::Vec3f& from, + const osg::Vec3f& to, const sol::optional& options) { + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + + context.mLuaManager->addAction([context, ignore, callback = LuaUtil::Callback::fromLua(callback), from, + to] { MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); }); }; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index be2601dc37..38466c0c4c 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -86,7 +86,7 @@ namespace MWMechanics return MWBase::Environment::get() .getWorld() ->getRayCasting() - ->castRay(position, visibleDestination, actor, {}, mask) + ->castRay(position, visibleDestination, { actor }, {}, mask) .mHit; } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 02c94a6f9d..b63cd568a8 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -12,7 +12,7 @@ namespace MWPhysics btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { const auto* hitObject = rayResult.m_collisionObject; - if (hitObject == mMe) + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), hitObject) != mIgnoreList.end()) return 1.f; if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index f7f567ed4e..660f24424d 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,10 +14,10 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - explicit ClosestNotMeRayResultCallback(const btCollisionObject* me, std::span targets, - const btVector3& from, const btVector3& to) + explicit ClosestNotMeRayResultCallback(std::span ignore, + std::span targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) + , mIgnoreList(ignore) , mTargets(targets) { } @@ -25,7 +25,7 @@ namespace MWPhysics btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: - const btCollisionObject* mMe; + const std::span mIgnoreList; const std::span mTargets; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 43888ac306..20a9c38b0f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -192,7 +192,8 @@ namespace MWPhysics } RayCastingResult PhysicsSystem::castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const + const std::vector& ignore, const std::vector& targets, int mask, + int group) const { if (from == to) { @@ -203,19 +204,22 @@ namespace MWPhysics btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); - const btCollisionObject* me = nullptr; + std::vector ignoreList; std::vector targetCollisionObjects; - if (!ignore.isEmpty()) + for (const auto& ptr : ignore) { - const Actor* actor = getActor(ignore); - if (actor) - me = actor->getCollisionObject(); - else + if (!ptr.isEmpty()) { - const Object* object = getObject(ignore); - if (object) - me = object->getCollisionObject(); + const Actor* actor = getActor(ptr); + if (actor) + ignoreList.push_back(actor->getCollisionObject()); + else + { + const Object* object = getObject(ptr); + if (object) + ignoreList.push_back(object->getCollisionObject()); + } } } @@ -229,7 +233,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); + ClosestNotMeRayResultCallback resultCallback(ignoreList, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6734682092..9cc55fecc6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -209,12 +209,11 @@ namespace MWPhysics const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const; osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight); - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const override; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const override; using RayCastingInterface::castRay; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 6b1a743d54..78b6ab4678 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -23,16 +23,15 @@ namespace MWPhysics public: virtual ~RayCastingInterface() = default; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const = 0; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const = 0; RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask) const { - return castRay(from, to, MWWorld::ConstPtr(), std::vector(), mask); + return castRay(from, to, {}, {}, mask); } virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15faabb6df..224774d102 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -59,6 +59,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" +#include "../mwworld/scene.hpp" #include "../mwgui/postprocessorhud.hpp" @@ -1014,20 +1015,17 @@ namespace MWRender return osg::Vec4f(min_x, min_y, max_x, max_y); } - RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector) + RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector, + const osg::ref_ptr& visitor, std::span ignoreList = {}) { RenderingManager::RayResult result; result.mHit = false; result.mRatio = 0; - if (intersector->containsIntersections()) - { - result.mHit = true; - osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); - result.mHitPointWorld = intersection.getWorldIntersectPoint(); - result.mHitNormalWorld = intersection.getWorldIntersectNormal(); - result.mRatio = intersection.ratio; + if (!intersector->containsIntersections()) + return result; + auto test = [&](const osgUtil::LineSegmentIntersector::Intersection& intersection) { PtrHolder* ptrHolder = nullptr; std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); @@ -1039,9 +1037,16 @@ namespace MWRender for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) - ptrHolder = p; + { + if (std::find(ignoreList.begin(), ignoreList.end(), p->mPtr) == ignoreList.end()) + { + ptrHolder = p; + } + } if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) + { refnumMarkers.push_back(r); + } } } @@ -1056,21 +1061,113 @@ namespace MWRender || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) { - result.mHitRefnum = refnumMarkers[i]->mRefnum; + auto it = std::find_if( + ignoreList.begin(), ignoreList.end(), [target = refnumMarkers[i]->mRefnum](const auto& ptr) { + return target == ptr.getCellRef().getRefNum(); + }); + + if (it == ignoreList.end()) + { + result.mHitRefnum = refnumMarkers[i]->mRefnum; + } + break; } vertexCounter += refnumMarkers[i]->mNumVertices; } + + if (!result.mHitObject.isEmpty() || result.mHitRefnum.isSet()) + { + result.mHit = true; + result.mHitPointWorld = intersection.getWorldIntersectPoint(); + result.mHitNormalWorld = intersection.getWorldIntersectNormal(); + result.mRatio = intersection.ratio; + } + }; + + if (ignoreList.empty() || intersector->getIntersectionLimit() != osgUtil::LineSegmentIntersector::NO_LIMIT) + { + test(intersector->getFirstIntersection()); + } + else + { + for (const auto& intersection : intersector->getIntersections()) + { + test(intersection); + + if (result.mHit) + { + break; + } + } } return result; } + class IntersectionVisitorWithIgnoreList : public osgUtil::IntersectionVisitor + { + public: + bool skipTransform(osg::Transform& transform) + { + if (mContainsPagedRefs) + return false; + + osg::UserDataContainer* userDataContainer = transform.getUserDataContainer(); + if (!userDataContainer) + return false; + + for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) + { + if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) + { + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), p->mPtr) != mIgnoreList.end()) + { + return true; + } + } + } + + return false; + } + + void apply(osg::Transform& transform) override + { + if (skipTransform(transform)) + { + return; + } + osgUtil::IntersectionVisitor::apply(transform); + } + + void setIgnoreList(std::span ignoreList) { mIgnoreList = ignoreList; } + void setContainsPagedRefs(bool contains) { mContainsPagedRefs = contains; } + + private: + std::span mIgnoreList; + bool mContainsPagedRefs = false; + }; + osg::ref_ptr RenderingManager::getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) + osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors, + std::span ignoreList) { if (!mIntersectionVisitor) - mIntersectionVisitor = new osgUtil::IntersectionVisitor; + mIntersectionVisitor = new IntersectionVisitorWithIgnoreList; + + mIntersectionVisitor->setIgnoreList(ignoreList); + mIntersectionVisitor->setContainsPagedRefs(false); + + MWWorld::Scene* worldScene = MWBase::Environment::get().getWorldScene(); + for (const auto& ptr : ignoreList) + { + if (worldScene->isPagedRef(ptr)) + { + mIntersectionVisitor->setContainsPagedRefs(true); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + break; + } + } mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); @@ -1088,16 +1185,16 @@ namespace MWRender return mIntersectionVisitor; } - RenderingManager::RayResult RenderingManager::castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { osg::ref_ptr intersector( new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors, ignoreList)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor, ignoreList); } RenderingManager::RayResult RenderingManager::castCameraToViewportRay( @@ -1117,7 +1214,7 @@ namespace MWRender mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor); } void RenderingManager::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 22ef987c01..e138cc5b7d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H +#include + #include #include #include @@ -87,6 +89,7 @@ namespace MWRender class StateUpdater; class SharedUniformStateUpdater; class PerViewUniformStateUpdater; + class IntersectionVisitorWithIgnoreList; class EffectManager; class ScreenshotManager; @@ -177,8 +180,8 @@ namespace MWRender float mRatio; }; - RayResult castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors = false); + RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, + bool ignoreActors = false, std::span ignoreList = {}); /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen /// coordinates, where (0,0) is the top left corner. @@ -299,10 +302,10 @@ namespace MWRender const bool mSkyBlending; - osg::ref_ptr getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}); - osg::ref_ptr mIntersectionVisitor; + osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 8424076758..a5787e301e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -99,6 +99,10 @@ namespace return ptr.getClass().getCorrectedModel(ptr); } + // Null node meant to distinguish objects that aren't in the scene from paged objects + // TODO: find a more clever way to make paging exclusion more reliable? + static osg::ref_ptr pagedNode = new SceneUtil::PositionAttitudeTransform; + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { @@ -111,11 +115,6 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); - // Null node meant to distinguish objects that aren't in the scene from paged objects - // TODO: find a more clever way to make paging exclusion more reliable? - static const osg::ref_ptr pagedNode( - new SceneUtil::PositionAttitudeTransform); - ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); @@ -164,13 +163,13 @@ namespace Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin()); const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint)); - const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint)); - const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; @@ -274,7 +273,6 @@ namespace namespace MWWorld { - void Scene::removeFromPagedRefs(const Ptr& ptr) { ESM::RefNum refnum = ptr.getCellRef().getRefNum(); @@ -288,6 +286,11 @@ namespace MWWorld } } + bool Scene::isPagedRef(const Ptr& ptr) const + { + return ptr.getRefData().getBaseNode() == pagedNode.get(); + } + void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) { const auto rot = makeNodeRotation(ptr, order); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index f3dd377845..fdca9bb87f 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -190,6 +190,8 @@ namespace MWWorld void removeFromPagedRefs(const Ptr& ptr); + bool isPagedRef(const Ptr& ptr) const; + void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 689edaf62e..e28efbf671 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1817,9 +1817,10 @@ namespace MWWorld } bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { - MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + MWRender::RenderingManager::RayResult rayRes + = mRendering->castRay(from, to, ignorePlayer, ignoreActors, ignoreList); res.mHit = rayRes.mHit; res.mHitPos = rayRes.mHitPointWorld; res.mHitNormal = rayRes.mHitNormalWorld; @@ -2598,7 +2599,7 @@ namespace MWWorld collisionTypes |= MWPhysics::CollisionType_Water; } MWPhysics::RayCastingResult result - = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector(), collisionTypes); + = mPhysics->castRay(from, to, { MWWorld::Ptr() }, std::vector(), collisionTypes); if (!result.mHit) return maxDist; @@ -3064,8 +3065,8 @@ namespace MWWorld actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); // Check for impact, if yes, handle hit, if not, launch projectile - MWPhysics::RayCastingResult result - = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::RayCastingResult result = mPhysics->castRay( + sourcePos, worldPos, { actor }, targetActors, 0xff, MWPhysics::CollisionType_Projectile); if (result.mHit) MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); else diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b5d56753b0..4e36419e7f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -392,7 +392,7 @@ namespace MWWorld const MWPhysics::RayCastingInterface* getRayCasting() const override; bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) override; + bool ignorePlayer, bool ignoreActors, std::span ignoreList) override; void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 70b09efd90..ea1b8738bc 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -89,6 +89,11 @@ -- radius = 10, -- }) +--- +-- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay} +-- @type CastRenderingRayOptions +-- @field #table ignore A list of @{openmw.core#GameObject} to ignore while doing the ray cast + --- -- Cast ray from one point to another and find the first visual intersection with anything in the scene. -- As opposite to `castRay` can find an intersection with an object without collisions. @@ -97,6 +102,7 @@ -- @function [parent=#nearby] castRenderingRay -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions -- @return #RayCastingResult --- @@ -105,6 +111,7 @@ -- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions --- -- @type NAVIGATOR_FLAGS From 1523a067c9bfe26df2dd08c7ca7785bfc4d90f79 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 12 Feb 2024 17:32:43 +0100 Subject: [PATCH 0981/2167] Use concepts and aggregate initialization --- apps/openmw_test_suite/esm3/testsaveload.cpp | 63 +++++++++----------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 8010e1d7ef..eda1fa963e 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -90,18 +90,17 @@ namespace ESM constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); - template > - struct HasSave : std::false_type + template + concept HasSave = requires(T v, ESMWriter& w) { + v.save(w); }; template - struct HasSave().save(std::declval()))>> : std::true_type - { - }; + concept NotHasSave = !HasSave; - template - auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + template + auto save(const T& record, ESMWriter& writer) { record.save(writer); } @@ -111,8 +110,8 @@ namespace ESM record.save(writer, true); } - template - auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + template + auto save(const T& record, ESMWriter& writer) { writer.writeComposite(record); } @@ -130,36 +129,29 @@ namespace ESM return stream; } - template > - struct HasLoad : std::false_type + template + concept HasLoad = requires(T v, ESMReader& r) { + v.load(r); }; template - struct HasLoad().load(std::declval()))>> : std::true_type + concept HasLoadWithDelete = requires(T v, ESMReader& r, bool& d) { + v.load(r, d); }; template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + concept NotHasLoad = !HasLoad && !HasLoadWithDelete; + + template + void load(ESMReader& reader, T& record) { record.load(reader); } - template > - struct HasLoadWithDelete : std::false_type - { - }; - - template - struct HasLoadWithDelete().load(std::declval(), std::declval()))>> - : std::true_type - { - }; - - template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + template + void load(ESMReader& reader, T& record) { bool deleted = false; record.load(reader, deleted); @@ -171,8 +163,8 @@ namespace ESM record.load(reader, deleted, true); } - template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + template + void load(ESMReader& reader, T& record) { reader.getComposite(record); } @@ -515,12 +507,13 @@ namespace ESM TEST_P(Esm3SaveLoadRecordTest, aiDataShouldNotChange) { - AIData record; - record.mHello = 1; - record.mFight = 2; - record.mFlee = 3; - record.mAlarm = 4; - record.mServices = 5; + AIData record = { + .mHello = 1, + .mFight = 2, + .mFlee = 3, + .mAlarm = 4, + .mServices = 5, + }; AIData result; saveAndLoadRecord(record, GetParam(), result); From 35448bf0fe863662c22032f9623cada7841503e1 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 20:28:56 +0100 Subject: [PATCH 0982/2167] Fix crash when passing a non-callback table to a callback argument --- components/lua/asyncpackage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 8316ab2cde..863680ae9e 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -24,7 +24,11 @@ namespace LuaUtil Callback Callback::fromLua(const sol::table& t) { - return Callback{ t.raw_get(1), t.raw_get(2).mHiddenData }; + const sol::object& function = t.get_or(1, sol::nil); + const sol::object& asyncPackageId = t.get_or(2, sol::nil); + if (!function.is() || !asyncPackageId.is()) + throw std::domain_error("Expected an async:callback, received a table"); + return Callback{ function.as(), asyncPackageId.as().mHiddenData }; } bool Callback::isLuaCallback(const sol::object& t) From 851e291501fa05514a5f0ad9675b5b506e9fc3a9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 13 Feb 2024 00:56:14 +0100 Subject: [PATCH 0983/2167] Simplify and fix the storage subscribe test --- apps/openmw_test_suite/lua/test_storage.cpp | 24 +++++++------- components/lua/asyncpackage.cpp | 36 +++++++++++++-------- components/lua/asyncpackage.hpp | 2 ++ components/lua/luastate.cpp | 2 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index a36a527e0c..165737d1ed 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -15,7 +15,7 @@ namespace return lua.safe_script("return " + luaCode).get(); } - TEST(LuaUtilStorageTest, Basic) + TEST(LuaUtilStorageTest, Subscribe) { // Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState LuaUtil::LuaState luaState{ nullptr, nullptr }; @@ -24,21 +24,21 @@ namespace LuaUtil::LuaStorage storage(mLua); storage.setActive(true); - std::vector callbackCalls; sol::table callbackHiddenData(mLua, sol::create); callbackHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{}; - sol::table callback(mLua, sol::create); - callback[1] = [&](const std::string& section, const sol::optional& key) { - if (key) - callbackCalls.push_back(section + "_" + *key); - else - callbackCalls.push_back(section + "_*"); - }; - callback[2] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; + LuaUtil::getAsyncPackageInitializer( + mLua.lua_state(), []() { return 0.0; }, []() { return 0.0; })(callbackHiddenData); + mLua["async"] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); - mLua["ro"]["subscribe"](mLua["ro"], callback); + + mLua.safe_script(R"( + callbackCalls = {} + ro:subscribe(async:callback(function(section, key) + table.insert(callbackCalls, section .. '_' .. (key or '*')) + end)) + )"); mLua.safe_script("mutable:set('x', 5)"); EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); @@ -58,7 +58,7 @@ namespace EXPECT_EQ(get(mLua, "ro:get('x')"), 4); EXPECT_EQ(get(mLua, "ro:get('y')"), 7); - EXPECT_THAT(callbackCalls, ::testing::ElementsAre("test_x", "test_*", "test_*")); + EXPECT_THAT(get(mLua, "table.concat(callbackCalls, ', ')"), "test_x, test_*, test_*"); } TEST(LuaUtilStorageTest, Table) diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 863680ae9e..6e13406511 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -24,13 +24,32 @@ namespace LuaUtil Callback Callback::fromLua(const sol::table& t) { - const sol::object& function = t.get_or(1, sol::nil); - const sol::object& asyncPackageId = t.get_or(2, sol::nil); + const sol::object& function = t.raw_get(1); + const sol::object& asyncPackageId = t.raw_get(2); if (!function.is() || !asyncPackageId.is()) throw std::domain_error("Expected an async:callback, received a table"); return Callback{ function.as(), asyncPackageId.as().mHiddenData }; } + sol::table Callback::makeMetatable(lua_State* L) + { + sol::table callbackMeta = sol::table::create(L); + callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { + return Callback::fromLua(callback).call(sol::as_args(va)); + }; + callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; + callbackMeta[sol::meta_function::metatable] = false; + callbackMeta["isCallback"] = true; + return callbackMeta; + } + sol::table Callback::make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable) + { + sol::table c = sol::table::create(fn.lua_state(), 2); + c.raw_set(1, std::move(fn), 2, asyncId); + c[sol::metatable_key] = metatable; + return c; + } + bool Callback::isLuaCallback(const sol::object& t) { if (!t.is()) @@ -73,18 +92,9 @@ namespace LuaUtil TimerType::GAME_TIME, gameTimeFn() + delay, asyncId.mScriptId, std::move(callback)); }; - sol::table callbackMeta = sol::table::create(L); - callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { - return Callback::fromLua(callback).call(sol::as_args(va)); - }; - callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; - callbackMeta[sol::meta_function::metatable] = false; - callbackMeta["isCallback"] = true; + sol::table callbackMeta = Callback::makeMetatable(L); api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { - sol::table c = sol::table::create(fn.lua_state(), 2); - c.raw_set(1, std::move(fn), 2, asyncId); - c[sol::metatable_key] = callbackMeta; - return c; + return Callback::make(asyncId, fn, callbackMeta); }; auto initializer = [](sol::table hiddenData) { diff --git a/components/lua/asyncpackage.hpp b/components/lua/asyncpackage.hpp index e8d5f04271..7e9c36de09 100644 --- a/components/lua/asyncpackage.hpp +++ b/components/lua/asyncpackage.hpp @@ -24,6 +24,8 @@ namespace LuaUtil static bool isLuaCallback(const sol::object&); static Callback fromLua(const sol::table&); + static sol::table makeMetatable(lua_State* L); + static sol::table make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable); bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 0a350a2d9f..13e2208b68 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -377,7 +377,7 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); } From 4ffcb5b197e4707dadbb3491bf0c8d59a7b24db0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 13 Feb 2024 00:59:39 +0100 Subject: [PATCH 0984/2167] Prevent deepContentCopy corrupting non-plain tables --- files/data/openmw_aux/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/openmw_aux/ui.lua b/files/data/openmw_aux/ui.lua index 0abd3f2f6d..cadae2a033 100644 --- a/files/data/openmw_aux/ui.lua +++ b/files/data/openmw_aux/ui.lua @@ -24,7 +24,7 @@ function aux_ui.deepLayoutCopy(layout) for k, v in pairs(layout) do if k == 'content' then result[k] = deepContentCopy(v) - elseif type(v) == 'table' then + elseif type(v) == 'table' and getmetatable(v) == nil then result[k] = aux_ui.deepLayoutCopy(v) else result[k] = v From a95a4c19fcac54b03763373a0d80923425b592ab Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 12 Feb 2024 20:06:07 -0600 Subject: [PATCH 0985/2167] Added fixes for several doc files --- .../installation/install-game-files.rst | 36 ++++++++++--------- .../lua-scripting/interface_controls.rst | 2 +- .../lua-scripting/setting_renderers.rst | 2 +- .../modding/custom-shader-effects.rst | 2 +- .../modding/settings/postprocessing.rst | 2 ++ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 6da5d3d55a..8a7a594691 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -101,18 +101,7 @@ Assuming you used the filepath above, your ``.esm`` files will be located in ``~ You can now run the OpenMW launcher, and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ------ -Steam ------ - -Windows -------- - -Windows users can download Morrowind through Steam. -Afterwards, you can point OpenMW to the Steam install location at -``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` -and find ``Morrowind.esm`` there. - +--------------------- XBox Game Pass for PC --------------------- @@ -125,8 +114,21 @@ option and the app will prompt you to move the Morrowind files to a new folder. Once done you can find ``Morrowind.esm`` in the folder you chose. -macOS ----- +Steam +----- + +Windows +^^^^^^^ + +Windows users can download Morrowind through Steam. +Afterwards, you can point OpenMW to the Steam install location at +``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` +and find ``Morrowind.esm`` there. + + +macOS +^^^^^ If you are running macOS, you can also download Morrowind through Steam: @@ -151,9 +153,9 @@ If you are running macOS, you can also download Morrowind through Steam: ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` Linux ------ +^^^^^ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". ---------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Install Steam from "Ubuntu Software" Center #. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles" #. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick. @@ -161,10 +163,10 @@ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". #. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'. #. Everything should now be in place, click that big "PLAY" button and fire up OpenMW. -Nb. Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you dont have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. +Note, Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you don't have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. Wine ----- +~~~~ Users of other platforms running Wine can run Steam within it and find ``Morrowind.esm`` at diff --git a/docs/source/reference/lua-scripting/interface_controls.rst b/docs/source/reference/lua-scripting/interface_controls.rst index c4b1709f59..f8e8fd0d4e 100644 --- a/docs/source/reference/lua-scripting/interface_controls.rst +++ b/docs/source/reference/lua-scripting/interface_controls.rst @@ -4,5 +4,5 @@ Interface Controls .. include:: version.rst .. raw:: html - :file: generated_html/scripts_omw_playercontrols.html + :file: generated_html/scripts_omw_input_playercontrols.html diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..eac35f9187 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -128,7 +128,7 @@ Table with the following optional fields: - Disables changing the setting from the UI inputBinding ------ +------------ Allows the user to bind inputs to an action or trigger diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 60a306a97a..f0a517f9f1 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -61,7 +61,7 @@ This effect is used to imitate effects such as refraction and heat distortion. A diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture. Blue and alpha channels are ignored. -To use this feature the :ref:`post processing` setting must be enabled. +To use this feature the :ref:`post processing ` setting must be enabled. This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: :: diff --git a/docs/source/reference/modding/settings/postprocessing.rst b/docs/source/reference/modding/settings/postprocessing.rst index b4b2f5a8d4..ed876c88d6 100644 --- a/docs/source/reference/modding/settings/postprocessing.rst +++ b/docs/source/reference/modding/settings/postprocessing.rst @@ -1,6 +1,8 @@ Post Processing Settings ######################## +.. _Post Processing: + enabled ------- From 6486f3f2cff1b70f53197e8da3dd120cc84a5555 Mon Sep 17 00:00:00 2001 From: Epoch Date: Tue, 13 Feb 2024 09:18:18 +0000 Subject: [PATCH 0986/2167] Add option to use camera as sound listener --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/launcher/settingspage.cpp | 4 ++++ apps/launcher/ui/settingspage.ui | 10 +++++++++ apps/openmw/mwrender/camera.cpp | 9 ++++++-- apps/openmw/mwrender/camera.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 22 +++++++++++-------- components/settings/categories/sound.hpp | 1 + .../reference/modding/settings/sound.rst | 13 +++++++++++ files/lang/launcher_de.ts | 8 +++++++ files/lang/launcher_fr.ts | 8 +++++++ files/lang/launcher_ru.ts | 8 +++++++ files/settings-default.cfg | 3 +++ 13 files changed, 79 insertions(+), 11 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 8873113da2..4d03fba227 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -79,6 +79,7 @@ Programmers Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) + Epoch Eris Caffee (eris) eroen escondida diff --git a/CHANGELOG.md b/CHANGELOG.md index 93eb959e18..c2a8c8e34d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,7 @@ Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics Feature #5926: Refraction based on water depth + Feature #5944: Option to use camera as sound listener Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index c274b75f79..0b5f542888 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -294,6 +294,7 @@ bool Launcher::SettingsPage::loadSettings() hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); } } + loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox); } // Interface Changes @@ -490,6 +491,9 @@ void Launcher::SettingsPage::saveSettings() Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); else Settings::sound().mHrtf.set({}); + + const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked; + Settings::sound().mCameraListener.set(cCameraListener); } // Interface Changes diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 2d06c1802e..a30309bd1b 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1320,6 +1320,16 @@ + + + + In third-person view, use the camera as the sound listener instead of the player character. + + + Use the camera as the sound listener + + + diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 86d5699d75..1c163a6701 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -109,8 +109,7 @@ namespace MWRender void Camera::updateCamera(osg::Camera* cam) { - osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) - * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + osg::Quat orient = getOrient(); osg::Vec3d forward = orient * osg::Vec3d(0, 1, 0); osg::Vec3d up = orient * osg::Vec3d(0, 0, 1); @@ -209,6 +208,12 @@ namespace MWRender mPosition = focal + offset; } + osg::Quat Camera::getOrient() const + { + return osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) + * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + } + void Camera::setMode(Mode newMode, bool force) { if (mMode == newMode) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index c6500160fd..e09a265293 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -72,6 +72,8 @@ namespace MWRender void setExtraYaw(float angle) { mExtraYaw = angle; } void setExtraRoll(float angle) { mExtraRoll = angle; } + osg::Quat getOrient() const; + /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force = false); bool toggleVanityMode(bool enable); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 689edaf62e..2a776ce051 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1738,23 +1738,27 @@ namespace MWWorld void World::updateSoundListener() { - osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); + const MWRender::Camera* camera = mRendering->getCamera(); const auto& player = getPlayerPtr(); const ESM::Position& refpos = player.getRefData().getPosition(); - osg::Vec3f listenerPos; + osg::Vec3f listenerPos, up, forward; + osg::Quat listenerOrient; - if (isFirstPerson()) - listenerPos = cameraPosition; + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerPos = camera->getPosition(); else listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(player).z()); - osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) - * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerOrient = camera->getOrient(); + else + listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); - osg::Vec3f forward = listenerOrient * osg::Vec3f(0, 1, 0); - osg::Vec3f up = listenerOrient * osg::Vec3f(0, 0, 1); + forward = listenerOrient * osg::Vec3f(0, 1, 0); + up = listenerOrient * osg::Vec3f(0, 0, 1); - bool underwater = isUnderwater(player.getCell(), cameraPosition); + bool underwater = isUnderwater(player.getCell(), camera->getPosition()); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 995bce2a58..8398a38c55 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -23,6 +23,7 @@ namespace Settings SettingValue mBufferCacheMax{ mIndex, "Sound", "buffer cache max", makeMaxSanitizerInt(1) }; SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; + SettingValue mCameraListener{ mIndex, "Sound", "camera listener" }; }; } diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 7a5718735c..dbcad65d0d 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -127,3 +127,16 @@ Allowed values for this field are enumerated in openmw.log file is an HRTF enabl The default value is empty, which uses the default profile. This setting can be controlled in the Settings tab of the launcher. + +camera listener +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +When true, uses the camera position and direction for audio instead of the player position. +This makes audio in third person sound relative to camera instead of the player. +False is vanilla Morrowind behaviour. + +This setting can be controlled in the Settings tab of the launcher. \ No newline at end of file diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 1c23f90425..5d785267a8 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + In third-person view, use the camera as the sound listener instead of the player character. + + + + Use the camera as the sound listener + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f604044368..58232efb7c 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + In third-person view, use the camera as the sound listener instead of the player character. + + + + Use the camera as the sound listener + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 844e36898c..1a0ea8d2cf 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1462,5 +1462,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness Минимальный уровень освещения в помещениях + + In third-person view, use the camera as the sound listener instead of the player character. + Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. + + + Use the camera as the sound listener + Использовать камеру как слушателя + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a90a46cc5..1caf03c123 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -608,6 +608,9 @@ hrtf enable = -1 # Specifies which HRTF to use when HRTF is used. Blank means use the default. hrtf = +# Specifies whether to use camera as audio listener +camera listener = false + [Video] # Resolution of the OpenMW window or screen. From 8f88838ff515baafa0798f55a9b063259f253dc6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 13 Feb 2024 19:30:32 +0100 Subject: [PATCH 0987/2167] Remove dead code --- apps/openmw/mwlua/animationbindings.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index d024c41307..5c4ccf7212 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -21,24 +21,6 @@ #include "animationbindings.hpp" #include -namespace MWLua -{ - struct AnimationGroup; - struct TextKeyCallback; -} - -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; - template <> - struct is_automagical> : std::false_type - { - }; -} - namespace MWLua { using BlendMask = MWRender::Animation::BlendMask; From aa4303fc385fd35fa63997290153835c9c7d2cf8 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:25:12 +0100 Subject: [PATCH 0988/2167] Fix crash when throwing in index meta methods --- CMakeLists.txt | 1 + components/lua/luastate.hpp | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b1b18e41e..3a69f28c75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,6 +477,7 @@ if (NOT WIN32) endif() # C++ library binding to Lua +add_compile_definitions(SOL_ALL_SAFETIES_ON) set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index aea1e32590..f14fc44867 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -232,16 +232,36 @@ namespace LuaUtil } } + // work around for a (likely) sol3 bug + // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error + template + sol::object safe_get(const sol::table& table, const Key& key) + { + auto index = table.traverse_raw_get>( + sol::metatable_key, sol::meta_function::index); + if (index) + { + sol::protected_function_result result = index.value()(table, key); + if (result.valid()) + return result.get(); + else + throw result.get(); + } + else + return table.raw_get(key); + } + // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. template sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) { if (!table.is()) return sol::nil; + sol::object value = safe_get(table.as(), first); if constexpr (sizeof...(str) == 0) - return table.as()[first]; + return value; else - return getFieldOrNil(table.as()[first], str...); + return getFieldOrNil(value, str...); } // String representation of a Lua object. Should be used for debugging/logging purposes only. From 550659c2d936cb658c676f461effa55f32934e89 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:40:07 +0100 Subject: [PATCH 0989/2167] Fix loadVFS error handling --- components/lua/luastate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 13e2208b68..3cf6378f2f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -376,7 +376,7 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { - if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) + if (!res.valid()) throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); @@ -397,7 +397,7 @@ namespace LuaUtil std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text); if (!res.valid()) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); return res; } From 08b7ee8a44f40fc50621495ad29ed51f46928954 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:54:35 +0100 Subject: [PATCH 0990/2167] Test LuaUtil::safeGet preventing crash --- apps/openmw_test_suite/lua/test_lua.cpp | 12 +++++++++++- components/lua/luastate.hpp | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 90c987522d..a669a3c670 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -51,6 +51,9 @@ return { } )X"); + TestingOpenMW::VFSTestFile metaIndexErrorFile( + "return setmetatable({}, { __index = function(t, key) error('meta index error') end })"); + std::string genBigScript() { std::stringstream buf; @@ -70,7 +73,7 @@ return { { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "aaa/counter.lua", &counterFile }, { "bbb/tests.lua", &testsFile }, { "invalid.lua", &invalidScriptFile }, { "big.lua", &bigScriptFile }, - { "requireBig.lua", &requireBigScriptFile } }); + { "requireBig.lua", &requireBigScriptFile }, { "metaIndexError.lua", &metaIndexErrorFile } }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{ mVFS.get(), &mCfg }; @@ -223,4 +226,11 @@ return { // At this moment all instances of the script should be garbage-collected. EXPECT_LT(memWithoutScript, memWithScript); } + + TEST_F(LuaStateTest, SafeIndexMetamethod) + { + sol::table t = mLua.runInNewSandbox("metaIndexError.lua"); + // without safe get we crash here + EXPECT_ERROR(LuaUtil::safeGet(t, "any key"), "meta index error"); + } } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index f14fc44867..509b5e16e1 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -235,7 +235,7 @@ namespace LuaUtil // work around for a (likely) sol3 bug // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error template - sol::object safe_get(const sol::table& table, const Key& key) + sol::object safeGet(const sol::table& table, const Key& key) { auto index = table.traverse_raw_get>( sol::metatable_key, sol::meta_function::index); @@ -257,7 +257,7 @@ namespace LuaUtil { if (!table.is()) return sol::nil; - sol::object value = safe_get(table.as(), first); + sol::object value = safeGet(table.as(), first); if constexpr (sizeof...(str) == 0) return value; else From d6f112aef295734913de09eca12f0a66147d4c8d Mon Sep 17 00:00:00 2001 From: Anton Uramer Date: Tue, 13 Feb 2024 12:35:28 +0100 Subject: [PATCH 0991/2167] Revert Lua sol safeties flag --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a69f28c75..7b1b18e41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,7 +477,6 @@ if (NOT WIN32) endif() # C++ library binding to Lua -add_compile_definitions(SOL_ALL_SAFETIES_ON) set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) From 91e7eebefb2c0d9847d67258da27f0d834fa4f46 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 14 Feb 2024 13:32:39 +0000 Subject: [PATCH 0992/2167] Clarify interaction between clamp lighting and terrain --- docs/source/reference/modding/settings/shaders.rst | 4 ++-- files/settings-default.cfg | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 8bd152857f..121ecef2b1 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -38,8 +38,8 @@ clamp lighting :Default: True Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -Only affects objects drawn with shaders (see :ref:`force shaders` option). -Always affects terrain. +Only affects objects drawn with shaders (see :ref:`force shaders` option) as objects drawn without shaders always have clamped lighting. +When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, but the lighting may appear dull and there might be colour shifts. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5f68f055d4..4fc632f497 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -420,7 +420,8 @@ force shaders = false force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. +# Only affects objects that render with shaders (see 'force shaders' option). +# When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. # Setting this option to 'true' results in fixed-function compatible lighting, but the lighting # may appear 'dull' and there might be color shifts. # Setting this option to 'false' results in more realistic lighting. From b31664a78ff10a97c9f59993985bb660e4301b86 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 1 Jan 2024 12:47:01 -0600 Subject: [PATCH 0993/2167] Fix(CS): Scale actors according to their race's stats --- CHANGELOG.md | 1 + apps/opencs/model/world/actoradapter.cpp | 5 +++++ apps/opencs/model/world/actoradapter.hpp | 2 ++ apps/opencs/view/render/actor.cpp | 14 +++++++++++++- apps/opencs/view/render/actor.hpp | 3 ++- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c473f068dd..b649ebcb90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7742: Governing attribute training limit should use the modified attribute + Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7765: OpenMW-CS: Touch Record option is broken diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 37aaf08445..e4b577480d 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -163,6 +163,11 @@ namespace CSMWorld return it->second.first; } + const ESM::RefId& ActorAdapter::ActorData::getActorRaceName() const + { + return mRaceData->getId(); + } + bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 9747f448ae..5ab4da2e58 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -96,6 +96,8 @@ namespace CSMWorld std::string getSkeleton() const; /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; + + const ESM::RefId& getActorRaceName() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d1bfac0ec6..cfa06012ff 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include #include +#include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" namespace CSVRender @@ -29,7 +31,7 @@ namespace CSVRender Actor::Actor(const ESM::RefId& id, CSMWorld::Data& data) : mId(id) , mData(data) - , mBaseNode(new osg::Group()) + , mBaseNode(new osg::PositionAttitudeTransform()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); @@ -60,6 +62,16 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); + + const CSMWorld::IdCollection& races = mData.getRaces(); + const auto& targetRace = races.getRecord(mActorData->getActorRaceName()).get().mData; + osg::Vec3d scale; + + mActorData.get()->isFemale() + ? scale = osg::Vec3(targetRace.mFemaleWeight, targetRace.mFemaleWeight, targetRace.mFemaleHeight) + : scale = osg::Vec3(targetRace.mMaleWeight, targetRace.mMaleWeight, targetRace.mMaleHeight); + + mBaseNode->setScale(scale); } else { diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 86c7e7ff2d..a9cc34b00d 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -59,7 +60,7 @@ namespace CSVRender CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; - osg::ref_ptr mBaseNode; + osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; SceneUtil::NodeMapVisitor::NodeMap mNodeMap; }; From 049550d73ef927fd76b596cef99fd2e483912846 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 1 Jan 2024 21:00:00 -0600 Subject: [PATCH 0994/2167] Cleanup(Actoradapter.cpp): Create new struct for race stats, use std::pair instead --- apps/opencs/model/world/actoradapter.cpp | 18 ++++++++++++---- apps/opencs/model/world/actoradapter.hpp | 27 ++++++++++++++++++++++-- apps/opencs/view/render/actor.cpp | 10 ++------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index e4b577480d..f3c2161c73 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,6 +67,11 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } + const std::pair& ActorAdapter::RaceData::getRaceAttributes(bool isFemale) + { + return isFemale == true ? mHeightsWeights.mFemaleAttributes : mHeightsWeights.mMaleAttributes; + } + bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); @@ -90,10 +95,11 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const BodyAttributes& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; + mHeightsWeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) @@ -163,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const ESM::RefId& ActorAdapter::ActorData::getActorRaceName() const + const std::pair& ActorAdapter::ActorData::getRaceHeightWeight() const { - return mRaceData->getId(); + return mRaceData->getRaceAttributes(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -509,7 +515,11 @@ namespace CSMWorld } auto& race = raceRecord.get(); - data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); + + BodyAttributes scaleStats = BodyAttributes( + race.mData.mMaleHeight, race.mData.mMaleWeight, race.mData.mFemaleHeight, race.mData.mFemaleWeight); + + data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 5ab4da2e58..21298d33df 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,6 +41,25 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; + class BodyAttributes + { + public: + std::pair mMaleAttributes; + std::pair mFemaleAttributes; + + BodyAttributes() + : mMaleAttributes(std::make_pair(1.0f, 1.0f)) + , mFemaleAttributes(std::make_pair(1.0f, 1.0f)) + { + } + + BodyAttributes(float maleHeight, float maleWeight, float femaleHeight, float femaleWeight) + { + mMaleAttributes = std::make_pair(maleHeight, maleWeight); + mFemaleAttributes = std::make_pair(femaleHeight, femaleWeight); + } + }; + /// Contains base race data shared between actors class RaceData { @@ -57,6 +76,8 @@ namespace CSMWorld const ESM::RefId& getFemalePart(ESM::PartReferenceType index) const; /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; + + const std::pair& getRaceAttributes(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -67,7 +88,8 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data(const ESM::RefId& raceId, bool isBeast = false); + void reset_data( + const ESM::RefId& raceId, const BodyAttributes& raceStats = BodyAttributes(), bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -75,6 +97,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; + BodyAttributes mHeightsWeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -97,7 +120,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const ESM::RefId& getActorRaceName() const; + const std::pair& getRaceHeightWeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index cfa06012ff..abe7b910de 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -63,15 +63,9 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const CSMWorld::IdCollection& races = mData.getRaces(); - const auto& targetRace = races.getRecord(mActorData->getActorRaceName()).get().mData; - osg::Vec3d scale; + const std::pair attributes = mActorData.get()->getRaceHeightWeight(); - mActorData.get()->isFemale() - ? scale = osg::Vec3(targetRace.mFemaleWeight, targetRace.mFemaleWeight, targetRace.mFemaleHeight) - : scale = osg::Vec3(targetRace.mMaleWeight, targetRace.mMaleWeight, targetRace.mMaleHeight); - - mBaseNode->setScale(scale); + mBaseNode->setScale(osg::Vec3d(attributes.second, attributes.second, attributes.first)); } else { From 98ad059806e1ec7d8fb3ff15d55658d2640c3930 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 2 Jan 2024 16:08:25 -0600 Subject: [PATCH 0995/2167] Cleanup(actoradapter): Use more explicit names & vec2 for racial height/weight --- apps/opencs/model/world/actoradapter.cpp | 14 ++++++------- apps/opencs/model/world/actoradapter.hpp | 26 ++++++++++++------------ apps/opencs/view/render/actor.cpp | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index f3c2161c73..40bfe17989 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,9 +67,9 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } - const std::pair& ActorAdapter::RaceData::getRaceAttributes(bool isFemale) + const osg::Vec2f& ActorAdapter::RaceData::getHeightWeight(bool isFemale) { - return isFemale == true ? mHeightsWeights.mFemaleAttributes : mHeightsWeights.mMaleAttributes; + return isFemale ? mHeightsWeights.mFemaleHeightWeight : mHeightsWeights.mMaleHeightWeight; } bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const @@ -95,7 +95,7 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const BodyAttributes& raceStats, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const HeightsWeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; @@ -169,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const std::pair& ActorAdapter::ActorData::getRaceHeightWeight() const + const osg::Vec2f& ActorAdapter::ActorData::getRaceHeightWeight() const { - return mRaceData->getRaceAttributes(isFemale()); + return mRaceData->getHeightWeight(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -516,8 +516,8 @@ namespace CSMWorld auto& race = raceRecord.get(); - BodyAttributes scaleStats = BodyAttributes( - race.mData.mMaleHeight, race.mData.mMaleWeight, race.mData.mFemaleHeight, race.mData.mFemaleWeight); + HeightsWeights scaleStats = HeightsWeights(osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), + osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight)); data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 21298d33df..e516325455 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,22 +41,22 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; - class BodyAttributes + class HeightsWeights { public: - std::pair mMaleAttributes; - std::pair mFemaleAttributes; + osg::Vec2f mMaleHeightWeight; + osg::Vec2f mFemaleHeightWeight; - BodyAttributes() - : mMaleAttributes(std::make_pair(1.0f, 1.0f)) - , mFemaleAttributes(std::make_pair(1.0f, 1.0f)) + HeightsWeights() + : mMaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) + , mFemaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) { } - BodyAttributes(float maleHeight, float maleWeight, float femaleHeight, float femaleWeight) + HeightsWeights(const osg::Vec2f& maleHeightWeight, const osg::Vec2f& femaleHeightWeight) { - mMaleAttributes = std::make_pair(maleHeight, maleWeight); - mFemaleAttributes = std::make_pair(femaleHeight, femaleWeight); + mMaleHeightWeight = maleHeightWeight; + mFemaleHeightWeight = femaleHeightWeight; } }; @@ -77,7 +77,7 @@ namespace CSMWorld /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; - const std::pair& getRaceAttributes(bool isFemale); + const osg::Vec2f& getHeightWeight(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -89,7 +89,7 @@ namespace CSMWorld void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies void reset_data( - const ESM::RefId& raceId, const BodyAttributes& raceStats = BodyAttributes(), bool isBeast = false); + const ESM::RefId& raceId, const HeightsWeights& raceStats = HeightsWeights(), bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -97,7 +97,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; - BodyAttributes mHeightsWeights; + HeightsWeights mHeightsWeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -120,7 +120,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const std::pair& getRaceHeightWeight() const; + const osg::Vec2f& getRaceHeightWeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index abe7b910de..e609efa5a1 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -63,9 +63,9 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const std::pair attributes = mActorData.get()->getRaceHeightWeight(); + const osg::Vec2f& attributes = mActorData.get()->getRaceHeightWeight(); - mBaseNode->setScale(osg::Vec3d(attributes.second, attributes.second, attributes.first)); + mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y())); } else { From a0ba0d781a0bf7427ada397fd89de471c3483098 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 15 Feb 2024 01:50:49 +0300 Subject: [PATCH 0996/2167] Hide magnitude for Fortify Maximum Magicka when requested as well (#7832) --- CHANGELOG.md | 1 + apps/openmw/mwgui/widgets.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96eb36c9f..ca925516f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,7 @@ Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7796: Absorbed enchantments don't restore magicka + Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index d824682308..fea6d490c5 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -371,7 +371,7 @@ namespace MWGui::Widgets std::string spellLine = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); - if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) + if ((mEffectParams.mMagnMin || mEffectParams.mMagnMax) && !mEffectParams.mNoMagnitude) { ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) @@ -386,7 +386,7 @@ namespace MWGui::Widgets spellLine += formatter.str(); } - else if (displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) + else if (displayType != ESM::MagicEffect::MDT_None) { spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) From 1b1f0c4971a792622bff7f323b9642ceb0d17b4a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 14 Feb 2024 16:41:24 -0600 Subject: [PATCH 0997/2167] Switch height/weight in names and make the stats a simple struct instead --- apps/opencs/model/world/actoradapter.cpp | 16 ++++++------- apps/opencs/model/world/actoradapter.hpp | 29 +++++++----------------- apps/opencs/view/render/actor.cpp | 3 +-- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 40bfe17989..acbe6b5d38 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,9 +67,9 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } - const osg::Vec2f& ActorAdapter::RaceData::getHeightWeight(bool isFemale) + const osg::Vec2f& ActorAdapter::RaceData::getGenderWeightHeight(bool isFemale) { - return isFemale ? mHeightsWeights.mFemaleHeightWeight : mHeightsWeights.mMaleHeightWeight; + return isFemale ? mWeightsHeights.mFemaleWeightHeight : mWeightsHeights.mMaleWeightHeight; } bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const @@ -95,11 +95,11 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const HeightsWeights& raceStats, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const WeightsHeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; - mHeightsWeights = raceStats; + mWeightsHeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) @@ -169,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const osg::Vec2f& ActorAdapter::ActorData::getRaceHeightWeight() const + const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const { - return mRaceData->getHeightWeight(isFemale()); + return mRaceData->getGenderWeightHeight(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -516,8 +516,8 @@ namespace CSMWorld auto& race = raceRecord.get(); - HeightsWeights scaleStats = HeightsWeights(osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), - osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight)); + WeightsHeights scaleStats = { osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), + osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight) }; data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index e516325455..1650fc9006 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,23 +41,10 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; - class HeightsWeights + struct WeightsHeights { - public: - osg::Vec2f mMaleHeightWeight; - osg::Vec2f mFemaleHeightWeight; - - HeightsWeights() - : mMaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) - , mFemaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) - { - } - - HeightsWeights(const osg::Vec2f& maleHeightWeight, const osg::Vec2f& femaleHeightWeight) - { - mMaleHeightWeight = maleHeightWeight; - mFemaleHeightWeight = femaleHeightWeight; - } + osg::Vec2f mMaleWeightHeight; + osg::Vec2f mFemaleWeightHeight; }; /// Contains base race data shared between actors @@ -77,7 +64,7 @@ namespace CSMWorld /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; - const osg::Vec2f& getHeightWeight(bool isFemale); + const osg::Vec2f& getGenderWeightHeight(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -88,8 +75,8 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data( - const ESM::RefId& raceId, const HeightsWeights& raceStats = HeightsWeights(), bool isBeast = false); + void reset_data(const ESM::RefId& raceId, + const WeightsHeights& raceStats = { osg::Vec2f(1.f, 1.f), osg::Vec2f(1.f, 1.f) }, bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -97,7 +84,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; - HeightsWeights mHeightsWeights; + WeightsHeights mWeightsHeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -120,7 +107,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const osg::Vec2f& getRaceHeightWeight() const; + const osg::Vec2f& getRaceWeightHeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index e609efa5a1..8aa08e443e 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -21,7 +21,6 @@ #include #include -#include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" namespace CSVRender @@ -63,7 +62,7 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const osg::Vec2f& attributes = mActorData.get()->getRaceHeightWeight(); + const osg::Vec2f& attributes = mActorData->getRaceWeightHeight(); mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y())); } From 54e90b4ac2693eaeaebe5caeed96728286204d98 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 14 Feb 2024 20:00:22 -0600 Subject: [PATCH 0998/2167] Legacy(columnimp): Add TESCS limits for race weight/height scaling --- apps/opencs/model/world/columnimp.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eba09bd8b1..099dd25983 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -585,19 +585,22 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); + + float bodyAttr = std::max(0.5f, std::min(2.0f, data.toFloat())); + if (mWeight) { if (mMale) - record2.mData.mMaleWeight = data.toFloat(); + record2.mData.mMaleWeight = bodyAttr; else - record2.mData.mFemaleWeight = data.toFloat(); + record2.mData.mFemaleWeight = bodyAttr; } else { if (mMale) - record2.mData.mMaleHeight = data.toFloat(); + record2.mData.mMaleHeight = bodyAttr; else - record2.mData.mFemaleHeight = data.toFloat(); + record2.mData.mFemaleHeight = bodyAttr; } record.setModified(record2); } From 9a7b9572fdf9b4acbba46ac4ea5cc0c27bc919aa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 5 Feb 2024 19:13:32 +0400 Subject: [PATCH 0999/2167] Deploy base Qt translations when needed --- CMakeLists.txt | 39 ++++++++++++++++++++++++++---- apps/launcher/main.cpp | 13 ++-------- apps/wizard/main.cpp | 13 ++-------- components/CMakeLists.txt | 5 ++++ components/l10n/qttranslations.cpp | 37 ++++++++++++++++++++++++++++ components/l10n/qttranslations.hpp | 16 ++++++++++++ 6 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 components/l10n/qttranslations.cpp create mode 100644 components/l10n/qttranslations.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 313cc46cbe..3f5745d4a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) endif() +if (APPLE OR WIN32) + set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON) +else () + set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF) +endif () + +option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT}) + # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) @@ -1099,13 +1107,13 @@ if (USE_QT) if (BUILD_LAUNCHER OR BUILD_WIZARD) if (APPLE) - set(QT_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") else () get_generator_is_multi_config(multi_config) if (multi_config) - set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") else () - set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") endif () endif () @@ -1121,9 +1129,30 @@ if (USE_QT) qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent) + if (DEPLOY_QT_TRANSLATIONS) + # Once we set a Qt 6.2.0 as a minimum required version, we may use "qtpaths --qt-query" instead. + get_target_property(QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) + execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_TRANSLATIONS + OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + + foreach(QM_FILE ${QM_FILES}) + get_filename_component(QM_BASENAME ${QM_FILE} NAME) + string(REGEX REPLACE "[^_]+_(.*)\\.qm" "\\1" LANG_NAME ${QM_BASENAME}) + if (EXISTS "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm") + set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm") + elseif (EXISTS "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm") + set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm") + else () + message(FATAL_ERROR "Qt translations for '${LANG_NAME}' locale are not found in the '${QT_TRANSLATIONS_DIR}' folder.") + endif () + endforeach(QM_FILE) + + list(REMOVE_DUPLICATES QM_FILES) + endif () + add_custom_target(qm-files - COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_TRANSLATIONS_PATH} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH} + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_OPENMW_TRANSLATIONS_PATH} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_OPENMW_TRANSLATIONS_PATH} DEPENDS ${QM_FILES} COMMENT "Copy *.qm files to resources folder") endif () diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 3cbd1f5092..7fdcac77ab 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED @@ -41,16 +41,7 @@ int runLauncher(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); - - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/launcher_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); + l10n::installQtTranslations(app, "launcher", resourcesPath); Launcher::MainDialog mainWin(configurationManager); diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index a911ac7350..b62428f946 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -1,11 +1,11 @@ #include #include -#include #include #include #include +#include #include "mainwizard.hpp" @@ -45,16 +45,7 @@ int main(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); - - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/wizard_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); + l10n::installQtTranslations(app, "wizard", resourcesPath); Wizard::MainWizard wizard; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bb6fd1dd37..317786e96a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -500,11 +500,16 @@ if (USE_QT) model/loadordererror view/combobox view/contentselector ) + add_component_qt_dir (config gamesettings launchersettings ) + add_component_qt_dir (l10n + qttranslations + ) + add_component_qt_dir (process processinvoker ) diff --git a/components/l10n/qttranslations.cpp b/components/l10n/qttranslations.cpp new file mode 100644 index 0000000000..9bc146699e --- /dev/null +++ b/components/l10n/qttranslations.cpp @@ -0,0 +1,37 @@ +#include "qttranslations.hpp" + +#include +#include + +namespace l10n +{ + QTranslator AppTranslator{}; + QTranslator ComponentsTranslator{}; + QTranslator QtBaseAppTranslator{}; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath) + { + // Try to load OpenMW translations from resources folder first. + // If we loaded them, try to load Qt translations from both + // resources folder and default translations folder as well. +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto qtPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); +#else + auto qtPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); +#endif + auto localPath = resourcesPath + "/translations"; + + if (AppTranslator.load(QLocale::system(), appName, "_", localPath) + && ComponentsTranslator.load(QLocale::system(), "components", "_", localPath)) + { + app.installTranslator(&AppTranslator); + app.installTranslator(&ComponentsTranslator); + + if (QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", qtPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", qtPath)) + app.installTranslator(&QtBaseAppTranslator); + } + } +} diff --git a/components/l10n/qttranslations.hpp b/components/l10n/qttranslations.hpp new file mode 100644 index 0000000000..3ce87f0837 --- /dev/null +++ b/components/l10n/qttranslations.hpp @@ -0,0 +1,16 @@ +#ifndef COMPONENTS_L10N_QTTRANSLATIONS_H +#define COMPONENTS_L10N_QTTRANSLATIONS_H + +#include +#include + +namespace l10n +{ + extern QTranslator AppTranslator; + extern QTranslator ComponentsTranslator; + extern QTranslator QtBaseAppTranslator; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath); +} + +#endif // COMPONENTS_L10N_QTTRANSLATIONS_H From 9c959d9698ff05adc493c0e8d57282d0b6d1cf94 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 16 Feb 2024 01:33:52 +0000 Subject: [PATCH 1000/2167] Make a long sentence more concise Thanks Bing Chat for doing a mediocre job of this that inspired me to do a competent job of it. --- docs/source/reference/modding/settings/shaders.rst | 2 +- files/settings-default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 121ecef2b1..22ceb34f44 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -10,7 +10,7 @@ force shaders Force rendering with shaders, even for objects that don't strictly need them. By default, only objects with certain effects, such as bump or normal maps will use shaders. -Many visual enhancements, such as :ref:`enable shadows` and :ref:`reverse z` require shaders to be used for all objects, and so behave as if this setting is true. +With enhancements enabled, such as :ref:`enable shadows` and :ref:`reverse z`, shaders must be used for all objects, as if this setting is true. Typically, one or more of these enhancements will be enabled, and shaders will be needed for everything anyway, meaning toggling this setting will have no effect. Some settings, such as :ref:`clamp lighting` only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4fc632f497..15f82760c5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -407,7 +407,7 @@ console history buffer size = 4096 # Force rendering with shaders, even for objects that don't strictly need them. # By default, only objects with certain effects, such as bump or normal maps will use shaders. -# Many visual enhancements, such as "enable shadows" and "reverse z" require shaders to be used for all objects, and so behave as if this setting is true. +# With enhancements enabled, such as "enable shadows" and "reverse z", shaders must be used for all objects, as if this setting is true. # Some settings, such as "clamp lighting" only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. # Otherwise, there should not be a visual difference. force shaders = false From 54f4c69d37795c9045221339b4dce602c6ff8cc7 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 15 Feb 2024 21:25:29 -0600 Subject: [PATCH 1001/2167] Cleanup(columnimp): Use std::clamp to limit race scaling --- apps/opencs/model/world/columnimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 099dd25983..0cd95b0ed2 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -586,7 +586,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - float bodyAttr = std::max(0.5f, std::min(2.0f, data.toFloat())); + float bodyAttr = std::clamp(data.toFloat(), 0.5f, 2.0f); if (mWeight) { From 6e81927d60d7df17195f16f8436430f173443807 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 13:06:48 +0300 Subject: [PATCH 1002/2167] Make extra sure to ignore movement input for scripted animations (#7833) --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cbd09b30d8..be3652b0a9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2070,7 +2070,7 @@ namespace MWMechanics vec.x() *= speed; vec.y() *= speed; - if (isKnockedOut() || isKnockedDown() || isRecovery()) + if (isKnockedOut() || isKnockedDown() || isRecovery() || isScriptedAnimPlaying()) vec = osg::Vec3f(); CharacterState movestate = CharState_None; From 78737141034169ee844b7e67b91c77ab48f30400 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 13:34:41 +0300 Subject: [PATCH 1003/2167] Restore vertical movement reset for various movement states (#7833) Note getJump already handles incapacitation states (dead/paralyzed/KO) --- apps/openmw/mwmechanics/character.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index be3652b0a9..49221cbae9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2144,6 +2144,10 @@ namespace MWMechanics bool wasInJump = mInJump; mInJump = false; + const float jumpHeight = cls.getJump(mPtr); + if (jumpHeight <= 0.f || sneak || inwater || flying || !solid) + vec.z() = 0.f; + if (!inwater && !flying && solid) { // In the air (either getting up —ascending part of jump— or falling). @@ -2162,20 +2166,16 @@ namespace MWMechanics vec.z() = 0.0f; } // Started a jump. - else if (mJumpState != JumpState_InAir && vec.z() > 0.f && !sneak) + else if (mJumpState != JumpState_InAir && vec.z() > 0.f) { - float z = cls.getJump(mPtr); - if (z > 0.f) + mInJump = true; + if (vec.x() == 0 && vec.y() == 0) + vec.z() = jumpHeight; + else { - mInJump = true; - if (vec.x() == 0 && vec.y() == 0) - vec.z() = z; - else - { - osg::Vec3f lat(vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } + osg::Vec3f lat(vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * jumpHeight * 0.707f; } } } From aae74224e8cebb590e9f31b99225453d0f10934a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 14:28:43 +0300 Subject: [PATCH 1004/2167] Prevent swim upward correction from causing false-positive jumping events (#7833) --- apps/openmw/mwmechanics/character.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 49221cbae9..d1f41ad514 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2146,7 +2146,12 @@ namespace MWMechanics mInJump = false; const float jumpHeight = cls.getJump(mPtr); if (jumpHeight <= 0.f || sneak || inwater || flying || !solid) + { vec.z() = 0.f; + // Following code might assign some vertical movement regardless, need to reset this manually + // This is used for jumping detection + movementSettings.mPosition[2] = 0; + } if (!inwater && !flying && solid) { From d9ee54ae98a2a75cd37fd980ebd16dabe5e2f251 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 11:33:57 +0300 Subject: [PATCH 1005/2167] DebugCustomDraw: Correct PerContextProgram use, clean up drawImplementation --- components/debug/debugdraw.cpp | 54 +++++++++++++--------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index bbbf2c238e..e7aa7d8cce 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -265,59 +265,45 @@ namespace Debug const osg::StateSet* stateSet = getStateSet(); - auto program = static_cast(stateSet->getAttribute(osg::StateAttribute::PROGRAM)); - const osg::Program::PerContextProgram* pcp = program->getPCP(*state); - if (!pcp) - { - return; - } + const osg::Program::PerContextProgram& pcp = *state->getLastAppliedProgramObject(); + auto transLocation = pcp.getUniformLocation(stateSet->getUniform("trans")->getNameID()); + auto colLocation = pcp.getUniformLocation(stateSet->getUniform("color")->getNameID()); + auto scaleLocation = pcp.getUniformLocation(stateSet->getUniform("scale")->getNameID()); + auto normalAsColorLocation = pcp.getUniformLocation(stateSet->getUniform("useNormalAsColor")->getNameID()); - const osg::Uniform* uTrans = stateSet->getUniform("trans"); - const osg::Uniform* uCol = stateSet->getUniform("color"); - const osg::Uniform* uScale = stateSet->getUniform("scale"); - const osg::Uniform* uUseNormalAsColor = stateSet->getUniform("useNormalAsColor"); + auto drawPrimitive = [&](const osg::Drawable* primitive, const osg::Vec3f& pos, const osg::Vec3f& color, + const osg::Vec3f& scale, const bool normalAsColor) { + ext->glUniform3f(transLocation, pos.x(), pos.y(), pos.z()); + ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); + ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); + ext->glUniform1i(normalAsColorLocation, normalAsColor); + primitive->drawImplementation(renderInfo); + }; - auto transLocation = pcp->getUniformLocation(uTrans->getNameID()); - auto colLocation = pcp->getUniformLocation(uCol->getNameID()); - auto scaleLocation = pcp->getUniformLocation(uScale->getNameID()); - auto normalAsColorLocation = pcp->getUniformLocation(uUseNormalAsColor->getNameID()); - - ext->glUniform3f(transLocation, 0., 0., 0.); - ext->glUniform3f(colLocation, 1., 1., 1.); - ext->glUniform3f(scaleLocation, 1., 1., 1.); - ext->glUniform1i(normalAsColorLocation, true); - - mLinesToDraw->drawImplementation(renderInfo); - - ext->glUniform1i(normalAsColorLocation, false); + drawPrimitive(mLinesToDraw, { 0.f, 0.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, true); for (const auto& shapeToDraw : mShapesToDraw) { - osg::Vec3f translation = shapeToDraw.mPosition; - osg::Vec3f color = shapeToDraw.mColor; - osg::Vec3f scale = shapeToDraw.mDims; - - ext->glUniform3f(transLocation, translation.x(), translation.y(), translation.z()); - ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); - ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); - + const osg::Geometry* geometry = nullptr; switch (shapeToDraw.mDrawShape) { case DrawShape::Cube: - mCubeGeometry->drawImplementation(renderInfo); + geometry = mCubeGeometry; break; case DrawShape::Cylinder: - mCylinderGeometry->drawImplementation(renderInfo); + geometry = mCylinderGeometry; break; case DrawShape::WireCube: - mWireCubeGeometry->drawImplementation(renderInfo); + geometry = mWireCubeGeometry; break; } + drawPrimitive(geometry, shapeToDraw.mPosition, shapeToDraw.mColor, shapeToDraw.mDims, false); } mShapesToDraw.clear(); static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); static_cast(mLinesToDraw->getPrimitiveSet(0))->setCount(0); + pcp.resetAppliedUniforms(); } } From d147d1d250d7ec19cc59a4c25d1d946eab9e6bc9 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:19:42 +0100 Subject: [PATCH 1006/2167] Initialize FileSystemArchive index in constructor It should be initialize for each created archive anyway. There is no good reason to have additional complexity for lazy initialization. And it helps to catch problems with specific directory when it's added to the VFS not when all are added and index is built. --- components/vfs/filesystemarchive.cpp | 62 +++++++++++----------------- components/vfs/filesystemarchive.hpp | 1 - 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index c72798e7ea..b8374eaba9 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -12,48 +12,36 @@ namespace VFS { FileSystemArchive::FileSystemArchive(const std::filesystem::path& path) - : mBuiltIndex(false) - , mPath(path) + : mPath(path) { + const auto str = mPath.u8string(); + std::size_t prefix = str.size(); + + if (prefix > 0 && str[prefix - 1] != '\\' && str[prefix - 1] != '/') + ++prefix; + + for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) + { + if (std::filesystem::is_directory(i)) + continue; + + const std::filesystem::path& filePath = i.path(); + const std::string proper = Files::pathToUnicodeString(filePath); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); + FileSystemArchiveFile file(filePath); + + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); + if (!inserted.second) + Log(Debug::Warning) + << "Found duplicate file for '" << proper + << "', please check your file system for two files with the same name in different cases."; + } } void FileSystemArchive::listResources(FileMap& out) { - if (!mBuiltIndex) - { - const auto str = mPath.u8string(); - size_t prefix = str.size(); - - if (!mPath.empty() && str[prefix - 1] != '\\' && str[prefix - 1] != '/') - ++prefix; - - for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) - { - if (std::filesystem::is_directory(i)) - continue; - - const auto& path = i.path(); - const std::string proper = Files::pathToUnicodeString(path); - - FileSystemArchiveFile file(path); - - VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - - const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); - if (!inserted.second) - Log(Debug::Warning) - << "Warning: found duplicate file for '" << proper - << "', please check your file system for two files with the same name in different cases."; - else - out[inserted.first->first] = &inserted.first->second; - } - mBuiltIndex = true; - } - else - { - for (auto& [k, v] : mIndex) - out[k] = &v; - } + for (auto& [k, v] : mIndex) + out[k] = &v; } bool FileSystemArchive::contains(Path::NormalizedView file) const diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index b158ef3472..215c443b58 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -36,7 +36,6 @@ namespace VFS private: std::map> mIndex; - bool mBuiltIndex; std::filesystem::path mPath; }; From 95ade48292eb922579334c20d2d7bdafd755fad7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 16 Feb 2024 19:22:34 -0600 Subject: [PATCH 1007/2167] Fix menu doc --- files/lua_api/openmw/core.lua | 7 +++++++ files/lua_api/openmw/types.lua | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 330f0e20a0..169a41b2d2 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -24,6 +24,13 @@ -- @param #string eventName -- @param eventData +--- +-- Send an event to menu scripts. +-- @function [parent=#core] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + --- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d16f560a58..56deb6d558 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1097,13 +1097,6 @@ -- @field #string texture Birth sign texture -- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. ---- --- Send an event to menu scripts. --- @function [parent=#core] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - -------------------------------------------------------------------------------- -- @{#Armor} functions -- @field [parent=#types] #Armor Armor From a2345194c87170ca215f7eac9b83ac86bf4f5447 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 00:46:38 +0100 Subject: [PATCH 1008/2167] Optimize lookup for a file in the BSA archive Use binary search in sorted vector or normalized paths instead of linear search in the original file struct. With number of files from 1k to 10k in vanilla archives this gives some benefits. --- components/vfs/bsaarchive.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 847aeca509..2276933684 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace VFS { template @@ -44,7 +46,10 @@ namespace VFS for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it) { mResources.emplace_back(&*it, mFile.get()); + mFiles.emplace_back(it->name()); } + + std::sort(mFiles.begin(), mFiles.end()); } virtual ~BsaArchive() {} @@ -57,12 +62,7 @@ namespace VFS bool contains(Path::NormalizedView file) const override { - for (const auto& it : mResources) - { - if (Path::pathEqual(file.value(), it.mInfo->name())) - return true; - } - return false; + return std::binary_search(mFiles.begin(), mFiles.end(), file); } std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); } @@ -70,6 +70,7 @@ namespace VFS private: std::unique_ptr mFile; std::vector> mResources; + std::vector mFiles; }; template From 1b1ed55762c4edab2335c3ca9bc46db35cc0d125 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 17 Feb 2024 14:10:45 +0100 Subject: [PATCH 1009/2167] Add context to the errors on recursive iteration over directory To avoid showing users errors like: recursive_directory_iterator::operator++: Access is denied. And show something like this: Failed to recursively iterate over "/home/elsid/.local/share/openmw/test_data" when incrementing to the next item from "/home/elsid/.local/share/openmw/test_data/permission_denied": Permission denied --- components/vfs/filesystemarchive.cpp | 39 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index b8374eaba9..01e5c2a1b5 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -20,21 +20,36 @@ namespace VFS if (prefix > 0 && str[prefix - 1] != '\\' && str[prefix - 1] != '/') ++prefix; - for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) + std::filesystem::recursive_directory_iterator iterator(mPath); + + for (auto it = std::filesystem::begin(iterator), end = std::filesystem::end(iterator); it != end;) { - if (std::filesystem::is_directory(i)) - continue; + const auto& i = *it; - const std::filesystem::path& filePath = i.path(); - const std::string proper = Files::pathToUnicodeString(filePath); - VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - FileSystemArchiveFile file(filePath); + if (!std::filesystem::is_directory(i)) + { + const std::filesystem::path& filePath = i.path(); + const std::string proper = Files::pathToUnicodeString(filePath); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); + FileSystemArchiveFile file(filePath); - const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); - if (!inserted.second) - Log(Debug::Warning) - << "Found duplicate file for '" << proper - << "', please check your file system for two files with the same name in different cases."; + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); + if (!inserted.second) + Log(Debug::Warning) + << "Found duplicate file for '" << proper + << "', please check your file system for two files with the same name in different cases."; + } + + // Exception thrown by the operator++ may not contain the context of the error like what exact path caused + // the problem which makes it hard to understand what's going on when iteration happens over a directory + // with thousands of files and subdirectories. + const std::filesystem::path prevPath = i.path(); + std::error_code ec; + it.increment(ec); + if (ec != std::error_code()) + throw std::runtime_error("Failed to recursively iterate over \"" + Files::pathToUnicodeString(mPath) + + "\" when incrementing to the next item from \"" + Files::pathToUnicodeString(prevPath) + + "\": " + ec.message()); } } From 41d41780a836b5e7f7ec4ffd052c86c945d17f8c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 18 Feb 2024 13:16:47 +0300 Subject: [PATCH 1010/2167] CharacterController: rework movement queueing logic (#7835) --- apps/openmw/mwmechanics/character.cpp | 88 +++++++++++++++------------ 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cbd09b30d8..d89d4d8d53 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2237,8 +2237,6 @@ namespace MWMechanics if (mAnimation->isPlaying(mCurrentJump)) jumpstate = JumpState_Landing; - vec.x() *= scale; - vec.y() *= scale; vec.z() = 0.0f; if (movementSettings.mIsStrafing) @@ -2371,7 +2369,8 @@ namespace MWMechanics const float speedMult = speed / mMovementAnimSpeed; mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); // Make sure the actual speed is the "expected" speed even though the animation is slower - scale *= std::max(1.f, speedMult / maxSpeedMult); + if (isMovementAnimationControlled()) + scale *= std::max(1.f, speedMult / maxSpeedMult); } if (!mSkipAnim) @@ -2390,20 +2389,17 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + updateHeadTracking(duration); } movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; + + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicsSystem will + // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will + // be reset in PhysicsSystem::move once the jump is handled. if (movement.z() == 0.f) movementSettings.mPosition[2] = 0; - // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will - // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will - // be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); } else if (cls.getCreatureStats(mPtr).isDead()) { @@ -2420,34 +2416,41 @@ namespace MWMechanics osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && !isScriptedAnimPlaying()) { - if (duration > 0.0f) - movementFromAnimation /= duration; - else - movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - - movementFromAnimation.x() *= scale; - movementFromAnimation.y() *= scale; - - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + if (isMovementAnimationControlled()) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from - // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive - // diagonal movement, we have to rotate the movement taken from the animation to the intended - // direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual - // frame will, and therefore we have to determine the direction based on the currently playing cycle - // instead. - float animMovementAngle = getAnimationMovementDirection(); - float targetMovementAngle = std::atan2(-movement.x(), movement.y()); - float diff = targetMovementAngle - animMovementAngle; - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; - } + if (duration != 0.f && movementFromAnimation != osg::Vec3f()) + { + movementFromAnimation /= duration; - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - movement = movementFromAnimation; + // Ensure we're moving in the right general direction. + // In vanilla, all horizontal movement is taken from animations, even when moving diagonally (which + // doesn't have a corresponding animation). So to achieve diagonal movement, we have to rotate the + // movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no + // individual frame will, and therefore we have to determine the direction based on the currently + // playing cycle instead. + if (speed > 0.f) + { + float animMovementAngle = getAnimationMovementDirection(); + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + } + + movement = movementFromAnimation; + } + else + { + movement = osg::Vec3f(); + } + } + else if (mSkipAnim) + { + movement = osg::Vec3f(); + } if (mFloatToSurface) { @@ -2463,8 +2466,11 @@ namespace MWMechanics } } + movement.x() *= scale; + movement.y() *= scale; // Update movement - world->queueMovement(mPtr, movement); + if (movement != osg::Vec3f()) + world->queueMovement(mPtr, movement); } mSkipAnim = false; @@ -2681,11 +2687,15 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) + return false; + + if (mInJump) + return false; + bool movementAnimationControlled = mIdleState != CharState_None; if (mMovementState != CharState_None) movementAnimationControlled = mMovementAnimationHasMovement; - if (mInJump) - movementAnimationControlled = false; return movementAnimationControlled; } From 8cc665ec4354ca9163f0e07d022b170bfecda673 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 14:24:54 +0100 Subject: [PATCH 1011/2167] Update google benchmark to 1.8.3 --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 10d75c1057..4a2cf1162b 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -212,8 +212,8 @@ if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) include(FetchContent) FetchContent_Declare(benchmark - URL https://github.com/google/benchmark/archive/refs/tags/v1.7.1.zip - URL_HASH SHA512=bec4016263587a57648e02b094c69e838c0a21e16c3dcfc6f03100397ab8f95d5fab1f5fd0d7e0e8adbb8212fff1eb574581158fdda1fa7fd6ff12762154b0cc + URL https://github.com/google/benchmark/archive/refs/tags/v1.8.3.zip + URL_HASH SHA512=d73587ad9c49338749e1d117a6f8c7ff9c603a91a2ffa91a7355c7df7dea82710b9a810d34ddfef20973ecdc77092ec10fb2b4e4cc8d2e7810cbed79617b3828 SOURCE_DIR fetched/benchmark ) FetchContent_MakeAvailableExcludeFromAll(benchmark) From da5ab2b2c97ec2c284d035ef480099e8304f7479 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 14:22:07 +0100 Subject: [PATCH 1012/2167] Fix benchmark warning: -Wdeprecated-declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp: In function ‘void {anonymous}::getFromFilledCache(benchmark::State&)’: /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp:186:37: warning: ‘typename std::enable_if<((! std::is_trivially_copyable<_Tp>::value) || (sizeof (Tp) > sizeof (Tp*)))>::type benchmark::DoNotOptimize(const Tp&) [with Tp = DetourNavigator::NavMeshTilesCache::Value; typename std::enable_if<((! std::is_trivially_copyable<_Tp>::value) || (sizeof (Tp) > sizeof (Tp*)))>::type = void]’ is deprecated: The const-ref version of this method can permit undesired compiler optimizations in benchmarks [-Wdeprecated-declarations] 186 | benchmark::DoNotOptimize(result); | ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ In file included from /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp:1: /home/elsid/dev/benchmark/build/gcc/release/install/include/benchmark/benchmark.h:507:5: note: declared here 507 | DoNotOptimize(Tp const& value) { | ^~~~~~~~~~~~~ --- apps/benchmarks/detournavigator/navmeshtilescache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 746739c856..26873d9a03 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -182,7 +182,7 @@ namespace for (auto _ : state) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); + auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); benchmark::DoNotOptimize(result); } } @@ -241,7 +241,7 @@ namespace while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.set( + auto result = cache.set( key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); benchmark::DoNotOptimize(result); } From df077a2524095333d89d231b735396867f2d2ff8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:13:19 +0100 Subject: [PATCH 1013/2167] Simplify and reduce code duplication for BSA archive creation --- apps/niftest/niftest.cpp | 43 ++++++++++------------------ components/vfs/bsaarchive.hpp | 44 ++++++++++++----------------- components/vfs/registerarchives.cpp | 13 +-------- 3 files changed, 34 insertions(+), 66 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e18da8e3f6..fe60238cd5 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -42,29 +42,10 @@ bool isBSA(const std::filesystem::path& filename) return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); } -std::unique_ptr makeBsaArchive(const std::filesystem::path& path) -{ - switch (Bsa::BSAFile::detectVersion(path)) - { - case Bsa::BSAVER_COMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_GNRL: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_DX10: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNCOMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNKNOWN: - default: - std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl; - return nullptr; - } -} - std::unique_ptr makeArchive(const std::filesystem::path& path) { if (isBSA(path)) - return makeBsaArchive(path); + return VFS::makeBsaArchive(path); if (std::filesystem::is_directory(path)) return std::make_unique(path); return nullptr; @@ -124,17 +105,23 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat if (!archivePath.empty() && !isBSA(archivePath)) { - Files::PathContainer dataDirs = { archivePath }; - const Files::Collections fileCollections = Files::Collections(dataDirs); + const Files::Collections fileCollections({ archivePath }); const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); - for (auto& file : bsaCol) + for (const Files::MultiDirCollection& collection : { bsaCol, ba2Col }) { - readVFS(makeBsaArchive(file.second), file.second, quiet); - } - for (auto& file : ba2Col) - { - readVFS(makeBsaArchive(file.second), file.second, quiet); + for (auto& file : collection) + { + try + { + readVFS(VFS::makeBsaArchive(file.second), file.second, quiet); + } + catch (const std::exception& e) + { + std::cerr << "Failed to read archive file '" << Files::pathToUnicodeString(file.second) + << "': " << e.what() << std::endl; + } + } } } } diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 2276933684..83a68a0589 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include namespace VFS { @@ -73,34 +75,24 @@ namespace VFS std::vector mFiles; }; - template - struct ArchiveSelector + inline std::unique_ptr makeBsaArchive(const std::filesystem::path& path) { - }; + switch (Bsa::BSAFile::detectVersion(path)) + { + case Bsa::BSAVER_UNKNOWN: + break; + case Bsa::BSAVER_UNCOMPRESSED: + return std::make_unique>(path); + case Bsa::BSAVER_COMPRESSED: + return std::make_unique>(path); + case Bsa::BSAVER_BA2_GNRL: + return std::make_unique>(path); + case Bsa::BSAVER_BA2_DX10: + return std::make_unique>(path); + } - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; + throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'"); + } } #endif diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index 9dbe878bca..f017b5f73c 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -25,18 +25,7 @@ namespace VFS // Last BSA has the highest priority const auto archivePath = collections.getPath(*archive); Log(Debug::Info) << "Adding BSA archive " << archivePath; - Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(archivePath); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_GNRL) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_DX10) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_UNCOMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else - throw std::runtime_error("Unknown archive type '" + *archive + "'"); + vfs->addArchive(makeBsaArchive(archivePath)); } else { From cc9f9b53bafaaecaf9d3bf961e64cb4c31a8987b Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:29:38 +0100 Subject: [PATCH 1014/2167] Convert BsaVersion to enum class --- apps/bsatool/bsatool.cpp | 20 +++++++++++--------- apps/launcher/datafilespage.cpp | 2 +- components/bsa/bsa_file.cpp | 18 +++++++++--------- components/bsa/bsa_file.hpp | 12 ++++++------ components/bsa/compressedbsafile.cpp | 2 +- components/vfs/bsaarchive.hpp | 10 +++++----- 6 files changed, 33 insertions(+), 31 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 28711df929..171e5606c4 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -329,17 +329,19 @@ int main(int argc, char** argv) switch (bsaVersion) { - case Bsa::BSAVER_COMPRESSED: - return call(info); - case Bsa::BSAVER_BA2_GNRL: - return call(info); - case Bsa::BSAVER_BA2_DX10: - return call(info); - case Bsa::BSAVER_UNCOMPRESSED: + case Bsa::BsaVersion::Unknown: + break; + case Bsa::BsaVersion::Uncompressed: return call(info); - default: - throw std::runtime_error("Unrecognised BSA archive"); + case Bsa::BsaVersion::Compressed: + return call(info); + case Bsa::BsaVersion::BA2GNRL: + return call(info); + case Bsa::BsaVersion::BA2DX10: + return call(info); } + + throw std::runtime_error("Unrecognised BSA archive"); } catch (std::exception& e) { diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9b14a91934..92b86e9cec 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -819,7 +819,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) for (const auto& fileinfo : dir.entryInfoList(archiveFilter)) { const auto absPath = fileinfo.absoluteFilePath(); - if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN) + if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown) continue; const auto fileName = fileinfo.fileName(); diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index b3e24c75ab..c8b0021152 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -335,7 +335,7 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) if (fsize < 12) { - return BSAVER_UNKNOWN; + return BsaVersion::Unknown; } // Get essential header numbers @@ -345,23 +345,23 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) input.read(reinterpret_cast(head), 12); - if (head[0] == static_cast(BSAVER_UNCOMPRESSED)) + if (head[0] == static_cast(BsaVersion::Uncompressed)) { - return BSAVER_UNCOMPRESSED; + return BsaVersion::Uncompressed; } - if (head[0] == static_cast(BSAVER_COMPRESSED) || head[0] == ESM::fourCC("BTDX")) + if (head[0] == static_cast(BsaVersion::Compressed) || head[0] == ESM::fourCC("BTDX")) { if (head[1] == static_cast(0x01)) { if (head[2] == ESM::fourCC("GNRL")) - return BSAVER_BA2_GNRL; + return BsaVersion::BA2GNRL; if (head[2] == ESM::fourCC("DX10")) - return BSAVER_BA2_DX10; - return BSAVER_UNKNOWN; + return BsaVersion::BA2DX10; + return BsaVersion::Unknown; } - return BSAVER_COMPRESSED; + return BsaVersion::Compressed; } - return BSAVER_UNKNOWN; + return BsaVersion::Unknown; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 8a953245d2..03a0703885 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -35,13 +35,13 @@ namespace Bsa { - enum BsaVersion + enum class BsaVersion : std::uint32_t { - BSAVER_UNKNOWN = 0x0, - BSAVER_UNCOMPRESSED = 0x100, - BSAVER_COMPRESSED = 0x415342, // B, S, A, - BSAVER_BA2_GNRL, // used by FO4, BSA which contains files - BSAVER_BA2_DX10 // used by FO4, BSA which contains textures + Unknown = 0x0, + Uncompressed = 0x100, + Compressed = 0x415342, // B, S, A, + BA2GNRL, // used by FO4, BSA which contains files + BA2DX10 // used by FO4, BSA which contains textures }; /** diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index b916de7919..99efe7a587 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -70,7 +70,7 @@ namespace Bsa input.read(reinterpret_cast(&mHeader), sizeof(mHeader)); - if (mHeader.mFormat != BSAVER_COMPRESSED) // BSA + if (mHeader.mFormat != static_cast(BsaVersion::Compressed)) // BSA fail("Unrecognized compressed BSA format"); if (mHeader.mVersion != Version_TES4 && mHeader.mVersion != Version_FO3 && mHeader.mVersion != Version_SSE) fail("Unrecognized compressed BSA version"); diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 83a68a0589..9418cce745 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -79,15 +79,15 @@ namespace VFS { switch (Bsa::BSAFile::detectVersion(path)) { - case Bsa::BSAVER_UNKNOWN: + case Bsa::BsaVersion::Unknown: break; - case Bsa::BSAVER_UNCOMPRESSED: + case Bsa::BsaVersion::Uncompressed: return std::make_unique>(path); - case Bsa::BSAVER_COMPRESSED: + case Bsa::BsaVersion::Compressed: return std::make_unique>(path); - case Bsa::BSAVER_BA2_GNRL: + case Bsa::BsaVersion::BA2GNRL: return std::make_unique>(path); - case Bsa::BSAVER_BA2_DX10: + case Bsa::BsaVersion::BA2DX10: return std::make_unique>(path); } From 8c6e0866e0194465a77560ed887edcb4c78cffb2 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:47:49 +0100 Subject: [PATCH 1015/2167] Avoid seek for detecting BSA type Seek is pretty expensive operation. Try to read first 12 bytes instead. --- components/bsa/bsa_file.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index c8b0021152..4704e6e7e0 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -325,25 +325,15 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) { std::ifstream input(filePath, std::ios_base::binary); - // Total archive size - std::streamoff fsize = 0; - if (input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if (fsize < 12) - { - return BsaVersion::Unknown; - } - // Get essential header numbers // First 12 bytes uint32_t head[3]; - input.read(reinterpret_cast(head), 12); + input.read(reinterpret_cast(head), sizeof(head)); + + if (input.gcount() != sizeof(head)) + return BsaVersion::Unknown; if (head[0] == static_cast(BsaVersion::Uncompressed)) { From e2e1d913af97c13115a4f5ef62126264f770b173 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 14 Feb 2024 22:37:56 +0100 Subject: [PATCH 1016/2167] Remove redundant destructor --- components/vfs/bsaarchive.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 9418cce745..664466fa40 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -54,8 +54,6 @@ namespace VFS std::sort(mFiles.begin(), mFiles.end()); } - virtual ~BsaArchive() {} - void listResources(FileMap& out) override { for (auto& resource : mResources) From e9c672b29771433cd73e1bd560da62049e9bf38c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 19:14:51 +0100 Subject: [PATCH 1017/2167] Fix build with cmake flag BUILD_SHARED_LIBS=ON Always build opencs-lib as static library instead of BUILD_SHARED_LIBS deciding whether it's static or shared library. --- .gitlab-ci.yml | 1 + CI/before_script.linux.sh | 2 +- apps/opencs/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7dc69484c6..1a8e22ed82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,7 @@ Ubuntu_GCC_Debug: CCACHE_SIZE: 3G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 + BUILD_SHARED_LIBS: 1 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 0edd38628f..ab61ed3e59 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -22,7 +22,7 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=install - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS="${BUILD_SHARED_LIBS:-OFF}" -DUSE_SYSTEM_TINYXML=ON -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 610c5157aa..9bf02e10c9 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -172,7 +172,7 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -add_library(openmw-cs-lib +add_library(openmw-cs-lib STATIC ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} From efbc37d22fec87633e39a02fc19a54908201cccb Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 19:36:53 +0100 Subject: [PATCH 1018/2167] Build components with position independent code only for Android openmw is build as shared library with position independent code enabled there so linked static libraries need to have this too. --- components/CMakeLists.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 317786e96a..dc195d8d0b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -526,11 +526,9 @@ if (USE_QT) QT_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() -if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) - add_definitions(-fPIC) - endif() -endif () +if (ANDROID) + set_property(TARGET components PROPERTY POSTION_INDEPENDENT_CODE ON) +endif() include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) From 731095831d7ab5394bf006ad213243d56510e0e4 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 13:15:56 -0600 Subject: [PATCH 1019/2167] Add missing function types.Item.isCarriable() --- apps/openmw/mwlua/types/item.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index d1f80e44f4..ff05c3f232 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,3 +1,4 @@ +#include #include #include "../../mwmechanics/spellutil.hpp" @@ -24,6 +25,15 @@ namespace MWLua item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; + item["isCarriable"] = [](const Object& object) -> bool { + if (object.ptr().getClass().isItem(object.ptr())) + { + return true; + } + return object.ptr().mRef->getType() == ESM::REC_LIGH + && (object.ptr().get()->mBase->mData.mFlags & ESM::Light::Carry) != 0; + }; + addItemDataBindings(item, context); } } From 92242a3d5420dd08e2900921d704d625ccce0cc5 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 13:20:58 -0600 Subject: [PATCH 1020/2167] Simplify --- apps/openmw/mwlua/types/item.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index ff05c3f232..7d6a5975e2 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -25,14 +25,7 @@ namespace MWLua item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; - item["isCarriable"] = [](const Object& object) -> bool { - if (object.ptr().getClass().isItem(object.ptr())) - { - return true; - } - return object.ptr().mRef->getType() == ESM::REC_LIGH - && (object.ptr().get()->mBase->mData.mFlags & ESM::Light::Carry) != 0; - }; + item["isCarriable"] = [](const Object& object) -> bool { return object.ptr().getClass().isItem(object.ptr()); }; addItemDataBindings(item, context); } From fed62a851753b491be12e6be003b0173f4d8960b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 15:58:18 -0600 Subject: [PATCH 1021/2167] Remove unneeded line --- apps/openmw/mwlua/types/item.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 7d6a5975e2..8f05ce8e93 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,4 +1,3 @@ -#include #include #include "../../mwmechanics/spellutil.hpp" From 6f1710dee1ce5e1ecf0ae535070214c394acfe07 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 19 Feb 2024 16:14:52 +0400 Subject: [PATCH 1022/2167] Fix viewing distance spinbox in the launcher (bug 7840) --- CHANGELOG.md | 1 + apps/launcher/ui/settingspage.ui | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671103cd21..509fa34b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,7 @@ Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect + Bug #7840: First run of the launcher doesn't save viewing distance as the default value Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index ca23ec1bb4..5dd5cdc23d 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -663,6 +663,9 @@ + + 3 + cells @@ -670,7 +673,7 @@ 0.000000000000000 - 0.500000000000000 + 0.125000000000000 From ef730c439525c353c01d017dc0ff12cf3df44876 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 18:26:58 +0100 Subject: [PATCH 1023/2167] Clean up global setting groups, don't try to destroy setting pages before they were rendered --- files/data/scripts/omw/settings/menu.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 88913143e8..473e690e38 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -386,7 +386,9 @@ local function onGroupRegistered(global, key) if not pages[group.page] then return end if pageOptions[group.page] then - pageOptions[group.page].element:destroy() + if pageOptions[group.page].element then + pageOptions[group.page].element:destroy() + end else pageOptions[group.page] = {} end @@ -423,21 +425,24 @@ local menuPages = {} local function resetPlayerGroups() local playerGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do - for groupKey, group in pairs(page) do - if not menuGroups[groupKey] and not group.global then + for groupKey in pairs(page) do + if not menuGroups[groupKey] then page[groupKey] = nil playerGroupsSection:set(groupKey, nil) end end - if pageOptions[pageKey] then - pageOptions[pageKey].element:destroy() + local options = pageOptions[pageKey] + if options then + if options.element then + options.element:destroy() + end if not menuPages[pageKey] then - ui.removeSettingsPage(pageOptions[pageKey]) + ui.removeSettingsPage(options) pageOptions[pageKey] = nil else local renderedOptions = renderPage(pages[pageKey]) for k, v in pairs(renderedOptions) do - pageOptions[pageKey][k] = v + options[k] = v end end end From 586706ffe07cb859daa33da1c8dcb281c85c5ad6 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 19:35:41 +0100 Subject: [PATCH 1024/2167] Handle group resets gracefully --- files/data/scripts/omw/settings/menu.lua | 46 +++++++++--------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 473e690e38..dc9ae6cba5 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -268,7 +268,7 @@ local function generateSearchHints(page) return table.concat(hints, ' ') end -local function renderPage(page) +local function renderPage(page, options) local l10n = core.l10n(page.l10n) local sortedGroups = {} for _, group in pairs(groups[page.key]) do @@ -329,11 +329,10 @@ local function renderPage(page) bigSpacer, }, } - return { - name = l10n(page.name), - element = ui.create(layout), - searchHints = generateSearchHints(page), - } + if options.element then options.element:destroy() end + options.name = l10n(page.name) + options.element = ui.create(layout) + options.searchHints = generateSearchHints(page) end local function onSettingChanged(global) @@ -341,8 +340,12 @@ local function onSettingChanged(global) local group = common.getSection(global, common.groupSectionKey):get(groupKey) if not group or not pageOptions[group.page] then return end - local value = common.getSection(global, group.key):get(settingKey) + if not settingKey then + renderPage(pages[group.page], pageOptions[group.page]) + return + end + local value = common.getSection(global, group.key):get(settingKey) local element = pageOptions[group.page].element local groupsLayout = element.layout.content.groups local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] @@ -385,17 +388,8 @@ local function onGroupRegistered(global, key) groups[group.page][pageGroup.key] = pageGroup if not pages[group.page] then return end - if pageOptions[group.page] then - if pageOptions[group.page].element then - pageOptions[group.page].element:destroy() - end - else - pageOptions[group.page] = {} - end - local renderedOptions = renderPage(pages[group.page]) - for k, v in pairs(renderedOptions) do - pageOptions[group.page][k] = v - end + pageOptions[group.page] = pageOptions[group.page] or {} + renderPage(pages[group.page], pageOptions[group.page]) end local function updateGroups(global) @@ -433,17 +427,14 @@ local function resetPlayerGroups() end local options = pageOptions[pageKey] if options then - if options.element then - options.element:destroy() - end if not menuPages[pageKey] then + if options.element then + options.element:destroy() + end ui.removeSettingsPage(options) pageOptions[pageKey] = nil else - local renderedOptions = renderPage(pages[pageKey]) - for k, v in pairs(renderedOptions) do - options[k] = v - end + renderPage(pages[pageKey], options) end end end @@ -477,10 +468,7 @@ local function registerPage(options) pageOptions[page.key].element:destroy() end pageOptions[page.key] = pageOptions[page.key] or {} - local renderedOptions = renderPage(page) - for k, v in pairs(renderedOptions) do - pageOptions[page.key][k] = v - end + renderPage(page, pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key]) end From cfa6dc076b30656cc5699e892bb16f7dbfb980f9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 19:39:45 +0100 Subject: [PATCH 1025/2167] Render global setting groups on renderlua --- files/data/scripts/omw/settings/menu.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index dc9ae6cba5..704b29f032 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -407,10 +407,7 @@ local function updateGroups(global) end end)) end - local updatePlayerGroups = function() updateGroups(false) end -updatePlayerGroups() - local updateGlobalGroups = function() updateGroups(true) end local menuGroups = {} @@ -472,6 +469,11 @@ local function registerPage(options) ui.registerSettingsPage(pageOptions[page.key]) end +updatePlayerGroups() +if menu.getState() == menu.STATE.Running then -- handle reloadlua correctly + updateGlobalGroups() +end + return { interfaceName = 'Settings', interface = { From dc5371d157833b5cc834cfcaf5e460dc3af26504 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:28:33 +0100 Subject: [PATCH 1026/2167] Remove unused RipplesSurface::State::mOffset --- apps/openmw/mwrender/ripples.cpp | 1 - apps/openmw/mwrender/ripples.hpp | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index dea372666e..6dcee1c9bd 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -136,7 +136,6 @@ namespace MWRender osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; mLastPlayerPos = mCurrentPlayerPos; mState[frameId].mPaused = mPaused; - mState[frameId].mOffset = offset; mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); mState[frameId].mStateset->getUniform("offset")->set(offset); diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 0d5b055eb5..6dd48e221f 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -54,7 +54,6 @@ namespace MWRender struct State { - osg::Vec2f mOffset; osg::ref_ptr mStateset; bool mPaused = true; }; From 56e69cf7a2626ce6c196c5543db100f834481765 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:30:51 +0100 Subject: [PATCH 1027/2167] Make some RipplesSurface members private --- apps/openmw/mwrender/ripples.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 6dd48e221f..41ca055174 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -50,6 +50,10 @@ namespace MWRender // e.g. texel to cell unit ratio static constexpr float mWorldScaleFactor = 2.5; + private: + void setupFragmentPipeline(); + void setupComputePipeline(); + Resource::ResourceSystem* mResourceSystem; struct State @@ -63,10 +67,6 @@ namespace MWRender std::array mState; - private: - void setupFragmentPipeline(); - void setupComputePipeline(); - osg::Vec2f mCurrentPlayerPos; osg::Vec2f mLastPlayerPos; From 3b01e209b1c180841eda565e491ad4680412cae7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:31:50 +0100 Subject: [PATCH 1028/2167] Use proper names for static members --- apps/openmw/mwrender/ripples.cpp | 20 ++++++++++---------- apps/openmw/mwrender/ripples.hpp | 4 ++-- apps/openmw/mwrender/water.cpp | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 6dcee1c9bd..7017101011 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -63,7 +63,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("offset", osg::Vec2f())); stateset->addUniform(new osg::Uniform("positionCount", 0)); stateset->addUniform(new osg::Uniform(osg::Uniform::Type::FLOAT_VEC3, "positions", 100)); - stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize)); + stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize)); mState[i].mStateset = stateset; } @@ -78,7 +78,7 @@ namespace MWRender texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); texture->setBorderColor(osg::Vec4(0, 0, 0, 0)); - texture->setTextureSize(mRTTSize, mRTTSize); + texture->setTextureSize(sRTTSize, sRTTSize); mTextures[i] = texture; @@ -99,7 +99,7 @@ namespace MWRender { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(mRTTSize) + ".0" } }; + Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); @@ -132,7 +132,7 @@ namespace MWRender const ESM::Position& playerPos = player.getRefData().getPosition(); mCurrentPlayerPos = osg::Vec2f( - std::floor(playerPos.pos[0] / mWorldScaleFactor), std::floor(playerPos.pos[1] / mWorldScaleFactor)); + std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; mLastPlayerPos = mCurrentPlayerPos; mState[frameId].mPaused = mPaused; @@ -145,9 +145,9 @@ namespace MWRender { osg::Vec3f pos = mPositions[i] - osg::Vec3f( - mCurrentPlayerPos.x() * mWorldScaleFactor, mCurrentPlayerPos.y() * mWorldScaleFactor, 0.0) - + osg::Vec3f(mRTTSize * mWorldScaleFactor / 2, mRTTSize * mWorldScaleFactor / 2, 0.0); - pos /= mWorldScaleFactor; + mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) + + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); + pos /= sWorldScaleFactor; positions->setElement(i, pos); } positions->dirty(); @@ -195,7 +195,7 @@ namespace MWRender bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); } else @@ -217,7 +217,7 @@ namespace MWRender bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); } else @@ -270,7 +270,7 @@ namespace MWRender setReferenceFrame(osg::Camera::ABSOLUTE_RF); setNodeMask(Mask_RenderToTexture); setClearMask(GL_NONE); - setViewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize); + setViewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize); addChild(mRipples); setCullingActive(false); setImplicitBufferAttachmentMask(0, 0); diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 41ca055174..3559c164a6 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -46,9 +46,9 @@ namespace MWRender void releaseGLObjects(osg::State* state) const override; - static constexpr size_t mRTTSize = 1024; + static constexpr size_t sRTTSize = 1024; // e.g. texel to cell unit ratio - static constexpr float mWorldScaleFactor = 2.5; + static constexpr float sWorldScaleFactor = 2.5; private: void setupFragmentPipeline(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9fdb0583a2..283ec85c48 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -704,8 +704,8 @@ namespace MWRender defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); const int rippleDetail = Settings::water().mRainRippleDetail; defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); - defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); - defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; + defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); + defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; Stereo::shaderStereoDefines(defineMap); From 2c1c8bc8de0b12f71152d822c04e939bb6f0a7de Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 19 Feb 2024 23:16:50 +0000 Subject: [PATCH 1029/2167] Work around for listAllAvailablePlugins --- components/misc/osgpluginchecker.cpp.in | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 9bc165c5d6..4b89551206 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -30,6 +32,31 @@ namespace Misc bool checkRequiredOSGPluginsArePresent() { + // work around osgDB::listAllAvailablePlugins() not working on some platforms due to a suspected OSG bug + std::filesystem::path pluginDirectoryName = std::string("osgPlugins-") + std::string(osgGetVersion()); + osgDB::FilePathList& filepath = osgDB::getLibraryFilePathList(); + for (const auto& path : filepath) + { +#ifdef OSG_USE_UTF8_FILENAME + std::filesystem::path osgPath {stringToU8String(path)}; +#else + std::filesystem::path osgPath {path}; +#endif + if (!osgPath.has_filename()) + osgPath = osgPath.parent_path(); + + if (osgPath.filename() == pluginDirectoryName) + { + osgPath = osgPath.parent_path(); +#ifdef OSG_USE_UTF8_FILENAME + std::string extraPath = u8StringToString(osgPath.u8string_view()); +#else + std::string extraPath = osgPath.string(); +#endif + filepath.emplace_back(std::move(extraPath)); + } + } + auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) From c9b4c8632a4bcddb260324bdc53b747c23d7c881 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 23:14:40 +0100 Subject: [PATCH 1030/2167] Update ripples surface only when there is need to do so This depends on the difference between FPS which is dynamic and ripples update frequency which is contant. If FPS > ripples update frequency, some frames do nothing. If FPS <= ripples update frequency each frame runs shaders once. Update offset, possitions shader uniforms only when it will be run. --- apps/openmw/mwrender/ripples.cpp | 147 +++++++++++++++++-------------- apps/openmw/mwrender/ripples.hpp | 18 ++-- 2 files changed, 93 insertions(+), 72 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 7017101011..94135eeec5 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -119,58 +119,83 @@ namespace MWRender nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); } + void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) + { + state.mPaused = mPaused; + + if (mPaused) + return; + + constexpr double updateFrequency = 60.0; + constexpr double updatePeriod = 1.0 / updateFrequency; + + const double simulationTime = frameStamp.getSimulationTime(); + const double frameDuration = simulationTime - mLastSimulationTime; + mLastSimulationTime = simulationTime; + + mRemainingWaveTime += frameDuration; + const double ticks = std::floor(mRemainingWaveTime * updateFrequency); + mRemainingWaveTime -= ticks * updatePeriod; + + if (ticks == 0) + { + state.mPaused = true; + return; + } + + const MWWorld::Ptr player = MWMechanics::getPlayer(); + const ESM::Position& playerPos = player.getRefData().getPosition(); + + mCurrentPlayerPos = osg::Vec2f( + std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); + const osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; + mLastPlayerPos = mCurrentPlayerPos; + + state.mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); + state.mStateset->getUniform("offset")->set(offset); + + osg::Uniform* const positions = state.mStateset->getUniform("positions"); + + for (std::size_t i = 0; i < mPositionCount; ++i) + { + osg::Vec3f pos = mPositions[i] + - osg::Vec3f(mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) + + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); + pos /= sWorldScaleFactor; + positions->setElement(i, pos); + } + positions->dirty(); + + mPositionCount = 0; + } + void RipplesSurface::traverse(osg::NodeVisitor& nv) { - if (!nv.getFrameStamp()) + const osg::FrameStamp* const frameStamp = nv.getFrameStamp(); + + if (frameStamp == nullptr) return; if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; + updateState(*frameStamp, mState[frameStamp->getFrameNumber() % 2]); - const auto& player = MWMechanics::getPlayer(); - const ESM::Position& playerPos = player.getRefData().getPosition(); - - mCurrentPlayerPos = osg::Vec2f( - std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); - osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; - mLastPlayerPos = mCurrentPlayerPos; - mState[frameId].mPaused = mPaused; - mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); - mState[frameId].mStateset->getUniform("offset")->set(offset); - - auto* positions = mState[frameId].mStateset->getUniform("positions"); - - for (size_t i = 0; i < mPositionCount; ++i) - { - osg::Vec3f pos = mPositions[i] - - osg::Vec3f( - mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) - + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); - pos /= sWorldScaleFactor; - positions->setElement(i, pos); - } - positions->dirty(); - - mPositionCount = 0; - } osg::Geometry::traverse(nv); } void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const { osg::State& state = *renderInfo.getState(); - osg::GLExtensions& ext = *state.get(); - size_t contextID = state.getContextID(); - - size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; + const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const State& frameState = mState[currentFrame]; if (frameState.mPaused) { return; } - auto bindImage = [contextID, &state, &ext](osg::Texture2D* texture, GLuint index, GLenum access) { + osg::GLExtensions& ext = *state.get(); + const std::size_t contextID = state.getContextID(); + + const auto bindImage = [&](osg::Texture2D* texture, GLuint index, GLenum access) { osg::Texture::TextureObject* to = texture->getTextureObject(contextID); if (!to || texture->isDirty(contextID)) { @@ -180,52 +205,42 @@ namespace MWRender ext.glBindImageTexture(index, to->id(), 0, GL_FALSE, 0, access, GL_RGBA16F); }; - // Run simulation at a fixed rate independent on current FPS - // FIXME: when we skip frames we need to preserve positions. this doesn't work now - size_t ticks = 1; - // PASS: Blot in all ripple spawners mProgramBlobber->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[0]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[0]); + osg::Geometry::drawImplementation(renderInfo); } // PASS: Wave simulation mProgramSimulation->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[1]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[1]); + osg::Geometry::drawImplementation(renderInfo); } } diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 3559c164a6..e355b16ecd 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -51,17 +51,20 @@ namespace MWRender static constexpr float sWorldScaleFactor = 2.5; private: - void setupFragmentPipeline(); - void setupComputePipeline(); - - Resource::ResourceSystem* mResourceSystem; - struct State { - osg::ref_ptr mStateset; bool mPaused = true; + osg::ref_ptr mStateset; }; + void setupFragmentPipeline(); + + void setupComputePipeline(); + + inline void updateState(const osg::FrameStamp& frameStamp, State& state); + + Resource::ResourceSystem* mResourceSystem; + size_t mPositionCount = 0; std::array mPositions; @@ -78,6 +81,9 @@ namespace MWRender bool mPaused = false; bool mUseCompute = false; + + double mLastSimulationTime = 0; + double mRemainingWaveTime = 0; }; class Ripples : public osg::Camera From 3971abf5e6e6c09717e5ff00bc3d0b20d6fe9be9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 20 Feb 2024 14:02:59 +0400 Subject: [PATCH 1031/2167] Minor launcher improvements (feature 7843) --- apps/launcher/settingspage.cpp | 13 +++- apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/settingspage.ui | 114 ++++++++++++++++--------------- files/lang/launcher_de.ts | 36 +++++----- files/lang/launcher_fr.ts | 36 +++++----- files/lang/launcher_ru.ts | 18 ++--- 6 files changed, 111 insertions(+), 107 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 0b5f542888..93a724909e 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -193,8 +193,10 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); - distantLandCheckBox->setCheckState( - Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); + connect(distantLandCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotDistantLandToggled); + bool distantLandEnabled = Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging; + distantLandCheckBox->setCheckState(distantLandEnabled ? Qt::Checked : Qt::Unchecked); + slotDistantLandToggled(distantLandEnabled); loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox); viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance)); @@ -583,9 +585,16 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) fadeStartSpinBox->setEnabled(checked); } +void Launcher::SettingsPage::slotDistantLandToggled(bool checked) +{ + activeGridObjectPagingCheckBox->setEnabled(checked); + objectPagingMinSizeComboBox->setEnabled(checked); +} + void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) { lightsMaximumDistanceSpinBox->setEnabled(index != 0); + lightFadeMultiplierSpinBox->setEnabled(index != 0); lightsMaxLightsSpinBox->setEnabled(index != 0); lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ea675857ea..652b8ce82d 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -33,6 +33,7 @@ namespace Launcher void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); void slotShadowDistLimitToggled(bool checked); + void slotDistantLandToggled(bool checked); void slotLightTypeCurrentIndexChanged(int index); private: diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 5dd5cdc23d..665c9e9712 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -652,45 +652,6 @@ - - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - Distant land - - - - - - - 3 - - - cells - - - 0.000000000000000 - - - 0.125000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> @@ -700,14 +661,7 @@ - - - - Viewing distance - - - - + 3 @@ -723,7 +677,53 @@ - + + + + Viewing distance + + + + + + + cells + + + 3 + + + 0.000000000000000 + + + 0.125000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + + + Distant land + + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> @@ -869,6 +869,9 @@ 81920 + + 128 + 8192 @@ -972,6 +975,9 @@ 1.000000000000000 + + 0.010000000000000 + 0.900000000000000 @@ -1027,7 +1033,7 @@ <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - Lights maximum distance + Maximum light distance @@ -1050,7 +1056,7 @@ <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Max light sources + Max lights @@ -1060,7 +1066,7 @@ <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - Lights fade multiplier + Fade start multiplier @@ -1102,7 +1108,7 @@ <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Lights bounding sphere multiplier + Bounding sphere multiplier @@ -1112,7 +1118,7 @@ <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - Lights minimum interior brightness + Minimum interior brightness @@ -1323,7 +1329,7 @@ - + In third-person view, use the camera as the sound listener instead of the player character. diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 11dd865c56..925733f9d3 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1396,26 +1396,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - - Lights maximum distance - - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - - Max light sources - - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - - Lights fade multiplier - - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1435,18 +1423,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - - Lights bounding sphere multiplier - - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - - Lights minimum interior brightness - - In third-person view, use the camera as the sound listener instead of the player character. @@ -1455,5 +1435,21 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener + + Maximum light distance + + + + Max lights + + + + Bounding sphere multiplier + + + + Minimum interior brightness + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 5df4822808..3471fc6c5c 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1396,26 +1396,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - - Lights maximum distance - - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - - Max light sources - - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - - Lights fade multiplier - - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1435,18 +1423,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - - Lights bounding sphere multiplier - - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - - Lights minimum interior brightness - - In third-person view, use the camera as the sound listener instead of the player character. @@ -1455,5 +1435,21 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener + + Maximum light distance + + + + Max lights + + + + Bounding sphere multiplier + + + + Minimum interior brightness + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 1db804e2df..ec7aeccc57 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1409,7 +1409,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> - Lights maximum distance + Maximum light distance Дальность отображения источников света @@ -1417,17 +1417,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> - Max light sources + Max lights Макс. кол-во источников света <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> <html><head/><body><p>Доля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.</p><p>Низкие значения ведут к плавному затуханию, высокие - к резкому.</p></body></html> - - Lights fade multiplier - Множитель начала затухания - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1451,17 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> - Lights bounding sphere multiplier + Bounding sphere multiplier Множитель размера ограничивающей сферы <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> <html><head/><body><p>Минимальный уровень фонового освещения в помещениях.</p><p>Увеличьте значение, если помещения в игре кажутся слишком темными.</p></body></html> - - Lights minimum interior brightness - Минимальный уровень освещения в помещениях - In third-person view, use the camera as the sound listener instead of the player character. Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. @@ -1470,5 +1462,9 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener Использовать камеру как слушателя + + Minimum interior brightness + Минимальный уровень освещения в помещениях + From 873877795ac6a3da1f7bf0073420c06eae20492e Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 12:06:19 -0600 Subject: [PATCH 1032/2167] Move gamepad controls to lua interface --- apps/openmw/mwlua/inputbindings.cpp | 4 +-- docs/source/luadoc_data_paths.sh | 1 + docs/source/reference/lua-scripting/api.rst | 1 + .../interface_gamepadcontrols.rst | 8 +++++ .../lua-scripting/tables/interfaces.rst | 4 +++ files/data/CMakeLists.txt | 3 +- files/data/builtin.omwscripts | 1 + .../scripts/omw/input/gamepadcontrols.lua | 29 +++++++++++++++++++ files/lua_api/openmw/input.lua | 10 ------- 9 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_gamepadcontrols.rst create mode 100644 files/data/scripts/omw/input/gamepadcontrols.lua diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 315b0db7bc..a5a6399c96 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -202,8 +202,8 @@ namespace MWLua }; api["isMouseButtonPressed"] = [](int button) -> bool { return SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(button); }; - api["isGamepadCursorActive"] = [input]() -> bool { return input->isGamepadGuiCursorEnabled(); }; - api["setGamepadCursorActive"] = [input](bool v) { + api["_isGamepadCursorActive"] = [input]() -> bool { return input->isGamepadGuiCursorEnabled(); }; + api["_setGamepadCursorActive"] = [input](bool v) { input->setGamepadGuiCursorEnabled(v); MWBase::Environment::get().getWindowManager()->setCursorActive(v); }; diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 02b03cbd69..9af80d6444 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -4,6 +4,7 @@ paths=( scripts/omw/ai.lua scripts/omw/mechanics/animationcontroller.lua scripts/omw/playercontrols.lua + scripts/omw/input/gamepadcontrols.lua scripts/omw/camera/camera.lua scripts/omw/mwui/init.lua scripts/omw/settings/player.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 1bb7e0b6e9..8fef9c475e 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -37,6 +37,7 @@ Lua API reference interface_animation interface_camera interface_controls + interface_gamepadcontrols interface_item_usage interface_mwui interface_settings diff --git a/docs/source/reference/lua-scripting/interface_gamepadcontrols.rst b/docs/source/reference/lua-scripting/interface_gamepadcontrols.rst new file mode 100644 index 0000000000..f89738b25b --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_gamepadcontrols.rst @@ -0,0 +1,8 @@ +Interface GamepadControls +========================= + +.. include:: version.rst + +.. raw:: html + :file: generated_html/scripts_omw_input_gamepadcontrols.html + diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 5029baf0a3..606b68b8e0 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -21,6 +21,10 @@ - by player scripts - | Allows to alter behavior of the built-in script | that handles player controls. + * - :ref:`Controls ` + - by player scripts + - | Allows to alter behavior of the built-in script + | that handles player gamepad controls. * - :ref:`ItemUsage ` - by global scripts - | Allows to extend or override built-in item usage diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 3ab30c87ff..817a64c014 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -95,7 +95,8 @@ set(BUILTIN_DATA_FILES scripts/omw/worldeventhandlers.lua scripts/omw/input/actionbindings.lua scripts/omw/input/smoothmovement.lua - + scripts/omw/input/gamepadcontrols.lua + shaders/adjustments.omwfx shaders/bloomlinear.omwfx shaders/debug.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index a6f4ca5f33..1549920dab 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -16,6 +16,7 @@ PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua +PLAYER: scripts/omw/input/gamepadcontrols.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/scripts/omw/input/gamepadcontrols.lua b/files/data/scripts/omw/input/gamepadcontrols.lua new file mode 100644 index 0000000000..0af17efa39 --- /dev/null +++ b/files/data/scripts/omw/input/gamepadcontrols.lua @@ -0,0 +1,29 @@ +local input = require('openmw.input') + +return { + + interfaceName = 'GamepadControls', + --- + -- Gamepad control interface + -- @module GamepadControls + + interface = { + --- Interface version + -- @field [parent=#GamepadControls] #number version + version = 0, + + --- Checks if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. + -- @function [parent=#GamepadControls] isGamepadCursorActive + -- @return #boolean + isGamepadCursorActive = function() + return input._isGamepadCursorActive() + end, + + --- Set if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. + -- @function [parent=#GamepadControls] setGamepadCursorActive + -- @param #boolean value + setGamepadCursorActive = function(state) + input._setGamepadCursorActive(state) + end, + } +} diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 12bd51b47a..0a85602bcc 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -28,16 +28,6 @@ -- @param #number buttonId Button index (see @{openmw.input#CONTROLLER_BUTTON}) -- @return #boolean ---- --- Checks if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. --- @function [parent=#input] isGamepadCursorActive --- @return #boolean - ---- --- Set if the gamepad cursor is active. If it is active, the left stick can move the cursor, and A will be interpreted as a mouse click. --- @function [parent=#input] setGamepadCursorActive --- @param #boolean value - --- -- Is `Shift` key pressed. -- @function [parent=#input] isShiftPressed From 254b5335124abc8d93a31745df5a82c8a733ba4e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 20 Feb 2024 20:04:28 +0100 Subject: [PATCH 1033/2167] Allow the NAM9 field to be used if COUN is omitted --- components/esm3/objectstate.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 7d26f431d6..25cbdc9a98 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -30,10 +30,7 @@ namespace ESM esm.getHNOT(mEnabled, "ENAB"); if (mVersion <= MaxOldCountFormatVersion) - { - mRef.mCount = 1; esm.getHNOT(mRef.mCount, "COUN"); - } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); From ccb506385f93ea179fc856e5d3569dd607fa5de3 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 13:07:44 -0600 Subject: [PATCH 1034/2167] Fix player looking/controls --- apps/openmw/mwinput/mousemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index ffbe40a2db..0a179bd259 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -220,7 +220,7 @@ namespace MWInput }; // Only actually turn player when we're not in vanity mode - bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"); + bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); From b4c5a2777a43465704b7382d957c6b9d6f74693a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 13:20:09 -0600 Subject: [PATCH 1035/2167] Rename var --- apps/openmw/mwinput/mousemanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 0a179bd259..f18ec2ac87 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -220,14 +220,14 @@ namespace MWInput }; // Only actually turn player when we're not in vanity mode - bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.yaw(-rot[2]); player.pitch(-rot[0]); } - else if (!controls) + else if (!playerLooking) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); From d7bd50e45fc5d020358824ec312f998f807eaf3b Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 20 Feb 2024 20:20:38 +0100 Subject: [PATCH 1036/2167] Only set controls.jump to true for one frame when jumping --- files/data/scripts/omw/input/playercontrols.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/files/data/scripts/omw/input/playercontrols.lua b/files/data/scripts/omw/input/playercontrols.lua index 311b5a16a9..202e604087 100644 --- a/files/data/scripts/omw/input/playercontrols.lua +++ b/files/data/scripts/omw/input/playercontrols.lua @@ -83,6 +83,7 @@ end local movementControlsOverridden = false local autoMove = false +local attemptToJump = false local function processMovement() local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward') local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') @@ -97,6 +98,7 @@ local function processMovement() self.controls.movement = movement self.controls.sideMovement = sideMovement self.controls.run = run + self.controls.jump = attemptToJump if not settings:get('toggleSneak') then self.controls.sneak = input.getBooleanActionValue('Sneak') @@ -115,7 +117,7 @@ end input.registerTriggerHandler('Jump', async:callback(function() if not movementAllowed() then return end - self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) + attemptToJump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) end)) input.registerTriggerHandler('ToggleSneak', async:callback(function() @@ -223,6 +225,7 @@ local function onFrame(_) if combatAllowed() then processAttacking() end + attemptToJump = false end local function onSave() From 535c5e328a7c11b6e36364e5e6f640c2708ae4fa Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 21:02:31 +0000 Subject: [PATCH 1037/2167] Affect correct texture units when disabling shadows for stateset Knowing which are right required making the function non-static, so the shadow manager had to become a singleton as the results of passing it around to where it's needed were hellish. I'm seeing a bunch of OpenGL errors when actually using this, so I'll investigate whether they're happening on master. I'm hesitant to look into it too much, though, as I'm affected by https://gitlab.com/OpenMW/openmw/-/issues/7811, and also have the Windows setting enabled that turns driver timeouts into a BSOD so a kernel dump is collected that I can send to AMD. --- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 4 +-- apps/openmw/mwrender/water.cpp | 5 ++-- components/sceneutil/shadow.cpp | 36 +++++++++++++++-------- components/sceneutil/shadow.hpp | 10 +++++-- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 9914aec7ca..a4c0181d35 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 892a8b5428..9e934d6f20 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index af41d2c590..9c8b0658a9 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } private: @@ -274,7 +274,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9fdb0583a2..35c10b81f4 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -276,8 +276,7 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet( - Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -353,7 +352,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 04f3b65edd..273016501d 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -13,6 +13,16 @@ namespace SceneUtil { using namespace osgShadow; + ShadowManager* ShadowManager::sInstance = nullptr; + + const ShadowManager& ShadowManager::instance() + { + if (sInstance) + return *sInstance; + else + throw std::logic_error("No ShadowManager exists yet"); + } + void ShadowManager::setupShadowSettings( const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { @@ -75,15 +85,11 @@ namespace SceneUtil mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) + void ShadowManager::disableShadowsForStateSet(osg::StateSet& stateset) const { - if (!settings.mEnableShadows) + if (!mEnableShadows) return; - const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; - - int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; - osg::ref_ptr fakeShadowMapImage = new osg::Image(); fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); @@ -92,14 +98,15 @@ namespace SceneUtil fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); - for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + for (int i = mShadowSettings->getBaseShadowTextureUnit(); + i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset.addUniform( - new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset.addUniform( - new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset.addUniform(new osg::Uniform( + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); + stateset.addUniform(new osg::Uniform( + ("shadowTextureUnit" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); } } @@ -111,6 +118,9 @@ namespace SceneUtil , mOutdoorShadowCastingMask(outdoorShadowCastingMask) , mIndoorShadowCastingMask(indoorShadowCastingMask) { + if (sInstance) + throw std::logic_error("A ShadowManager already exists"); + mShadowedScene->setShadowTechnique(mShadowTechnique); if (Stereo::getStereo()) @@ -127,6 +137,8 @@ namespace SceneUtil mShadowTechnique->setWorldMask(worldMask); enableOutdoorMode(); + + sInstance = this; } ShadowManager::~ShadowManager() @@ -135,7 +147,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) const { if (!mEnableShadows) return getShadowsDisabledDefines(); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index fd82e828b6..952d750051 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -26,10 +26,10 @@ namespace SceneUtil class ShadowManager { public: - static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); - static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + static const ShadowManager& instance(); + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); @@ -37,13 +37,17 @@ namespace SceneUtil void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); + void disableShadowsForStateSet(osg::StateSet& stateset) const; + + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings) const; void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); protected: + static ShadowManager* sInstance; + bool mEnableShadows; osg::ref_ptr mShadowedScene; From 7391bf2814ce24683fb718f8367db8dbe799cba7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 21:23:23 +0000 Subject: [PATCH 1038/2167] Fix OpenGL errors There's no reason to use the AndModes variant as we never (intentionally) attempt to sample from a shadow map via the FFP. --- components/sceneutil/shadow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 273016501d..d1e4cf814c 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -101,7 +101,7 @@ namespace SceneUtil for (int i = mShadowSettings->getBaseShadowTextureUnit(); i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { - stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); From 132c43affa64c2d75678231bb2acdd4d9bd367c7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 22:14:13 +0000 Subject: [PATCH 1039/2167] Fix warning Also attempt to make an equivalent warning fire with MSVC, then have to fix other stuff because /WX wasn't working, then back out of enabling the warning because none of the ones I could find disliked the old code. --- CMakeLists.txt | 33 ++++++++++++++++----------------- components/sceneutil/shadow.cpp | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f13def9ab0..d1ad7fa387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -718,67 +718,66 @@ if (WIN32) ) foreach(d ${WARNINGS_DISABLE}) - set(WARNINGS "${WARNINGS} /wd${d}") + list(APPEND WARNINGS "/wd${d}") endforeach(d) if(OPENMW_MSVC_WERROR) - set(WARNINGS "${WARNINGS} /WX") + list(APPEND WARNINGS "/WX") endif() - set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") - set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(components PRIVATE ${WARNINGS}) + target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS}) if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) endif() if (BUILD_BSATOOL) - set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(bsatool PRIVATE ${WARNINGS}) endif() if (BUILD_ESMTOOL) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(esmtool PRIVATE ${WARNINGS}) endif() if (BUILD_ESSIMPORTER) - set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-essimporter PRIVATE ${WARNINGS}) endif() if (BUILD_LAUNCHER) - set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-launcher PRIVATE ${WARNINGS}) endif() if (BUILD_MWINIIMPORTER) - set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS}) endif() if (BUILD_OPENCS) - set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-cs PRIVATE ${WARNINGS}) endif() if (BUILD_OPENMW) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw PRIVATE ${WARNINGS}) endif() if (BUILD_WIZARD) - set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-wizard PRIVATE ${WARNINGS}) endif() if (BUILD_UNITTESTS) - set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_test_suite PRIVATE ${WARNINGS}) endif() if (BUILD_BENCHMARKS) - set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS}) endif() if (BUILD_NAVMESHTOOL) - set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS}) endif() if (BUILD_BULLETOBJECTTOOL) - set(WARNINGS "${WARNINGS} ${MT_BUILD}") - set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD}) endif() endif(MSVC) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index d1e4cf814c..9351ec249e 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -98,7 +98,7 @@ namespace SceneUtil fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); - for (int i = mShadowSettings->getBaseShadowTextureUnit(); + for (unsigned int i = mShadowSettings->getBaseShadowTextureUnit(); i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { stateset.setTextureAttribute(i, fakeShadowMapTexture, From d282fdb77a7bfd6dd5a14dde30708b8f671c5ff8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:10:03 +0000 Subject: [PATCH 1040/2167] Eliminate unused uniform --- components/sceneutil/mwshadowtechnique.cpp | 9 --------- components/sceneutil/shadow.cpp | 2 -- files/shaders/compatibility/shadows_vertex.glsl | 1 - 3 files changed, 12 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 06930ebe59..d0c270971a 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1025,7 +1025,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { dummyState->setTextureAttribute(i, _fallbackShadowMapTexture, osg::StateAttribute::ON); dummyState->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseUnit)).c_str(), i)); - dummyState->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseUnit)).c_str(), i)); } cv.pushStateSet(dummyState); @@ -1711,14 +1710,6 @@ void MWShadowTechnique::createShaders() for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureSampler.get()); } - - { - std::stringstream sstr; - sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); - for (auto& perFrameUniformList : _uniforms) - perFrameUniformList.emplace_back(shadowTextureUnit.get()); - } } switch(settings->getShaderHint()) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 9351ec249e..b32be08386 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -105,8 +105,6 @@ namespace SceneUtil osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); - stateset.addUniform(new osg::Uniform( - ("shadowTextureUnit" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); } } diff --git a/files/shaders/compatibility/shadows_vertex.glsl b/files/shaders/compatibility/shadows_vertex.glsl index a99a4a10e6..23fbc74988 100644 --- a/files/shaders/compatibility/shadows_vertex.glsl +++ b/files/shaders/compatibility/shadows_vertex.glsl @@ -3,7 +3,6 @@ #if SHADOWS @foreach shadow_texture_unit_index @shadow_texture_unit_list uniform mat4 shadowSpaceMatrix@shadow_texture_unit_index; - uniform int shadowTextureUnit@shadow_texture_unit_index; varying vec4 shadowSpaceCoords@shadow_texture_unit_index; #if @perspectiveShadowMaps From 8c92f6ee87f315255281c5ee7216b19a5de3d682 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:10:23 +0000 Subject: [PATCH 1041/2167] Make uniform a signed int again --- components/sceneutil/shadow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index b32be08386..37a11031aa 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -104,7 +104,7 @@ namespace SceneUtil stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( - ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), static_cast(i))); } } From 3335ccbc32facfe6afc8e6a433f8fa2bca2de23c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:51:42 +0000 Subject: [PATCH 1042/2167] Capitulate --- components/sceneutil/shadow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 37a11031aa..0d68ccaa0f 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -104,7 +104,8 @@ namespace SceneUtil stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( - ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), static_cast(i))); + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), + static_cast(i))); } } From fc55b876648f08471cb7d5694b1bbf1093807616 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 23:38:49 -0600 Subject: [PATCH 1043/2167] Put it in the right place, again --- files/lua_api/openmw/core.lua | 7 ------- files/lua_api/openmw/types.lua | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 169a41b2d2..330f0e20a0 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -24,13 +24,6 @@ -- @param #string eventName -- @param eventData ---- --- Send an event to menu scripts. --- @function [parent=#core] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - --- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 56deb6d558..df9014cf04 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1017,6 +1017,13 @@ -- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries -- types.Player.quests(player)["ms_fargothring"].stage = 0 +--- +-- Send an event to menu scripts. +-- @function [parent=#Player] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + --- -- @type PlayerQuest -- @field #string id The quest id. From d96340c9028795691943cdd044feedd881af9a09 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 23:42:27 -0600 Subject: [PATCH 1044/2167] Return to original order --- files/lua_api/openmw/types.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index df9014cf04..bd5e64901b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1017,13 +1017,6 @@ -- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries -- types.Player.quests(player)["ms_fargothring"].stage = 0 ---- --- Send an event to menu scripts. --- @function [parent=#Player] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - --- -- @type PlayerQuest -- @field #string id The quest id. @@ -1104,6 +1097,13 @@ -- @field #string texture Birth sign texture -- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. +--- +-- Send an event to menu scripts. +-- @function [parent=#Player] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + -------------------------------------------------------------------------------- -- @{#Armor} functions -- @field [parent=#types] #Armor Armor From 8ecf1a116a05352924dd43c6ec75fa5484d6b4b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 20 Feb 2024 14:38:08 +0300 Subject: [PATCH 1045/2167] Add missing .49 changelog entries --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 509fa34b67..96e6de5648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile @@ -56,6 +57,7 @@ Bug #6973: Fade in happens after the scene load and is shown Bug #6974: Only harmful effects are reflected Bug #6977: Sun damage implementation does not match research + Bug #6985: Issues with Magic Cards numbers readability Bug #6986: Sound magic effect does not make noise Bug #6987: Set/Mod Blindness should not darken the screen Bug #6992: Crossbow reloading doesn't look the same as in Morrowind @@ -76,7 +78,9 @@ Bug #7131: MyGUI log spam when post processing HUD is open Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI + Bug #7168: Fix average scene luminance Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7202: Post-processing normals for terrain, water randomly stop rendering Bug #7204: Missing actor scripts freeze the game Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files @@ -136,6 +140,7 @@ Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7763: Bullet shape loading problems, assorted Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7769: Sword of the Perithia: Broken NPCs Bug #7770: Sword of the Perithia: Script execution failure @@ -151,14 +156,15 @@ Feature #5492: Let rain and snow collide with statics Feature #5926: Refraction based on water depth Feature #5944: Option to use camera as sound listener - Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources Feature #6411: Support translations in openmw-launcher Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds + Feature #6679: Design a custom Input Action API Feature #6726: Lua API for creating new objects + Feature #6727: Lua API for records of all object types Feature #6864: Lua file access API Feature #6922: Improve launcher appearance Feature #6933: Support high-resolution cursor textures @@ -171,10 +177,12 @@ Feature #7125: Remembering console commands between sessions Feature #7129: Add support for non-adaptive VSync Feature #7130: Ability to set MyGUI logging verbosity + Feature #7142: MWScript Lua API Feature #7148: Optimize string literal lookup in mwscript + Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier Feature #7194: Ori to show texture paths Feature #7214: Searching in the in-game console - Feature #7284: Searching in the console with regex and toggleable case-sensitivity + Feature #7248: Searching in the console with regex and toggleable case-sensitivity Feature #7468: Factions API for Lua Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field @@ -194,7 +202,10 @@ Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context Task #5896: Do not use deprecated MyGUI properties + Task #6085: Replace boost::filesystem with std::filesystem + Task #6149: Dehardcode Lua API_REVISION Task #6624: Drop support for saves made prior to 0.45 + Task #7048: Get rid of std::bind Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector Task #7151: Do not use std::strerror to get errno error message From 4ca3b83ecbf3f6034026348947bbb209932920b8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 21 Feb 2024 09:07:00 +0300 Subject: [PATCH 1046/2167] Lua docs: equipment -> getEquipment --- files/lua_api/openmw/types.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d16f560a58..b089336e68 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -68,7 +68,7 @@ -- @field #number Ammunition --- --- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.equipment(obj)` and `Actor.setEquipment(obj, eqp)`. +-- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.getEquipment(obj)` and `Actor.setEquipment(obj, eqp)`. -- @field [parent=#Actor] #EQUIPMENT_SLOT EQUIPMENT_SLOT --- From fdd88fd2953c2beccf83c913487fc82055728657 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 21 Feb 2024 13:30:09 +0000 Subject: [PATCH 1047/2167] c h a n g e l o g --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671103cd21..7bf9d48b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ 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 Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7102: Some HQ Creatures mod models can hit the 8 texture slots limit with 0.48 Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7131: MyGUI log spam when post processing HUD is open From c2ac1ce046ff84df787f1584e8e6fff14c4359ac Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 21 Feb 2024 21:35:15 +0100 Subject: [PATCH 1048/2167] Use is_directory member function To reduce the number of syscalls. --- components/vfs/filesystemarchive.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 01e5c2a1b5..3303c6656c 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -24,11 +24,11 @@ namespace VFS for (auto it = std::filesystem::begin(iterator), end = std::filesystem::end(iterator); it != end;) { - const auto& i = *it; + const std::filesystem::directory_entry& entry = *it; - if (!std::filesystem::is_directory(i)) + if (!entry.is_directory()) { - const std::filesystem::path& filePath = i.path(); + const std::filesystem::path& filePath = entry.path(); const std::string proper = Files::pathToUnicodeString(filePath); VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); FileSystemArchiveFile file(filePath); @@ -43,7 +43,7 @@ namespace VFS // Exception thrown by the operator++ may not contain the context of the error like what exact path caused // the problem which makes it hard to understand what's going on when iteration happens over a directory // with thousands of files and subdirectories. - const std::filesystem::path prevPath = i.path(); + const std::filesystem::path prevPath = entry.path(); std::error_code ec; it.increment(ec); if (ec != std::error_code()) From 9fc66d5de613a9035c5d0b43890be9173339424d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 21 Feb 2024 15:25:13 -0600 Subject: [PATCH 1049/2167] Fix(idvalidator): Allow any printable character in refIds --- apps/opencs/view/world/idvalidator.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 6f790d20cb..b089c1df39 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -4,13 +4,7 @@ bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const { - if (c.isLetter() || c == '_') - return true; - - if (!first && (c.isDigit() || c.isSpace())) - return true; - - return false; + return c.isPrint() ? true : false; } CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) From f27564ec784f120e3871f10342788f90ef8261b6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 22 Feb 2024 00:16:41 +0000 Subject: [PATCH 1050/2167] Actually use the plane distances we just computed We don't get any of the speedup if we don't do this. We also forget about any objects nearer the camera than the previous value except the groundcover we're just about to deal with. Fixes https://gitlab.com/OpenMW/openmw/-/issues/7844 --- apps/openmw/mwrender/groundcover.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 8656af9d2f..4f99ee7560 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,8 @@ namespace MWRender { // Other objects are likely cheaper and should let us skip all but a few groundcover instances cullVisitor.computeNearPlane(); + computedZNear = cullVisitor.getCalculatedNearPlane(); + computedZFar = cullVisitor.getCalculatedFarPlane(); if (dNear < computedZNear) { From 2a5f8d5bab6746e28e98ecf2fe3f35b844e91fae Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 22 Feb 2024 00:24:44 +0000 Subject: [PATCH 1051/2167] Skip the check on MacOS It doesn't work, the workaround isn't enough to make it work, I can't be bothered making a more powerful workaround, and it's impossible to *package* a MacOS build missing the plugins we need anyway, even if you can build and attempt to run it. --- components/misc/osgpluginchecker.cpp.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 4b89551206..e58c6c59b2 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -15,11 +15,14 @@ namespace Misc { -#ifdef OSG_LIBRARY_STATIC +#if defined(OSG_LIBRARY_STATIC) || defined(__APPLE__) bool checkRequiredOSGPluginsArePresent() { // assume they were linked in at build time and CMake would have failed if they were missing + // true-ish for MacOS - they're copied into the package and that'd fail if they were missing, + // but if you don't actually make a MacOS package and run a local build, this won't notice. + // the workaround in the real implementation isn't powerful enough to make MacOS work, though. return true; } From 090a389febf317f3480d29ce8c11f9c320bfb827 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 02:52:58 -0600 Subject: [PATCH 1052/2167] Cleanup(idvalidator): Just don't use isValid function and instead directly check if input is a printable char --- apps/opencs/view/world/idvalidator.cpp | 7 +------ apps/opencs/view/world/idvalidator.hpp | 3 --- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index b089c1df39..078bd6bce5 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -2,11 +2,6 @@ #include -bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const -{ - return c.isPrint() ? true : false; -} - CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) : QValidator(parent) , mRelaxed(relaxed) @@ -86,7 +81,7 @@ QValidator::State CSVWorld::IdValidator::validate(QString& input, int& pos) cons { prevScope = false; - if (!isValid(*iter, first)) + if (!iter->isPrint()) return QValidator::Invalid; } } diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index e831542961..6b98d35672 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -13,9 +13,6 @@ namespace CSVWorld std::string mNamespace; mutable std::string mError; - private: - bool isValid(const QChar& c, bool first) const; - public: IdValidator(bool relaxed = false, QObject* parent = nullptr); ///< \param relaxed Relaxed rules for IDs that also functino as user visible text From 9c5f269e52b48a7dd1b6cba479289b3071942fb5 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 02:54:04 -0600 Subject: [PATCH 1053/2167] Add changelog entry for #7721 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e6de5648..3815905684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects + Bug #7721: CS: Special Chars Not Allowed in IDs Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name From 1b431bf63372220fab856d0ef8dbb01b25edd03f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 19 Feb 2024 05:25:20 -0600 Subject: [PATCH 1054/2167] Fix(editor): Don't save dirty water height values --- components/esm3/loadcell.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 829cf9e916..5b8521f9a1 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -190,25 +190,15 @@ namespace ESM if (mData.mFlags & Interior) { - if (mWaterInt) - { - int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); - esm.writeHNT("INTV", water); - } - else - { + // Try to avoid saving ambient information when it's unnecessary. + // This is to fix black lighting and flooded water + // in resaved cell records that lack this information. + if (mWaterInt && mWater != 0) esm.writeHNT("WHGT", mWater); - } - if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); - else - { - // Try to avoid saving ambient lighting information when it's unnecessary. - // This is to fix black lighting in resaved cell records that lack this information. - if (mHasAmbi) - esm.writeHNT("AMBI", mAmbi, 16); - } + else if (mHasAmbi) + esm.writeHNT("AMBI", mAmbi, 16); } else { From bb35f0366a12c1eea5e68d7aeabe4e19aac66758 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 07:12:42 -0600 Subject: [PATCH 1055/2167] Fix(loadcell): Save water height regardless of value, if the user actually adjusted it --- apps/opencs/model/world/nestedcoladapterimp.cpp | 3 +++ components/esm3/loadcell.cpp | 5 ++++- components/esm3/loadcell.hpp | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 13ae821a77..ea3a3bde26 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -996,7 +996,10 @@ namespace CSMWorld case 5: { if (isInterior && interiorWater) + { cell.mWater = value.toFloat(); + cell.setHasWater(true); + } else return; // return without saving break; diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 5b8521f9a1..f550c190cb 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -118,6 +118,7 @@ namespace ESM bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; + mHasWater = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -133,6 +134,7 @@ namespace ESM float waterLevel; esm.getHT(waterLevel); mWaterInt = false; + mHasWater = true; if (!std::isfinite(waterLevel)) { if (!overriding) @@ -193,7 +195,7 @@ namespace ESM // Try to avoid saving ambient information when it's unnecessary. // This is to fix black lighting and flooded water // in resaved cell records that lack this information. - if (mWaterInt && mWater != 0) + if (mHasWater) esm.writeHNT("WHGT", mWater); if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); @@ -323,6 +325,7 @@ namespace ESM mData.mY = 0; mHasAmbi = true; + mHasWater = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index bfabdd58f9..d54ba9573a 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -112,6 +112,7 @@ namespace ESM , mHasAmbi(true) , mWater(0) , mWaterInt(false) + , mHasWater(false) , mMapColor(0) , mRefNumCounter(0) { @@ -132,6 +133,7 @@ namespace ESM float mWater; // Water level bool mWaterInt; + bool mHasWater; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. @@ -163,6 +165,8 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } + void setHasWater(bool hasWater) { mHasWater = hasWater; } + bool hasAmbient() const { return mHasAmbi; } void setHasAmbient(bool hasAmbi) { mHasAmbi = hasAmbi; } From f95cad07f2a5fc7087d10a1a3ca13c28b1bccf62 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 08:01:08 -0600 Subject: [PATCH 1056/2167] Cleanup(loadcell): Remove unused integer water flag --- components/esm3/loadcell.cpp | 3 --- components/esm3/loadcell.hpp | 2 -- 2 files changed, 5 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index f550c190cb..1d54cd84bf 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -128,12 +128,10 @@ namespace ESM int32_t waterl; esm.getHT(waterl); mWater = static_cast(waterl); - mWaterInt = true; break; case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); - mWaterInt = false; mHasWater = true; if (!std::isfinite(waterLevel)) { @@ -316,7 +314,6 @@ namespace ESM mName.clear(); mRegion = ESM::RefId(); mWater = 0; - mWaterInt = false; mMapColor = 0; mRefNumCounter = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index d54ba9573a..1397479154 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -111,7 +111,6 @@ namespace ESM , mRegion(ESM::RefId()) , mHasAmbi(true) , mWater(0) - , mWaterInt(false) , mHasWater(false) , mMapColor(0) , mRefNumCounter(0) @@ -132,7 +131,6 @@ namespace ESM bool mHasAmbi; float mWater; // Water level - bool mWaterInt; bool mHasWater; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. From d04572ac847180c569aa793c6c80557762ce181d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 08:39:43 -0600 Subject: [PATCH 1057/2167] Cleanup(loadcell): Rename mHasWater to mHasWaterHeightSub for clarity. --- components/esm3/loadcell.cpp | 8 ++++---- components/esm3/loadcell.hpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 1d54cd84bf..473c4c7d72 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -118,7 +118,7 @@ namespace ESM bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; - mHasWater = false; + mHasWaterHeightSub = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -132,7 +132,7 @@ namespace ESM case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); - mHasWater = true; + mHasWaterHeightSub = true; if (!std::isfinite(waterLevel)) { if (!overriding) @@ -193,7 +193,7 @@ namespace ESM // Try to avoid saving ambient information when it's unnecessary. // This is to fix black lighting and flooded water // in resaved cell records that lack this information. - if (mHasWater) + if (mHasWaterHeightSub) esm.writeHNT("WHGT", mWater); if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); @@ -322,7 +322,7 @@ namespace ESM mData.mY = 0; mHasAmbi = true; - mHasWater = true; + mHasWaterHeightSub = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 1397479154..a22110be32 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -111,7 +111,7 @@ namespace ESM , mRegion(ESM::RefId()) , mHasAmbi(true) , mWater(0) - , mHasWater(false) + , mHasWaterHeightSub(false) , mMapColor(0) , mRefNumCounter(0) { @@ -131,7 +131,7 @@ namespace ESM bool mHasAmbi; float mWater; // Water level - bool mHasWater; + bool mHasWaterHeightSub; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. @@ -163,7 +163,7 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } - void setHasWater(bool hasWater) { mHasWater = hasWater; } + void setHasWater(bool hasWater) { mHasWaterHeightSub = hasWater; } bool hasAmbient() const { return mHasAmbi; } From b2b1c98396edb05888b0c59e3ebb5bfa1188e140 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 22:23:53 -0600 Subject: [PATCH 1058/2167] fix(esmtool): Don't try to log a variable that doesn't exist --- apps/esmtool/record.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 245012ce13..b1185a4d33 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -612,7 +612,6 @@ namespace EsmTool } else std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; - std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } From 7f67d2e805bfba82c84163f429d266a9c4d84826 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 03:02:10 -0600 Subject: [PATCH 1059/2167] Add changelog entry for #7841 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e6de5648..c4fbaf3166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,7 @@ Bug #7796: Absorbed enchantments don't restore magicka Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value + Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty From ce2787e15e7581c2aae5cd5a0b40203a5b3fe017 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 03:23:23 -0600 Subject: [PATCH 1060/2167] Cleanup(loadcell): Rename setHasWater to setHasWaterHeightSub --- apps/opencs/model/world/nestedcoladapterimp.cpp | 2 +- components/esm3/loadcell.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index ea3a3bde26..8b8c7b17be 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -998,7 +998,7 @@ namespace CSMWorld if (isInterior && interiorWater) { cell.mWater = value.toFloat(); - cell.setHasWater(true); + cell.setHasWaterHeightSub(true); } else return; // return without saving diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index a22110be32..3f16bcca31 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -163,7 +163,7 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } - void setHasWater(bool hasWater) { mHasWaterHeightSub = hasWater; } + void setHasWaterHeightSub(bool hasWater) { mHasWaterHeightSub = hasWater; } bool hasAmbient() const { return mHasAmbi; } From 38990b1fd227acca441c98296a9841f878c559a2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 11:15:39 +0100 Subject: [PATCH 1061/2167] Set components property after it is defined --- components/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dc195d8d0b..6ab7fc4795 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -526,16 +526,16 @@ if (USE_QT) QT_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() -if (ANDROID) - set_property(TARGET components PROPERTY POSTION_INDEPENDENT_CODE ON) -endif() - include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) find_package(SQLite3 REQUIRED) add_library(components STATIC ${COMPONENT_FILES}) +if (ANDROID) + set_property(TARGET components PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + target_link_libraries(components ${COLLADA_DOM_LIBRARIES} From 7c4b42ab2a7cee4eec39fabf7ba8a95c2c735da0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 22 Feb 2024 19:06:15 +0400 Subject: [PATCH 1062/2167] Add a Lua function to check if actor's death is finished --- CMakeLists.txt | 2 +- apps/openmw/mwlua/types/actor.cpp | 5 +++++ files/lua_api/openmw/types.lua | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f13def9ab0..107a8691ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 54) +set(OPENMW_LUA_API_REVISION 55) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 4fda04e7c5..3b0142e441 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -403,6 +403,11 @@ namespace MWLua return target.getClass().getCreatureStats(target).isDead(); }; + actor["isDeathFinished"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDeathAnimationFinished(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a7f57d3a6c..0c51544f64 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -16,11 +16,17 @@ -- @return #number --- --- Check if the given actor is dead. +-- Check if the given actor is dead (health reached 0, so death process started). -- @function [parent=#Actor] isDead -- @param openmw.core#GameObject actor -- @return #boolean +--- +-- Check if the given actor's death process is finished. +-- @function [parent=#Actor] isDeathFinished +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds From 0bab37327c0055120687d9db64ac04de99fd3249 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 22 Feb 2024 20:23:21 +0100 Subject: [PATCH 1063/2167] Account for pre-0.46 saves storing a gold value of 0 for everything --- components/esm3/formatversion.hpp | 1 + components/esm3/objectstate.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 9f499a7231..d90742a512 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,6 +9,7 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; + inline constexpr FormatVersion MaxOldGoldValueFormatVersion = 5; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 25cbdc9a98..f8905cfaea 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -30,7 +30,11 @@ namespace ESM esm.getHNOT(mEnabled, "ENAB"); if (mVersion <= MaxOldCountFormatVersion) - esm.getHNOT(mRef.mCount, "COUN"); + { + if (mVersion <= MaxOldGoldValueFormatVersion) + mRef.mCount = std::max(1, mRef.mCount); + esm.getHNOT("COUN", mRef.mCount); + } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); From db5a43db30fc1d1a05018be032d3aff55c3575df Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Dec 2023 17:48:40 +0000 Subject: [PATCH 1064/2167] Allow top-level prefix to be found in the middle of a path --- components/misc/resourcehelpers.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 7386dceb9f..c9a3591046 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -75,6 +75,17 @@ std::string Misc::ResourceHelpers::correctResourcePath( needsPrefix = false; break; } + else + { + std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + '\\'; + size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); + if (topLevelPos != std::string::npos) + { + correctedPath.erase(0, topLevelPos + 1); + needsPrefix = false; + break; + } + } } if (needsPrefix) correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath; From 1717e696b16ff852a8e274f0f0be8c1699084376 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 00:06:51 +0000 Subject: [PATCH 1065/2167] Format before clang notices and sends me an angry email --- components/misc/resourcehelpers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index c9a3591046..119936f2ab 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -69,7 +69,8 @@ std::string Misc::ResourceHelpers::correctResourcePath( bool needsPrefix = true; for (std::string_view potentialTopLevelDirectory : topLevelDirectories) { - if (correctedPath.starts_with(potentialTopLevelDirectory) && correctedPath.size() > potentialTopLevelDirectory.size() + if (correctedPath.starts_with(potentialTopLevelDirectory) + && correctedPath.size() > potentialTopLevelDirectory.size() && correctedPath[potentialTopLevelDirectory.size()] == '\\') { needsPrefix = false; From 36a75cdb29546f226b87fcda86d4caf683879561 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 19:05:26 +0100 Subject: [PATCH 1066/2167] Get the GLExtensions instance when a context is created --- apps/opencs/view/render/scenewidget.cpp | 3 +++ apps/openmw/engine.cpp | 30 ++++++++++++---------- apps/openmw/mwrender/ripples.cpp | 7 ++--- components/CMakeLists.txt | 2 +- components/resource/imagemanager.cpp | 8 +++--- components/sceneutil/depth.cpp | 4 +-- components/sceneutil/glextensions.cpp | 28 ++++++++++++++++++++ components/sceneutil/glextensions.hpp | 20 +++++++++++++++ components/sceneutil/lightmanager.cpp | 7 ++--- components/sceneutil/mwshadowtechnique.cpp | 4 +-- components/shader/shadervisitor.cpp | 4 +-- 11 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 components/sceneutil/glextensions.cpp create mode 100644 components/sceneutil/glextensions.hpp diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 953e3076b3..716a087d02 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include "../widget/scenetoolmode.hpp" @@ -76,6 +77,8 @@ namespace CSVRender = new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height()); mWidget->setGraphicsWindowEmbedded(window); + mRenderer->setRealizeOperation(new SceneUtil::GetGLExtensionsOperation()); + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); mRenderer->setRunMaxFrameRate(frameRateLimit); mRenderer->setUseConfigureAffinity(false); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 75687ff281..2e0e591538 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -600,6 +601,7 @@ void OMW::Engine::createWindow() mViewer->setRealizeOperation(realizeOperations); osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); realizeOperations->add(identifyOp); + realizeOperations->add(new SceneUtil::GetGLExtensionsOperation()); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); @@ -780,13 +782,13 @@ void OMW::Engine::prepareEngine() // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + bool shadersSupported = exts.glslLanguageVersion >= 1.2f; #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 - if (exts) - exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; + if (!osg::isGLExtensionSupported(exts.contextID, "NV_framebuffer_multisample_coverage")) + exts.glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif osg::ref_ptr guiRoot = new osg::Group; @@ -844,18 +846,18 @@ void OMW::Engine::prepareEngine() const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader( [gmst, misses = std::set>()](std::string_view gmstName) mutable { - const ESM::GameSetting* res = gmst->search(gmstName); - if (res && res->mValue.getType() == ESM::VT_String) - return res->mValue.getString(); - else + const ESM::GameSetting* res = gmst->search(gmstName); + if (res && res->mValue.getType() == ESM::VT_String) + return res->mValue.getString(); + else + { + if (misses.count(gmstName) == 0) { - if (misses.count(gmstName) == 0) - { - misses.emplace(gmstName); - Log(Debug::Error) << "GMST " << gmstName << " not found"; - } - return std::string("GMST:") + std::string(gmstName); + misses.emplace(gmstName); + Log(Debug::Error) << "GMST " << gmstName << " not found"; } + return std::string("GMST:") + std::string(gmstName); + } }); mWindowManager->setStore(mWorld->getStore()); diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index dea372666e..4599c2c946 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "../mwworld/ptr.hpp" @@ -43,9 +44,9 @@ namespace MWRender mUseCompute = false; #else constexpr float minimumGLVersionRequiredForCompute = 4.4; - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - mUseCompute = exts->glVersion >= minimumGLVersionRequiredForCompute - && exts->glslLanguageVersion >= minimumGLVersionRequiredForCompute; + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + mUseCompute = exts.glVersion >= minimumGLVersionRequiredForCompute + && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; #endif if (mUseCompute) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dc195d8d0b..9feec375a2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -122,7 +122,7 @@ add_component_dir (sceneutil lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod clearcolor - cullsafeboundsvisitor keyframe nodecallback textkeymap + cullsafeboundsvisitor keyframe nodecallback textkeymap glextensions ) add_component_dir (nif diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 26fd60d7ea..124ff9b6ad 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -65,12 +66,11 @@ namespace Resource case (GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case (GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - if (exts - && !exts->isTextureCompressionS3TCSupported + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + if (!exts.isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a // patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) + && !osg::isGLExtensionSupported(exts.contextID, "GL_S3_s3tc")) { return false; } diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index 738fa93dd8..5232d321dc 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace SceneUtil @@ -116,8 +117,7 @@ namespace SceneUtil if (Settings::camera().mReverseZ) { - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isClipControlSupported) + if (SceneUtil::getGLExtensions().isClipControlSupported) { enableReverseZ = true; Log(Debug::Info) << "Using reverse-z depth buffer"; diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp new file mode 100644 index 0000000000..078af90c3c --- /dev/null +++ b/components/sceneutil/glextensions.cpp @@ -0,0 +1,28 @@ +#include "glextensions.hpp" + +namespace SceneUtil +{ + namespace + { + osg::observer_ptr sGLExtensions; + } + + osg::GLExtensions& getGLExtensions() + { + if (!sGLExtensions) + throw std::runtime_error( + "GetGLExtensionsOperation was not used when the current context was created or there is no current " + "context"); + return *sGLExtensions; + } + + GetGLExtensionsOperation::GetGLExtensionsOperation() + : GraphicsOperation("GetGLExtensionsOperation", false) + { + } + + void GetGLExtensionsOperation::operator()(osg::GraphicsContext* graphicsContext) + { + sGLExtensions = graphicsContext->getState()->get(); + } +} diff --git a/components/sceneutil/glextensions.hpp b/components/sceneutil/glextensions.hpp new file mode 100644 index 0000000000..17a4eb8488 --- /dev/null +++ b/components/sceneutil/glextensions.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H +#define OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H + +#include +#include + +namespace SceneUtil +{ + osg::GLExtensions& getGLExtensions(); + + class GetGLExtensionsOperation : public osg::GraphicsOperation + { + public: + GetGLExtensionsOperation(); + + void operator()(osg::GraphicsContext* graphicsContext) override; + }; +} + +#endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 8f7304416b..48efb7fda9 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -824,9 +825,9 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - bool supportsUBO = exts && exts->isUniformBufferObjectSupported; - bool supportsGPU4 = exts && exts->isGpuShader4Supported; + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + bool supportsUBO = exts.isUniformBufferObjectSupported; + bool supportsGPU4 = exts.isGpuShader4Supported; mSupported[static_cast(LightingMethod::FFP)] = true; mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index d0c270971a..d1553cc8d8 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -30,6 +30,7 @@ #include #include +#include "glextensions.hpp" #include "shadowsbin.hpp" namespace { @@ -920,8 +921,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting.vert"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; + std::string useGPUShader4 = SceneUtil::getGLExtensions().isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e281f64448..7bce9de2a6 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -676,8 +677,7 @@ namespace Shader defineMap["adjustCoverage"] = "1"; // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isGpuShader4Supported) + if (SceneUtil::getGLExtensions().isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } From 53afa6b1854725973a21b3e15b7f058d4b6b54f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:06:21 +0100 Subject: [PATCH 1067/2167] Appease clang-format by changing something I didn't touch --- apps/openmw/engine.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2e0e591538..49833040d6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -846,18 +846,18 @@ void OMW::Engine::prepareEngine() const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader( [gmst, misses = std::set>()](std::string_view gmstName) mutable { - const ESM::GameSetting* res = gmst->search(gmstName); - if (res && res->mValue.getType() == ESM::VT_String) - return res->mValue.getString(); - else - { - if (misses.count(gmstName) == 0) + const ESM::GameSetting* res = gmst->search(gmstName); + if (res && res->mValue.getType() == ESM::VT_String) + return res->mValue.getString(); + else { - misses.emplace(gmstName); - Log(Debug::Error) << "GMST " << gmstName << " not found"; + if (misses.count(gmstName) == 0) + { + misses.emplace(gmstName); + Log(Debug::Error) << "GMST " << gmstName << " not found"; + } + return std::string("GMST:") + std::string(gmstName); } - return std::string("GMST:") + std::string(gmstName); - } }); mWindowManager->setStore(mWorld->getStore()); From ec4731d454a6eadfb33cefe46c9c0398581443c2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:38:43 +0100 Subject: [PATCH 1068/2167] Cope with scene widgets being destroyed in a weird order I can't actually test this as the CS still doesn't get far enough with this MR. --- components/sceneutil/glextensions.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index 078af90c3c..eb7783c45f 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -4,16 +4,29 @@ namespace SceneUtil { namespace { - osg::observer_ptr sGLExtensions; + std::set> sGLExtensions; + + class GLExtensionsObserver : public osg::Observer + { + public: + static GLExtensionsObserver sInstance; + + void objectDeleted(void* referenced) override + { + sGLExtensions.erase(static_cast(referenced)); + } + }; + + GLExtensionsObserver GLExtensionsObserver::sInstance{}; } osg::GLExtensions& getGLExtensions() { - if (!sGLExtensions) + if (sGLExtensions.empty()) throw std::runtime_error( "GetGLExtensionsOperation was not used when the current context was created or there is no current " "context"); - return *sGLExtensions; + return **sGLExtensions.begin(); } GetGLExtensionsOperation::GetGLExtensionsOperation() @@ -23,6 +36,7 @@ namespace SceneUtil void GetGLExtensionsOperation::operator()(osg::GraphicsContext* graphicsContext) { - sGLExtensions = graphicsContext->getState()->get(); + auto [itr, _] = sGLExtensions.emplace(graphicsContext->getState()->get()); + (*itr)->addObserver(&GLExtensionsObserver::sInstance); } } From 2bc091fc05f22b89d6019a7440f179cd1fcca3bb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:51:16 +0100 Subject: [PATCH 1069/2167] Include missing header I thought I'd seen this class defined in one of the existing headers with a different name, but I was muddling its forward declaration and a different class being in a non-obvious header. --- components/sceneutil/glextensions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index eb7783c45f..310823a8ea 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -1,5 +1,7 @@ #include "glextensions.hpp" +#include + namespace SceneUtil { namespace From 6406095bfb478a25c0831c41db7dfc1bd26a480e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 01:34:01 +0000 Subject: [PATCH 1070/2167] s p a n --- components/misc/resourcehelpers.cpp | 8 ++++---- components/misc/resourcehelpers.hpp | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 119936f2ab..4e7f7c41e3 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -47,7 +47,7 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) } std::string Misc::ResourceHelpers::correctResourcePath( - const std::vector& topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -124,17 +124,17 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "textures", "bookart" }, resPath, vfs); + return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "icons" }, resPath, vfs); + return correctResourcePath({ { "icons" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "bookart", "textures" }, resPath, vfs); + return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index c98840dd61..bd95f2376b 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -1,6 +1,7 @@ #ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H +#include #include #include #include @@ -23,8 +24,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath(const std::vector& topLevelDirectories, - std::string_view resPath, const VFS::Manager* vfs); + std::string correctResourcePath( + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 83ab028bef1b632de03f2cefdc16f2ede426634c Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Fri, 23 Feb 2024 12:37:20 +0000 Subject: [PATCH 1071/2167] Improve in-game text --- AUTHORS.md | 1 + files/data/l10n/OMWCamera/en.yaml | 34 ++++++++-------- files/data/l10n/OMWControls/en.yaml | 61 ++++++++++++++--------------- files/data/l10n/OMWEngine/en.yaml | 16 ++++---- files/data/l10n/OMWShaders/en.yaml | 10 ++--- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 4d03fba227..7c06d72287 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ Programmers Nicolay Korslund - Project leader 2008-2010 scrawl - Top contributor + AbduSharif Adam Hogan (aurix) Aesylwinn aegis diff --git a/files/data/l10n/OMWCamera/en.yaml b/files/data/l10n/OMWCamera/en.yaml index d030bd22ec..609b028167 100644 --- a/files/data/l10n/OMWCamera/en.yaml +++ b/files/data/l10n/OMWCamera/en.yaml @@ -1,43 +1,43 @@ Camera: "OpenMW Camera" -settingsPageDescription: "OpenMW Camera settings" +settingsPageDescription: "OpenMW camera settings." -thirdPersonSettings: "Third person mode" +thirdPersonSettings: "Third Person Mode" -viewOverShoulder: "View over the shoulder" +viewOverShoulder: "View Over the Shoulder" viewOverShoulderDescription: | Controls third person view mode. No: view is centered on the character's head. Crosshair is hidden. Yes: while weapon sheathed the camera is positioned behind the character's shoulder, crosshair is always visible. -shoulderOffsetX: "Shoulder view horizontal offset" +shoulderOffsetX: "Shoulder View Horizontal Offset" shoulderOffsetXDescription: > Horizontal offset of the over-the-shoulder view. For the left shoulder use a negative value. -shoulderOffsetY: "Shoulder view vertical offset" +shoulderOffsetY: "Shoulder View Vertical Offset" shoulderOffsetYDescription: > Vertical offset of the over-the-shoulder view. -autoSwitchShoulder: "Auto switch shoulder" +autoSwitchShoulder: "Auto Switch Shoulder" autoSwitchShoulderDescription: > When there are obstacles that would push the camera close to the player character, this setting makes the camera automatically switch to the shoulder farther away from the obstacles. -zoomOutWhenMoveCoef: "Zoom out when move coef" +zoomOutWhenMoveCoef: "Zoom Out When Move Coef" zoomOutWhenMoveCoefDescription: > Moves the camera away (positive value) or towards (negative value) the player character while the character is moving. Works only if "view over the shoulder" is enabled. Set this to zero to disable (default: 20.0). -previewIfStandStill: "Preview if stand still" +previewIfStandStill: "Preview if Stand Still" previewIfStandStillDescription: > Prevents the player character from turning towards the camera direction while they're idle and have their weapon sheathed. -deferredPreviewRotation: "Deferred preview rotation" +deferredPreviewRotation: "Deferred Preview Rotation" deferredPreviewRotationDescription: | If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. If disabled then the camera rotates rather than the character. -ignoreNC: "Ignore 'No Collision' flag" +ignoreNC: "Ignore 'No Collision' Flag" ignoreNCDescription: > Prevents the camera from clipping through objects that have NC (No Collision) flag turned on in the NIF model. @@ -46,27 +46,27 @@ move360Description: > Makes the movement direction independent from the camera direction while the player character's weapon is sheathed. For example, the player character will look at the camera while running backwards. -move360TurnSpeed: "Move 360 turning speed" +move360TurnSpeed: "Move 360 Turning Speed" move360TurnSpeedDescription: "Turning speed multiplier (default: 5.0)." -slowViewChange: "Smooth view change" +slowViewChange: "Smooth View Change" slowViewChangeDescription: "Makes the transition from 1st person to 3rd person view non-instantaneous." -povAutoSwitch: "First person auto switch" +povAutoSwitch: "First Person Auto Switch" povAutoSwitchDescription: "Auto switch to the first person view if there is an obstacle right behind the player." -headBobbingSettings: "Head bobbing in first person view" +headBobbingSettings: "Head Bobbing in First Person View" headBobbing_enabled: "Enabled" headBobbing_enabledDescription: "" -headBobbing_step: "Base step length" +headBobbing_step: "Base Step Length" headBobbing_stepDescription: "The length of each step (default: 90.0)." -headBobbing_height: "Step height" +headBobbing_height: "Step Height" headBobbing_heightDescription: "The amplitude of the head bobbing (default: 3.0)." -headBobbing_roll: "Max roll angle" +headBobbing_roll: "Max Roll Angle" headBobbing_rollDescription: "The maximum roll angle in degrees (default: 0.2)." diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index 9c45c1d1e5..c034eb8683 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -1,22 +1,21 @@ ControlsPage: "OpenMW Controls" -ControlsPageDescription: "Additional settings related to player controls" +ControlsPageDescription: "Additional settings related to player controls." MovementSettings: "Movement" -alwaysRun: "Always run" +alwaysRun: "Always Run" alwaysRunDescription: | - If this setting is true, the character is running by default, otherwise the character is walking by default. - The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". + If this setting is true, the character will run by default, otherwise the character will walk by default. + The Shift key will temporarily invert this setting, and the Caps Lock key will invert this setting while it's "locked". -toggleSneak: "Toggle sneak" +toggleSneak: "Toggle Sneak" toggleSneakDescription: | - This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off - rather than requiring the key to be held down while sneaking. + This setting makes the Sneak key (bound to Ctrl by default) toggle sneaking instead of having to be held down to sneak. Players that spend significant time sneaking may find the character easier to control with this option enabled. -smoothControllerMovement: "Smooth controller movement" +smoothControllerMovement: "Smooth Controller Movement" smoothControllerMovementDescription: | - Enables smooth movement with controller stick, with no abrupt switch from walking to running. + Enables smooth controller stick movement. This makes the transition from walking to running less abrupt. TogglePOV_name: "Toggle POV" TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode." @@ -25,61 +24,61 @@ Zoom3rdPerson_name: "Zoom In/Out" Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view." MoveForward_name: "Move Forward" -MoveForward_description: "Can cancel out with Move Backward" +MoveForward_description: "Can cancel out with Move Backward." MoveBackward_name: "Move Backward" -MoveBackward_description: "Can cancel out with Move Forward" +MoveBackward_description: "Can cancel out with Move Forward." MoveLeft_name: "Move Left" -MoveLeft_description: "Can cancel out with Move Right" +MoveLeft_description: "Can cancel out with Move Right." MoveRight_name: "Move Right" -MoveRight_description: "Can cancel out with Move Left" +MoveRight_description: "Can cancel out with Move Left." Use_name: "Use" -Use_description: "Attack with a weapon or cast a spell depending on current stance" +Use_description: "Attack with a weapon or cast a spell depending on the current stance." Run_name: "Run" -Run_description: "Hold to run/walk, depending on the Always Run setting" +Run_description: "Hold to run/walk depending on the Always Run setting." AlwaysRun_name: "Always Run" -AlwaysRun_description: "Toggle the Always Run setting" +AlwaysRun_description: "Toggle the Always Run setting." Jump_name: "Jump" -Jump_description: "Jump whenever you are on the ground" +Jump_description: "Jump whenever you are on the ground." AutoMove_name: "Auto Run" -AutoMove_description: "Toggle continous forward movement" +AutoMove_description: "Toggle continuous forward movement." Sneak_name: "Sneak" -Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off" +Sneak_description: "Hold to sneak if the Toggle Sneak setting is off." ToggleSneak_name: "Toggle Sneak" -ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on" +ToggleSneak_description: "Toggle sneak if the Toggle Sneak setting is on." ToggleWeapon_name: "Ready Weapon" -ToggleWeapon_description: "Enter or leave the weapon stance" +ToggleWeapon_description: "Enter or leave the weapon stance." ToggleSpell_name: "Ready Magic" -ToggleSpell_description: "Enter or leave the magic stance" +ToggleSpell_description: "Enter or leave the magic stance." Inventory_name: "Inventory" -Inventory_description: "Open the inventory" +Inventory_description: "Open the inventory." Journal_name: "Journal" -Journal_description: "Open the journal" +Journal_description: "Open the journal." -QuickKeysMenu_name: "QuickKeysMenu" -QuickKeysMenu_description: "Open the quick keys menu" +QuickKeysMenu_name: "Quick Menu" +QuickKeysMenu_description: "Open the quick keys menu." SmoothMoveForward_name: "Smooth Move Forward" -SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions" +SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions." SmoothMoveBackward_name: "Smooth Move Backward" -SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions" +SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions." SmoothMoveLeft_name: "Smooth Move Left" -SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions" +SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions." -SkmoothMoveRight_name: "SmoothMove Right" -SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions" +SkmoothMoveRight_name: "Smooth Move Right" +SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index f6ad237394..55ebbf3e94 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -13,7 +13,7 @@ PhysicsProfiler: "Physics Profiler" # Messages AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -BuildingNavigationMesh: "Building navigation mesh" +BuildingNavigationMesh: "Building Navigation Mesh" InitializingData: "Initializing Data..." LoadingExterior: "Loading Area" LoadingFailed: "Failed to load saved game" @@ -57,7 +57,7 @@ MissingContentFilesListCopy: |- } OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" SelectCharacter: "Select Character..." -TimePlayed: "Time played" +TimePlayed: "Time Played" # Settings menu @@ -131,12 +131,12 @@ PrimaryLanguageTooltip: "Localization files for this language have the highest p QualityHigh: "High" QualityLow: "Low" QualityMedium: "Medium" -RainRippleDetail: "Rain ripple detail" +RainRippleDetail: "Rain Ripple Detail" RainRippleDetailDense: "Dense" RainRippleDetailSimple: "Simple" RainRippleDetailSparse: "Sparse" RebindAction: "Press a key or button to rebind this control." -ReflectionShaderDetail: "Reflection shader detail" +ReflectionShaderDetail: "Reflection Shader Detail" ReflectionShaderDetailActors: "Actors" ReflectionShaderDetailGroundcover: "Groundcover" ReflectionShaderDetailObjects: "Objects" @@ -154,8 +154,8 @@ SensitivityHigh: "High" SensitivityLow: "Low" SettingsWindow: "Options" Subtitles: "Subtitles" -TestingExteriorCells: "Testing exterior cells" -TestingInteriorCells: "Testing interior cells" +TestingExteriorCells: "Testing Exterior Cells" +TestingInteriorCells: "Testing Interior Cells" TextureFiltering: "Texture Filtering" TextureFilteringBilinear: "Bilinear" TextureFilteringDisabled: "None" @@ -170,8 +170,8 @@ ViewDistance: "View Distance" VSync: "VSync" VSyncAdaptive: "Adaptive" Water: "Water" -WaterShader: "Water shader" -WaterShaderTextureQuality: "Texture quality" +WaterShader: "Water Shader" +WaterShaderTextureQuality: "Texture Quality" WindowBorder: "Window Border" WindowMode: "Window Mode" WindowModeFullscreen: "Fullscreen" diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 6588591f00..a8c13da34b 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -14,7 +14,7 @@ KeyboardControls: | Shift+Left-Arrow > Deactive shader Shift+Up-Arrow > Move shader up Shift+Down-Arrow > Move shader down -PostProcessHUD: "Postprocess HUD" +PostProcessHUD: "Post Processor HUD" ResetShader: "Reset shader to default state" ShaderLocked: "Locked" ShaderLockedDescription: "Cannot be toggled or moved, controlled by external Lua script" @@ -30,11 +30,11 @@ BloomDescription: "Bloom shader performing its calculations in (approximately) l DebugDescription: "Debug shader." DebugHeaderDepth: "Depth Buffer" DebugHeaderNormals: "Normals" -DisplayDepthFactorName: "Depth colour factor" +DisplayDepthFactorName: "Depth Colour Factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." -DisplayDepthName: "Visualize depth buffer" -DisplayNormalsName: "Visualize pass normals" -NormalsInWorldSpace: "Show normals in world space" +DisplayDepthName: "Visualize Depth Buffer" +DisplayNormalsName: "Visualize Pass Normals" +NormalsInWorldSpace: "Show Normals in World Space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." From 3fbd97ffc876cf776779db3e3b4cde6984b1cacb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 12:48:39 +0000 Subject: [PATCH 1072/2167] Remove unused header --- components/misc/resourcehelpers.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index bd95f2376b..a4d46f2611 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -4,7 +4,6 @@ #include #include #include -#include namespace VFS { From fc1f2446278516e7936663c470addf5c5abd076c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:01:59 +0400 Subject: [PATCH 1073/2167] Add missing initialization --- components/debug/debugdraw.cpp | 2 -- components/debug/debugdraw.hpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index e7aa7d8cce..2bc7358259 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -316,8 +316,6 @@ Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copy Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) { - mCurrentFrame = 0; - auto program = shaderManager.getProgram("debug"); setCullingActive(false); diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 2518813cad..eb4219e06b 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -101,7 +101,7 @@ namespace Debug void addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color = colorWhite); private: - unsigned int mCurrentFrame; + unsigned int mCurrentFrame = 0; std::array, 2> mCustomDebugDrawer; }; From 1126f38a1e7c6b08cf8777ffdcad0c29c0023653 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:02:40 +0400 Subject: [PATCH 1074/2167] Do not copy the whole attributes store --- apps/openmw/mwlua/stats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index c6492c1ec2..eaa1f89d97 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -476,7 +476,7 @@ namespace MWLua auto skillIncreasesForAttributeStatsT = context.mLua->sol().new_usertype("SkillIncreasesForAttributeStats"); - for (auto attribute : MWBase::Environment::get().getESMStore()->get()) + for (const auto& attribute : MWBase::Environment::get().getESMStore()->get()) { skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property( [=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); }, From cf6b6020a013f279a42bc4e63bbb1d6393e4adec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:03:13 +0400 Subject: [PATCH 1075/2167] Move local variables --- apps/openmw/mwlua/nearbybindings.cpp | 4 ++-- components/lua/asyncpackage.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 7eda965e96..af6980fb7f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -163,8 +163,8 @@ namespace MWLua ignore = parseIgnoreList(*options); } - context.mLuaManager->addAction([context, ignore, callback = LuaUtil::Callback::fromLua(callback), from, - to] { + context.mLuaManager->addAction([context, ignore = std::move(ignore), + callback = LuaUtil::Callback::fromLua(callback), from, to] { MWPhysics::RayCastingResult res; MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 6e13406511..5d563e6276 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -94,7 +94,7 @@ namespace LuaUtil sol::table callbackMeta = Callback::makeMetatable(L); api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { - return Callback::make(asyncId, fn, callbackMeta); + return Callback::make(asyncId, std::move(fn), callbackMeta); }; auto initializer = [](sol::table hiddenData) { From dec8d32b3a7cf12666157082e7600778756cc67b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 24 Feb 2024 00:54:40 +0000 Subject: [PATCH 1076/2167] FIx static destruction order chaos --- components/sceneutil/glextensions.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index 310823a8ea..eb55e17b1c 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -13,12 +13,23 @@ namespace SceneUtil public: static GLExtensionsObserver sInstance; + ~GLExtensionsObserver() override + { + for (auto& ptr : sGLExtensions) + { + osg::ref_ptr ref; + if (ptr.lock(ref)) + ref->removeObserver(this); + } + } + void objectDeleted(void* referenced) override { sGLExtensions.erase(static_cast(referenced)); } }; + // construct after sGLExtensions so this gets destroyed first. GLExtensionsObserver GLExtensionsObserver::sInstance{}; } From 92d57d6e46d8d661969e71be5b39ff36113c9141 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 23:59:23 +0100 Subject: [PATCH 1077/2167] Make Normalized constructor from const char* explicit --- apps/openmw_test_suite/testing_util.hpp | 7 +++++++ components/vfs/manager.cpp | 5 +++++ components/vfs/manager.hpp | 2 ++ components/vfs/pathutil.hpp | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index ad1b0423ef..60367ffbe9 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -2,6 +2,7 @@ #define TESTING_UTIL_H #include +#include #include #include @@ -73,6 +74,12 @@ namespace TestingOpenMW return vfs; } + inline std::unique_ptr createTestVFS( + std::initializer_list> files) + { + return createTestVFS(VFS::FileMap(files.begin(), files.end())); + } + #define EXPECT_ERROR(X, ERR_SUBSTR) \ try \ { \ diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index a6add0861a..ef5dd495c9 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -57,6 +57,11 @@ namespace VFS return mIndex.find(name) != mIndex.end(); } + bool Manager::exists(Path::NormalizedView name) const + { + return mIndex.find(name) != mIndex.end(); + } + std::string Manager::getArchive(const Path::Normalized& name) const { for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 7598b77e68..955538627f 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -43,6 +43,8 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const Path::Normalized& name) const; + bool exists(Path::NormalizedView name) const; + /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index aa7cad8524..45355cd129 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -122,7 +122,7 @@ namespace VFS::Path { } - Normalized(const char* value) + explicit Normalized(const char* value) : Normalized(std::string_view(value)) { } From ec9c82902189c0189a5532f089e69ac99d96074a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 23:44:12 +0100 Subject: [PATCH 1078/2167] Use normalized path for correctSoundPath --- apps/openmw/mwbase/soundmanager.hpp | 6 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- apps/openmw/mwgui/charactercreation.cpp | 2 +- apps/openmw/mwlua/soundbindings.cpp | 6 +- apps/openmw/mwscript/soundextensions.cpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 4 +- apps/openmw/mwsound/openal_output.hpp | 4 +- apps/openmw/mwsound/sound_buffer.cpp | 5 +- apps/openmw/mwsound/sound_buffer.hpp | 4 +- apps/openmw/mwsound/sound_output.hpp | 3 +- apps/openmw/mwsound/soundmanagerimp.cpp | 8 +-- apps/openmw/mwsound/soundmanagerimp.hpp | 6 +- .../misc/test_resourcehelpers.cpp | 13 +---- apps/openmw_test_suite/testing_util.hpp | 2 +- apps/openmw_test_suite/vfs/testpathutil.cpp | 55 +++++++++++++++++++ components/misc/resourcehelpers.cpp | 17 +++--- components/misc/resourcehelpers.hpp | 6 +- components/vfs/pathutil.hpp | 45 ++++++++++++++- 18 files changed, 143 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 1f0337869b..05b925f87d 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../mwsound/type.hpp" #include "../mwworld/ptr.hpp" @@ -129,11 +131,11 @@ namespace MWBase /// \param name of the folder that contains the playlist /// Title music playlist is predefined - virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0; + virtual void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - virtual void say(const std::string& filename) = 0; + virtual void say(VFS::Path::NormalizedView filename) = 0; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 3b0ba47250..556b5b53d7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -653,7 +653,7 @@ namespace MWDialogue if (Settings::gui().mSubtitles) winMgr->messageBox(info->mResponse); if (!info->mSound.empty()) - sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(info->mSound)); + sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(info->mSound))); if (!info->mResultScript.empty()) executeScript(info->mResultScript, actor); } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index c5280d1615..be2d22ae84 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -39,7 +39,7 @@ namespace { const std::string mText; const Response mResponses[3]; - const std::string mSound; + const VFS::Path::Normalized mSound; }; Step sGenerateClassSteps(int number) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index e8b7089eb8..ad4a498153 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -174,12 +174,12 @@ namespace MWLua api["say"] = sol::overload( [luaManager = context.mLuaManager]( std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), std::string(fileName)); + MWBase::Environment::get().getSoundManager()->say(object.ptr(), VFS::Path::Normalized(fileName)); if (text) luaManager->addUIMessage(*text); }, [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(std::string(fileName)); + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); if (text) luaManager->addUIMessage(*text); }); @@ -227,7 +227,7 @@ namespace MWLua soundT["maxRange"] = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mMaxRange; }); soundT["fileName"] = sol::readonly_property([](const ESM::Sound& rec) -> std::string { - return VFS::Path::normalizeFilename(Misc::ResourceHelpers::correctSoundPath(rec.mSound)); + return Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value(); }); return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 44cdc25064..ee39860584 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -33,7 +33,7 @@ namespace MWScript MWScript::InterpreterContext& context = static_cast(runtime.getContext()); - std::string file{ runtime.getStringLiteral(runtime[0].mInteger) }; + VFS::Path::Normalized file{ runtime.getStringLiteral(runtime[0].mInteger) }; runtime.pop(); std::string_view text = runtime.getStringLiteral(runtime[0].mInteger); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 99003d5ce3..0261649fa9 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1034,7 +1034,7 @@ namespace MWSound return ret; } - std::pair OpenAL_Output::loadSound(const std::string& fname) + std::pair OpenAL_Output::loadSound(VFS::Path::NormalizedView fname) { getALError(); @@ -1045,7 +1045,7 @@ namespace MWSound try { DecoderPtr decoder = mManager.getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, *decoder->mResourceMgr)); ChannelConfig chans; SampleType type; diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 7636f7bda9..b419038eab 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "al.h" #include "alc.h" #include "alext.h" @@ -85,7 +87,7 @@ namespace MWSound std::vector enumerateHrtf() override; - std::pair loadSound(const std::string& fname) override; + std::pair loadSound(VFS::Path::NormalizedView fname) override; size_t unloadSound(Sound_Handle data) override; bool playSound(Sound* sound, Sound_Handle data, float offset) override; diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index a3fdcb8b5c..f28b268df2 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -183,9 +183,8 @@ namespace MWSound min = std::max(min, 1.0f); max = std::max(min, max); - Sound_Buffer& sfx - = mSoundBuffers.emplace_back(Misc::ResourceHelpers::correctSoundPath(sound.mSound), volume, min, max); - VFS::Path::normalizeFilenameInPlace(sfx.mResourceName); + Sound_Buffer& sfx = mSoundBuffers.emplace_back( + Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(sound.mSound)), volume, min, max); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 3bf734a4b6..7de6dab9ae 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -35,7 +35,7 @@ namespace MWSound { } - const std::string& getResourceName() const noexcept { return mResourceName; } + const VFS::Path::Normalized& getResourceName() const noexcept { return mResourceName; } Sound_Handle getHandle() const noexcept { return mHandle; } @@ -46,7 +46,7 @@ namespace MWSound float getMaxDist() const noexcept { return mMaxDist; } private: - std::string mResourceName; + VFS::Path::Normalized mResourceName; float mVolume; float mMinDist; float mMaxDist; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index df95f0909e..5a77124985 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwbase/soundmanager.hpp" @@ -39,7 +40,7 @@ namespace MWSound virtual std::vector enumerateHrtf() = 0; - virtual std::pair loadSound(const std::string& fname) = 0; + virtual std::pair loadSound(VFS::Path::NormalizedView fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; virtual bool playSound(Sound* sound, Sound_Handle data, float offset) = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0cc276807f..3658be4819 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -172,12 +172,12 @@ namespace MWSound return std::make_shared(mVFS); } - DecoderPtr SoundManager::loadVoice(const std::string& voicefile) + DecoderPtr SoundManager::loadVoice(VFS::Path::NormalizedView voicefile) { try { DecoderPtr decoder = getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, *decoder->mResourceMgr)); return decoder; } catch (std::exception& e) @@ -380,7 +380,7 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(const MWWorld::ConstPtr& ptr, const std::string& filename) + void SoundManager::say(const MWWorld::ConstPtr& ptr, VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; @@ -412,7 +412,7 @@ namespace MWSound return 0.0f; } - void SoundManager::say(const std::string& filename) + void SoundManager::say(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 6154d202cd..75b1193118 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -116,7 +116,7 @@ namespace MWSound Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound* sound); // returns a decoder to start streaming, or nullptr if the sound was not found - DecoderPtr loadVoice(const std::string& voicefile); + DecoderPtr loadVoice(VFS::Path::NormalizedView voicefile); SoundPtr getSoundRef(); StreamPtr getStreamRef(); @@ -188,11 +188,11 @@ namespace MWSound /// \param name of the folder that contains the playlist /// Title music playlist is predefined - void say(const MWWorld::ConstPtr& reference, const std::string& filename) override; + void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) override; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - void say(const std::string& filename) override; + void say(VFS::Path::NormalizedView filename) override; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp index 0db147d8a3..5290630394 100644 --- a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -8,26 +8,19 @@ namespace TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/bar.wav", mVFS.get()), "sound/bar.wav"); + EXPECT_EQ(correctSoundPath("sound/bar.wav", *mVFS), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); + EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); - } - - TEST(CorrectSoundPath, correct_path_normalize_paths) - { - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound\\foo.wav", mVFS.get()), "sound/foo.mp3"); - EXPECT_EQ(correctSoundPath("SOUND\\foo.WAV", mVFS.get()), "sound/foo.mp3"); + EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); } namespace diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 60367ffbe9..0afd04e639 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -75,7 +75,7 @@ namespace TestingOpenMW } inline std::unique_ptr createTestVFS( - std::initializer_list> files) + std::initializer_list> files) { return createTestVFS(VFS::FileMap(files.begin(), files.end())); } diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 23a4d46d12..7b9c9abfb5 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -65,6 +65,53 @@ namespace VFS::Path EXPECT_EQ(stream.str(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportOperatorDivEqual) + { + Normalized value("foo/bar"); + value /= NormalizedView("baz"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldNormalizeExtension) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("SO")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithoutADot) + { + Normalized value("foo/bar"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator) + { + Normalized value("foo.bar/baz"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo.bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension(".so"), std::invalid_argument); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument); + } + template struct NormalizedOperatorsTest : Test { @@ -135,5 +182,13 @@ namespace VFS::Path { EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); } + + TEST(NormalizedView, shouldSupportOperatorDiv) + { + const NormalizedView a("foo/bar"); + const NormalizedView b("baz"); + const Normalized result = a / b; + EXPECT_EQ(result.value(), "foo/bar/baz"); + } } } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ab6aa7907c..1d5b57bfd9 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -180,9 +180,10 @@ std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) return res; } -std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::NormalizedView resPath) { - return "sound\\" + resPath; + static constexpr VFS::Path::NormalizedView prefix("sound"); + return prefix / resPath; } std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) @@ -201,17 +202,17 @@ std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath return resPath.substr(prefix.size() + 1); } -std::string Misc::ResourceHelpers::correctSoundPath(std::string_view resPath, const VFS::Manager* vfs) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath( + VFS::Path::NormalizedView resPath, const VFS::Manager& vfs) { // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if (!vfs->exists(resPath)) + if (!vfs.exists(resPath)) { - std::string sound{ resPath }; - changeExtension(sound, ".mp3"); - VFS::Path::normalizeFilenameInPlace(sound); + VFS::Path::Normalized sound(resPath); + sound.changeExtension("mp3"); return sound; } - return VFS::Path::normalizeFilename(resPath); + return VFS::Path::Normalized(resPath); } bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index e79dae0887..cda99d928d 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -1,6 +1,8 @@ #ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H +#include + #include #include #include @@ -37,7 +39,7 @@ namespace Misc std::string correctMeshPath(std::string_view resPath); // Adds "sound\\". - std::string correctSoundPath(const std::string& resPath); + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); // Adds "music\\". std::string correctMusicPath(const std::string& resPath); @@ -45,7 +47,7 @@ namespace Misc // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); - std::string correctSoundPath(std::string_view resPath, const VFS::Manager* vfs); + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath, const VFS::Manager& vfs); /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(const ESM::RefId& id); diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 45355cd129..5c5746cf6f 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -11,9 +11,12 @@ namespace VFS::Path { + inline constexpr char separator = '/'; + inline constexpr char extensionSeparator = '.'; + inline constexpr char normalize(char c) { - return c == '\\' ? '/' : Misc::StringUtils::toLower(c); + return c == '\\' ? separator : Misc::StringUtils::toLower(c); } inline constexpr bool isNormalized(std::string_view name) @@ -21,9 +24,14 @@ namespace VFS::Path return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); }); } + inline void normalizeFilenameInPlace(auto begin, auto end) + { + std::transform(begin, end, begin, normalize); + } + inline void normalizeFilenameInPlace(std::string& name) { - std::transform(name.begin(), name.end(), name.begin(), normalize); + normalizeFilenameInPlace(name.begin(), name.end()); } /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing. @@ -59,6 +67,11 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + inline constexpr auto findSeparatorOrExtensionSeparator(auto begin, auto end) + { + return std::find_if(begin, end, [](char v) { return v == extensionSeparator || v == separator; }); + } + class Normalized; class NormalizedView @@ -153,6 +166,27 @@ namespace VFS::Path operator const std::string&() const { return mValue; } + bool changeExtension(std::string_view extension) + { + if (findSeparatorOrExtensionSeparator(extension.begin(), extension.end()) != extension.end()) + throw std::invalid_argument("Invalid extension: " + std::string(extension)); + const auto it = findSeparatorOrExtensionSeparator(mValue.rbegin(), mValue.rend()); + if (it == mValue.rend() || *it == separator) + return false; + const std::string::difference_type pos = mValue.rend() - it; + mValue.replace(pos, mValue.size(), extension); + normalizeFilenameInPlace(mValue.begin() + pos, mValue.end()); + return true; + } + + Normalized& operator/=(NormalizedView value) + { + mValue.reserve(mValue.size() + value.value().size() + 1); + mValue += separator; + mValue += value.value(); + return *this; + } + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } @@ -207,6 +241,13 @@ namespace VFS::Path : mValue(value.view()) { } + + inline Normalized operator/(NormalizedView lhs, NormalizedView rhs) + { + Normalized result(lhs); + result /= rhs; + return result; + } } #endif From ec1c6ee1715be1aceb8498d2f7cc881b0472e19d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 24 Feb 2024 14:03:24 +0100 Subject: [PATCH 1079/2167] Use ESM::decompose to handle ENAMstruct --- apps/openmw_test_suite/esm3/testsaveload.cpp | 29 ++++++++++++++++++++ components/esm3/aipackage.cpp | 12 +++----- components/esm3/effectlist.cpp | 13 +++++++-- components/esm3/effectlist.hpp | 4 --- components/esm3/esmreader.hpp | 11 ++++++++ components/esm3/loadcrea.cpp | 3 +- components/esm3/loadnpc.cpp | 3 +- 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index eda1fa963e..8c7896b08a 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -525,6 +526,34 @@ namespace ESM EXPECT_EQ(result.mServices, record.mServices); } + TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange) + { + EffectList record; + record.mList.emplace_back(ENAMstruct{ + .mEffectID = 1, + .mSkill = 2, + .mAttribute = 3, + .mRange = 4, + .mArea = 5, + .mDuration = 6, + .mMagnMin = 7, + .mMagnMax = 8, + }); + + EffectList result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mList.size(), record.mList.size()); + EXPECT_EQ(result.mList[0].mEffectID, record.mList[0].mEffectID); + EXPECT_EQ(result.mList[0].mSkill, record.mList[0].mSkill); + EXPECT_EQ(result.mList[0].mAttribute, record.mList[0].mAttribute); + EXPECT_EQ(result.mList[0].mRange, record.mList[0].mRange); + EXPECT_EQ(result.mList[0].mArea, record.mList[0].mArea); + EXPECT_EQ(result.mList[0].mDuration, record.mList[0].mDuration); + EXPECT_EQ(result.mList[0].mMagnMin, record.mList[0].mMagnMin); + EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 2cadb9fb22..33b8a0bca2 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -54,29 +54,25 @@ namespace ESM else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; - esm.getSubHeader(); - esm.getComposite(pack.mWander); + esm.getSubComposite(pack.mWander); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; - esm.getSubHeader(); - esm.getComposite(pack.mTravel); + esm.getSubComposite(pack.mTravel); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getSubHeader(); - esm.getComposite(pack.mTarget); + esm.getSubComposite(pack.mTarget); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; - esm.getSubHeader(); - esm.getComposite(pack.mActivate); + esm.getSubComposite(pack.mActivate); mList.push_back(pack); } } diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 701552b312..4f21f47fa2 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mEffectID, v.mSkill, v.mAttribute, v.mRange, v.mArea, v.mDuration, v.mMagnMin, v.mMagnMax); + } void EffectList::load(ESMReader& esm) { @@ -18,15 +25,15 @@ namespace ESM void EffectList::add(ESMReader& esm) { ENAMstruct s; - esm.getHT(s.mEffectID, s.mSkill, s.mAttribute, s.mRange, s.mArea, s.mDuration, s.mMagnMin, s.mMagnMax); + esm.getSubComposite(s); mList.push_back(s); } void EffectList::save(ESMWriter& esm) const { - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const ENAMstruct& enam : mList) { - esm.writeHNT("ENAM", *it, 24); + esm.writeNamedComposite("ENAM", enam); } } diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index 8f2cb959d6..de13797496 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -9,9 +9,6 @@ namespace ESM class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - /** Defines a spell effect. Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item enchantments) records */ @@ -28,7 +25,6 @@ namespace ESM int32_t mRange; // 0 - self, 1 - touch, 2 - target (RangeType enum) int32_t mArea, mDuration, mMagnMin, mMagnMax; }; -#pragma pack(pop) /// EffectList, ENAM subrecord struct EffectList diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 276adf749c..ca9f191a5d 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -189,6 +189,17 @@ namespace ESM decompose(value, [&](auto&... args) { (getT(args), ...); }); } + void getSubComposite(auto& value) + { + decompose(value, [&](auto&... args) { + constexpr size_t size = (0 + ... + sizeof(decltype(args))); + getSubHeader(); + if (mCtx.leftSub != size) + reportSubSizeMismatch(size, mCtx.leftSub); + (getT(args), ...); + }); + } + template >> void skipHT() { diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 1db79e8e76..5a0d8048bc 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -69,8 +69,7 @@ namespace ESM mSpells.add(esm); break; case fourCC("AIDT"): - esm.getSubHeader(); - esm.getComposite(mAiData); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 92b16638c2..58a8bfa55e 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -102,8 +102,7 @@ namespace ESM mInventory.add(esm); break; case fourCC("AIDT"): - esm.getSubHeader(); - esm.getComposite(mAiData); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): From 7d7e8939abc16301fba58c8975faae72e02b0936 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 24 Feb 2024 16:55:58 +0100 Subject: [PATCH 1080/2167] Use ESM::decompose to handle WPDTstruct --- apps/openmw/mwclass/creature.cpp | 6 +-- apps/openmw/mwclass/npc.cpp | 6 +-- apps/openmw/mwmechanics/combat.cpp | 18 ++++---- apps/openmw_test_suite/esm3/testsaveload.cpp | 48 ++++++++++++++++++++ components/esm3/esmreader.hpp | 8 +--- components/esm3/loadweap.cpp | 22 ++++++--- components/esm3/loadweap.hpp | 6 +-- 7 files changed, 82 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2de58c6127..b6c607b415 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -316,11 +316,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b7540ebe04..98384254d3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -635,11 +635,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 831c3ff7ab..b9852e1b41 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -246,14 +246,16 @@ namespace MWMechanics return; } - const unsigned char* attack = weapon.get()->mBase->mData.mChop; - damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage - - // Arrow/bolt damage - // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon - attack = projectile.get()->mBase->mData.mChop; - damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); - + { + const auto& attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage + } + { + // Arrow/bolt damage + // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon + const auto& attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); + } adjustWeaponDamage(damage, weapon, attacker); } diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 8c7896b08a..6d5fdf1c14 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -554,6 +555,53 @@ namespace ESM EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); } + TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange) + { + Weapon record = { + .mData = { + .mWeight = 0, + .mValue = 1, + .mType = 2, + .mHealth = 3, + .mSpeed = 4, + .mReach = 5, + .mEnchant = 6, + .mChop = { 7, 8 }, + .mSlash = { 9, 10 }, + .mThrust = { 11, 12 }, + .mFlags = 13, + }, + .mRecordFlags = 0, + .mId = generateRandomRefId(32), + .mEnchant = generateRandomRefId(32), + .mScript = generateRandomRefId(32), + .mName = generateRandomString(32), + .mModel = generateRandomString(32), + .mIcon = generateRandomString(32), + }; + + Weapon result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mWeight, record.mData.mWeight); + EXPECT_EQ(result.mData.mValue, record.mData.mValue); + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mHealth, record.mData.mHealth); + EXPECT_EQ(result.mData.mSpeed, record.mData.mSpeed); + EXPECT_EQ(result.mData.mReach, record.mData.mReach); + EXPECT_EQ(result.mData.mEnchant, record.mData.mEnchant); + EXPECT_EQ(result.mData.mChop, record.mData.mChop); + EXPECT_EQ(result.mData.mSlash, record.mData.mSlash); + EXPECT_EQ(result.mData.mThrust, record.mData.mThrust); + EXPECT_EQ(result.mData.mFlags, record.mData.mFlags); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mEnchant, record.mEnchant); + EXPECT_EQ(result.mScript, record.mScript); + EXPECT_EQ(result.mName, record.mName); + EXPECT_EQ(result.mModel, record.mModel); + EXPECT_EQ(result.mIcon, record.mIcon); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index ca9f191a5d..4af2264828 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -191,13 +191,7 @@ namespace ESM void getSubComposite(auto& value) { - decompose(value, [&](auto&... args) { - constexpr size_t size = (0 + ... + sizeof(decltype(args))); - getSubHeader(); - if (mCtx.leftSub != size) - reportSubSizeMismatch(size, mCtx.leftSub); - (getT(args), ...); - }); + decompose(value, [&](auto&... args) { getHT(args...); }); } template >> diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index 31c03b00fe..f06abf4e7c 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -1,11 +1,20 @@ #include "loadweap.hpp" -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mType, v.mHealth, v.mSpeed, v.mReach, v.mEnchant, v.mChop, v.mSlash, v.mThrust, + v.mFlags); + } + void Weapon::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -29,8 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("WPDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mType, mData.mHealth, mData.mSpeed, mData.mReach, - mData.mEnchant, mData.mChop, mData.mSlash, mData.mThrust, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("WPDT", mData, 32); + esm.writeNamedComposite("WPDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCRefId("ENAM", mEnchant); @@ -84,9 +92,9 @@ namespace ESM mData.mSpeed = 0; mData.mReach = 0; mData.mEnchant = 0; - mData.mChop[0] = mData.mChop[1] = 0; - mData.mSlash[0] = mData.mSlash[1] = 0; - mData.mThrust[0] = mData.mThrust[1] = 0; + mData.mChop.fill(0); + mData.mSlash.fill(0); + mData.mThrust.fill(0); mData.mFlags = 0; mName.clear(); diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index ba1599b1df..8323176a64 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_WEAP_H #define OPENMW_ESM_WEAP_H +#include #include #include "components/esm/refid.hpp" @@ -59,8 +60,6 @@ namespace ESM Silver = 0x02 }; -#pragma pack(push) -#pragma pack(1) struct WPDTstruct { float mWeight; @@ -69,10 +68,9 @@ namespace ESM uint16_t mHealth; float mSpeed, mReach; uint16_t mEnchant; // Enchantment points. The real value is mEnchant/10.f - unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max + std::array mChop, mSlash, mThrust; // Min and max int32_t mFlags; }; // 32 bytes -#pragma pack(pop) WPDTstruct mData; From a761e417f17b9cb98e1c4d879390dafa1926cabc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 24 Feb 2024 16:59:11 +0000 Subject: [PATCH 1081/2167] Accept that it's too much work to defer light manager creation in the CS and instead use something akin to the old approach --- components/sceneutil/glextensions.cpp | 5 +++++ components/sceneutil/glextensions.hpp | 1 + components/sceneutil/lightmanager.cpp | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index eb55e17b1c..3a14dab8ed 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -33,6 +33,11 @@ namespace SceneUtil GLExtensionsObserver GLExtensionsObserver::sInstance{}; } + bool glExtensionsReady() + { + return !sGLExtensions.empty(); + } + osg::GLExtensions& getGLExtensions() { if (sGLExtensions.empty()) diff --git a/components/sceneutil/glextensions.hpp b/components/sceneutil/glextensions.hpp index 17a4eb8488..05e7f715c1 100644 --- a/components/sceneutil/glextensions.hpp +++ b/components/sceneutil/glextensions.hpp @@ -6,6 +6,7 @@ namespace SceneUtil { + bool glExtensionsReady(); osg::GLExtensions& getGLExtensions(); class GetGLExtensionsOperation : public osg::GraphicsOperation diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 48efb7fda9..c76f0b6b5c 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -825,9 +825,9 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - osg::GLExtensions& exts = SceneUtil::getGLExtensions(); - bool supportsUBO = exts.isUniformBufferObjectSupported; - bool supportsGPU4 = exts.isGpuShader4Supported; + osg::GLExtensions* exts = SceneUtil::glExtensionsReady() ? &SceneUtil::getGLExtensions() : nullptr; + bool supportsUBO = exts && exts->isUniformBufferObjectSupported; + bool supportsGPU4 = exts && exts->isGpuShader4Supported; mSupported[static_cast(LightingMethod::FFP)] = true; mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; From 65aa222efa3d4a4cf49c8cdfb42e119d89b5220b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 24 Feb 2024 19:51:08 +0300 Subject: [PATCH 1082/2167] Move full help text after everything else (#7623) --- CHANGELOG.md | 1 + apps/openmw/mwclass/activator.cpp | 6 ++---- apps/openmw/mwclass/apparatus.cpp | 4 ++-- apps/openmw/mwclass/armor.cpp | 4 ++-- apps/openmw/mwclass/book.cpp | 4 ++-- apps/openmw/mwclass/clothing.cpp | 4 ++-- apps/openmw/mwclass/container.cpp | 6 +++--- apps/openmw/mwclass/creature.cpp | 4 +--- apps/openmw/mwclass/door.cpp | 4 ++-- apps/openmw/mwclass/ingredient.cpp | 4 ++-- apps/openmw/mwclass/light.cpp | 4 ++-- apps/openmw/mwclass/lockpick.cpp | 4 ++-- apps/openmw/mwclass/misc.cpp | 4 ++-- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 4 ++-- apps/openmw/mwclass/probe.cpp | 4 ++-- apps/openmw/mwclass/repair.cpp | 4 ++-- apps/openmw/mwclass/weapon.cpp | 4 ++-- apps/openmw/mwgui/tooltips.cpp | 23 ++++++++++++++++++++++- apps/openmw/mwgui/tooltips.hpp | 1 + 20 files changed, 57 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea44cab4b1..69d0f6c3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7617: The death prompt asks the player if they wanted to load the character's last created save Bug #7619: Long map notes may get cut off + Bug #7623: Incorrect placement of the script info in the engraved ring of healing tooltip Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7633: Groundcover should ignore non-geometry Drawables diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 678c4e054b..e0ee315bc1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -104,13 +104,11 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 1bf6f9c845..bf5e4dc2f1 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -102,8 +102,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index ffa5b36a4d..3853f53fc4 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -257,8 +257,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 2c375547d0..95453e7a58 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -121,8 +121,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 17519405de..cdf51ef663 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -164,8 +164,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e2023ef8c3..75b8543b0a 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -265,10 +265,10 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); if (ptr.getCellRef().getRefId() == "stolen_goods") - text += "\nYou can not use evidence chests"; + info.extra += "\nYou cannot use evidence chests"; } info.text = std::move(text); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2de58c6127..0024755f91 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -591,10 +591,8 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); - info.text = std::move(text); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 015c454915..7509fbf71f 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -290,8 +290,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 9af9a5703b..e18d6ad5f3 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index dc37b8d154..06b1901864 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -173,8 +173,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 42b5634b64..dc3b73da63 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -118,8 +118,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 0470a89a16..dcd91c51af 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -163,8 +163,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b7540ebe04..43962f44b5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1118,7 +1118,7 @@ namespace MWClass } if (fullHelp) - info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e5da876d06..42c122cb48 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -114,8 +114,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4f5e7be5cb..beab45945c 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 3000ea4087..279352263e 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -119,8 +119,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index b5a3415717..5f1f7f2772 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -239,8 +239,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0a0343831d..938d4176f2 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -410,10 +410,13 @@ namespace MWGui const std::string& image = info.icon; int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; + std::string extra = info.extra; // remove the first newline (easier this way) - if (text.size() > 0 && text[0] == '\n') + if (!text.empty() && text[0] == '\n') text.erase(0, 1); + if (!extra.empty() && extra[0] == '\n') + extra.erase(0, 1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -572,6 +575,24 @@ namespace MWGui } } + if (!extra.empty()) + { + Gui::EditBox* extraWidget = mDynamicToolTipBox->createWidget("SandText", + MyGUI::IntCoord(padding.left, totalSize.height + 12, 300 - padding.left, 300 - totalSize.height), + MyGUI::Align::Stretch, "ToolTipExtraText"); + + extraWidget->setEditStatic(true); + extraWidget->setEditMultiLine(true); + extraWidget->setEditWordWrap(info.wordWrap); + extraWidget->setCaptionWithReplacing(extra); + extraWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); + extraWidget->setNeedKeyFocus(false); + + MyGUI::IntSize extraTextSize = extraWidget->getTextSize(); + totalSize.height += extraTextSize.height + 4; + totalSize.width = std::max(totalSize.width, extraTextSize.width); + } + captionWidget->setCoord((totalSize.width - captionSize.width) / 2 + imageSize, (captionHeight - captionSize.height) / 2, captionSize.width - imageSize, captionSize.height); diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 69f6856840..132698475f 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -29,6 +29,7 @@ namespace MWGui std::string caption; std::string text; + std::string extra; std::string icon; int imageSize; From fe78e5739155a785d32a51b0d5e783f9760f3466 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 15:17:17 +0300 Subject: [PATCH 1083/2167] Russian localization updates Localize new player controls lines Fix some other minor inconsistencies --- files/data/l10n/OMWCamera/ru.yaml | 2 +- files/data/l10n/OMWControls/ru.yaml | 73 +++++++++++++++++++++++++++-- files/data/l10n/OMWEngine/ru.yaml | 8 ++-- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/files/data/l10n/OMWCamera/ru.yaml b/files/data/l10n/OMWCamera/ru.yaml index 2b41ef0ee7..49821157cc 100644 --- a/files/data/l10n/OMWCamera/ru.yaml +++ b/files/data/l10n/OMWCamera/ru.yaml @@ -1,5 +1,5 @@ Camera: "Камера OpenMW" -settingsPageDescription: "Настройки камеры для OpenMW" +settingsPageDescription: "Настройки камеры OpenMW." thirdPersonSettings: "Режим от третьего лица" diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 0ce3609e16..e293b7f44d 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -1,5 +1,5 @@ ControlsPage: "Управление OpenMW" -ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком" +ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком." MovementSettings: "Движение" @@ -14,5 +14,72 @@ toggleSneakDescription: | чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Плавное движение на геймпаде" +smoothControllerMovementDescription: | + Эта настройка сглаживает движение при использовании стика. Это делает переход от ходьбы к бегу не таким резким. + +TogglePOV_name: "Изменить вид" +TogglePOV_description: "Переключиться между видами от первого и третьего лица. При зажатии будет включен режим предпросмотра." + +Zoom3rdPerson_name: "Приблизить/отдалить камеру" +Zoom3rdPerson_description: "Приблизить или отдалить от персонажа камеру в виде от третьего лица." + +MoveForward_name: "Двигаться вперед" +MoveForward_description: "Одновременное зажатие кнопки движения назад отменит движение." + +MoveBackward_name: "Двигаться назад" +MoveBackward_description: "Одновременное зажатие кнопки движения вперед отменит движение." + +MoveLeft_name: "Двигаться влево" +MoveLeft_description: "Одновременное зажатие кнопки движения вправо отменит движение." + +MoveRight_name: "Двигаться вправо" +MoveRight_description: "Одновременное зажатие кнопки движения влево отменит движение." + +Use_name: "Использовать" +Use_description: "Совершить атаку оружием или использовать заклинание в зависимости от текущей стойки." + +Run_name: "Бежать" +Run_description: "Инвертировать состояние бега/ходьбы при зажатии -- состояние по умолчанию зависит от настройки постоянного бега." + +AlwaysRun_name: "Постоянный бег" +AlwaysRun_description: "Переключить настройку постоянного бега." + +Jump_name: "Прыгать" +Jump_description: "Прыгнуть, если вы находитесь на земле." + +AutoMove_name: "Автоматический бег" +AutoMove_description: "Переключить непрерывное движение вперед." + +Sneak_name: "Красться" +Sneak_description: "Красться при зажатии, если настройка переключения движения крадучись выключена." + +ToggleSneak_name: "Переключить движение крадучись" +ToggleSneak_description: "Переключить движение крадучись, если соответствующая настройка включена." + +ToggleWeapon_name: "Приготовить оружие" +ToggleWeapon_description: "Принять стойку оружия или покинуть её." + +ToggleSpell_name: "Приготовить магию" +ToggleSpell_description: "Принять стойку магии или покинуть её." + +Inventory_name: "Инвентарь" +Inventory_description: "Открыть инвентарь." + +Journal_name: "Дневник" +Journal_description: "Открыть дневник." + +QuickKeysMenu_name: "Меню быстрых клавиш" +QuickKeysMenu_description: "Открыть меню быстрых клавиш." + +SmoothMoveForward_name: "Плавно двигаться вперед" +SmoothMoveForward_description: "Движение вперед с плавными переходами от ходьбы к бегу." + +SmoothMoveBackward_name: "Плавно двигаться назад" +SmoothMoveBackward_description: "Движение назад с плавными переходами от ходьбы к бегу." + +SmoothMoveLeft_name: "Плавно двигаться влево" +SmoothMoveLeft_description: "Движение влево с плавными переходами от ходьбы к бегу." + +SkmoothMoveRight_name: "Плавно двигаться вправо" +SkmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index a9f396f73c..07fc376675 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -135,6 +135,7 @@ RainRippleDetail: "Капли дождя на воде" RainRippleDetailDense: "Плотные" RainRippleDetailSimple: "Упрощенные" RainRippleDetailSparse: "Редкие" +RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ReflectionShaderDetail: "Детализация отражений" ReflectionShaderDetailActors: "Персонажи" ReflectionShaderDetailGroundcover: "Трава" @@ -143,7 +144,6 @@ ReflectionShaderDetailSky: "Небо" ReflectionShaderDetailTerrain: "Ландшафт" ReflectionShaderDetailWorld: "Мир" Refraction: "Рефракция" -RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ResetControls: "Сбросить" Screenshot: "Снимок экрана" Scripts: "Скрипты" @@ -154,17 +154,17 @@ SensitivityHigh: "Высокая" SensitivityLow: "Низкая" SettingsWindow: "Настройки" Subtitles: "Субтитры" -TestingExteriorCells: "Проверка наружних ячеек" +TestingExteriorCells: "Проверка наружных ячеек" TestingInteriorCells: "Проверка ячеек-помещений" TextureFiltering: "Фильтрация текстур" TextureFilteringBilinear: "Билинейная" TextureFilteringDisabled: "Отключена" TextureFilteringOther: "Другая" TextureFilteringTrilinear: "Трилинейная" -TransparencyFull: "Прозрачное" -TransparencyNone: "Непрозрачное" ToggleHUD: "Переключить HUD" TogglePostProcessorHUD: "Меню настроек постобработки" +TransparencyFull: "Прозрачное" +TransparencyNone: "Непрозрачное" ViewDistance: "Дальность обзора" Video: "Видео" VSync: "Вертикальная синхронизация" From 25b6230a546b3b9d4fb205967d1ac902ea3deec2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 16:13:45 +0300 Subject: [PATCH 1084/2167] Fix Smooth Move Right action localization --- files/data/l10n/OMWControls/en.yaml | 4 ++-- files/data/l10n/OMWControls/ru.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index c034eb8683..d4df56436b 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -80,5 +80,5 @@ SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run SmoothMoveLeft_name: "Smooth Move Left" SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions." -SkmoothMoveRight_name: "Smooth Move Right" -SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." +SmoothMoveRight_name: "Smooth Move Right" +SmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index e293b7f44d..4c7f6d379e 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -81,5 +81,5 @@ SmoothMoveBackward_description: "Движение назад с плавными SmoothMoveLeft_name: "Плавно двигаться влево" SmoothMoveLeft_description: "Движение влево с плавными переходами от ходьбы к бегу." -SkmoothMoveRight_name: "Плавно двигаться вправо" -SkmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." +SmoothMoveRight_name: "Плавно двигаться вправо" +SmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." From 059191c84069e8edca3b9ac1d48d032b0969508a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 25 Feb 2024 07:30:23 -0600 Subject: [PATCH 1085/2167] Also apply hasWaterHeightSub for INTV --- components/esm3/loadcell.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 473c4c7d72..0c37e64f1e 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -127,6 +127,7 @@ namespace ESM case fourCC("INTV"): int32_t waterl; esm.getHT(waterl); + mHasWaterHeightSub = true; mWater = static_cast(waterl); break; case fourCC("WHGT"): From ba78729f35097be942b17f4d1965a1fbe034bf39 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 16:42:18 +0300 Subject: [PATCH 1086/2167] Make Run action ru description more faithful to en description --- files/data/l10n/OMWControls/ru.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 4c7f6d379e..4675321969 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -40,7 +40,7 @@ Use_name: "Использовать" Use_description: "Совершить атаку оружием или использовать заклинание в зависимости от текущей стойки." Run_name: "Бежать" -Run_description: "Инвертировать состояние бега/ходьбы при зажатии -- состояние по умолчанию зависит от настройки постоянного бега." +Run_description: "Бежать или идти при зажатии (в зависимости от значения настройки постоянного бега)." AlwaysRun_name: "Постоянный бег" AlwaysRun_description: "Переключить настройку постоянного бега." From 357bf3db61ca4dcb44569b00560bc956579b9aab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 14:01:20 +0000 Subject: [PATCH 1087/2167] Load all config files --- apps/launcher/maindialog.cpp | 10 ++-------- components/files/qtconfigpath.hpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5d558ef38f..f9d07d54a5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -349,17 +349,11 @@ bool Launcher::MainDialog::setupGameSettings() if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile)) return false; - // Now the rest - priority: user > local > global - if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { - // Load global if local wasn't found - if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + if (!loadFile(path, &Config::GameSettings::readFile)) return false; } - else - return false; - if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile)) - return false; return true; } diff --git a/components/files/qtconfigpath.hpp b/components/files/qtconfigpath.hpp index e6d7b4202c..16e0499cd5 100644 --- a/components/files/qtconfigpath.hpp +++ b/components/files/qtconfigpath.hpp @@ -22,6 +22,16 @@ namespace Files { return Files::pathToQString(cfgMgr.getGlobalPath() / openmwCfgFile); } + + inline QStringList getActiveConfigPathsQString(const Files::ConfigurationManager& cfgMgr) + { + const auto& activePaths = cfgMgr.getActiveConfigPaths(); + QStringList result; + result.reserve(static_cast(activePaths.size())); + for (const auto& path : activePaths) + result.append(Files::pathToQString(path / openmwCfgFile)); + return result; + } } #endif // OPENMW_COMPONENTS_FILES_QTCONFIGPATH_H From 6b07718871a616ec8d73ce882e3c6075571badfd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 25 Feb 2024 01:01:55 +0100 Subject: [PATCH 1088/2167] Add morrowind test for moving object into container --- scripts/data/morrowind_tests/test.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/data/morrowind_tests/test.lua b/scripts/data/morrowind_tests/test.lua index 8898420b82..3515002f2d 100644 --- a/scripts/data/morrowind_tests/test.lua +++ b/scripts/data/morrowind_tests/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local util = require('openmw.util') local world = require('openmw.world') local core = require('openmw.core') +local types = require('openmw.types') if not core.contentFiles.has('Morrowind.esm') then error('This test requires Morrowind.esm') @@ -18,6 +19,28 @@ local tests = { coroutine.yield() testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)') end}, + {'Should keep reference to an object moved into container (#7663)', function() + world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0))) + coroutine.yield() + local barrel = world.createObject('barrel_01', 1) + local fargothRing = world.createObject('ring_keley', 1) + coroutine.yield() + testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil) + fargothRing:moveInto(types.Container.inventory(barrel)) + coroutine.yield() + testing.expectEqual(fargothRing.recordId, 'ring_keley') + local isFargothRing = function(actual) + if actual == nil then + return 'ring_keley is not found' + end + if actual.id ~= fargothRing.id then + return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id) + .. ', expected=' .. tostring(fargothRing.id) + end + return '' + end + testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing) + end}, } return { From 86a82ae3f11e9d39270988dd89db17e863ceca94 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 25 Feb 2024 13:13:28 +0100 Subject: [PATCH 1089/2167] Open matching version of documentation for Launcher Help --- CMakeLists.txt | 2 +- components/CMakeLists.txt | 3 ++- components/misc/helpviewer.cpp | 5 ++++- components/version/version.cpp.in | 7 +++++++ components/version/version.hpp | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..c3c1e47500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ set(OPENMW_VERSION_COMMITDATE "") set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") -set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/stable/") +set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/") set(GIT_CHECKOUT FALSE) if(EXISTS ${PROJECT_SOURCE_DIR}/.git) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6ab7fc4795..65c431cfd6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -28,6 +28,7 @@ if (GIT_CHECKOUT) -DOPENMW_LUA_API_REVISION=${OPENMW_LUA_API_REVISION} -DOPENMW_POSTPROCESSING_API_REVISION=${OPENMW_POSTPROCESSING_API_REVISION} -DOPENMW_VERSION=${OPENMW_VERSION} + -DOPENMW_DOC_BASEURL=${OPENMW_DOC_BASEURL} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} @@ -596,7 +597,6 @@ endif() if (USE_QT) add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt::Widgets Qt::Core) - target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if (BUILD_LAUNCHER OR BUILD_WIZARD) add_dependencies(components_qt qm-files) @@ -636,6 +636,7 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) +target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if(OSG_STATIC) unset(_osg_plugins_static_files) diff --git a/components/misc/helpviewer.cpp b/components/misc/helpviewer.cpp index 0ff4abb9d3..ebfca9ad14 100644 --- a/components/misc/helpviewer.cpp +++ b/components/misc/helpviewer.cpp @@ -4,9 +4,12 @@ #include #include +#include + void Misc::HelpViewer::openHelp(const char* url) { - QString link{ OPENMW_DOC_BASEURL }; + std::string_view docsUrl = Version::getDocumentationUrl(); + QString link = QString::fromUtf8(docsUrl.data(), docsUrl.size()); link.append(url); QDesktopServices::openUrl(QUrl(link)); } diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in index 312520acbb..12192785b7 100644 --- a/components/version/version.cpp.in +++ b/components/version/version.cpp.in @@ -52,4 +52,11 @@ namespace Version return getVersion() == version && getCommitHash() == commitHash && getTagHash() == tagHash; } + std::string_view getDocumentationUrl() + { + if constexpr (std::string_view("@OPENMW_VERSION_COMMITHASH@") == "@OPENMW_VERSION_TAGHASH@") + return OPENMW_DOC_BASEURL "openmw-@OPENMW_VERSION_MAJOR@.@OPENMW_VERSION_MINOR@.@OPENMW_VERSION_RELEASE@/"; + else + return OPENMW_DOC_BASEURL "latest/"; + } } diff --git a/components/version/version.hpp b/components/version/version.hpp index c05cf8a594..a8a8117dee 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -17,6 +17,8 @@ namespace Version std::string getOpenmwVersionDescription(); bool checkResourcesVersion(const std::filesystem::path& resourcePath); + + std::string_view getDocumentationUrl(); } #endif // VERSION_HPP From 42c7fc8e921272d18830054fd217e6188b81a110 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 18:53:19 +0000 Subject: [PATCH 1090/2167] Update 2 files - /components/CMakeLists.txt - /cmake/GitVersion.cmake --- cmake/GitVersion.cmake | 2 +- components/CMakeLists.txt | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index a77b0d5b0a..44b57b17d4 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -29,4 +29,4 @@ endif () include(${MACROSFILE}) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) -configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +configure_file("${VERSION_CPP_FILE_IN}" "${VERSION_CPP_FILE_OUT}") diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6ab7fc4795..730423d84e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -12,8 +12,8 @@ set (VERSION_CPP_FILE "components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_command ( - OUTPUT "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" + add_custom_target ( + BYPRODUCTS "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} @@ -21,7 +21,8 @@ if (GIT_CHECKOUT) -DOpenMW_BINARY_DIR=${OpenMW_BINARY_DIR} -DVERSION_RESOURCE_FILE_IN=${VERSION_RESOURCE_FILE_IN} -DVERSION_RESOURCE_FILE_RELATIVE=${VERSION_RESOURCE_FILE_RELATIVE} - -DVERSION_CPP_FILE=${VERSION_CPP_FILE} + -DVERSION_CPP_FILE_IN=${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in + -DVERSION_CPP_FILE_OUT=${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} @@ -32,7 +33,9 @@ if (GIT_CHECKOUT) "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + COMMAND ${CMAKE_COMMAND} + -E copy_if_different ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE} + VERBATIM) else (GIT_CHECKOUT) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") From 93a84b38ac47d31d6e3131130f3a077e07097cb3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 19:05:38 +0000 Subject: [PATCH 1091/2167] Give git-version its name back --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 730423d84e..4b3a661253 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -12,7 +12,7 @@ set (VERSION_CPP_FILE "components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_target ( + add_custom_target (get-version BYPRODUCTS "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} From 02ef7ae3ccd1512e35165f75ae6974f2a053a028 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 22:49:53 +0000 Subject: [PATCH 1092/2167] Give up rearranging the CS --- components/resource/imagemanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 124ff9b6ad..09c7048059 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -66,6 +66,8 @@ namespace Resource case (GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case (GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { + if (!SceneUtil::glExtensionsReady()) + return true; // hashtag yolo (CS might not have context when loading assets) osg::GLExtensions& exts = SceneUtil::getGLExtensions(); if (!exts.isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a From bcd54ab1ff30facaafa9c9ccc6a37c1c223c5048 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 23:01:52 +0000 Subject: [PATCH 1093/2167] Format osgpluginchecker.cpp.in I formatted the generated file that's part of the VS solution, then diffed it against the input and changed it to match. --- components/misc/osgpluginchecker.cpp.in | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index e58c6c59b2..b570c8f858 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -41,13 +41,13 @@ namespace Misc for (const auto& path : filepath) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath {stringToU8String(path)}; + std::filesystem::path osgPath{ stringToU8String(path) }; #else - std::filesystem::path osgPath {path}; + std::filesystem::path osgPath{ path }; #endif if (!osgPath.has_filename()) osgPath = osgPath.parent_path(); - + if (osgPath.filename() == pluginDirectoryName) { osgPath = osgPath.parent_path(); @@ -64,14 +64,16 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), + [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; + std::filesystem::path pluginPath{ stringToU8String(availablePlugin) }; #else std::filesystem::path pluginPath {availablePlugin}; #endif - return pluginPath.filename() == plugin; - }) == availableOSGPlugins.end()) + return pluginPath.filename() == plugin; + }) + == availableOSGPlugins.end()) { Log(Debug::Error) << "Missing OSG plugin: " << plugin; haveAllPlugins = false; From 98447a16909f9703886bd12b6acad48472e124c8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 26 Feb 2024 12:48:59 +0300 Subject: [PATCH 1094/2167] Remove legacy ownership documentation --- files/lua_api/openmw/core.lua | 4 ++-- files/lua_api/openmw/self.lua | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 330f0e20a0..66fb817362 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -142,9 +142,9 @@ -- @return #string -- @usage if obj.recordId == core.getFormId('Skyrim.esm', 0x4d7da) then ... end -- @usage -- In ESM3 content files (e.g. Morrowind) ids are human-readable strings --- obj.ownerFactionId = 'blades' +-- obj.owner.factionId = 'blades' -- -- In ESM4 (e.g. Skyrim) ids should be constructed using `core.getFormId`: --- obj.ownerFactionId = core.getFormId('Skyrim.esm', 0x72834) +-- obj.owner.factionId = core.getFormId('Skyrim.esm', 0x72834) -- @usage -- local scripts -- local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) -- @usage -- global scripts diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 005017e5c3..6123c36ae6 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -22,15 +22,6 @@ -- The object the script is attached to (readonly) -- @field [parent=#self] openmw.core#GameObject object ---- NPC who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerRecordId - ---- Faction who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerFactionId - ---- Rank required to be allowed to pick up the object (mutable). --- @field [parent=#self] #number ownerFactionRank - --- -- Movement controls (only for actors) From 626f438dcc4de7a2581f630356dce91bd5717cba Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:09:46 +0000 Subject: [PATCH 1095/2167] Make builtin.omwscripts actually mandatory Previously it was quasi-mandatory - lots of things would add it, e.g. when running openmw through the CS, but it could technically be disabled. Now it's treated like the resources/vfs directory and implicitly added by the engine etc. --- apps/bulletobjecttool/main.cpp | 4 +++- apps/navmeshtool/main.cpp | 4 +++- apps/opencs/model/doc/runner.cpp | 1 - apps/openmw/main.cpp | 3 ++- components/contentselector/model/contentmodel.cpp | 4 ---- files/openmw.cfg | 1 - files/openmw.cfg.local | 1 - 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 884c196e53..b27c8135d6 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -146,7 +146,9 @@ namespace dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 3ec34114af..94ab7ef082 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -165,7 +165,9 @@ namespace NavMeshTool dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 0099cb2f94..d647d6b498 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -93,7 +93,6 @@ void CSMDoc::Runner::start(bool delayed) arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\""; arguments << "--replace=content"; - arguments << "--content=builtin.omwscripts"; for (const auto& mContentFile : mContentFiles) { diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5bbc0211c1..beaa452f19 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -108,7 +108,8 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } - std::set contentDedupe; + engine.addContentFile("builtin.omwscripts"); + std::set contentDedupe{ "builtin.omwscripts" }; for (const auto& contentFile : content) { if (!contentDedupe.insert(contentFile).second) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index d800112712..377edc3b1c 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -434,10 +434,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf { QFileInfo info(dir.absoluteFilePath(path2)); - // Enabled by default in system openmw.cfg; shouldn't be shown in content list. - if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) - continue; - EsmFile* file = const_cast(item(info.fileName())); bool add = file == nullptr; std::unique_ptr newFile; diff --git a/files/openmw.cfg b/files/openmw.cfg index 37ecda3b1d..20e11323cf 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index c9949f2447..65f8b31136 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" From 90966ecc477bd14661a862e2bd9915f94bbf59dd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:39:49 +0000 Subject: [PATCH 1096/2167] Handle replace= lines properly in the launcher --- components/config/gamesettings.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ad7c73d3d9..339acd01fe 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -98,8 +98,32 @@ bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent) bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent) { QMultiMap cache; + QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$"); QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$"); + auto initialPos = stream.pos(); + + while (!stream.atEnd()) + { + QString line = stream.readLine(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + QRegularExpressionMatch match = keyRe.match(line); + if (match.hasMatch()) + { + QString key = match.captured(1).trimmed(); + // Replace composing entries with a replace= line + if (key == QLatin1String("data") || key == QLatin1String("fallback-archive") + || key == QLatin1String("content") || key == QLatin1String("groundcover") + || key == QLatin1String("script-blacklist")) + settings.remove(key); + } + } + + stream.seek(initialPos); + while (!stream.atEnd()) { QString line = stream.readLine(); From dbdecfe94b814a85a16aac0760d7dce6789c29cc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:41:12 +0000 Subject: [PATCH 1097/2167] Use approved safety comment for path escaping explanation I thought I'd got this one already --- components/config/gamesettings.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 339acd01fe..cf0f7e382d 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -147,8 +147,11 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap Date: Tue, 27 Feb 2024 02:14:31 +0000 Subject: [PATCH 1098/2167] data-local is already unquoted when it's read --- components/config/gamesettings.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index cf0f7e382d..53924c4313 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -47,11 +47,6 @@ void Config::GameSettings::validatePaths() // Do the same for data-local QString local = mSettings.value(QString("data-local")); - if (local.length() && local.at(0) == QChar('\"')) - { - local.remove(0, 1); - local.chop(1); - } if (local.isEmpty()) return; From f476301670d208690f660a054a3463d35b80142a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 14:11:48 +0000 Subject: [PATCH 1099/2167] There's no such thing as the global data directory That's what resources/vfs is for. --- apps/launcher/datafilespage.cpp | 10 +++++----- components/config/gamesettings.cpp | 10 ++++------ components/config/gamesettings.hpp | 3 ++- components/config/launchersettings.cpp | 6 +++--- components/files/configurationmanager.cpp | 5 ----- components/files/configurationmanager.hpp | 1 - 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 92b86e9cec..a0287afd87 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -279,9 +279,9 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!mDataLocal.isEmpty()) directories.insert(0, mDataLocal); - const auto& globalDataDir = mGameSettings.getGlobalDataDir(); - if (!globalDataDir.empty()) - directories.insert(0, Files::pathToQString(globalDataDir)); + const auto& resourcesVfs = mGameSettings.getResourcesVfs(); + if (!resourcesVfs.isEmpty()) + directories.insert(0, resourcesVfs); std::unordered_set visitedDirectories; for (const QString& currentDir : directories) @@ -315,8 +315,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) item->setFont(font); } - // deactivate data-local and global data directory: they are always included - if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir) + // deactivate data-local and resources/vfs: they are always included + if (currentDir == mDataLocal || currentDir == resourcesVfs) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 53924c4313..f8ff3d362c 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -64,13 +64,11 @@ void Config::GameSettings::validatePaths() } } -std::filesystem::path Config::GameSettings::getGlobalDataDir() const +QString Config::GameSettings::getResourcesVfs() const { - // global data dir may not exists if OpenMW is not installed (ie if run from build directory) - const auto& path = mCfgMgr.getGlobalDataPath(); - if (std::filesystem::exists(path)) - return std::filesystem::canonical(path); - return {}; + QString resources = mSettings.value(QString("resources"), QString("./resources")); + resources += "/vfs"; + return QFileInfo(resources).canonicalFilePath(); } QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 34e3dc73ea..bef108e2c7 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -53,7 +53,8 @@ namespace Config } QStringList getDataDirs() const; - std::filesystem::path getGlobalDataDir() const; + + QString getResourcesVfs() const; inline void removeDataDir(const QString& dir) { diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 9d12535619..2f4decb762 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -233,10 +233,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) return; } - // global and local data directories are not part of any profile - const auto globalDataDir = Files::pathToQString(gameSettings.getGlobalDataDir()); + // local data directory and resources/vfs are not part of any profile + const auto resourcesVfs = gameSettings.getResourcesVfs(); const auto dataLocal = gameSettings.getDataLocal(); - dirs.removeAll(globalDataDir); + dirs.removeAll(resourcesVfs); dirs.removeAll(dataLocal); // if any existing profile in launcher matches the content list, make that profile the default diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 210261cdf4..10e10375bb 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -411,11 +411,6 @@ namespace Files return mFixedPath.getLocalPath(); } - const std::filesystem::path& ConfigurationManager::getGlobalDataPath() const - { - return mFixedPath.getGlobalDataPath(); - } - const std::filesystem::path& ConfigurationManager::getCachePath() const { return mFixedPath.getCachePath(); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 8a5bca0869..aec7799fea 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -45,7 +45,6 @@ namespace Files const std::filesystem::path& getGlobalPath() const; const std::filesystem::path& getLocalPath() const; - const std::filesystem::path& getGlobalDataPath() const; const std::filesystem::path& getUserConfigPath() const; const std::filesystem::path& getUserDataPath() const; const std::filesystem::path& getLocalDataPath() const; From c82c111ee163d9a897b43764302ed29113403f99 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 27 Feb 2024 19:28:51 +0100 Subject: [PATCH 1100/2167] Use correct index for Athletics_SwimOneSecond --- components/esm3/loadskil.hpp | 2 +- files/data/scripts/omw/skillhandlers.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index 9cae87903c..d8e365aca1 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -75,7 +75,7 @@ namespace ESM Speechcraft_Fail = 1, Armorer_Repair = 0, Athletics_RunOneSecond = 0, - Athletics_SwimOneSecond = 0, + Athletics_SwimOneSecond = 1, }; diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index f6a8ec4248..57fc224cee 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -32,7 +32,7 @@ local Skill = core.stats.Skill -- @field #number Speechcraft_Fail 1 -- @field #number Armorer_Repair 0 -- @field #number Athletics_RunOneSecond 0 --- @field #number Athletics_SwimOneSecond 0 +-- @field #number Athletics_SwimOneSecond 1 --- -- Table of valid sources for skill increases @@ -240,7 +240,7 @@ return { Speechcraft_Fail = 1, Armorer_Repair = 0, Athletics_RunOneSecond = 0, - Athletics_SwimOneSecond = 0, + Athletics_SwimOneSecond = 1, }, --- Trigger a skill level up, activating relevant handlers From ddd09456453f31bcefc18688655edd7b8165e00f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 24 Feb 2024 15:57:11 +0400 Subject: [PATCH 1101/2167] Add a storage mode to drop section on game exit --- CMakeLists.txt | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 9 +++--- components/lua/storage.cpp | 46 ++++++++++++++++++++++------- components/lua/storage.hpp | 22 ++++++++++---- files/lua_api/openmw/storage.lua | 29 ++++++++++++++++-- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..ca25fd05ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 55) +set(OPENMW_LUA_API_REVISION 56) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 4e16b396cd..256a11a0b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -112,13 +112,12 @@ namespace MWLua mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); - mGlobalScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); + mGlobalScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua, &mGlobalStorage)); mMenuScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage)); - mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); + "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua, &mGlobalStorage, &mPlayerStorage)); + mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua, &mGlobalStorage); mPlayerPackages["openmw.storage"] - = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); + = LuaUtil::LuaStorage::initPlayerPackage(mLua, &mGlobalStorage, &mPlayerStorage); mPlayerStorage.setActive(true); mGlobalStorage.setActive(false); diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index dd53fdffcb..db81b6e172 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -17,6 +17,15 @@ namespace LuaUtil { LuaStorage::Value LuaStorage::Section::sEmpty; + void LuaStorage::registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res) + { + res["LIFE_TIME"] = LuaUtil::makeStrictReadOnly(luaState.tableFromPairs({ + { "Persistent", Section::LifeTime::Persistent }, + { "GameSession", Section::LifeTime::GameSession }, + { "Temporary", Section::LifeTime::Temporary }, + })); + } + sol::object LuaStorage::Value::getCopy(lua_State* L) const { return deserialize(L, mSerializedValue); @@ -142,7 +151,12 @@ namespace LuaUtil sview["removeOnExit"] = [](const SectionView& section) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); - section.mSection->mPermanent = false; + section.mSection->mLifeTime = Section::Temporary; + }; + sview["setLifeTime"] = [](const SectionView& section, Section::LifeTime lifeTime) { + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); + section.mSection->mLifeTime = lifeTime; }; sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { if (section.mReadOnly) @@ -151,26 +165,33 @@ namespace LuaUtil }; } - sol::table LuaStorage::initGlobalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); }; res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initLocalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; res["playerSection"] @@ -179,9 +200,12 @@ namespace LuaUtil return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section, /*forMenuScripts=*/true); }; @@ -199,7 +223,7 @@ namespace LuaUtil it->second->mCallbacks.clear(); // Note that we don't clear menu callbacks for permanent sections // because starting/loading a game doesn't reset menu scripts. - if (!it->second->mPermanent) + if (it->second->mLifeTime == Section::Temporary) { it->second->mMenuScriptsCallbacks.clear(); it->second->mValues.clear(); @@ -238,7 +262,7 @@ namespace LuaUtil sol::table data(mLua, sol::create); for (const auto& [sectionName, section] : mData) { - if (section->mPermanent && !section->mValues.empty()) + if (section->mLifeTime == Section::Persistent && !section->mValues.empty()) data[sectionName] = section->asTable(); } std::string serializedData = serialize(data); diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 75e0e14a16..061a5aace3 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -14,11 +14,13 @@ namespace LuaUtil class LuaStorage { public: - static void initLuaBindings(lua_State*); - static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); - static sol::table initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); + static void initLuaBindings(lua_State* L); + static sol::table initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); + static sol::table initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); explicit LuaStorage(lua_State* lua) : mLua(lua) @@ -78,6 +80,13 @@ namespace LuaUtil struct Section { + enum LifeTime + { + Persistent, + GameSession, + Temporary + }; + explicit Section(LuaStorage* storage, std::string name) : mStorage(storage) , mSectionName(std::move(name)) @@ -96,7 +105,7 @@ namespace LuaUtil std::vector mCallbacks; std::vector mMenuScriptsCallbacks; // menu callbacks are in a separate vector because we don't // remove them in clear() - bool mPermanent = true; + LifeTime mLifeTime = Persistent; static Value sEmpty; void checkIfActive() const { mStorage->checkIfActive(); } @@ -120,6 +129,7 @@ namespace LuaUtil if (!mActive) throw std::logic_error("Trying to access inactive storage"); } + static void registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res); }; } diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua index 2335719be8..575b0f83d9 100644 --- a/files/lua_api/openmw/storage.lua +++ b/files/lua_api/openmw/storage.lua @@ -15,6 +15,15 @@ -- end -- end)) +--- Possible @{#LifeTime} values +-- @field [parent=#storage] #LifeTime LIFE_TIME + +--- `storage.LIFE_TIME` +-- @type LifeTime +-- @field #number Persistent "0" Data is stored for the whole game session and remains on disk after quitting the game +-- @field #number GameSession "1" Data is stored for the whole game session +-- @field #number Temporary "2" Data is stored until script context reset + --- -- Get a section of the global storage; can be used by any script, but only global scripts can change values. -- Menu scripts can only access it when a game is running. @@ -83,12 +92,28 @@ -- @param #table values (optional) New values --- --- Make the whole section temporary: will be removed on exit or when load a save. +-- (DEPRECATED, use `setLifeTime(openmw.storage.LIFE_TIME.Temporary)`) Make the whole section temporary: will be removed on exit or when load a save. -- Temporary sections have the same interface to get/set values, the only difference is they will not -- be saved to the permanent storage on exit. --- This function can not be used for a global storage section from a local script. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. -- @function [parent=#StorageSection] removeOnExit -- @param self +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:removeOnExit() + +--- +-- Set the life time of given storage section. +-- New sections initially have a Persistent life time. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. +-- @function [parent=#StorageSection] setLifeTime +-- @param self +-- @param #LifeTime lifeTime Section life time +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:setLifeTime(storage.LIFE_TIME.Temporary) --- -- Set value by a string key; can not be used for global storage from a local script. From 2a4f12b96e197a9fef5fb964b9f2dbfb6a798698 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 27 Feb 2024 08:04:45 +0400 Subject: [PATCH 1102/2167] Use a new life time API --- files/data/scripts/omw/settings/global.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/settings/global.lua b/files/data/scripts/omw/settings/global.lua index f7356d15c4..15a9614636 100644 --- a/files/data/scripts/omw/settings/global.lua +++ b/files/data/scripts/omw/settings/global.lua @@ -1,7 +1,7 @@ local storage = require('openmw.storage') local common = require('scripts.omw.settings.common') -common.getSection(true, common.groupSectionKey):removeOnExit() +common.getSection(true, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.Temporary) return { interfaceName = 'Settings', From cd118ee2639c120ae7c5acb54d03adbb72a31b67 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 22 Feb 2024 19:29:19 +0100 Subject: [PATCH 1103/2167] Expose races to Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/birthsignbindings.cpp | 23 +++-- apps/openmw/mwlua/racebindings.cpp | 120 ++++++++++++++++++++++++ apps/openmw/mwlua/racebindings.hpp | 13 +++ apps/openmw/mwlua/types/npc.cpp | 2 + files/lua_api/openmw/types.lua | 29 ++++++ 6 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 apps/openmw/mwlua/racebindings.cpp create mode 100644 apps/openmw/mwlua/racebindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cf9f265730..5fb06881ec 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings birthsignbindings + classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index e569bc1b8f..95b427eaa4 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -21,6 +21,10 @@ namespace sol struct is_automagical> : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -43,12 +47,19 @@ namespace MWLua signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); }); - signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mPowers.mList.size(); ++i) - res[i + 1] = rec.mPowers.mList[i].serializeText(); - return res; - }); + signT["spells"] + = sol::readonly_property([](const ESM::BirthSign& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + + auto spellListT = lua.new_usertype("ESM3_SpellList"); + spellListT[sol::meta_function::length] = [](const ESM::SpellList& list) { return list.mList.size(); }; + spellListT[sol::meta_function::index] + = [](const ESM::SpellList& list, size_t index) -> sol::optional { + if (index == 0 || index > list.mList.size()) + return sol::nullopt; + return list.mList[index - 1].serializeText(); // Translate from Lua's 1-based indexing. + }; + spellListT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + spellListT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); return LuaUtil::makeReadOnly(birthSigns); } diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp new file mode 100644 index 0000000000..b5ea3c12bf --- /dev/null +++ b/apps/openmw/mwlua/racebindings.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "luamanagerimp.hpp" +#include "racebindings.hpp" +#include "types/types.hpp" + +namespace +{ + struct RaceAttributes + { + const ESM::Race& mRace; + const sol::state_view mLua; + + sol::table getAttribute(ESM::RefId id) const + { + sol::table res(mLua, sol::create); + res["male"] = mRace.mData.getAttribute(id, true); + res["female"] = mRace.mData.getAttribute(id, false); + return LuaUtil::makeReadOnly(res); + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table races(context.mLua->sol(), sol::create); + addRecordFunctionBinding(races, context); + + auto raceT = lua.new_usertype("ESM3_Race"); + raceT[sol::meta_function::to_string] + = [](const ESM::Race& rec) -> std::string { return "ESM3_Race[" + rec.mId.toDebugString() + "]"; }; + raceT["id"] = sol::readonly_property([](const ESM::Race& rec) { return rec.mId.serializeText(); }); + raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; }); + raceT["description"] + = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; }); + raceT["spells"] + = sol::readonly_property([lua](const ESM::Race& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + for (const auto& skillBonus : rec.mData.mBonus) + { + ESM::RefId skill = ESM::Skill::indexToRefId(skillBonus.mSkill); + if (!skill.empty()) + res[skill.serializeText()] = skillBonus.mBonus; + } + return res; + }); + raceT["isPlayable"] = sol::readonly_property( + [](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Playable; }); + raceT["isBeast"] + = sol::readonly_property([](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Beast; }); + raceT["height"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleHeight; + res["female"] = rec.mData.mFemaleHeight; + return LuaUtil::makeReadOnly(res); + }); + raceT["weight"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleWeight; + res["female"] = rec.mData.mFemaleWeight; + return LuaUtil::makeReadOnly(res); + }); + + raceT["attributes"] = sol::readonly_property([lua](const ESM::Race& rec) -> RaceAttributes { + return { rec, lua }; + }); + + auto attributesT = lua.new_usertype("ESM3_RaceAttributes"); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + attributesT[sol::meta_function::index] + = [&](const RaceAttributes& attributes, std::string_view stringId) -> sol::optional { + ESM::RefId id = ESM::RefId::deserializeText(stringId); + if (!store.search(id)) + return sol::nullopt; + return attributes.getAttribute(id); + }; + attributesT[sol::meta_function::pairs] = [&](sol::this_state ts, RaceAttributes& attributes) { + auto iterator = store.begin(); + return sol::as_function( + [iterator, attributes, + &store]() mutable -> std::pair, sol::optional> { + if (iterator != store.end()) + { + ESM::RefId id = iterator->mId; + ++iterator; + return { id.serializeText(), attributes.getAttribute(id) }; + } + return { sol::nullopt, sol::nullopt }; + }); + }; + + return LuaUtil::makeReadOnly(races); + } +} diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp new file mode 100644 index 0000000000..43ba9237c5 --- /dev/null +++ b/apps/openmw/mwlua/racebindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_RACEBINDINGS_H +#define MWLUA_RACEBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context); +} + +#endif // MWLUA_RACEBINDINGS_H diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index d7d459bb81..29147a826b 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -15,6 +15,7 @@ #include "../classbindings.hpp" #include "../localscripts.hpp" +#include "../racebindings.hpp" #include "../stats.hpp" namespace sol @@ -86,6 +87,7 @@ namespace MWLua addActorServicesBindings(record, context); npc["classes"] = initClassRecordBindings(context); + npc["races"] = initRaceRecordBindings(context); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 0c51544f64..d085b355c8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -958,6 +958,35 @@ -- @param #any objectOrRecordId -- @return #NpcRecord +--- @{#Races}: Race data +-- @field [parent=#NPC] #Races races + +--- +-- A read-only list of all @{#RaceRecord}s in the world database. +-- @field [parent=#Races] #list<#RaceRecord> records + +--- +-- Returns a read-only @{#RaceRecord} +-- @function [parent=#Races] record +-- @param #string recordId +-- @return #RaceRecord + +--- +-- Race data record +-- @type RaceRecord +-- @field #string id Race id +-- @field #string name Race name +-- @field #string description Race description +-- @field #map<#string, #number> skills A map of bonus skill points by skill ID +-- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race +-- @field #bool isPlayable True if the player can pick this race in character generation +-- @field #bool isBeast True if this race is a beast race +-- @field #map<#string, #number> height A read-only table with male and female fields +-- @field #map<#string, #number> weight A read-only table with male and female fields +-- @field #map<#string, #map<#string, #number>> attributes A read-only table of attribute ID to male and female base values +-- @usage -- Get base strength for men +-- strength = types.NPC.races.records[1].attributes.strength.male + --- -- @type NpcRecord -- @field #string id The record ID of the NPC From f346295975e29c2c6640878e4a048ad4158e385c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 27 Feb 2024 21:57:01 +0100 Subject: [PATCH 1104/2167] Add a number-per-sex type --- files/lua_api/openmw/types.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d085b355c8..60f3e79628 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -981,12 +981,17 @@ -- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race -- @field #bool isPlayable True if the player can pick this race in character generation -- @field #bool isBeast True if this race is a beast race --- @field #map<#string, #number> height A read-only table with male and female fields --- @field #map<#string, #number> weight A read-only table with male and female fields --- @field #map<#string, #map<#string, #number>> attributes A read-only table of attribute ID to male and female base values +-- @field #GenderedNumber height Height values +-- @field #GenderedNumber weight Weight values +-- @field #map<#string, #GenderedNumber> attributes A read-only table of attribute ID to base value -- @usage -- Get base strength for men -- strength = types.NPC.races.records[1].attributes.strength.male +--- +-- @type GenderedNumber +-- @field #number male Male value +-- @field #number female Female value + --- -- @type NpcRecord -- @field #string id The record ID of the NPC From 27b1434f5b7e9fc40bc5cd8ebeac411994d4eaf5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 28 Feb 2024 01:06:42 +0300 Subject: [PATCH 1105/2167] Use string_view for full help text --- apps/openmw/mwgui/tooltips.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 938d4176f2..0e0c56c194 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -410,13 +410,13 @@ namespace MWGui const std::string& image = info.icon; int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; - std::string extra = info.extra; + std::string_view extra = info.extra; // remove the first newline (easier this way) if (!text.empty() && text[0] == '\n') text.erase(0, 1); if (!extra.empty() && extra[0] == '\n') - extra.erase(0, 1); + extra = extra.substr(1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); From 322a378907b034abfd722a861c7ab04f929ea0fb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 00:49:15 +0000 Subject: [PATCH 1106/2167] Load correct config files in the wizard --- apps/wizard/main.cpp | 2 +- apps/wizard/mainwizard.cpp | 8 +++----- apps/wizard/mainwizard.hpp | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index b62428f946..e740f06015 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) l10n::installQtTranslations(app, "wizard", resourcesPath); - Wizard::MainWizard wizard; + Wizard::MainWizard wizard(std::move(configurationManager)); wizard.show(); return app.exec(); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2f1f373cfd..e8bd6f7007 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -26,9 +26,10 @@ using namespace Process; -Wizard::MainWizard::MainWizard(QWidget* parent) +Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent) : QWizard(parent) , mInstallations() + , mCfgMgr(cfgMgr) , mError(false) , mGameSettings(mCfgMgr) { @@ -172,10 +173,7 @@ void Wizard::MainWizard::setupGameSettings() file.close(); // Now the rest - QStringList paths; - paths.append(Files::getUserConfigPathQString(mCfgMgr)); - paths.append(QLatin1String("openmw.cfg")); - paths.append(Files::getGlobalConfigPathQString(mCfgMgr)); + QStringList paths = Files::getActiveConfigPathsQString(mCfgMgr); for (const QString& path2 : paths) { diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 60f46fa1a5..2f120f1955 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -45,7 +45,7 @@ namespace Wizard Page_Conclusion }; - MainWizard(QWidget* parent = nullptr); + MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent = nullptr); ~MainWizard() override; bool findFiles(const QString& name, const QString& path); From d111b4bbd9bd8570ab8524a72dab044585eea911 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 00:58:30 +0000 Subject: [PATCH 1107/2167] Handle built-in content files in content model There's also handling for files declared as originating from a lower-priority openmw.cfg, e.g. anything in the local config or any intermediate ones, as they can't be disabled or reordered. There's no way to mark such files yet, but the logic's the same as built-in files, so everything will be fine once that's set up. --- .../contentselector/model/contentmodel.cpp | 38 +++++++++++++++---- components/contentselector/model/esmfile.cpp | 27 +++++++++++++ components/contentselector/model/esmfile.hpp | 19 +++++++++- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 377edc3b1c..003f2ee241 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -109,6 +109,9 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index if (!file) return Qt::NoItemFlags; + if (file->builtIn() || file->fromAnotherConfigFile()) + return Qt::NoItemFlags; + // game files can always be checked if (file == mGameFile) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; @@ -130,7 +133,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index continue; noGameFiles = false; - if (mCheckedFiles.contains(depFile)) + if (depFile->builtIn() || depFile->fromAnotherConfigFile() || mCheckedFiles.contains(depFile)) { gamefileChecked = true; break; @@ -217,14 +220,14 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked; + return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked : Qt::Unchecked; } case Qt::UserRole: { if (file == mGameFile) return ContentType_GameFile; - else if (flags(index)) + else return ContentType_Addon; break; @@ -279,7 +282,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); bool setState = false; - if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) + if (file->builtIn() || file->fromAnotherConfigFile()) + { + setState = false; + success = false; + } + else if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) { setState = true; success = true; @@ -374,6 +382,13 @@ bool ContentSelectorModel::ContentModel::dropMimeData( else if (parent.isValid()) beginRow = parent.row(); + int firstModifiable = 0; + while (item(firstModifiable)->builtIn() || item(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + + if (beginRow < firstModifiable) + return false; + QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); @@ -449,6 +464,9 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf file->setGameFiles({}); } + if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) + file->setBuiltIn(true); + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { file->setDate(info.lastModified()); @@ -579,15 +597,20 @@ void ContentSelectorModel::ContentModel::setCurrentGameFile(const EsmFile* file) void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); + + int firstModifiable = 0; + while (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + // Dependency sort std::unordered_set moved; - for (int i = mFiles.size() - 1; i > 0;) + for (int i = mFiles.size() - 1; i > firstModifiable;) { const auto file = mFiles.at(i); if (moved.find(file) == moved.end()) { int index = -1; - for (int j = 0; j < i; ++j) + for (int j = firstModifiable; j < i; ++j) { const QStringList& gameFiles = mFiles.at(j)->gameFiles(); // All addon files are implicitly dependent on the game file @@ -650,6 +673,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL { if (setCheckState(filepath, true)) { + // setCheckState already gracefully handles builtIn and fromAnotherConfigFile // as necessary, move plug-ins in visible list to match sequence of supplied filelist const EsmFile* file = item(filepath); int filePosition = indexFromItem(file).row(); @@ -747,7 +771,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, const EsmFile* file = item(filepath); - if (!file) + if (!file || file->builtIn() || file->fromAnotherConfigFile()) return false; if (checkState) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index e4280baef7..02ac0efa70 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -41,6 +41,16 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description) mDescription = description; } +void ContentSelectorModel::EsmFile::setBuiltIn(bool builtIn) +{ + mBuiltIn = builtIn; +} + +void ContentSelectorModel::EsmFile::setFromAnotherConfigFile(bool fromAnotherConfigFile) +{ + mFromAnotherConfigFile = fromAnotherConfigFile; +} + bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) @@ -76,6 +86,14 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co return mDescription; break; + case FileProperty_BuiltIn: + return mBuiltIn; + break; + + case FileProperty_FromAnotherConfigFile: + return mFromAnotherConfigFile; + break; + case FileProperty_GameFile: return mGameFiles; break; @@ -113,6 +131,15 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con mDescription = value; break; + // todo: check these work + case FileProperty_BuiltIn: + mBuiltIn = value == "true"; + break; + + case FileProperty_FromAnotherConfigFile: + mFromAnotherConfigFile = value == "true"; + break; + case FileProperty_GameFile: mGameFiles << value; break; diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 606cc3d319..42b6cfeff6 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -26,7 +26,9 @@ namespace ContentSelectorModel FileProperty_DateModified = 3, FileProperty_FilePath = 4, FileProperty_Description = 5, - FileProperty_GameFile = 6 + FileProperty_BuiltIn = 6, + FileProperty_FromAnotherConfigFile = 7, + FileProperty_GameFile = 8, }; EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); @@ -40,6 +42,8 @@ namespace ContentSelectorModel void setFilePath(const QString& path); void setGameFiles(const QStringList& gameFiles); void setDescription(const QString& description); + void setBuiltIn(bool builtIn); + void setFromAnotherConfigFile(bool fromAnotherConfigFile); void addGameFile(const QString& name) { mGameFiles.append(name); } QVariant fileProperty(const FileProperty prop) const; @@ -49,18 +53,27 @@ namespace ContentSelectorModel QDateTime modified() const { return mModified; } QString formatVersion() const { return mVersion; } QString filePath() const { return mPath; } + bool builtIn() const { return mBuiltIn; } + bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } /// @note Contains file names, not paths. const QStringList& gameFiles() const { return mGameFiles; } QString description() const { return mDescription; } QString toolTip() const { - return mTooltipTemlate.arg(mAuthor) + QString tooltip = mTooltipTemlate.arg(mAuthor) .arg(mVersion) .arg(mModified.toString(Qt::ISODate)) .arg(mPath) .arg(mDescription) .arg(mGameFiles.join(", ")); + + if (mBuiltIn) + tooltip += tr("
This content file cannot be disabled because it is part of OpenMW.
"); + else if (mFromAnotherConfigFile) + tooltip += tr("
This content file cannot be disabled because it is enabled in a config file other than the user one.
"); + + return tooltip; } bool isGameFile() const; @@ -82,6 +95,8 @@ namespace ContentSelectorModel QStringList mGameFiles; QString mDescription; QString mToolTip; + bool mBuiltIn = false; + bool mFromAnotherConfigFile = false; }; } From 0519e1215f608fa82374a220ebefa182f25f05db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 28 Feb 2024 17:20:46 +0100 Subject: [PATCH 1108/2167] Unify the creation of RefId tables --- apps/openmw/mwlua/birthsignbindings.cpp | 17 +++--------- apps/openmw/mwlua/classbindings.cpp | 30 +++++----------------- apps/openmw/mwlua/factionbindings.cpp | 21 +++------------ apps/openmw/mwlua/idcollectionbindings.hpp | 25 ++++++++++++++++++ apps/openmw/mwlua/racebindings.cpp | 5 ++-- 5 files changed, 41 insertions(+), 57 deletions(-) create mode 100644 apps/openmw/mwlua/idcollectionbindings.hpp diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 95b427eaa4..f9266991ae 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -8,6 +8,7 @@ #include "../mwworld/esmstore.hpp" #include "birthsignbindings.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" @@ -47,19 +48,9 @@ namespace MWLua signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); }); - signT["spells"] - = sol::readonly_property([](const ESM::BirthSign& rec) -> const ESM::SpellList* { return &rec.mPowers; }); - - auto spellListT = lua.new_usertype("ESM3_SpellList"); - spellListT[sol::meta_function::length] = [](const ESM::SpellList& list) { return list.mList.size(); }; - spellListT[sol::meta_function::index] - = [](const ESM::SpellList& list, size_t index) -> sol::optional { - if (index == 0 || index > list.mList.size()) - return sol::nullopt; - return list.mList[index - 1].serializeText(); // Translate from Lua's 1-based indexing. - }; - spellListT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - spellListT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { + return createReadOnlyRefIdTable(lua, rec.mPowers.mList); + }); return LuaUtil::makeReadOnly(birthSigns); } diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index ea1ea8e7ef..a272a5b407 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -6,6 +6,7 @@ #include "../mwworld/esmstore.hpp" #include "classbindings.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "stats.hpp" #include "types/types.hpp" @@ -40,34 +41,15 @@ namespace MWLua = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto attribute = rec.mData.mAttribute; - for (size_t i = 0; i < attribute.size(); ++i) - { - ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]); - res[i + 1] = attributeId.serializeText(); - } - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[1]); }); }); classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[0]); }); }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index 87ce6ced39..e4c65386bf 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -9,6 +9,7 @@ #include "../mwmechanics/npcstats.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" namespace @@ -96,26 +97,10 @@ namespace MWLua return res; }); factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto attributeIndex : rec.mData.mAttribute) - { - ESM::RefId id = ESM::Attribute::indexToRefId(attributeIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); factionT["skills"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto skillIndex : rec.mData.mSkills) - { - ESM::RefId id = ESM::Skill::indexToRefId(skillIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mSkills, ESM::Skill::indexToRefId); }); auto rankT = lua.new_usertype("ESM3_FactionRank"); rankT[sol::meta_function::to_string] = [](const FactionRank& rec) -> std::string { diff --git a/apps/openmw/mwlua/idcollectionbindings.hpp b/apps/openmw/mwlua/idcollectionbindings.hpp new file mode 100644 index 0000000000..15e2b14fb9 --- /dev/null +++ b/apps/openmw/mwlua/idcollectionbindings.hpp @@ -0,0 +1,25 @@ +#ifndef MWLUA_IDCOLLECTIONBINDINGS_H +#define MWLUA_IDCOLLECTIONBINDINGS_H + +#include + +#include +#include + +namespace MWLua +{ + template + sol::table createReadOnlyRefIdTable(const sol::state_view& lua, const C& container, P projection = {}) + { + sol::table res(lua, sol::create); + for (const auto& element : container) + { + ESM::RefId id = projection(element); + if (!id.empty()) + res.add(id.serializeText()); + } + return LuaUtil::makeReadOnly(res); + } +} + +#endif diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index b5ea3c12bf..4ee2f7b5f7 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "racebindings.hpp" #include "types/types.hpp" @@ -58,8 +59,8 @@ namespace MWLua raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; }); raceT["description"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; }); - raceT["spells"] - = sol::readonly_property([lua](const ESM::Race& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + raceT["spells"] = sol::readonly_property( + [lua](const ESM::Race& rec) -> sol::table { return createReadOnlyRefIdTable(lua, rec.mPowers.mList); }); raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { sol::table res(lua, sol::create); for (const auto& skillBonus : rec.mData.mBonus) From 9e1334cc097b6765f42e7943d98fe1f0a69540df Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 23:49:55 +0000 Subject: [PATCH 1109/2167] Resync composing and path openmw.cfg settings with options.cpp --- components/config/gamesettings.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index f8ff3d362c..8ddf72325f 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -108,9 +108,10 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap Date: Thu, 29 Feb 2024 00:48:03 +0000 Subject: [PATCH 1111/2167] Tooltips for data-local and resources/vfs --- apps/launcher/datafilespage.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index a0287afd87..de72aa2577 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -295,7 +295,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // add new achives files presents in current directory addArchivesFromDir(currentDir); - QString tooltip; + QStringList tooltip; // add content files presents in current directory mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); @@ -308,7 +308,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) { - tooltip += tr("Will be added to the current profile"); + tooltip << tr("Will be added to the current profile"); QFont font = item->font(); font.setBold(true); font.setItalic(true); @@ -321,15 +321,17 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); } + if (currentDir == mDataLocal) + tooltip << tr("This is the data-local directory and cannot be disabled"); + else if (currentDir == resourcesVfs) + tooltip << tr("This directory is part of OpenMW and cannot be disabled"); // Add a "data file" icon if the directory contains a content file if (mSelector->containsDataFiles(currentDir)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); - if (!tooltip.isEmpty()) - tooltip += "\n"; - tooltip += tr("Contains content file(s)"); + tooltip << tr("Contains content file(s)"); } else { @@ -339,7 +341,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto emptyIcon = QIcon(pixmap); item->setIcon(emptyIcon); } - item->setToolTip(tooltip); + item->setToolTip(tooltip.join('\n')); } mSelector->sortFiles(); From e54decc830fde07cdf95192e25f7689bdea34eee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 1 Mar 2024 12:24:36 +0100 Subject: [PATCH 1112/2167] Remove redundant is_automagicals --- apps/openmw/mwlua/birthsignbindings.cpp | 8 -------- apps/openmw/mwlua/classbindings.cpp | 4 ---- apps/openmw/mwlua/racebindings.cpp | 4 ---- 3 files changed, 16 deletions(-) diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index f9266991ae..6993ae0105 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -18,14 +18,6 @@ namespace sol struct is_automagical : std::false_type { }; - template <> - struct is_automagical> : std::false_type - { - }; - template <> - struct is_automagical : std::false_type - { - }; } namespace MWLua diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index a272a5b407..c9d5a9fb7b 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -17,10 +17,6 @@ namespace sol struct is_automagical : std::false_type { }; - template <> - struct is_automagical> : std::false_type - { - }; } namespace MWLua diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index 4ee2f7b5f7..e2e2ae2a8a 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -35,10 +35,6 @@ namespace sol { }; template <> - struct is_automagical> : std::false_type - { - }; - template <> struct is_automagical : std::false_type { }; From 958f70736f838a4c92d1ab93d9c617ec6ca79dd1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 2 Mar 2024 12:45:48 +0100 Subject: [PATCH 1113/2167] Implement auto calculated potion values --- CHANGELOG.md | 1 + apps/esmtool/labels.cpp | 14 +++++++++++++ apps/esmtool/labels.hpp | 1 + apps/esmtool/record.cpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 4 ++-- apps/openmw/mwclass/potion.cpp | 7 +++---- apps/openmw/mwmechanics/alchemy.cpp | 4 ++-- apps/openmw/mwmechanics/spellutil.cpp | 22 +++++++++++++++++++-- apps/openmw/mwmechanics/spellutil.hpp | 4 ++++ components/esm3/loadalch.cpp | 4 ++-- components/esm3/loadalch.hpp | 7 ++++++- 11 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03b91d364..fe081224ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs + Bug #7859: AutoCalc flag is not used to calculate potion value Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index d0a443de53..6def8b4a42 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,5 +1,6 @@ #include "labels.hpp" +#include #include #include #include @@ -987,3 +988,16 @@ std::string recordFlags(uint32_t flags) properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } + +std::string potionFlags(int flags) +{ + std::string properties; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Potion::Autocalc) + properties += "Autocalc "; + if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) + properties += "Invalid "; + properties += Misc::StringUtils::format("(0x%08X)", flags); + return properties; +} diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index df6d419ca3..c3a78141b4 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -60,6 +60,7 @@ std::string itemListFlags(int flags); std::string lightFlags(int flags); std::string magicEffectFlags(int flags); std::string npcFlags(int flags); +std::string potionFlags(int flags); std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b1185a4d33..d099bdfcfb 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -479,7 +479,7 @@ namespace EsmTool std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; + std::cout << " Flags: " << potionFlags(mData.mData.mFlags) << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index c6179facb8..bdccab9cda 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -33,7 +33,7 @@ QVariant CSMWorld::PotionRefIdAdapter::getData(const RefIdColumn* column, const data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); if (column == mAutoCalc) - return record.get().mData.mAutoCalc != 0; + return record.get().mData.mFlags & ESM::Potion::Autocalc; // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column == mColumns.mEffects) @@ -51,7 +51,7 @@ void CSMWorld::PotionRefIdAdapter::setData( ESM::Potion potion = record.get(); if (column == mAutoCalc) - potion.mData.mAutoCalc = value.toInt(); + potion.mData.mFlags = value.toBool(); else { InventoryRefIdAdapter::setData(column, data, index, value); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 42c122cb48..0d98302fe6 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -19,6 +19,7 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/spellutil.hpp" #include "classmodel.hpp" @@ -65,9 +66,7 @@ namespace MWClass int Potion::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef* ref = ptr.get(); - - return ref->mBase->mData.mValue; + return MWMechanics::getPotionValue(*ptr.get()->mBase); } const ESM::RefId& Potion::getUpSoundId(const MWWorld::ConstPtr& ptr) const @@ -101,7 +100,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 5a995e7f06..3adb399483 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -250,7 +250,7 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co if (iter->mName != toFind.mName || iter->mScript != toFind.mScript || iter->mData.mWeight != toFind.mData.mWeight || iter->mData.mValue != toFind.mData.mValue - || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) + || iter->mData.mFlags != toFind.mData.mFlags) continue; // Don't choose an ID that came from the content files, would have unintended side effects @@ -310,7 +310,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mData.mWeight /= countIngredients(); newRecord.mData.mValue = mValue; - newRecord.mData.mAutoCalc = 0; + newRecord.mData.mFlags = 0; newRecord.mRecordFlags = 0; newRecord.mName = name; diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 2a63a3a444..671939cb00 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -48,7 +49,7 @@ namespace MWMechanics bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; - if (method != EffectCostMethod::GameEnchantment) + if (method == EffectCostMethod::PlayerSpell || method == EffectCostMethod::GameSpell) { minMagn = std::max(1, minMagn); maxMagn = std::max(1, maxMagn); @@ -57,21 +58,28 @@ namespace MWMechanics if (!appliedOnce) duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + static const float iAlchemyMod = store.get().find("iAlchemyMod")->mValue.getFloat(); int durationOffset = 0; int minArea = 0; + float costMult = fEffectCostMult; if (method == EffectCostMethod::PlayerSpell) { durationOffset = 1; minArea = 1; } + else if (method == EffectCostMethod::GamePotion) + { + minArea = 1; + costMult = iAlchemyMod; + } float x = 0.5 * (minMagn + maxMagn); x *= 0.1 * magicEffect->mData.mBaseCost; x *= durationOffset + duration; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; - return x * fEffectCostMult; + return x * costMult; } int calcSpellCost(const ESM::Spell& spell) @@ -140,6 +148,16 @@ namespace MWMechanics return enchantment.mData.mCharge; } + int getPotionValue(const ESM::Potion& potion) + { + if (potion.mData.mFlags & ESM::Potion::Autocalc) + { + float cost = getTotalCost(potion.mEffects, EffectCostMethod::GamePotion); + return std::round(cost); + } + return potion.mData.mValue; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index a332a231e6..fa9b0c64b9 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -8,6 +8,7 @@ namespace ESM struct ENAMstruct; struct Enchantment; struct MagicEffect; + struct Potion; struct Spell; } @@ -23,6 +24,7 @@ namespace MWMechanics GameSpell, PlayerSpell, GameEnchantment, + GamePotion, }; float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, @@ -33,6 +35,8 @@ namespace MWMechanics int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor); int getEnchantmentCharge(const ESM::Enchantment& enchantment); + int getPotionValue(const ESM::Potion& potion); + /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 2b01dd9b09..b85bbd558e 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -36,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("ALDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mAutoCalc); + esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); hasData = true; break; case fourCC("ENAM"): @@ -80,7 +80,7 @@ namespace ESM mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; - mData.mAutoCalc = 0; + mData.mFlags = 0; mName.clear(); mModel.clear(); mIcon.clear(); diff --git a/components/esm3/loadalch.hpp b/components/esm3/loadalch.hpp index ddecd7e3c7..814d21937b 100644 --- a/components/esm3/loadalch.hpp +++ b/components/esm3/loadalch.hpp @@ -25,11 +25,16 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Potion"; } + enum Flags + { + Autocalc = 1 // Determines value + }; + struct ALDTstruct { float mWeight; int32_t mValue; - int32_t mAutoCalc; + int32_t mFlags; }; ALDTstruct mData; From 7a84f27eeb60c1814884b2b59b328eb02a13e3b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Mar 2024 09:57:55 +0300 Subject: [PATCH 1114/2167] Properly calculate touch spell hit position (#6156) Reorganize hit contact logic and remove dead code (distance checks, melee hit contact-relevant stuff) --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 106 ++++++++++++------------------- 2 files changed, 42 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03b91d364..98526c7c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item + Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cbef6789f1..8cafb1dbe0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2939,89 +2939,65 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit - // result. - std::vector targetActors; - if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) - stats.getAiSequence().getCombatTargets(targetActors); - - const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - - // for player we can take faced object first MWWorld::Ptr target; - if (actor == MWMechanics::getPlayer()) - target = getFacedObject(); - - // if the faced object can not be activated, do not use it - if (!target.isEmpty() && !target.getClass().hasToolTip(target)) - target = nullptr; - - if (target.isEmpty()) + // For scripted spells we should not use hit contact + if (manualSpell) { - // For scripted spells we should not use hit contact - if (manualSpell) + if (actor != MWMechanics::getPlayer()) { - if (actor != MWMechanics::getPlayer()) + for (const auto& package : stats.getAiSequence()) { - for (const auto& package : stats.getAiSequence()) + if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { - if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) - { - target = package->getTarget(); - break; - } + target = package->getTarget(); + break; } } } - else + } + else + { + if (actor == MWMechanics::getPlayer()) + target = getFacedObject(); + + if (target.isEmpty() || !target.getClass().hasToolTip(target)) { // For actor targets, we want to use melee hit contact. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would - // be very hard to aim at otherwise. For object targets, we want the detailed shapes (rendering - // raycast). If we used the bounding boxes for static objects, then we would not be able to target e.g. + // be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. // objects lying on a shelf. - const std::pair result1 = MWMechanics::getHitContact(actor, fCombatDistance); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); + target = MWMechanics::getHitContact(actor, fCombatDistance).first; - // Get the target to use for "on touch" effects, using the facing direction from Head node - osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - - osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) - * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); - - osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); - float distance = getMaxActivationDistance(); - osg::Vec3f dest = origin + direction * distance; - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); - - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) - dist1 = (origin - result1.second).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) + if (target.isEmpty()) { - target = result1.first; - hitPosition = result1.second; - if (dist1 > getMaxActivationDistance()) - target = nullptr; - } - else if (result2.mHit) - { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() - && !target.getClass().hasToolTip(target)) - target = nullptr; + // Get the target using the facing direction from Head node + const osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); + const osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); + const osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); + const osg::Vec3f dest = origin + direction * getMaxActivationDistance(); + const MWRender::RenderingManager::RayResult result = mRendering->castRay(origin, dest, true, true); + if (result.mHit) + target = result.mHitObject; } } } + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); + if (!target.isEmpty()) + { + // Touch explosion placement doesn't depend on where the target was "touched". + // For NPC targets, it also doesn't depend on the height. + // Using 0.75 of the collision box height seems accurate for actors and looks decent for non-actors. + // In Morrowind, touch explosion placement for non-actors is inexplicable, + // often putting the explosion way above the object. + hitPosition = target.getRefData().getPosition().asVec3(); + hitPosition.z() += mPhysics->getHalfExtents(target).z() * 2.f * Constants::TorsoHeight; + } + const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target, false, manualSpell); From cef59e8928c955875b4610783b2db7539fba5c67 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 27 Feb 2024 20:47:46 +0100 Subject: [PATCH 1115/2167] Replace fixed size writeHNT calls with decomposition --- apps/esmtool/record.cpp | 4 +- .../model/world/nestedcoladapterimp.cpp | 1 - apps/opencs/view/render/object.hpp | 2 +- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- apps/openmw/mwworld/refdata.hpp | 2 +- .../detournavigator/objecttransform.hpp | 2 +- components/esm/defs.hpp | 37 +-------------- components/esm/position.hpp | 47 +++++++++++++++++++ components/esm3/cellid.cpp | 10 +++- components/esm3/cellref.cpp | 8 ++-- components/esm3/cellref.hpp | 5 +- components/esm3/esmreader.hpp | 10 ++++ components/esm3/loadalch.cpp | 12 ++++- components/esm3/loadappa.cpp | 12 ++++- components/esm3/loadarmo.cpp | 11 ++++- components/esm3/loadbody.cpp | 12 ++++- components/esm3/loadbook.cpp | 12 ++++- components/esm3/loadcell.cpp | 19 ++++++-- components/esm3/loadclas.cpp | 15 ++++-- components/esm3/loadclot.cpp | 12 ++++- components/esm3/loadcont.cpp | 4 +- components/esm3/loadcrea.cpp | 12 +++-- components/esm3/loadench.cpp | 12 ++++- components/esm3/loadinfo.cpp | 14 ++++-- components/esm3/loadinfo.hpp | 3 +- components/esm3/loadingr.cpp | 12 ++++- components/esm3/loadligh.cpp | 12 ++++- components/esm3/loadlock.cpp | 12 ++++- components/esm3/loadmisc.cpp | 12 ++++- components/esm3/loadpgrd.cpp | 34 ++++++++------ components/esm3/loadpgrd.hpp | 2 - components/esm3/loadprob.cpp | 12 ++++- components/esm3/loadrepa.cpp | 12 ++++- components/esm3/loadskil.cpp | 11 ++++- components/esm3/loadsndg.cpp | 2 +- components/esm3/loadsoun.cpp | 12 ++++- components/esm3/loadspel.cpp | 12 ++++- components/esm3/objectstate.cpp | 7 +-- components/esm3/objectstate.hpp | 5 +- components/esm3/player.cpp | 4 +- components/esm3/player.hpp | 6 +-- components/esm3/savedgame.cpp | 17 +++++-- components/esm3/transport.cpp | 9 ++-- components/esm3/transport.hpp | 2 +- components/esm4/loadachr.hpp | 1 + components/esm4/loadrefr.hpp | 1 + components/esm4/reference.hpp | 2 +- components/misc/convert.hpp | 2 +- components/resource/foreachbulletobject.hpp | 2 +- 49 files changed, 339 insertions(+), 144 deletions(-) create mode 100644 components/esm/position.hpp diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index d099bdfcfb..912ad0d683 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -839,8 +839,7 @@ namespace EsmTool std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" << std::endl; - std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; - std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; + std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; @@ -1137,7 +1136,6 @@ namespace EsmTool std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; - std::cout << " Unknown: " << point.mUnknown << std::endl; i++; } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 8b8c7b17be..34205e9421 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -38,7 +38,6 @@ namespace CSMWorld point.mZ = 0; point.mAutogenerated = 0; point.mConnectionNum = 0; - point.mUnknown = 0; points.insert(points.begin() + position, point); pathgrid.mData.mPoints = pathgrid.mPoints.size(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 5c73b12211..31f0d93ac4 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "tagbase.hpp" diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 44566d3b45..0f688686cd 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include namespace MWWorld diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index ae80a0d64e..1b57971f11 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H -#include +#include #include #include diff --git a/components/detournavigator/objecttransform.hpp b/components/detournavigator/objecttransform.hpp index e56f5dd392..1d52817f52 100644 --- a/components/detournavigator/objecttransform.hpp +++ b/components/detournavigator/objecttransform.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H -#include +#include #include diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index cbc70582c0..52b2afb8de 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -4,12 +4,8 @@ #include #include -#include - -#include - -#include "components/esm/fourcc.hpp" #include +#include #include namespace ESM @@ -33,37 +29,6 @@ namespace ESM RT_Target = 2 }; - // Position and rotation - struct Position - { - float pos[3]{}; - - // In radians - float rot[3]{}; - - osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } - - osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } - - friend inline bool operator<(const Position& l, const Position& r) - { - const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; - return tuple(l) < tuple(r); - } - }; - - bool inline operator==(const Position& left, const Position& right) noexcept - { - return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] - && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; - } - - bool inline operator!=(const Position& left, const Position& right) noexcept - { - return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] - || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; - } - constexpr unsigned int sEsm4RecnameFlag = 0x00800000; constexpr unsigned int esm3Recname(const char (&name)[5]) diff --git a/components/esm/position.hpp b/components/esm/position.hpp new file mode 100644 index 0000000000..d48997610e --- /dev/null +++ b/components/esm/position.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_ESM3_POSITION_H +#define OPENMW_ESM3_POSITION_H + +#include +#include +#include + +namespace ESM +{ + // Position and rotation + struct Position + { + float pos[3]{}; + + // In radians + float rot[3]{}; + + osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } + + friend inline bool operator<(const Position& l, const Position& r) + { + const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; + return tuple(l) < tuple(r); + } + }; + + bool inline operator==(const Position& left, const Position& right) noexcept + { + return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] + && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; + } + + bool inline operator!=(const Position& left, const Position& right) noexcept + { + return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] + || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.pos, v.rot); + } +} +#endif \ No newline at end of file diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 9a5be3aada..4d08691034 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -3,9 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY); + } void CellId::load(ESMReader& esm) { @@ -13,7 +19,7 @@ namespace ESM mIndex.mX = 0; mIndex.mY = 0; - mPaged = esm.getHNOT("CIDX", mIndex.mX, mIndex.mY); + mPaged = esm.getOptionalComposite("CIDX", mIndex); } void CellId::save(ESMWriter& esm) const @@ -21,7 +27,7 @@ namespace ESM esm.writeHNString("SPAC", mWorldspace); if (mPaged) - esm.writeHNT("CIDX", mIndex, 8); + esm.writeNamedComposite("CIDX", mIndex); } struct VisitCellRefId diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 93a2ece669..ecba6f7f5e 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -112,7 +112,7 @@ namespace ESM case fourCC("DODT"): if constexpr (load) { - esm.getHT(cellRef.mDoorDest.pos, cellRef.mDoorDest.rot); + esm.getSubComposite(cellRef.mDoorDest); cellRef.mTeleport = true; } else @@ -132,7 +132,7 @@ namespace ESM break; case fourCC("DATA"): if constexpr (load) - esm.getHT(cellRef.mPos.pos, cellRef.mPos.rot); + esm.getSubComposite(cellRef.mPos); else esm.skipHSub(); break; @@ -224,7 +224,7 @@ namespace ESM if (!inInventory && mTeleport) { - esm.writeHNT("DODT", mDoorDest); + esm.writeNamedComposite("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } @@ -243,7 +243,7 @@ namespace ESM esm.writeHNT("UNAM", mReferenceBlocked); if (!inInventory) - esm.writeHNT("DATA", mPos, 24); + esm.writeNamedComposite("DATA", mPos); } void CellRef::blank() diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 84b6ae1d18..5079095889 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include +#include namespace ESM { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 4af2264828..7d0b9b980c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -184,6 +184,16 @@ namespace ESM decompose(value, [&](auto&... args) { getHNT(name, args...); }); } + bool getOptionalComposite(NAME name, auto& value) + { + if (isNextSub(name)) + { + getSubComposite(value); + return true; + } + return false; + } + void getComposite(auto& value) { decompose(value, [&](auto&... args) { (getT(args), ...); }); diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index b85bbd558e..4e6c2ad1e2 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Potion::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -36,7 +44,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("ALDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -71,7 +79,7 @@ namespace ESM esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("ALDT", mData, 12); + esm.writeNamedComposite("ALDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadappa.cpp b/components/esm3/loadappa.cpp index ecc00222b8..40d9fc3f72 100644 --- a/components/esm3/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mQuality, v.mWeight, v.mValue); + } + void Apparatus::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AADT"): - esm.getHT(mData.mType, mData.mQuality, mData.mWeight, mData.mValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); + esm.writeNamedComposite("AADT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNCString("ITEX", mIcon); } diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index 1832014173..37290ae39a 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mHealth, v.mEnchant, v.mArmor); + } void PartReferenceList::add(ESMReader& esm) { @@ -59,7 +66,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AODT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mHealth, mData.mEnchant, mData.mArmor); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -103,7 +110,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("AODT", mData, 24); + esm.writeNamedComposite("AODT", mData); esm.writeHNOCString("ITEX", mIcon); mParts.save(esm); esm.writeHNOCRefId("ENAM", mEnchant); diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 066e5ec949..8c944c6da0 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mPart, v.mVampire, v.mFlags, v.mType); + } + void BodyPart::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mRace = esm.getRefId(); break; case fourCC("BYDT"): - esm.getHT(mData.mPart, mData.mVampire, mData.mFlags, mData.mType); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -59,7 +67,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCRefId("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); + esm.writeNamedComposite("BYDT", mData); } void BodyPart::blank() diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index 8083c59828..bece59d31b 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mIsScroll, v.mSkillId, v.mEnchant); + } + void Book::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("BKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mIsScroll, mData.mSkillId, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -70,7 +78,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("BKDT", mData, 20); + esm.writeNamedComposite("BKDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOString("TEXT", mText); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 0c37e64f1e..3c651fac1a 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "esmreader.hpp" @@ -41,6 +42,18 @@ namespace ESM { const StringRefId Cell::sDefaultWorldspaceId = StringRefId("sys::default"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mFlags, v.mX, v.mY); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAmbient, v.mSunlight, v.mFog, v.mFogDensity); + } + // Some overloaded compare operators. bool operator==(const MovedCellRef& ref, const RefNum& refNum) { @@ -93,7 +106,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mFlags, mData.mX, mData.mY); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -181,7 +194,7 @@ namespace ESM void Cell::save(ESMWriter& esm, bool isDeleted) const { esm.writeHNCString("NAME", mName); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -199,7 +212,7 @@ namespace ESM if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); else if (mHasAmbi) - esm.writeHNT("AMBI", mAmbi, 16); + esm.writeNamedComposite("AMBI", mAmbi); } else { diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index ec4ff680fa..1fd22e2a49 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -2,7 +2,9 @@ #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -12,6 +14,12 @@ namespace ESM = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; const std::array Class::specializationIndexToLuaId = { "combat", "magic", "stealth" }; + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mSkills, v.mIsPlayable, v.mServices); + } + int32_t& Class::CLDTstruct::getSkill(int index, bool major) { return mSkills.at(index)[major ? 1 : 0]; @@ -42,8 +50,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CLDT"): - esm.getHT( - mData.mAttribute, mData.mSpecialization, mData.mSkills, mData.mIsPlayable, mData.mServices); + esm.getSubComposite(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; @@ -77,7 +84,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CLDT", mData, 60); + esm.writeNamedComposite("CLDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 7d60c82197..8e778243fc 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mEnchant); + } + void Clothing::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -30,7 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CTDT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -73,7 +81,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CTDT", mData, 12); + esm.writeNamedComposite("CTDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index d016654fea..9d90491448 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -100,8 +100,8 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CNDT", mWeight, 4); - esm.writeHNT("FLAG", mFlags, 4); + esm.writeHNT("CNDT", mWeight); + esm.writeHNT("FLAG", mFlags); esm.writeHNOCRefId("SCRI", mScript); diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 5a0d8048bc..83bdbd06ad 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -1,12 +1,19 @@ #include "loadcrea.hpp" #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mLevel, v.mAttributes, v.mHealth, v.mMana, v.mFatigue, v.mSoul, v.mCombat, v.mMagic, v.mStealth, + v.mAttack, v.mGold); + } void Creature::load(ESMReader& esm, bool& isDeleted) { @@ -48,8 +55,7 @@ namespace ESM mScript = esm.getRefId(); break; case fourCC("NPDT"): - esm.getHT(mData.mType, mData.mLevel, mData.mAttributes, mData.mHealth, mData.mMana, mData.mFatigue, - mData.mSoul, mData.mCombat, mData.mMagic, mData.mStealth, mData.mAttack, mData.mGold); + esm.getSubComposite(mData); hasNpdt = true; break; case fourCC("FLAG"): @@ -121,7 +127,7 @@ namespace ESM esm.writeHNOCRefId("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("NPDT", mData, 96); + esm.writeNamedComposite("NPDT", mData); esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index 1d19b690f0..9eb4fae301 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mCharge, v.mFlags); + } + void Enchantment::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -23,7 +31,7 @@ namespace ESM hasName = true; break; case fourCC("ENDT"): - esm.getHT(mData.mType, mData.mCost, mData.mCharge, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -55,7 +63,7 @@ namespace ESM return; } - esm.writeHNT("ENDT", mData, 16); + esm.writeNamedComposite("ENDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 9cff21da3e..714d59fef4 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,8 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mType, v.mDisposition, v.mRank, v.mGender, v.mPCrank, padding); + } + void DialInfo::load(ESMReader& esm, bool& isDeleted) { mId = esm.getHNRefId("INAM"); @@ -23,8 +32,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("DATA"): - esm.getHT(mData.mUnknown1, mData.mDisposition, mData.mRank, mData.mGender, mData.mPCrank, - mData.mUnknown2); + esm.getSubComposite(mData); break; case fourCC("ONAM"): mActor = esm.getRefId(); @@ -102,7 +110,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); esm.writeHNOCRefId("ONAM", mActor); esm.writeHNOCRefId("RNAM", mRace); esm.writeHNOCRefId("CNAM", mClass); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index 518e2eaa54..c2756e8d9c 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -35,7 +35,7 @@ namespace ESM struct DATAstruct { - int32_t mUnknown1 = 0; + int32_t mType = 0; // See Dialogue::Type union { int32_t mDisposition = 0; // Used for dialogue responses @@ -44,7 +44,6 @@ namespace ESM signed char mRank = -1; // Rank of NPC signed char mGender = Gender::NA; // See Gender enum signed char mPCrank = -1; // Player rank - signed char mUnknown2 = 0; }; // 12 bytes DATAstruct mData; diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 4e409ab63d..6a4753d8e4 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mEffectID, v.mSkills, v.mAttributes); + } + void Ingredient::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("IRDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mEffectID, mData.mSkills, mData.mAttributes); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -82,7 +90,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("IRDT", mData, 56); + esm.writeNamedComposite("IRDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index e22f6110c2..bb4f6bac7b 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mTime, v.mRadius, v.mColor, v.mFlags); + } + void Light::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -31,7 +39,7 @@ namespace ESM mIcon = esm.getHString(); break; case fourCC("LHDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mTime, mData.mRadius, mData.mColor, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); - esm.writeHNT("LHDT", mData, 24); + esm.writeNamedComposite("LHDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 578a8a36a7..019d6f9952 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Lockpick::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("LKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("LKDT", mData, 16); + esm.writeNamedComposite("LKDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index b38ce63294..63df1c6551 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Miscellaneous::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("MCDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("MCDT", mData, 12); + esm.writeNamedComposite("MCDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 4f0a62a9d4..ebd51dcff0 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -3,8 +3,23 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mGranularity, v.mPoints); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[2] = { 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mAutogenerated, v.mConnectionNum, padding); + } + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { mX = static_cast(rhs[0]); @@ -12,7 +27,6 @@ namespace ESM mZ = static_cast(rhs[2]); mAutogenerated = 0; mConnectionNum = 0; - mUnknown = 0; return *this; } Pathgrid::Point::Point(const float rhs[3]) @@ -21,7 +35,6 @@ namespace ESM , mZ(static_cast(rhs[2])) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } Pathgrid::Point::Point() @@ -30,7 +43,6 @@ namespace ESM , mZ(0) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } @@ -54,15 +66,14 @@ namespace ESM mCell = esm.getRefId(); break; case fourCC("DATA"): - esm.getHT(mData.mX, mData.mY, mData.mGranularity, mData.mPoints); + esm.getSubComposite(mData); hasData = true; break; case fourCC("PGRP"): { esm.getSubHeader(); uint32_t size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * path points - if (size != sizeof(Point) * mData.mPoints) + if (size != 16u * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { @@ -70,12 +81,7 @@ namespace ESM for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; - esm.getT(p.mX); - esm.getT(p.mY); - esm.getT(p.mZ); - esm.getT(p.mAutogenerated); - esm.getT(p.mConnectionNum); - esm.getT(p.mUnknown); + esm.getComposite(p); mPoints.push_back(p); edgeCount += p.mConnectionNum; } @@ -160,7 +166,7 @@ namespace ESM // Save esm.writeHNCRefId("NAME", mCell); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -173,7 +179,7 @@ namespace ESM esm.startSubRecord("PGRP"); for (const Point& point : correctedPoints) { - esm.writeT(point); + esm.writeComposite(point); } esm.endRecord("PGRP"); } diff --git a/components/esm3/loadpgrd.hpp b/components/esm3/loadpgrd.hpp index a343552efb..f2a33f9b9a 100644 --- a/components/esm3/loadpgrd.hpp +++ b/components/esm3/loadpgrd.hpp @@ -35,7 +35,6 @@ namespace ESM int32_t mX, mY, mZ; // Location of point unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point - int16_t mUnknown; Point& operator=(const float[3]); Point(const float[3]); Point(); @@ -45,7 +44,6 @@ namespace ESM , mZ(z) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } }; // 16 bytes diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 3f9ba95bf1..5e3086c7b9 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Probe::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("PBDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("PBDT", mData, 16); + esm.writeNamedComposite("PBDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index c911cb1a23..886072ab56 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mUses, v.mQuality); + } + void Repair::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RIDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mUses, mData.mQuality); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RIDT", mData, 16); + esm.writeNamedComposite("RIDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index fd53726f90..28ea3eadba 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include #include @@ -37,6 +38,12 @@ namespace ESM const SkillId Skill::Speechcraft("Speechcraft"); const SkillId Skill::HandToHand("HandToHand"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mUseValue); + } + void Skill::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) @@ -55,7 +62,7 @@ namespace ESM hasIndex = true; break; case fourCC("SKDT"): - esm.getHT(mData.mAttribute, mData.mSpecialization, mData.mUseValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("DESC"): @@ -78,7 +85,7 @@ namespace ESM void Skill::save(ESMWriter& esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", refIdToIndex(mId)); - esm.writeHNT("SKDT", mData, 24); + esm.writeNamedComposite("SKDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 4e2e2aa3f9..12a68b3afe 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -57,7 +57,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mType, 4); + esm.writeHNT("DATA", mType); esm.writeHNOCRefId("CNAM", mCreature); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index fd403e3429..6f72a49a60 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mVolume, v.mMinRange, v.mMaxRange); + } + void Sound::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -25,7 +33,7 @@ namespace ESM mSound = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mVolume, mData.mMinRange, mData.mMaxRange); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -55,7 +63,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mSound); - esm.writeHNT("DATA", mData, 3); + esm.writeNamedComposite("DATA", mData); } void Sound::blank() diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index e4f63b8219..e40c03d007 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mFlags); + } + void Spell::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -27,7 +35,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("SPDT"): - esm.getHT(mData.mType, mData.mCost, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -60,7 +68,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("SPDT", mData, 12); + esm.writeNamedComposite("SPDT", mData); mEffects.save(esm); } diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index f8905cfaea..f3017e2d0d 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -37,7 +37,7 @@ namespace ESM } mPosition = mRef.mPos; - esm.getHNOT("POS_", mPosition.pos, mPosition.rot); + esm.getOptionalComposite("POS_", mPosition); mFlags = 0; esm.getHNOT(mFlags, "FLAG"); @@ -66,10 +66,7 @@ namespace ESM if (!inInventory && mPosition != mRef.mPos) { - std::array pos; - memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); - memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); - esm.writeHNT("POS_", pos, 24); + esm.writeNamedComposite("POS_", mPosition); } if (mFlags != 0) diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index b3f7bd3d45..c947adcd97 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/luascripts.hpp" -#include "components/esm3/formatversion.hpp" +#include +#include +#include #include "animationstate.hpp" #include "cellref.hpp" diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index fd280bf12e..bf5864ce4c 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -15,7 +15,7 @@ namespace ESM esm.getHNT("LKEP", mLastKnownExteriorPosition); - mHasMark = esm.getHNOT("MARK", mMarkedPosition.pos, mMarkedPosition.rot); + mHasMark = esm.getOptionalComposite("MARK", mMarkedPosition); if (mHasMark) mMarkedCell = esm.getCellId(); @@ -90,7 +90,7 @@ namespace ESM if (mHasMark) { - esm.writeHNT("MARK", mMarkedPosition, 24); + esm.writeNamedComposite("MARK", mMarkedPosition); esm.writeCellId(mMarkedCell); } diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index 0f76a3b5eb..0cc0c22dc3 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -3,11 +3,11 @@ #include -#include "components/esm/defs.hpp" -#include "npcstate.hpp" +#include +#include -#include "components/esm/attr.hpp" #include "loadskil.hpp" +#include "npcstate.hpp" namespace ESM { diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 0dc1fb0653..212925b61d 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,10 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "../misc/algorithm.hpp" +#include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGameHour, v.mDay, v.mMonth, v.mYear); + } + void SavedGame::load(ESMReader& esm) { mPlayerName = esm.getHNString("PLNA"); @@ -19,7 +26,7 @@ namespace ESM mPlayerCellName = esm.getHNRefId("PLCE").toString(); else mPlayerCellName = esm.getHNString("PLCE"); - esm.getHNT("TSTM", mInGameTime.mGameHour, mInGameTime.mDay, mInGameTime.mMonth, mInGameTime.mYear); + esm.getNamedComposite("TSTM", mInGameTime); esm.getHNT(mTimePlayed, "TIME"); mDescription = esm.getHNString("DESC"); @@ -47,12 +54,12 @@ namespace ESM esm.writeHNString("PLCN", mPlayerClassName); esm.writeHNString("PLCE", mPlayerCellName); - esm.writeHNT("TSTM", mInGameTime, 16); + esm.writeNamedComposite("TSTM", mInGameTime); esm.writeHNT("TIME", mTimePlayed); esm.writeHNString("DESC", mDescription); - for (std::vector::const_iterator iter(mContentFiles.begin()); iter != mContentFiles.end(); ++iter) - esm.writeHNString("DEPE", *iter); + for (const std::string& dependency : mContentFiles) + esm.writeHNString("DEPE", dependency); esm.startSubRecord("SCRN"); esm.write(mScreenshot.data(), mScreenshot.size()); diff --git a/components/esm3/transport.cpp b/components/esm3/transport.cpp index 8b131b1b5f..a72cdbbaf8 100644 --- a/components/esm3/transport.cpp +++ b/components/esm3/transport.cpp @@ -13,7 +13,7 @@ namespace ESM if (esm.retSubName().toInt() == fourCC("DODT")) { Dest dodt; - esm.getHExact(&dodt.mPos, 24); + esm.getSubComposite(dodt.mPos); mList.push_back(dodt); } else if (esm.retSubName().toInt() == fourCC("DNAM")) @@ -28,11 +28,10 @@ namespace ESM void Transport::save(ESMWriter& esm) const { - typedef std::vector::const_iterator DestIter; - for (DestIter it = mList.begin(); it != mList.end(); ++it) + for (const Dest& dest : mList) { - esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); - esm.writeHNOCString("DNAM", it->mCellName); + esm.writeNamedComposite("DODT", dest.mPos); + esm.writeHNOCString("DNAM", dest.mCellName); } } diff --git a/components/esm3/transport.hpp b/components/esm3/transport.hpp index 555504c994..69bc1119c2 100644 --- a/components/esm3/transport.hpp +++ b/components/esm3/transport.hpp @@ -4,7 +4,7 @@ #include #include -#include "components/esm/defs.hpp" +#include namespace ESM { diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index 8abb47c8bc..526ab4c057 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include "reference.hpp" // Placement, EnableParent diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index ec76928827..99826200c9 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -32,6 +32,7 @@ #include "reference.hpp" // EnableParent #include +#include #include namespace ESM4 diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp index 9d6efdfd82..33e8fa82f3 100644 --- a/components/esm4/reference.hpp +++ b/components/esm4/reference.hpp @@ -30,8 +30,8 @@ #include #include -#include #include +#include namespace ESM4 { diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index a66fac9125..5d936b5d5f 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H -#include +#include #include #include diff --git a/components/resource/foreachbulletobject.hpp b/components/resource/foreachbulletobject.hpp index fe39a8ed8c..d7e99cf0b5 100644 --- a/components/resource/foreachbulletobject.hpp +++ b/components/resource/foreachbulletobject.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H #define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H -#include +#include #include #include From 1f629b13689ad040f7eb77859829b905bffc01d6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Mar 2024 22:06:39 +0300 Subject: [PATCH 1116/2167] Account for Hrnchamd's research in touch effect hit position calculation --- apps/openmw/mwworld/worldimp.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8cafb1dbe0..0468b36a2f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2939,11 +2939,12 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; // For scripted spells we should not use hit contact if (manualSpell) { - if (actor != MWMechanics::getPlayer()) + if (!casterIsPlayer) { for (const auto& package : stats.getAiSequence()) { @@ -2957,7 +2958,7 @@ namespace MWWorld } else { - if (actor == MWMechanics::getPlayer()) + if (casterIsPlayer) target = getFacedObject(); if (target.isEmpty() || !target.getClass().hasToolTip(target)) @@ -2990,12 +2991,21 @@ namespace MWWorld if (!target.isEmpty()) { // Touch explosion placement doesn't depend on where the target was "touched". - // For NPC targets, it also doesn't depend on the height. - // Using 0.75 of the collision box height seems accurate for actors and looks decent for non-actors. - // In Morrowind, touch explosion placement for non-actors is inexplicable, - // often putting the explosion way above the object. + // In Morrowind, it's at 0.7 of the actor's AABB height for actors + // or at 0.7 of the player's height for non-actors if the player is the caster + // This is probably meant to prevent the explosion from being too far above on large objects + // but it often puts the explosions way above small objects, so we'll deviate here + // and use the object's bounds when reasonable (it's $CURRENT_YEAR, we can afford that) + // Note collision object origin is intentionally not used hitPosition = target.getRefData().getPosition().asVec3(); - hitPosition.z() += mPhysics->getHalfExtents(target).z() * 2.f * Constants::TorsoHeight; + constexpr float explosionHeight = 0.7f; + float targetHeight = getHalfExtents(target).z() * 2.f; + if (!target.getClass().isActor() && casterIsPlayer) + { + const float playerHeight = getHalfExtents(actor).z() * 2.f; + targetHeight = std::min(targetHeight, playerHeight); + } + hitPosition.z() += targetHeight * explosionHeight; } const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); From fe3189557f988523b5214524f91e9378a9b5b11e Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 3 Mar 2024 21:59:06 +0000 Subject: [PATCH 1117/2167] bump macos --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1a8e22ed82..4f5933a8bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -521,13 +521,13 @@ Ubuntu_GCC_integration_tests_asan: - build/OpenMW-*.dmg - "build/**/*.log" -macOS13_Xcode14_arm64: +macOS13_Xcode15_arm64: extends: .MacOS - image: macos-12-xcode-14 + image: macos-14-xcode-15 tags: - saas-macos-medium-m1 cache: - key: macOS12_Xcode14_arm64.v4 + key: macOS14_Xcode15_arm64.v1 variables: CCACHE_SIZE: 3G From 4d52ab372c8d6f25f207f3dfcf117df37f54d8ca Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 3 Mar 2024 22:22:44 +0000 Subject: [PATCH 1118/2167] make the name more like the reality --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4f5933a8bc..2bb4193a79 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -521,7 +521,7 @@ Ubuntu_GCC_integration_tests_asan: - build/OpenMW-*.dmg - "build/**/*.log" -macOS13_Xcode15_arm64: +macOS14_Xcode15_arm64: extends: .MacOS image: macos-14-xcode-15 tags: From a130ca57a49d56c6c6125a4cfbc1b969dcccfb4c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:36:13 +0000 Subject: [PATCH 1119/2167] Track source of settings This one's a biggie. The basic idea's that GameSettings should know: * what the interpreted value of a setting is, so it can actually be used. * what the original value the user put in their config was, so it can be put back when the config's saved. * which path it's processing the openmw.cfg from so relative paths can be resolved correctly. * whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified. This had fairly wide-reaching implications. The first is that paths are resolved properly in cases where they previously wouldn't have been. Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into). That's not what the engine does, so is bad. It's also not something a user's likely to suspect. This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them. Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs. This is also fixed. Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc. This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts. Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up. One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value. --- apps/launcher/datafilespage.cpp | 150 +++++++++++++----- apps/launcher/datafilespage.hpp | 6 +- apps/launcher/importpage.cpp | 6 +- apps/launcher/maindialog.cpp | 14 +- apps/launcher/settingspage.cpp | 18 +-- apps/wizard/mainwizard.cpp | 28 ++-- components/config/gamesettings.cpp | 143 ++++++++++------- components/config/gamesettings.hpp | 82 +++++++--- components/config/launchersettings.cpp | 46 +++++- .../contentselector/model/contentmodel.cpp | 27 +++- .../contentselector/model/contentmodel.hpp | 3 +- .../contentselector/view/contentselector.cpp | 7 +- .../contentselector/view/contentselector.hpp | 1 + 13 files changed, 367 insertions(+), 164 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index de72aa2577..d87073df02 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C ui.setupUi(this); setObjectName("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true); - const QString encoding = mGameSettings.value("encoding", "win1252"); + const QString encoding = mGameSettings.value("encoding", { "win1252" }).value; mSelector->setEncoding(encoding); QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, @@ -163,11 +163,11 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); - connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); + connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); - connect( - ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); + connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories); + connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives); buildView(); loadSettings(); @@ -271,39 +271,50 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->clear(); ui.directoryListWidget->clear(); - QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); - if (directories.isEmpty()) - directories = mGameSettings.getDataDirs(); + QList directories = mGameSettings.getDataDirs(); + QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName); + if (!contentModelDirectories.isEmpty()) + { + directories.erase(std::remove_if(directories.begin(), directories.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + directories.end()); + for (const auto& dir : contentModelDirectories) + directories.push_back({ dir }); + } mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) - directories.insert(0, mDataLocal); + directories.insert(0, { mDataLocal }); const auto& resourcesVfs = mGameSettings.getResourcesVfs(); if (!resourcesVfs.isEmpty()) - directories.insert(0, resourcesVfs); + directories.insert(0, { resourcesVfs }); std::unordered_set visitedDirectories; - for (const QString& currentDir : directories) + for (const Config::SettingValue& currentDir : directories) { - // normalize user supplied directories: resolve symlink, convert to native separator, make absolute - const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath(); + // normalize user supplied directories: resolve symlink, convert to native separator + const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir.value)).canonicalPath(); if (!visitedDirectories.insert(canonicalDirPath).second) continue; // add new achives files presents in current directory - addArchivesFromDir(currentDir); + addArchivesFromDir(currentDir.value); QStringList tooltip; // add content files presents in current directory - mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); + mSelector->addFiles(currentDir.value, mNewDataDirs.contains(canonicalDirPath)); // add current directory to list - ui.directoryListWidget->addItem(currentDir); + ui.directoryListWidget->addItem(currentDir.originalRepresentation); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(currentDir)); + + if (currentDir.value != currentDir.originalRepresentation) + tooltip << tr("Resolved as %1").arg(currentDir.value); // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) @@ -316,18 +327,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) } // deactivate data-local and resources/vfs: they are always included - if (currentDir == mDataLocal || currentDir == resourcesVfs) + // same for ones from non-user config files + if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs + || !mGameSettings.isUserSetting(currentDir)) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + if (currentDir.value == mDataLocal) + tooltip << tr("This is the data-local directory and cannot be disabled"); + else if (currentDir.value == resourcesVfs) + tooltip << tr("This directory is part of OpenMW and cannot be disabled"); + else + tooltip << tr("This directory is enabled in an openmw.cfg other than the user one"); } - if (currentDir == mDataLocal) - tooltip << tr("This is the data-local directory and cannot be disabled"); - else if (currentDir == resourcesVfs) - tooltip << tr("This directory is part of OpenMW and cannot be disabled"); // Add a "data file" icon if the directory contains a content file - if (mSelector->containsDataFiles(currentDir)) + if (mSelector->containsDataFiles(currentDir.value)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); @@ -345,15 +360,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) } mSelector->sortFiles(); - QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); - if (selectedArchives.isEmpty()) - selectedArchives = mGameSettings.getArchiveList(); + QList selectedArchives = mGameSettings.getArchiveList(); + QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName); + if (contentModelSelectedArchives.isEmpty()) + { + selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + selectedArchives.end()); + for (const auto& dir : contentModelSelectedArchives) + selectedArchives.push_back({ dir }); + } // sort and tick BSA according to profile int row = 0; for (const auto& archive : selectedArchives) { - const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); + const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchExactly); if (match.isEmpty()) continue; const auto name = match[0]->text(); @@ -361,9 +383,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->takeItem(oldrow); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(archive)); + if (!mGameSettings.isUserSetting(archive)) + { + auto flags = ui.archiveListWidget->item(row)->flags(); + ui.archiveListWidget->item(row)->setFlags( + flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + ui.archiveListWidget->item(row)->setToolTip( + tr("This archive is enabled in an openmw.cfg other than the user one")); + } row++; } + QStringList nonUserContent; + for (const auto& content : mGameSettings.getContentList()) + { + if (!mGameSettings.isUserSetting(content)) + nonUserContent.push_back(content.value); + } + mSelector->setNonUserContent(nonUserContent); mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName)); } @@ -391,7 +429,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) { fileNames.append(item->fileName()); } - mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); QString language(mSelector->languageBox()->currentData().toString()); @@ -400,38 +450,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } } -QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const +QList Launcher::DataFilesPage::selectedDirectoriesPaths() const { - QStringList dirList; + QList dirList; for (int i = 0; i < ui.directoryListWidget->count(); ++i) { const QListWidgetItem* item = ui.directoryListWidget->item(i); if (item->flags() & Qt::ItemIsEnabled) - dirList.append(item->text()); + dirList.append(qvariant_cast(item->data(Qt::UserRole))); } return dirList; } -QStringList Launcher::DataFilesPage::selectedArchivePaths() const +QList Launcher::DataFilesPage::selectedArchivePaths() const { - QStringList archiveList; + QList archiveList; for (int i = 0; i < ui.archiveListWidget->count(); ++i) { const QListWidgetItem* item = ui.archiveListWidget->item(i); if (item->checkState() == Qt::Checked) - archiveList.append(item->text()); + archiveList.append(qvariant_cast(item->data(Qt::UserRole))); } return archiveList; } @@ -585,7 +635,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered() if (profile.isEmpty()) return; - mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); + const auto& dirList = selectedDirectoriesPaths(); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profile, dirNames, archiveNames, selectedFilePaths()); addProfile(profile, true); } @@ -704,6 +767,21 @@ void Launcher::DataFilesPage::sortDirectories() } } +void Launcher::DataFilesPage::sortArchives() +{ + // Ensure disabled entries (aka ones from non-user config files) are always at the top. + for (auto i = 1; i < ui.archiveListWidget->count(); ++i) + { + if (!(ui.archiveListWidget->item(i)->flags() & Qt::ItemIsEnabled) + && (ui.archiveListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) + { + const auto item = ui.archiveListWidget->takeItem(i); + ui.archiveListWidget->insertItem(i - 1, item); + ui.archiveListWidget->setCurrentRow(i); + } + } +} + void Launcher::DataFilesPage::moveDirectory(int step) { int selectedRow = ui.directoryListWidget->currentRow(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index e568137e8f..1b92354dab 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -25,6 +25,7 @@ namespace ContentSelectorView namespace Config { class GameSettings; + struct SettingValue; class LauncherSettings; } @@ -73,6 +74,7 @@ namespace Launcher void updateCloneProfileOkButton(const QString& text); void addSubdirectories(bool append); void sortDirectories(); + void sortArchives(); void removeDirectory(); void moveArchives(int step); void moveDirectory(int step); @@ -146,8 +148,8 @@ namespace Launcher * @return the file paths of all selected content files */ QStringList selectedFilePaths() const; - QStringList selectedArchivePaths() const; - QStringList selectedDirectoriesPaths() const; + QList selectedArchivePaths() const; + QList selectedDirectoriesPaths() const; }; } #endif diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp index 44c5867c0d..47075db1bc 100644 --- a/apps/launcher/importpage.cpp +++ b/apps/launcher/importpage.cpp @@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config: // Detect Morrowind configuration files QStringList iniPaths; - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - QDir dir(path); + QDir dir(path.value); dir.setPath(dir.canonicalPath()); // Resolve symlinks if (dir.exists(QString("Morrowind.ini"))) @@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked() arguments.append(QString("--fonts")); arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(mGameSettings.value(QString("encoding"), { "win1252" }).value); arguments.append(QString("--ini")); arguments.append(settingsComboBox->currentText()); arguments.append(QString("--cfg")); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index f9d07d54a5..aca8a64e31 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings() QFile file; - auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, bool), + auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, const QString&, bool), bool ignoreContent = false) -> std::optional { file.setFileName(path); if (file.exists()) @@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - (mGameSettings.*reader)(stream, ignoreContent); + (mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent); file.close(); return true; } @@ -360,12 +360,12 @@ bool Launcher::MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGameData() { - QStringList dataDirs; + bool foundData = false; // Check if the paths actually contain data files - for (const QString& path3 : mGameSettings.getDataDirs()) + for (const auto& path3 : mGameSettings.getDataDirs()) { - QDir dir(path3); + QDir dir(path3.value); QStringList filters; filters << "*.esp" << "*.esm" @@ -373,10 +373,10 @@ bool Launcher::MainDialog::setupGameData() << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) - dataDirs.append(path3); + foundData = true; } - if (dataDirs.isEmpty()) + if (!foundData) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 93a724909e..0df871c90d 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -340,7 +340,7 @@ bool Launcher::SettingsPage::loadSettings() { loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox); - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1; if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); @@ -348,8 +348,8 @@ bool Launcher::SettingsPage::loadSettings() startDefaultCharacterAtLabel->setEnabled(skipMenu); startDefaultCharacterAtField->setEnabled(skipMenu); - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + startDefaultCharacterAtField->setText(mGameSettings.value("start").value); + runScriptAfterStartupField->setText(mGameSettings.value("script-run").value); } return true; } @@ -536,17 +536,17 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor); int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + if (skipMenu != mGameSettings.value("skip-menu").value.toInt()) + mGameSettings.setValue("skip-menu", { QString::number(skipMenu) }); QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) + if (startCell != mGameSettings.value("start").value) { - mGameSettings.setValue("start", startCell); + mGameSettings.setValue("start", { startCell }); } QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); + if (scriptRun != mGameSettings.value("script-run").value) + mGameSettings.setValue("script-run", { scriptRun }); } } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e8bd6f7007..0b5cadd979 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -24,6 +24,8 @@ #include "installationpage.hpp" #endif +#include + using namespace Process; Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent) @@ -167,7 +169,7 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readUserFile(stream); + mGameSettings.readUserFile(stream, QFileInfo(path).dir().path()); } file.close(); @@ -196,7 +198,7 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readFile(stream); + mGameSettings.readFile(stream, QFileInfo(path2).dir().path()); } file.close(); } @@ -241,11 +243,11 @@ void Wizard::MainWizard::setupLauncherSettings() void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - if (findFiles(QLatin1String("Morrowind"), path)) - addInstallation(path); + if (findFiles(QLatin1String("Morrowind"), path.value)) + addInstallation(path.value); } } @@ -332,10 +334,12 @@ void Wizard::MainWizard::addInstallation(const QString& path) mInstallations.insert(QDir::toNativeSeparators(path), install); // Add it to the openmw.cfg too - if (!mGameSettings.getDataDirs().contains(path)) + const auto& dataDirs = mGameSettings.getDataDirs(); + if (std::none_of( + dataDirs.begin(), dataDirs.end(), [&](const Config::SettingValue& dir) { return dir.value == path; })) { - mGameSettings.setMultiValue(QLatin1String("data"), path); - mGameSettings.addDataDir(path); + mGameSettings.setMultiValue(QLatin1String("data"), { path }); + mGameSettings.addDataDir({ path }); } } @@ -394,15 +398,15 @@ void Wizard::MainWizard::writeSettings() if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } // Write the installation path so that openmw can find them @@ -410,7 +414,7 @@ void Wizard::MainWizard::writeSettings() // Make sure the installation path is the last data= entry mGameSettings.removeDataDir(path); - mGameSettings.addDataDir(path); + mGameSettings.addDataDir({ path }); QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath())); QDir dir(userPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 7cace721bf..9aed4656bc 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -13,7 +13,8 @@ const char Config::GameSettings::sDirectoryKey[] = "data"; namespace { - QStringList reverse(QStringList values) + template + QList reverse(QList values) { std::reverse(values.begin(), values.end()); return values; @@ -27,70 +28,69 @@ Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg) void Config::GameSettings::validatePaths() { - QStringList paths = mSettings.values(QString("data")); - Files::PathContainer dataDirs; + QList paths = mSettings.values(QString("data")); - for (const QString& path : paths) - { - dataDirs.emplace_back(Files::pathFromQString(path)); - } - - // Parse the data dirs to convert the tokenized paths - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); mDataDirs.clear(); - for (const auto& dataDir : dataDirs) + for (const auto& dataDir : paths) { - if (is_directory(dataDir)) - mDataDirs.append(Files::pathToQString(dataDir)); + if (QDir(dataDir.value).exists()) + mDataDirs.append(dataDir); } // Do the same for data-local - QString local = mSettings.value(QString("data-local")); + const QString& local = mSettings.value(QString("data-local")).value; - if (local.isEmpty()) - return; - - dataDirs.clear(); - dataDirs.emplace_back(Files::pathFromQString(local)); - - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); - - if (!dataDirs.empty()) - { - const auto& path = dataDirs.front(); - if (is_directory(path)) - mDataLocal = Files::pathToQString(path); - } + if (!local.isEmpty() && QDir(local).exists()) + mDataLocal = local; } QString Config::GameSettings::getResourcesVfs() const { - QString resources = mSettings.value(QString("resources"), QString("./resources")); + QString resources = mSettings.value(QString("resources"), { "./resources", "", "" }).value; resources += "/vfs"; return QFileInfo(resources).canonicalFilePath(); } -QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const +QList Config::GameSettings::values( + const QString& key, const QList& defaultValues) const { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool Config::GameSettings::readFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::containsValue(const QString& key, const QString& value) const { - return readFile(stream, mSettings, ignoreContent); + auto [itr, end] = mSettings.equal_range(key); + while (itr != end) + { + if (itr->value == value) + return true; + ++itr; + } + return false; } -bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::readFile(QTextStream& stream, const QString& context, bool ignoreContent) { - return readFile(stream, mUserSettings, ignoreContent); + if (readFile(stream, mSettings, context, ignoreContent)) + { + mContexts.push_back(context); + return true; + } + return false; } -bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent) +bool Config::GameSettings::readUserFile(QTextStream& stream, const QString& context, bool ignoreContent) { - QMultiMap cache; + return readFile(stream, mUserSettings, context, ignoreContent); +} + +bool Config::GameSettings::readFile( + QTextStream& stream, QMultiMap& settings, const QString& context, bool ignoreContent) +{ + QMultiMap cache; QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$"); QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -129,7 +129,7 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap values = cache.values(key); values.append(settings.values(key)); - if (!values.contains(value)) + bool exists = false; + for (const auto& existingValue : values) + { + if (existingValue.value == value.value) + { + exists = true; + break; + } + } + if (!exists) { cache.insert(key, value); } @@ -216,7 +230,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream) // Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = i.value(); + QString string = i.value().originalRepresentation; stream << delim; for (auto& it : string) @@ -231,7 +245,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream) continue; } - stream << i.key() << "=" << i.value() << "\n"; + stream << i.key() << "=" << i.value().originalRepresentation << "\n"; } return true; @@ -386,10 +400,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) *iter = QString(); // assume no match QString key = match.captured(1); QString keyVal = match.captured(1) + "=" + match.captured(2); - QMultiMap::const_iterator i = mUserSettings.find(key); + QMultiMap::const_iterator i = mUserSettings.find(key); while (i != mUserSettings.end() && i.key() == key) { - QString settingLine = i.key() + "=" + i.value(); + // todo: does this need to handle paths? + QString settingLine = i.key() + "=" + i.value().originalRepresentation; QRegularExpressionMatch keyMatch = settingRegex.match(settingLine); if (keyMatch.hasMatch()) { @@ -441,7 +456,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) // Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = it.value(); + QString string = it.value().originalRepresentation; settingLine += delim; for (auto& iter : string) @@ -453,7 +468,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) settingLine += delim; } else - settingLine = it.key() + "=" + it.value(); + settingLine = it.key() + "=" + it.value().originalRepresentation; QRegularExpressionMatch match = settingRegex.match(settingLine); if (match.hasMatch()) @@ -512,11 +527,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) bool Config::GameSettings::hasMaster() { bool result = false; - QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); + QList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { - if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) - || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) + if (content.at(i).value.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) + || content.at(i).value.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) { result = true; break; @@ -527,39 +542,49 @@ bool Config::GameSettings::hasMaster() } void Config::GameSettings::setContentList( - const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames) { auto const reset = [this](const char* key, const QStringList& list) { remove(key); for (auto const& item : list) - setMultiValue(key, item); + setMultiValue(key, { item }); }; - reset(sDirectoryKey, dirNames); - reset(sArchiveKey, archiveNames); + remove(sDirectoryKey); + for (auto const& item : dirNames) + setMultiValue(sDirectoryKey, item); + remove(sArchiveKey); + for (auto const& item : archiveNames) + setMultiValue(sArchiveKey, item); reset(sContentKey, fileNames); } -QStringList Config::GameSettings::getDataDirs() const +QList Config::GameSettings::getDataDirs() const { return reverse(mDataDirs); } -QStringList Config::GameSettings::getArchiveList() const +QList Config::GameSettings::getArchiveList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sArchiveKey)); } -QStringList Config::GameSettings::getContentList() const +QList Config::GameSettings::getContentList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sContentKey)); } +bool Config::GameSettings::isUserSetting(const SettingValue& settingValue) const +{ + return settingValue.context.isEmpty() || settingValue.context == getUserContext(); +} + void Config::GameSettings::clear() { mSettings.clear(); + mContexts.clear(); mUserSettings.clear(); mDataDirs.clear(); mDataLocal.clear(); diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index bef108e2c7..14a8fcb155 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -17,33 +17,48 @@ namespace Files namespace Config { + struct SettingValue + { + QString value = ""; + // value as found in openmw.cfg, e.g. relative path with ?slug? + QString originalRepresentation = value; + // path of openmw.cfg, e.g. to resolve relative paths + QString context = ""; + + friend auto operator<=>(const SettingValue&, const SettingValue&) = default; + }; + class GameSettings { public: explicit GameSettings(const Files::ConfigurationManager& cfg); - inline QString value(const QString& key, const QString& defaultValue = QString()) + inline SettingValue value(const QString& key, const SettingValue& defaultValue = {}) { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + return mSettings.contains(key) ? mSettings.value(key) : defaultValue; } - inline void setValue(const QString& key, const QString& value) + inline void setValue(const QString& key, const SettingValue& value) { mSettings.remove(key); mSettings.insert(key, value); mUserSettings.remove(key); - mUserSettings.insert(key, value); + if (isUserSetting(value)) + mUserSettings.insert(key, value); } - inline void setMultiValue(const QString& key, const QString& value) + inline void setMultiValue(const QString& key, const SettingValue& value) { - QStringList values = mSettings.values(key); + QList values = mSettings.values(key); if (!values.contains(value)) mSettings.insert(key, value); - values = mUserSettings.values(key); - if (!values.contains(value)) - mUserSettings.insert(key, value); + if (isUserSetting(value)) + { + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insert(key, value); + } } inline void remove(const QString& key) @@ -52,36 +67,48 @@ namespace Config mUserSettings.remove(key); } - QStringList getDataDirs() const; + QList getDataDirs() const; QString getResourcesVfs() const; - inline void removeDataDir(const QString& dir) + inline void removeDataDir(const QString& existingDir) { - if (!dir.isEmpty()) - mDataDirs.removeAll(dir); + if (!existingDir.isEmpty()) + { + // non-user settings can't be removed as we can't edit the openmw.cfg they're in + std::remove_if(mDataDirs.begin(), mDataDirs.end(), + [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }); + } } - inline void addDataDir(const QString& dir) + + inline void addDataDir(const SettingValue& dir) { - if (!dir.isEmpty()) + if (!dir.value.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() const { return mDataLocal; } bool hasMaster(); - QStringList values(const QString& key, const QStringList& defaultValues = QStringList()) const; + QList values(const QString& key, const QList& defaultValues = {}) const; + bool containsValue(const QString& key, const QString& value) const; - bool readFile(QTextStream& stream, bool ignoreContent = false); - bool readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent = false); - bool readUserFile(QTextStream& stream, bool ignoreContent = false); + bool readFile(QTextStream& stream, const QString& context, bool ignoreContent = false); + bool readFile(QTextStream& stream, QMultiMap& settings, const QString& context, + bool ignoreContent = false); + bool readUserFile(QTextStream& stream, const QString& context, bool ignoreContent = false); bool writeFile(QTextStream& stream); bool writeFileWithComments(QFile& file); - QStringList getArchiveList() const; - void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); - QStringList getContentList() const; + QList getArchiveList() const; + void setContentList( + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames); + QList getContentList() const; + + const QString& getUserContext() const { return mContexts.back(); } + bool isUserSetting(const SettingValue& settingValue) const; void clear(); @@ -89,10 +116,12 @@ namespace Config const Files::ConfigurationManager& mCfgMgr; void validatePaths(); - QMultiMap mSettings; - QMultiMap mUserSettings; + QMultiMap mSettings; + QMultiMap mUserSettings; - QStringList mDataDirs; + QStringList mContexts; + + QList mDataDirs; QString mDataLocal; static const char sArchiveKey[]; @@ -102,4 +131,7 @@ namespace Config static bool isOrderedLine(const QString& line); }; } + +Q_DECLARE_METATYPE(Config::SettingValue) + #endif // GAMESETTINGS_HPP diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 2f4decb762..f9f067e58a 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -223,9 +223,25 @@ QStringList Config::LauncherSettings::getContentLists() void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) { // obtain content list from game settings (if present) - QStringList dirs(gameSettings.getDataDirs()); - const QStringList archives(gameSettings.getArchiveList()); - const QStringList files(gameSettings.getContentList()); + QList dirs(gameSettings.getDataDirs()); + dirs.erase(std::remove_if( + dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return !gameSettings.isUserSetting(dir); }), + dirs.end()); + // archives and content files aren't preprocessed, so we don't need to track their original form + const QList archivesOriginal(gameSettings.getArchiveList()); + QStringList archives; + for (const auto& archive : archivesOriginal) + { + if (gameSettings.isUserSetting(archive)) + archives.push_back(archive.value); + } + const QList filesOriginal(gameSettings.getContentList()); + QStringList files; + for (const auto& file : filesOriginal) + { + if (gameSettings.isUserSetting(file)) + files.push_back(file.value); + } // if openmw.cfg has no content, exit so we don't create an empty content list. if (dirs.isEmpty() || files.isEmpty()) @@ -236,14 +252,25 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // local data directory and resources/vfs are not part of any profile const auto resourcesVfs = gameSettings.getResourcesVfs(); const auto dataLocal = gameSettings.getDataLocal(); - dirs.removeAll(resourcesVfs); - dirs.removeAll(dataLocal); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == resourcesVfs; }), + dirs.end()); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == dataLocal; }), + dirs.end()); // if any existing profile in launcher matches the content list, make that profile the default for (const QString& listName : getContentLists()) { - if (files == getContentListFiles(listName) && archives == getArchiveList(listName) - && dirs == getDataDirectoryList(listName)) + const auto& listDirs = getDataDirectoryList(listName); + if (dirs.length() != listDirs.length()) + continue; + for (int i = 0; i < dirs.length(); ++i) + { + if (dirs[i].value != listDirs[i]) + continue; + } + if (files == getContentListFiles(listName) && archives == getArchiveList(listName)) { setCurrentContentListName(listName); return; @@ -253,7 +280,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); - setContentList(newContentListName, dirs, archives, files); + QStringList newListDirs; + for (const auto& dir : dirs) + newListDirs.push_back(dir.value); + setContentList(newContentListName, newListDirs, archives, files); } void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 003f2ee241..66fde2063f 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -220,7 +220,8 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked : Qt::Unchecked; + return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked + : Qt::Unchecked; } case Qt::UserRole: @@ -467,6 +468,8 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) file->setBuiltIn(true); + file->setFromAnotherConfigFile(mNonUserContent.contains(info.fileName().toLower())); + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { file->setDate(info.lastModified()); @@ -660,6 +663,28 @@ void ContentSelectorModel::ContentModel::setNew(const QString& filepath, bool is mNewFiles[filepath] = isNew; } +void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fileList) +{ + mNonUserContent.clear(); + for (const auto& file : fileList) + mNonUserContent.insert(file.toLower()); + for (auto* file : mFiles) + file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower())); + + int insertPosition = 0; + while (mFiles.at(insertPosition)->builtIn()) + ++insertPosition; + + for (const auto& filepath : fileList) + { + const EsmFile* file = item(filepath); + int filePosition = indexFromItem(file).row(); + mFiles.move(filePosition, insertPosition++); + } + + sortFiles(); +} + bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const { return mPluginsWithLoadOrderError.contains(file->filePath()); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f754b9ea30..3cc05fd3cb 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -62,6 +62,7 @@ namespace ContentSelectorModel bool setCheckState(const QString& filepath, bool isChecked); bool isNew(const QString& filepath) const; void setNew(const QString& filepath, bool isChecked); + void setNonUserContent(const QStringList& fileList); void setContentList(const QStringList& fileList); ContentFileList checkedItems() const; void uncheckAll(); @@ -85,7 +86,7 @@ namespace ContentSelectorModel const EsmFile* mGameFile; ContentFileList mFiles; - QStringList mArchives; + QSet mNonUserContent; std::set mCheckedFiles; QHash mNewFiles; QSet mPluginsWithLoadOrderError; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 00c32e272d..a3fd224390 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -123,6 +123,11 @@ void ContentSelectorView::ContentSelector::buildContextMenu() mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); } +void ContentSelectorView::ContentSelector::setNonUserContent(const QStringList& fileList) +{ + mContentModel->setNonUserContent(fileList); +} + void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList) { clearCheckStates(); @@ -336,4 +341,4 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt void ContentSelectorView::ContentSelector::slotRowsMoved() { ui->addonView->selectionModel()->clearSelection(); -} \ No newline at end of file +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 2b739645ba..2fdd38c799 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,6 +40,7 @@ namespace ContentSelectorView void sortFiles(); bool containsDataFiles(const QString& path); void clearFiles(); + void setNonUserContent(const QStringList& fileList); void setProfileContent(const QStringList& fileList); void clearCheckStates(); From 1ae2cc82a1866d5178be218397bccefc0cc25793 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:46:01 +0000 Subject: [PATCH 1120/2167] I do not know how this escaped formatting locally. --- components/contentselector/model/esmfile.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 42b6cfeff6..4703df562c 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -62,16 +62,18 @@ namespace ContentSelectorModel QString toolTip() const { QString tooltip = mTooltipTemlate.arg(mAuthor) - .arg(mVersion) - .arg(mModified.toString(Qt::ISODate)) - .arg(mPath) - .arg(mDescription) - .arg(mGameFiles.join(", ")); + .arg(mVersion) + .arg(mModified.toString(Qt::ISODate)) + .arg(mPath) + .arg(mDescription) + .arg(mGameFiles.join(", ")); if (mBuiltIn) tooltip += tr("
This content file cannot be disabled because it is part of OpenMW.
"); else if (mFromAnotherConfigFile) - tooltip += tr("
This content file cannot be disabled because it is enabled in a config file other than the user one.
"); + tooltip += tr( + "
This content file cannot be disabled because it is enabled in a config file other than " + "the user one.
"); return tooltip; } From c23e5e105997209043acc8322c7cf055f40ebeb3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:47:31 +0000 Subject: [PATCH 1121/2167] I guess these aren't wired up as a dependency of the build I know the qm generation is so maybe it needs another look --- files/lang/components_de.ts | 8 ++++++++ files/lang/components_fr.ts | 8 ++++++++ files/lang/components_ru.ts | 8 ++++++++ files/lang/launcher_de.ts | 20 ++++++++++++++++++++ files/lang/launcher_fr.ts | 20 ++++++++++++++++++++ files/lang/launcher_ru.ts | 20 ++++++++++++++++++++ files/lang/wizard_de.ts | 28 ++++++++++++++-------------- files/lang/wizard_fr.ts | 28 ++++++++++++++-------------- files/lang/wizard_ru.ts | 28 ++++++++++++++-------------- 9 files changed, 126 insertions(+), 42 deletions(-) diff --git a/files/lang/components_de.ts b/files/lang/components_de.ts index 59ba558328..76b90229fc 100644 --- a/files/lang/components_de.ts +++ b/files/lang/components_de.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts index c1c70ba277..309424cda4 100644 --- a/files/lang/components_fr.ts +++ b/files/lang/components_fr.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index cca6591afe..1618961914 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> <b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 925733f9d3..6145d21d28 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3471fc6c5c..3affe3c83f 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index ec7aeccc57..4a71267eb4 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -372,6 +372,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected &Отключить выбранные + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 7bf54e90b1..4fecd1de72 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -590,59 +590,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard - - + + Error opening Wizard log file - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + Error opening OpenMW configuration file - + Quit Wizard - + Are you sure you want to exit the Wizard? - + Error creating OpenMW configuration directory - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - + + Error writing OpenMW configuration file diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 7f42087dbf..c2950c0a42 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -590,59 +590,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard - - + + Error opening Wizard log file - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + Error opening OpenMW configuration file - + Quit Wizard - + Are you sure you want to exit the Wizard? - + Error creating OpenMW configuration directory - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - + + Error writing OpenMW configuration file diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 3113774cd3..2461204681 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -592,59 +592,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard Мастер OpenMW - - + + Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - + Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - + Quit Wizard Завершить работу Мастера - + Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - + Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - + + Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW From bf24bb71b1a5e4f0259e8c3cd94227f331f4affc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 01:23:51 +0000 Subject: [PATCH 1122/2167] Explicitly use std::strong_ordering Otherwise it's ambiguous how to build <=> from <, == and > --- components/config/gamesettings.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 14a8fcb155..96e0864a9e 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -25,7 +25,7 @@ namespace Config // path of openmw.cfg, e.g. to resolve relative paths QString context = ""; - friend auto operator<=>(const SettingValue&, const SettingValue&) = default; + friend std::strong_ordering operator<=>(const SettingValue&, const SettingValue&) = default; }; class GameSettings From 7f1a6a81874a4b4272d5feefbc7316e23504f4ee Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 01:37:40 +0000 Subject: [PATCH 1123/2167] Fix file that's not used on Windows --- apps/wizard/installationpage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 60e9f3ccf9..aec64e275d 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -18,7 +18,7 @@ Wizard::InstallationPage::InstallationPage(QWidget* parent, Config::GameSettings mFinished = false; mThread = std::make_unique(); - mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").toLongLong()); + mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").value.toLongLong()); mUnshield->moveToThread(mThread.get()); connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract); From 1499dd2654fa025448a715a41f32d2d21c566105 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Mar 2024 18:16:55 +0100 Subject: [PATCH 1124/2167] Add getCompositeSize and handle NPC data --- components/esm/decompose.hpp | 7 +++ components/esm3/loadnpc.cpp | 95 ++++++++++++++++-------------------- components/esm3/loadnpc.hpp | 2 - components/esm3/loadpgrd.cpp | 2 +- 4 files changed, 51 insertions(+), 55 deletions(-) diff --git a/components/esm/decompose.hpp b/components/esm/decompose.hpp index eb6f5070d4..f9fecec067 100644 --- a/components/esm/decompose.hpp +++ b/components/esm/decompose.hpp @@ -5,6 +5,13 @@ namespace ESM { template void decompose(T&& value, const auto& apply) = delete; + + std::size_t getCompositeSize(const auto& value) + { + std::size_t result = 0; + decompose(value, [&](const auto&... args) { result = (0 + ... + sizeof(args)); }); + return result; + } } #endif diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 58a8bfa55e..03c47d4d73 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -3,8 +3,34 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + namespace + { + struct NPDTstruct12 + { + NPC::NPDTstruct52& mStruct; + }; + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding1 = 0; + char padding2 = 0; + f(v.mLevel, v.mAttributes, v.mSkills, padding1, v.mHealth, v.mMana, v.mFatigue, v.mDisposition, v.mReputation, + v.mRank, padding2, v.mGold); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[] = { 0, 0, 0 }; + f(v.mStruct.mLevel, v.mStruct.mDisposition, v.mStruct.mReputation, v.mStruct.mRank, padding, v.mStruct.mGold); + } + void NPC::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -56,37 +82,25 @@ namespace ESM case fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); - if (esm.getSubSize() == 52) + if (esm.getSubSize() == getCompositeSize(mNpdt)) { mNpdtType = NPC_DEFAULT; - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mAttributes); - esm.getT(mNpdt.mSkills); - esm.getT(mNpdt.mUnknown1); - esm.getT(mNpdt.mHealth); - esm.getT(mNpdt.mMana); - esm.getT(mNpdt.mFatigue); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.getT(mNpdt.mUnknown2); - esm.getT(mNpdt.mGold); - } - else if (esm.getSubSize() == 12) - { - mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - - // Clearing the mNdpt struct to initialize all values - blankNpdt(); - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.skip(3); - esm.getT(mNpdt.mGold); + esm.getComposite(mNpdt); } else - esm.fail("NPC_NPDT must be 12 or 52 bytes long"); + { + NPDTstruct12 data{ mNpdt }; + if (esm.getSubSize() == getCompositeSize(data)) + { + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; + + // Clearing the mNdpt struct to initialize all values + blankNpdt(); + esm.getComposite(data); + } + else + esm.fail("NPC_NPDT must be 12 or 52 bytes long"); + } break; case fourCC("FLAG"): hasFlags = true; @@ -154,32 +168,11 @@ namespace ESM if (mNpdtType == NPC_DEFAULT) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mAttributes); - esm.writeT(mNpdt.mSkills); - esm.writeT(mNpdt.mUnknown1); - esm.writeT(mNpdt.mHealth); - esm.writeT(mNpdt.mMana); - esm.writeT(mNpdt.mFatigue); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - esm.writeT(mNpdt.mUnknown2); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", mNpdt); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - constexpr char padding[] = { 0, 0, 0 }; - esm.writeT(padding); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", NPDTstruct12{ const_cast(mNpdt) }); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); @@ -238,9 +231,7 @@ namespace ESM mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; mNpdt.mDisposition = 0; - mNpdt.mUnknown1 = 0; mNpdt.mRank = 0; - mNpdt.mUnknown2 = 0; mNpdt.mGold = 0; } diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index 76930365c8..40ec0f0347 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -83,10 +83,8 @@ namespace ESM // mSkill can grow up to 200, it must be unsigned std::array mSkills; - char mUnknown1; uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; - char mUnknown2; int32_t mGold; }; // 52 bytes diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index ebd51dcff0..c438fd73eb 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -73,7 +73,7 @@ namespace ESM { esm.getSubHeader(); uint32_t size = esm.getSubSize(); - if (size != 16u * mData.mPoints) + if (size != getCompositeSize(Point{}) * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { From 312f6c90e018bc8a4a6e18576b1b6a8b1ce0b581 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 6 Mar 2024 18:13:21 +0100 Subject: [PATCH 1125/2167] Rewrite SkillProgression.skillUsed to allow directly adding xp instead of going via useType. --- files/data/builtin.omwscripts | 2 +- .../omw/mechanics/playercontroller.lua | 19 ++-- files/data/scripts/omw/skillhandlers.lua | 97 +++++++++++-------- 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 4021ef9f11..81fb76f023 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -12,6 +12,7 @@ GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua +PLAYER: scripts/omw/skillhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua MENU: scripts/omw/camera/settings.lua MENU: scripts/omw/input/settings.lua @@ -19,7 +20,6 @@ PLAYER: scripts/omw/input/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua -PLAYER: scripts/omw/skillhandlers.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 935bf5029f..333e097404 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -83,18 +83,16 @@ local function skillLevelUpHandler(skillid, source, params) if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end end -local function skillUsedHandler(skillid, useType, params) +local function skillUsedHandler(skillid, params) if NPC.isWerewolf(self) then return false end - if params.skillGain then - local skillStat = NPC.stats.skills[skillid](self) - skillStat.progress = skillStat.progress + params.skillGain + local skillStat = NPC.stats.skills[skillid](self) + skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid) - if skillStat.progress >= 1 then - I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) - end + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) end end @@ -106,14 +104,11 @@ local function onUpdate() processAutomaticDoors() end -local function onActive() - I.SkillProgression.addSkillUsedHandler(skillUsedHandler) - I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) -end +I.SkillProgression.addSkillUsedHandler(skillUsedHandler) +I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) return { engineHandlers = { onUpdate = onUpdate, - onActive = onActive, }, } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 57fc224cee..db726e8474 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -7,7 +7,7 @@ local Skill = core.stats.Skill --- -- Table of skill use types defined by morrowind. --- Each entry corresponds to an index into the available skill gain values +-- Each entry corresponds to an index into the available skill gain values -- of a @{openmw.types#SkillRecord} -- @type SkillUseType -- @field #number Armor_HitByOpponent 0 @@ -35,7 +35,7 @@ local Skill = core.stats.Skill -- @field #number Athletics_SwimOneSecond 1 --- --- Table of valid sources for skill increases +-- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. -- @type SkillLevelUpSource -- @field #string Book book -- @field #string Trainer trainer @@ -52,10 +52,16 @@ local function tableHasValue(table, value) return false end -local function getSkillProgressRequirementUnorm(npc, skillid) - local npcRecord = NPC.record(npc) +local function shallowCopy(t1) + local t2 = {} + for key, value in pairs(t1) do t2[key] = value end + return t2 +end + +local function getSkillProgressRequirement(skillid) + local npcRecord = NPC.record(self) local class = NPC.classes.record(npcRecord.class) - local skillStat = NPC.stats.skills[skillid](npc) + local skillStat = NPC.stats.skills[skillid](self) local skillRecord = Skill.record(skillid) local factor = core.getGMST('fMiscSkillBonus') @@ -72,32 +78,33 @@ local function getSkillProgressRequirementUnorm(npc, skillid) return (skillStat.base + 1) * factor end -local function skillUsed(skillid, useType, scale) + +local function skillUsed(skillid, options) if #skillUsedHandlers == 0 then -- If there are no handlers, then there won't be any effect, so skip calculations return end + + -- Make a copy so we don't change the caller's table + options = shallowCopy(options) + + -- Compute use value if it was not supplied directly + if not options.skillGain then + if not options.useType or options.useType > 3 or options.useType < 0 then + print('Error: Unknown useType: '..tostring(options.useType)) + return + end + local skillStat = NPC.stats.skills[skillid](self) + local skillRecord = Skill.record(skillid) + options.skillGain = skillRecord.skillGain[options.useType + 1] - if useType > 3 or useType < 0 then - print('Error: Unknown useType: '..tostring(useType)) - return + if options.scale then + options.skillGain = options.skillGain * options.scale + end end - -- Compute skill gain - local skillStat = NPC.stats.skills[skillid](self) - local skillRecord = Skill.record(skillid) - local skillGainUnorm = skillRecord.skillGain[useType + 1] - if scale then skillGainUnorm = skillGainUnorm * scale end - local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) - local skillGain = skillGainUnorm / skillProgressRequirementUnorm - - -- Put skill gain in a table so that handlers can modify it - local options = { - skillGain = skillGain, - } - for i = #skillUsedHandlers, 1, -1 do - if skillUsedHandlers[i](skillid, useType, options) == false then + if skillUsedHandlers[i](skillid, options) == false then return end end @@ -156,8 +163,8 @@ return { -- end) -- -- -- Scale sneak skill progression based on active invisibility effects - -- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params) - -- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then + -- I.SkillProgression.addSkillUsedHandler(function(skillid, params) + -- if skillid == 'sneak' and params.useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then -- local activeEffects = Actor.activeEffects(self) -- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100 -- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude @@ -172,9 +179,10 @@ return { -- @field [parent=#SkillProgression] #number version version = 0, - --- Add new skill level up handler for this actor + --- Add new skill level up handler for this actor. + -- For load order consistency, handlers should be added in the body if your script. -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) - -- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is + -- will be skipped. Where skillid and source are the parameters passed to @{#skillLevelUp}, and options is -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- @@ -191,14 +199,11 @@ return { skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler end, - --- Add new skillUsed handler for this actor - -- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler) - -- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed}, - -- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. - -- By default contains the single value: - -- - -- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. - -- + --- Add new skillUsed handler for this actor. + -- For load order consistency, handlers should be added in the body of your script. + -- If `handler(skillid, options)` returns false, other handlers (including the default skill progress handler) + -- will be skipped. Where options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. + -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#skillUsed}. -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) @@ -208,8 +213,19 @@ return { --- Trigger a skill use, activating relevant handlers -- @function [parent=#SkillProgression] skillUsed -- @param #string skillid The if of the skill that was used - -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} - -- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1. + -- @param options A table of parameters. Must contain one of `skillGain` or `useType`. It's best to always include `useType` if applicable, even if you set `skillGain`, as it may be used + -- by handlers to make decisions. See the addSkillUsedHandler example at the top of this page. + -- + -- * `skillGain` - The numeric amount of skill to be gained. + -- * `useType` - #SkillUseType, A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{#SkillUseType} + -- + -- And may contain the following optional parameter: + -- + -- * `scale` - A numeric value used to scale the skill gain. Ignored if the `skillGain` parameter is set. + -- + -- Note that a copy of this table is passed to skill used handlers, so any parameters passed to this method will also be passed to the handlers. This can be used to provide additional information to + -- custom handlers when making custom skill progressions. + -- skillUsed = skillUsed, --- @{#SkillUseType} @@ -256,11 +272,16 @@ return { Usage = 'usage', Trainer = 'trainer', }, + + --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. + -- @function [parent=#SkillProgression] getSkillProgressRequirement + -- @param #string skillid The id of the skill to compute skill progress requirement for + getSkillProgressRequirement = getSkillProgressRequirement }, engineHandlers = { -- Use the interface in these handlers so any overrides will receive the calls. _onSkillUse = function (skillid, useType, scale) - I.SkillProgression.skillUsed(skillid, useType, scale) + I.SkillProgression.skillUsed(skillid, {useType = useType, scale = scale}) end, _onSkillLevelUp = function (skillid, source) I.SkillProgression.skillLevelUp(skillid, source) From 5acfb0785031f3631ca59c4a5daffe533bce926f Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 6 Mar 2024 20:51:48 +0100 Subject: [PATCH 1126/2167] Fix build with OSG_USE_UTF8_FILENAME --- components/misc/osgpluginchecker.cpp.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index b570c8f858..f519447752 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -41,7 +41,7 @@ namespace Misc for (const auto& path : filepath) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath{ stringToU8String(path) }; + std::filesystem::path osgPath{ StringUtils::stringToU8String(path) }; #else std::filesystem::path osgPath{ path }; #endif @@ -52,7 +52,7 @@ namespace Misc { osgPath = osgPath.parent_path(); #ifdef OSG_USE_UTF8_FILENAME - std::string extraPath = u8StringToString(osgPath.u8string_view()); + std::string extraPath = StringUtils::u8StringToString(osgPath.u8string()); #else std::string extraPath = osgPath.string(); #endif @@ -67,7 +67,7 @@ namespace Misc if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath{ stringToU8String(availablePlugin) }; + std::filesystem::path pluginPath{ StringUtils::stringToU8String(availablePlugin) }; #else std::filesystem::path pluginPath {availablePlugin}; #endif From 7a5493796fa098af5deeca07befc658ffe08db8e Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 1 Mar 2024 22:57:41 +0100 Subject: [PATCH 1127/2167] Update setting page elements when possible --- files/data/scripts/omw/settings/menu.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 704b29f032..2246b5ad7e 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -329,10 +329,14 @@ local function renderPage(page, options) bigSpacer, }, } - if options.element then options.element:destroy() end options.name = l10n(page.name) - options.element = ui.create(layout) options.searchHints = generateSearchHints(page) + if options.element then + options.element.layout = layout + options.element:update() + else + options.element = ui.create(layout) + end end local function onSettingChanged(global) @@ -461,9 +465,6 @@ local function registerPage(options) } pages[page.key] = page groups[page.key] = groups[page.key] or {} - if pageOptions[page.key] then - pageOptions[page.key].element:destroy() - end pageOptions[page.key] = pageOptions[page.key] or {} renderPage(page, pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key]) From 9ae61f19322c711acec37e51af83b45e72b5990c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 6 Mar 2024 22:01:26 +0100 Subject: [PATCH 1128/2167] Fix child UI Elements created in the same frame as parent --- apps/openmw/mwlua/uibindings.cpp | 13 +++++---- components/lua_ui/element.cpp | 46 ++++++++++++++++++-------------- components/lua_ui/element.hpp | 14 +++++++--- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index f21fdb337a..a8df03ba25 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -134,7 +134,10 @@ namespace MWLua }; api["updateAll"] = [luaManager = context.mLuaManager, menu]() { - LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; }); + LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { + if (e->mState == LuaUi::Element::Created) + e->mState = LuaUi::Element::Update; + }); luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); }, "Update all menu UI elements"); }; @@ -305,15 +308,15 @@ namespace MWLua element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy || element->mUpdate) + if (element->mState != LuaUi::Element::Created) return; - element->mUpdate = true; + element->mState = LuaUi::Element::Update; luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); }; element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy) + if (element->mState == LuaUi::Element::Destroyed) return; - element->mDestroy = true; + element->mState = LuaUi::Element::Destroy; luaManager->addAction( [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); }; diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 5a54cd91b5..6e7fe9ee16 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -89,12 +89,16 @@ namespace LuaUi root->updateCoord(); } - WidgetExtension* pluckElementRoot(const sol::object& child) + WidgetExtension* pluckElementRoot(const sol::object& child, uint64_t depth) { std::shared_ptr element = child.as>(); - WidgetExtension* root = element->mRoot; - if (!root) + if (element->mState == Element::Destroyed || element->mState == Element::Destroy) throw std::logic_error("Using a destroyed element as a layout child"); + // child Element was created in the same frame and its action hasn't been processed yet + if (element->mState == Element::New) + element->create(depth + 1); + WidgetExtension* root = element->mRoot; + assert(root); WidgetExtension* parent = root->getParent(); if (parent) { @@ -107,7 +111,7 @@ namespace LuaUi return root; } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); std::vector updateContent( @@ -130,7 +134,7 @@ namespace LuaUi sol::object child = content.at(i); if (child.is()) { - WidgetExtension* root = pluckElementRoot(child); + WidgetExtension* root = pluckElementRoot(child, depth); if (ext != root) destroyChild(ext); result[i] = root; @@ -145,7 +149,7 @@ namespace LuaUi else { destroyChild(ext); - ext = createWidget(newLayout, depth); + ext = createWidget(newLayout, false, depth); } result[i] = ext; } @@ -156,9 +160,9 @@ namespace LuaUi { sol::object child = content.at(i); if (child.is()) - result[i] = pluckElementRoot(child); + result[i] = pluckElementRoot(child, depth); else - result[i] = createWidget(child.as(), depth); + result[i] = createWidget(child.as(), false, depth); } return result; } @@ -191,7 +195,7 @@ namespace LuaUi }); } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth) + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth) { static auto widgetTypeMap = widgetTypeToName(); std::string type = widgetType(layout); @@ -205,7 +209,7 @@ namespace LuaUi WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget, depth == 0); + ext->initialize(layout.lua_state(), widget, isRoot); updateWidget(ext, layout, depth); return ext; @@ -247,8 +251,7 @@ namespace LuaUi : mRoot(nullptr) , mLayout(std::move(layout)) , mLayer() - , mUpdate(false) - , mDestroy(false) + , mState(Element::New) { } @@ -267,12 +270,12 @@ namespace LuaUi sGameElements.erase(element); } - void Element::create() + void Element::create(uint64_t depth) { assert(!mRoot); - if (!mRoot) + if (mState == New) { - mRoot = createWidget(layout(), 0); + mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); } @@ -280,15 +283,16 @@ namespace LuaUi void Element::update() { - if (mRoot && mUpdate) + if (mState == Update) { + assert(mRoot); if (mRoot->widget()->getTypeName() != widgetType(layout())) { destroyRoot(mRoot); WidgetExtension* parent = mRoot->getParent(); auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); - mRoot = createWidget(layout(), 0); + mRoot = createWidget(layout(), true, 0); assert(it != children.end()); *it = mRoot; parent->setChildren(children); @@ -301,16 +305,18 @@ namespace LuaUi mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); } - mUpdate = false; + mState = Created; } void Element::destroy() { - if (mRoot) + if (mState != Destroyed) { destroyRoot(mRoot); mRoot = nullptr; - mLayout = sol::make_object(mLayout.lua_state(), sol::nil); + if (mState != New) + mLayout = sol::make_object(mLayout.lua_state(), sol::nil); + mState = Destroyed; } } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 4398a769df..39a1fdd769 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -21,10 +21,18 @@ namespace LuaUi WidgetExtension* mRoot; sol::object mLayout; std::string mLayer; - bool mUpdate; - bool mDestroy; - void create(); + enum State + { + New, + Created, + Update, + Destroy, + Destroyed, + }; + State mState; + + void create(uint64_t dept = 0); void update(); From a11e553de4000052e5f03ef9acbe360f460d9581 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 6 Mar 2024 22:23:07 +0100 Subject: [PATCH 1129/2167] Optimize setting group rendering by rendering them as separate elements, support element-rendered setting renderers --- files/data/scripts/omw/settings/menu.lua | 70 +++++++++++++++++------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 2246b5ad7e..7d425f684b 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -6,8 +6,11 @@ local core = require('openmw.core') local storage = require('openmw.storage') local I = require('openmw.interfaces') +local auxUi = require('openmw_aux.ui') + local common = require('scripts.omw.settings.common') --- :reset on startup instead of :removeOnExit +common.getSection(false, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.GameSession) +-- need to :reset() on reloadlua as well as game session end common.getSection(false, common.groupSectionKey):reset() local renderers = {} @@ -21,6 +24,7 @@ local interfaceL10n = core.l10n('Interface') local pages = {} local groups = {} local pageOptions = {} +local groupElements = {} local interval = { template = I.MWUI.templates.interval } local growingIntreval = { @@ -116,6 +120,11 @@ local function renderSetting(group, setting, value, global) } end local argument = common.getArgumentSection(global, group.key):get(setting.key) + local ok, rendererResult = pcall(renderFunction, value, set, argument) + if not ok then + print(string.format('Setting %s renderer "%s" error: %s', setting.key, setting.renderer, rendererResult)) + end + return { name = setting.key, type = ui.TYPE.Flex, @@ -129,7 +138,7 @@ local function renderSetting(group, setting, value, global) content = ui.content { titleLayout, growingIntreval, - renderFunction(value, set, argument), + ok and rendererResult or {}, -- TODO: display error? }, } end @@ -245,10 +254,12 @@ end local function generateSearchHints(page) local hints = {} - local l10n = core.l10n(page.l10n) - table.insert(hints, l10n(page.name)) - if page.description then - table.insert(hints, l10n(page.description)) + do + local l10n = core.l10n(page.l10n) + table.insert(hints, l10n(page.name)) + if page.description then + table.insert(hints, l10n(page.description)) + end end local pageGroups = groups[page.key] for _, pageGroup in pairs(pageGroups) do @@ -281,7 +292,15 @@ local function renderPage(page, options) if not group then error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key)) end - table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + local groupElement = groupElements[page.key][group.key] + if not groupElement or not groupElement.layout then + groupElement = ui.create(renderGroup(group, pageGroup.global)) + end + if groupElement.layout == nil then + error(string.format('Destroyed group element for %s %s', page.key, group.key)) + end + groupElements[page.key][group.key] = groupElement + table.insert(groupLayouts, groupElement) end local groupsLayout = { name = 'groups', @@ -344,18 +363,23 @@ local function onSettingChanged(global) local group = common.getSection(global, common.groupSectionKey):get(groupKey) if not group or not pageOptions[group.page] then return end + local groupElement = groupElements[group.page][group.key] + if not settingKey then - renderPage(pages[group.page], pageOptions[group.page]) + if groupElement then + groupElement.layout = renderGroup(group) + groupElement:update() + else + renderPage(pages[group.page], pageOptions[group.page]) + end return end local value = common.getSection(global, group.key):get(settingKey) - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) - element:update() + local settingsContent = groupElement.layout.content.settings.content + auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + groupElement:update() end) end @@ -364,6 +388,8 @@ local function onGroupRegistered(global, key) if not group then return end groups[group.page] = groups[group.page] or {} + groupElements[group.page] = groupElements[group.page] or {} + local pageGroup = { key = group.key, global = global, @@ -380,11 +406,9 @@ local function onGroupRegistered(global, key) local value = common.getSection(global, group.key):get(settingKey) - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + local element = groupElements[group.page][group.key] + local settingsContent = element.layout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) element:update() end)) end @@ -422,6 +446,11 @@ local function resetPlayerGroups() for pageKey, page in pairs(groups) do for groupKey in pairs(page) do if not menuGroups[groupKey] then + if groupElements[pageKey][groupKey] then + groupElements[pageKey][groupKey]:destroy() + print(string.format('destroyed group element %s %s', pageKey, groupKey)) + groupElements[pageKey][groupKey] = nil + end page[groupKey] = nil playerGroupsSection:set(groupKey, nil) end @@ -430,7 +459,8 @@ local function resetPlayerGroups() if options then if not menuPages[pageKey] then if options.element then - options.element:destroy() + auxUi.deepDestroy(options.element) + options.element = nil end ui.removeSettingsPage(options) pageOptions[pageKey] = nil From d8c74e1d6267cc3af3885ea03f2fea18bf20ec26 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 6 Mar 2024 23:22:03 +0000 Subject: [PATCH 1130/2167] conf.py: Set navigation_with_keys to allow navigating documentation through arrow keys --- docs/source/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 902e84c393..1dca7374e5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,7 +143,9 @@ html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +html_theme_options = { + 'navigation_with_keys': True +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] From ed23f487543181c70960bad5421740d131a4b729 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 23:44:24 +0000 Subject: [PATCH 1131/2167] Actually erase the things we're removing Caused by bad copy and paste --- components/config/gamesettings.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 96e0864a9e..02cf9f9b8b 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -76,8 +76,7 @@ namespace Config if (!existingDir.isEmpty()) { // non-user settings can't be removed as we can't edit the openmw.cfg they're in - std::remove_if(mDataDirs.begin(), mDataDirs.end(), - [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }); + mDataDirs.erase(std::remove_if(mDataDirs.begin(), mDataDirs.end(), [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), mDataDirs.end()); } } From 243b5b6666f9ccdc024f90a7f4a933b8d1e87e8e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 23:52:16 +0000 Subject: [PATCH 1132/2167] Hopefully convince the old MSVC version on GitLab CI to work The old code was legal, and the things it did worked in other places, so should have worked here, too. Hopefully just rearranging stuff convinces what I assume to be a compiler bug to not happen. --- components/config/gamesettings.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 9aed4656bc..976d5e20f2 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -544,19 +544,15 @@ bool Config::GameSettings::hasMaster() void Config::GameSettings::setContentList( const QList& dirNames, const QList& archiveNames, const QStringList& fileNames) { - auto const reset = [this](const char* key, const QStringList& list) { - remove(key); - for (auto const& item : list) - setMultiValue(key, { item }); - }; - remove(sDirectoryKey); for (auto const& item : dirNames) setMultiValue(sDirectoryKey, item); remove(sArchiveKey); for (auto const& item : archiveNames) setMultiValue(sArchiveKey, item); - reset(sContentKey, fileNames); + remove(sContentKey); + for (auto const& item : fileNames) + setMultiValue(sContentKey, { item }); } QList Config::GameSettings::getDataDirs() const From 36f5c819bbb228becb50c57e2ede1949d27ded5f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Mar 2024 01:48:16 +0000 Subject: [PATCH 1133/2167] capitulate --- components/config/gamesettings.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 02cf9f9b8b..d23f225eb0 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -76,7 +76,10 @@ namespace Config if (!existingDir.isEmpty()) { // non-user settings can't be removed as we can't edit the openmw.cfg they're in - mDataDirs.erase(std::remove_if(mDataDirs.begin(), mDataDirs.end(), [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), mDataDirs.end()); + mDataDirs.erase( + std::remove_if(mDataDirs.begin(), mDataDirs.end(), + [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), + mDataDirs.end()); } } From af8c2a94dfbc9292f014587eb8c2031a13a62a2c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 21:50:19 +0000 Subject: [PATCH 1134/2167] Fix: hardcoded weather meshes, use settings instead Signed-off-by: Sam Hellawell --- apps/openmw/mwworld/weather.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 36b5958dc3..58fea640f6 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -45,7 +46,7 @@ namespace MWWorld osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); - if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") + if (particleEffect == Settings::models().mWeatherashcloud.get() || particleEffect == Settings::models().mWeatherblightcloud.get()) { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; @@ -581,10 +582,10 @@ namespace MWWorld addWeather("Overcast", 0.7f, 0.0f); // 3 addWeather("Rain", 0.5f, 10.0f); // 4 addWeather("Thunderstorm", 0.5f, 20.0f); // 5 - addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 - addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 + addWeather("Ashstorm", 0.2f, 50.0f, Settings::models().mWeatherashcloud.get()); // 6 + addWeather("Blight", 0.2f, 60.0f, Settings::models().mWeatherblightcloud.get()); // 7 + addWeather("Snow", 0.5f, 40.0f, Settings::models().mWeathersnow.get()); // 8 + addWeather("Blizzard", 0.16f, 70.0f, Settings::models().mWeatherblizzard.get()); // 9 Store::iterator it = store.get().begin(); for (; it != store.get().end(); ++it) @@ -720,7 +721,7 @@ namespace MWWorld // For some reason Ash Storm is not considered as a precipitation weather in game mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) - && mResult.mParticleEffect != "meshes\\ashcloud.nif"; + && mResult.mParticleEffect != Settings::models().mWeatherashcloud.get(); mStormDirection = calculateStormDirection(mResult.mParticleEffect); mRendering.getSkyManager()->setStormParticleDirection(mStormDirection); From f28b3f660148ae73ddc2b3f7293b3ea60c8b466f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 21:51:48 +0000 Subject: [PATCH 1135/2167] Style tweak Signed-off-by: Sam Hellawell --- apps/openmw/mwworld/weather.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 58fea640f6..6cca2a8cc1 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,8 +1,9 @@ #include "weather.hpp" +#include + #include -#include #include #include #include From bf7819f71d9a75b8b2b532dc930d7984fb6d514a Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 22:06:09 +0000 Subject: [PATCH 1136/2167] fix clang format --- apps/openmw/mwworld/weather.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6cca2a8cc1..4f6f52a81a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -47,7 +47,8 @@ namespace MWWorld osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); - if (particleEffect == Settings::models().mWeatherashcloud.get() || particleEffect == Settings::models().mWeatherblightcloud.get()) + if (particleEffect == Settings::models().mWeatherashcloud.get() + || particleEffect == Settings::models().mWeatherblightcloud.get()) { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; From c6ee01b0bee074598e3acd86e2b14d4b7f8a27d3 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 7 Mar 2024 04:49:48 +0000 Subject: [PATCH 1137/2167] Apply fix to sky manager Signed-off-by: Sam Hellawell --- apps/openmw/mwrender/sky.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 9c8b0658a9..231f90fd78 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -528,7 +528,7 @@ namespace MWRender if (hasRain()) return mRainRipplesEnabled; - if (mParticleNode && mCurrentParticleEffect == "meshes\\snow.nif") + if (mParticleNode && mCurrentParticleEffect == Settings::models().mWeathersnow.get()) return mSnowRipplesEnabled; return false; @@ -554,7 +554,7 @@ namespace MWRender osg::Quat quat; quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == "meshes\\blizzard.nif") + if (mCurrentParticleEffect == Settings::models().mWeatherblizzard.get()) quat.makeRotate(osg::Vec3f(-1, 0, 0), mStormParticleDirection); mParticleNode->setAttitude(quat); } @@ -726,7 +726,7 @@ namespace MWRender const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); const bool occlusionEnabledForEffect - = !mRainEffect.empty() || mCurrentParticleEffect == "meshes\\snow.nif"; + = !mRainEffect.empty() || mCurrentParticleEffect == Settings::models().mWeathersnow.get(); for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { From 5dcac4c48f54e5666df8774f7d685abd7d8a75ce Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 7 Mar 2024 15:43:35 +0400 Subject: [PATCH 1138/2167] Do not treat Alt-Tab as resolution change (bug 7866) --- CHANGELOG.md | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 3 +++ components/sdlutil/sdlinputwrapper.cpp | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953801b345..ddbab574d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7866: Alt-tabbing is considered as a resolution change Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e463443b0c..4a87b38324 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1172,6 +1172,9 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { + if (x == Settings::video().mResolutionX && y == Settings::video().mResolutionY) + return; + Settings::video().mResolutionX.set(x); Settings::video().mResolutionY.set(y); diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index cc9706732e..43de84bb70 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -252,6 +252,11 @@ namespace SDLUtil SDL_GL_GetDrawableSize(mSDLWindow, &w, &h); int x, y; SDL_GetWindowPosition(mSDLWindow, &x, &y); + + // Happens when you Alt-Tab out of game + if (w == 0 && h == 0) + return; + mViewer->getCamera()->getGraphicsContext()->resized(x, y, w, h); mViewer->getEventQueue()->windowResize(x, y, w, h); From b055367b3b5ac2a837ac4699809412eb258c0b22 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 7 Mar 2024 21:36:21 +0100 Subject: [PATCH 1139/2167] Track map position using MWWorld::Cell --- apps/openmw/mwbase/windowmanager.hpp | 3 - apps/openmw/mwgui/mapwindow.cpp | 92 ++++++++++++++------------ apps/openmw/mwgui/mapwindow.hpp | 9 +-- apps/openmw/mwgui/windowmanagerimp.cpp | 16 ++--- apps/openmw/mwgui/windowmanagerimp.hpp | 7 +- apps/openmw/mwworld/cell.cpp | 2 + 6 files changed, 63 insertions(+), 66 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index a7859ad9e6..c252e0c490 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -202,9 +202,6 @@ namespace MWBase virtual bool getFullHelp() const = 0; - virtual void setActiveMap(int x, int y, bool interior) = 0; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible) = 0; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index cb6ba79f9e..ae6da7766f 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -95,6 +95,13 @@ namespace return std::clamp( viewingDistanceInCells, Constants::CellGridRadius, Settings::map().mMaxLocalViewingDistance.get()); } + + ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell* cell, int x, int y) + { + if (cell->isExterior()) + return ESM::Cell::generateIdForCell(true, {}, x, y); + return cell->getId(); + } } namespace MWGui @@ -170,12 +177,9 @@ namespace MWGui LocalMapBase::LocalMapBase( CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) - , mCurX(0) - , mCurY(0) - , mInterior(false) + , mActiveCell(nullptr) , mLocalMap(nullptr) , mCompass(nullptr) - , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mNumCells(1) @@ -231,12 +235,6 @@ namespace MWGui } } - void LocalMapBase::setCellPrefix(const std::string& prefix) - { - mPrefix = prefix; - mChanged = true; - } - bool LocalMapBase::toggleFogOfWar() { mFogOfWarToggled = !mFogOfWarToggled; @@ -262,8 +260,9 @@ namespace MWGui { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); - return MyGUI::IntPoint(std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), - std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize)); + return MyGUI::IntPoint( + std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mActiveCell->getGridX())) * mapWidgetSize), + std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mActiveCell->getGridY())) * mapWidgetSize)); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const @@ -272,7 +271,7 @@ namespace MWGui // normalized cell coordinates float nX, nY; - if (!mInterior) + if (mActiveCell->isExterior()) { ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(worldX, worldY); cellIndex.x() = cellPos.mX; @@ -336,7 +335,7 @@ namespace MWGui std::vector& LocalMapBase::currentDoorMarkersWidgets() { - return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; + return mActiveCell->isExterior() ? mExteriorDoorMarkerWidgets : mInteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() @@ -344,12 +343,14 @@ namespace MWGui for (MyGUI::Widget* widget : mCustomMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); - + if (!mActiveCell) + return; for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { - ESM::RefId cellRefId = ESM::Cell::generateIdForCell(!mInterior, mPrefix, mCurX + dX, mCurY + dY); + ESM::RefId cellRefId + = getCellIdInWorldSpace(mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; @@ -377,16 +378,25 @@ namespace MWGui redraw(); } - void LocalMapBase::setActiveCell(const int x, const int y, bool interior) + void LocalMapBase::setActiveCell(const MWWorld::Cell& cell) { - if (x == mCurX && y == mCurY && mInterior == interior && !mChanged) + if (&cell == mActiveCell) return; // don't do anything if we're still in the same cell - if (!interior && !(x == mCurX && y == mCurY)) + const int x = cell.getGridX(); + const int y = cell.getGridY(); + + if (cell.isExterior()) { - const MyGUI::IntRect intersection - = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, - std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; + int curX = 0; + int curY = 0; + if (mActiveCell) + { + curX = mActiveCell->getGridX(); + curY = mActiveCell->getGridY(); + } + const MyGUI::IntRect intersection = { std::max(x, curX) - mCellDistance, std::max(y, curY) - mCellDistance, + std::min(x, curX) + mCellDistance, std::min(y, curY) + mCellDistance }; const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); @@ -407,17 +417,14 @@ namespace MWGui for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); - for (auto const& cell : mMaps) + for (auto const& entry : mMaps) { - if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) - mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); + if (mHasALastActiveCell && !intersection.inside({ entry.mCellX, entry.mCellY })) + mLocalMapRender->removeExteriorCell(entry.mCellX, entry.mCellY); } } - mCurX = x; - mCurY = y; - mInterior = interior; - mChanged = false; + mActiveCell = &cell; for (int mx = 0; mx < mNumCells; ++mx) { @@ -441,7 +448,7 @@ namespace MWGui for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); - if (!mInterior) + if (mActiveCell->isExterior()) mHasALastActiveCell = true; updateMagicMarkers(); @@ -580,7 +587,7 @@ namespace MWGui if (!entry.mMapTexture) { - if (!mInterior) + if (mActiveCell->isExterior()) requestMapRender(&MWBase::Environment::get().getWorldModel()->getExterior( ESM::ExteriorCellLocation(entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId))); @@ -626,12 +633,12 @@ namespace MWGui mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); mInteriorDoorMarkerWidgets.clear(); - if (mInterior) + if (!mActiveCell->isExterior()) { for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) widget->setVisible(false); - MWWorld::CellStore& cell = worldModel->getInterior(mPrefix); + MWWorld::CellStore& cell = worldModel->getInterior(mActiveCell->getNameId()); world->getDoorMarkers(cell, doors); } else @@ -678,7 +685,7 @@ namespace MWGui } currentDoorMarkersWidgets().push_back(markerWidget); - if (!mInterior) + if (mActiveCell->isExterior()) mExteriorDoorsByCell[{ data->cellX, data->cellY }].push_back(markerWidget); } @@ -701,8 +708,7 @@ namespace MWGui MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell && markedCell->isExterior() == !mInterior - && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->getNameId(), mPrefix))) + if (markedCell && markedCell->getCell()->getWorldSpace() == mActiveCell->getWorldSpace()) { MarkerUserData markerPos(mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", @@ -870,11 +876,11 @@ namespace MWGui int y = (int(widgetPos.top / float(mapWidgetSize)) - mCellDistance) * -1; float nX = widgetPos.left / float(mapWidgetSize) - int(widgetPos.left / float(mapWidgetSize)); float nY = widgetPos.top / float(mapWidgetSize) - int(widgetPos.top / float(mapWidgetSize)); - x += mCurX; - y += mCurY; + x += mActiveCell->getGridX(); + y += mActiveCell->getGridY(); osg::Vec2f worldPos; - if (mInterior) + if (!mActiveCell->isExterior()) { worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } @@ -886,7 +892,7 @@ namespace MWGui mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); - ESM::RefId clickedId = ESM::Cell::generateIdForCell(!mInterior, LocalMapBase::mPrefix, x, y); + ESM::RefId clickedId = getCellIdInWorldSpace(mActiveCell, x, y); mEditingMarker.mCell = clickedId; @@ -977,7 +983,7 @@ namespace MWGui resizeGlobalMap(); float x = mCurPos.x(), y = mCurPos.y(); - if (mInterior) + if (!mActiveCell->isExterior()) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); x = pos.x(); @@ -1020,7 +1026,7 @@ namespace MWGui resizeGlobalMap(); } - MapWindow::~MapWindow() {} + MapWindow::~MapWindow() = default; void MapWindow::setCellName(const std::string& cellName) { @@ -1289,7 +1295,7 @@ namespace MWGui mMarkers.clear(); mGlobalMapRender->clear(); - mChanged = true; + mActiveCell = nullptr; for (auto& widgetPair : mGlobalMapMarkers) MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 29759a4365..8066256437 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -27,6 +27,7 @@ namespace ESM namespace MWWorld { + class Cell; class CellStore; } @@ -77,8 +78,7 @@ namespace MWGui virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); - void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior = false); + void setActiveCell(const MWWorld::Cell& cell); void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); @@ -115,15 +115,12 @@ namespace MWGui float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; - int mCurX, mCurY; // the position of the active cell on the global map (in cell coords) + const MWWorld::Cell* mActiveCell; bool mHasALastActiveCell = false; osg::Vec2f mCurPos; // the position of the player in the world (in cell coords) - bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; - std::string mPrefix; - bool mChanged; bool mFogOfWarToggled; bool mFogOfWarEnabled; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e463443b0c..29b9cb0e84 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -844,7 +844,7 @@ namespace MWGui if (!player.getCell()->isExterior()) { - setActiveMap(x, y, true); + setActiveMap(*player.getCell()->getCell()); } // else: need to know the current grid center, call setActiveMap from changeCell @@ -982,29 +982,23 @@ namespace MWGui mMap->addVisitedLocation(name, cellCommon->getGridX(), cellCommon->getGridY()); mMap->cellExplored(cellCommon->getGridX(), cellCommon->getGridY()); - - setActiveMap(cellCommon->getGridX(), cellCommon->getGridY(), false); } else { - mMap->setCellPrefix(std::string(cellCommon->getNameId())); - mHud->setCellPrefix(std::string(cellCommon->getNameId())); - osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); - - setActiveMap(0, 0, true); } + setActiveMap(*cellCommon); } - void WindowManager::setActiveMap(int x, int y, bool interior) + void WindowManager::setActiveMap(const MWWorld::Cell& cell) { - mMap->setActiveCell(x, y, interior); - mHud->setActiveCell(x, y, interior); + mMap->setActiveCell(cell); + mHud->setActiveCell(cell); } void WindowManager::setDrowningBarVisibility(bool visible) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index d6a286632c..3445ebdb9a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -50,6 +50,7 @@ namespace MyGUI namespace MWWorld { + class Cell; class ESMStore; } @@ -216,9 +217,6 @@ namespace MWGui bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) bool getFullHelp() const override; - void setActiveMap(int x, int y, bool interior) override; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar void setDrowningBarVisibility(bool visible) override; @@ -589,6 +587,9 @@ namespace MWGui void setCullMask(uint32_t mask) override; uint32_t getCullMask() override; + void setActiveMap(const MWWorld::Cell& cell); + ///< set the indices of the map texture that should be used + Files::ConfigurationManager& mCfgMgr; }; } diff --git a/apps/openmw/mwworld/cell.cpp b/apps/openmw/mwworld/cell.cpp index 56afc104cf..1bd9761f72 100644 --- a/apps/openmw/mwworld/cell.cpp +++ b/apps/openmw/mwworld/cell.cpp @@ -100,6 +100,8 @@ namespace MWWorld mWaterHeight = -1.f; mHasWater = true; } + else + mGridPos = {}; } ESM::RefId Cell::getWorldSpace() const From 5859fd464cc123e7c813268f26b99505017ea482 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Mar 2024 01:22:42 +0100 Subject: [PATCH 1140/2167] Add option to disable precompiled headers To be able to use ccache. Also fix compilation errors appeared due to absence of precompiled headers. --- CMakeLists.txt | 1 + apps/benchmarks/detournavigator/CMakeLists.txt | 2 +- apps/benchmarks/esm/CMakeLists.txt | 2 +- apps/benchmarks/settings/CMakeLists.txt | 2 +- apps/bsatool/CMakeLists.txt | 2 +- apps/bulletobjecttool/CMakeLists.txt | 2 +- apps/esmtool/CMakeLists.txt | 2 +- apps/essimporter/CMakeLists.txt | 2 +- apps/launcher/CMakeLists.txt | 2 +- apps/mwiniimporter/CMakeLists.txt | 2 +- apps/navmeshtool/CMakeLists.txt | 2 +- apps/niftest/CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 2 +- apps/opencs_tests/CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw_test_suite/CMakeLists.txt | 2 +- components/CMakeLists.txt | 2 +- components/detournavigator/commulativeaabb.hpp | 2 +- components/esm/esm3exteriorcellrefid.hpp | 2 +- components/esm/format.cpp | 1 + components/esm/generatedrefid.hpp | 1 + components/platform/platform.cpp | 5 ++++- extern/Base64/CMakeLists.txt | 2 +- extern/CMakeLists.txt | 2 +- extern/oics/CMakeLists.txt | 2 +- extern/osg-ffmpeg-videoplayer/CMakeLists.txt | 2 +- 26 files changed, 29 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index acec38fad0..50af98393e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF) +option(PRECOMPILE_HEADERS_WITH_MSVC "Precompile most common used headers with MSVC (alternative to ccache)" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. diff --git a/apps/benchmarks/detournavigator/CMakeLists.txt b/apps/benchmarks/detournavigator/CMakeLists.txt index 2b3a6abe51..ffe7818a5a 100644 --- a/apps/benchmarks/detournavigator/CMakeLists.txt +++ b/apps/benchmarks/detournavigator/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/esm/CMakeLists.txt b/apps/benchmarks/esm/CMakeLists.txt index 74870ceda1..9b5afd649d 100644 --- a/apps/benchmarks/esm/CMakeLists.txt +++ b/apps/benchmarks/esm/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_esm_refid_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/settings/CMakeLists.txt b/apps/benchmarks/settings/CMakeLists.txt index ccdd51eeac..51e2d2b0fd 100644 --- a/apps/benchmarks/settings/CMakeLists.txt +++ b/apps/benchmarks/settings/CMakeLists.txt @@ -8,7 +8,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_settings_access_benchmark PRIVATE ) endif() diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index a567499ac6..e893feb91a 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -18,7 +18,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(bsatool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(bsatool PRIVATE diff --git a/apps/bulletobjecttool/CMakeLists.txt b/apps/bulletobjecttool/CMakeLists.txt index 6e6e1cdbb3..d9bba10195 100644 --- a/apps/bulletobjecttool/CMakeLists.txt +++ b/apps/bulletobjecttool/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32) install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-bulletobjecttool PRIVATE diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 6f7fa1a993..d26e2a2128 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -25,7 +25,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(esmtool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(esmtool PRIVATE diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index c6c98791e3..5dfb747aee 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -47,7 +47,7 @@ if (WIN32) INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") endif(WIN32) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-essimporter PRIVATE diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 0c888afe9d..bd6a7062fd 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -94,7 +94,7 @@ if(USE_QT) set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-launcher PRIVATE diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 704393cd0d..49be8309ab 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -33,7 +33,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-iniimporter gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-iniimporter PRIVATE diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt index 9abd8dc283..13c8230abd 100644 --- a/apps/navmeshtool/CMakeLists.txt +++ b/apps/navmeshtool/CMakeLists.txt @@ -21,7 +21,7 @@ if (WIN32) install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-navmeshtool PRIVATE diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index f112e087e3..cf37162f6e 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -17,6 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(niftest gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(niftest PRIVATE ) endif() diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 9bf02e10c9..c2f249171a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -292,7 +292,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-lib gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-lib PRIVATE diff --git a/apps/opencs_tests/CMakeLists.txt b/apps/opencs_tests/CMakeLists.txt index 2b7309f8b9..3bf783bb68 100644 --- a/apps/opencs_tests/CMakeLists.txt +++ b/apps/opencs_tests/CMakeLists.txt @@ -26,7 +26,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-tests PRIVATE gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-tests PRIVATE ) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5fb06881ec..5fb4f398f1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -161,7 +161,7 @@ target_link_libraries(openmw components ) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw PRIVATE diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 71da2de590..3fe76623bf 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -126,7 +126,7 @@ target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR=u8"${CMAKE_CURRENT_BINARY_DIR}/data" OPENMW_PROJECT_SOURCE_DIR=u8"${PROJECT_SOURCE_DIR}") -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_test_suite PRIVATE diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8bdead1357..1c553e855e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -691,7 +691,7 @@ if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(components PUBLIC diff --git a/components/detournavigator/commulativeaabb.hpp b/components/detournavigator/commulativeaabb.hpp index 5d24c329ca..46cf64b348 100644 --- a/components/detournavigator/commulativeaabb.hpp +++ b/components/detournavigator/commulativeaabb.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace DetourNavigator { diff --git a/components/esm/esm3exteriorcellrefid.hpp b/components/esm/esm3exteriorcellrefid.hpp index 5fca8dc597..fd6a9b128d 100644 --- a/components/esm/esm3exteriorcellrefid.hpp +++ b/components/esm/esm3exteriorcellrefid.hpp @@ -3,7 +3,7 @@ #include #include - +#include #include #include diff --git a/components/esm/format.cpp b/components/esm/format.cpp index aa869ab998..04edc5c7db 100644 --- a/components/esm/format.cpp +++ b/components/esm/format.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/esm/generatedrefid.hpp b/components/esm/generatedrefid.hpp index c5cd1bcef5..e9d07ff314 100644 --- a/components/esm/generatedrefid.hpp +++ b/components/esm/generatedrefid.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/platform/platform.cpp b/components/platform/platform.cpp index 787cfa522e..9743c14337 100644 --- a/components/platform/platform.cpp +++ b/components/platform/platform.cpp @@ -1,12 +1,15 @@ #include "platform.hpp" +#ifdef WIN32 +#include +#endif + namespace Platform { static void increaseFileHandleLimit() { #ifdef WIN32 -#include // Increase limit for open files at the stream I/O level, see // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170#remarks _setmaxstdio(8192); diff --git a/extern/Base64/CMakeLists.txt b/extern/Base64/CMakeLists.txt index 94992a22b5..fc750823c7 100644 --- a/extern/Base64/CMakeLists.txt +++ b/extern/Base64/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(Base64 INTERFACE) target_include_directories(Base64 INTERFACE .) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(Base64 INTERFACE ) endif() diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4a2cf1162b..1a6fcf2625 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -234,7 +234,7 @@ if (NOT OPENMW_USE_SYSTEM_YAML_CPP) ) FetchContent_MakeAvailableExcludeFromAll(yaml-cpp) - if (MSVC) + if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(yaml-cpp PRIVATE ) endif() endif() diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 4bd3bc51ad..2d34f3f3e6 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -22,6 +22,6 @@ endif() target_link_libraries(oics SDL2::SDL2) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(oics PUBLIC ) endif() diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 10c8d356a0..8ff608bf04 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} SDL2::SDL2) link_directories(${CMAKE_CURRENT_BINARY_DIR}) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} PUBLIC From e0b13f0858309e328d512d056de4beda37171952 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Mar 2024 01:44:47 +0000 Subject: [PATCH 1141/2167] Ensure default config values are present Moving builtin.omwscripts out of the root openmw.cfg means we actually might need to use the defaults, so need to have some. --- components/files/configurationmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 10e10375bb..7b4cbac864 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -68,6 +68,9 @@ namespace Files bool silent = mSilent; mSilent = quiet; + // ensure defaults are present + bpo::store(bpo::parsed_options(&description), variables); + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); if (config) mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); From baab28440e4283d43a5c42c15fe6484f7e0d4ff3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Mar 2024 01:49:23 +0000 Subject: [PATCH 1142/2167] Russian translations from Capo --- files/lang/components_ru.ts | 4 ++-- files/lang/launcher_ru.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index 1618961914..b16168effb 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -31,11 +31,11 @@ <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> - + <br/><b>Этот контентный файл не может быть отключен, потому что он является частью OpenMW.</b><br/> <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> - + <br/><b>Этот контентный файл не может быть отключен, потому что он включен в конфигурационном файле, не являющемся пользовательским.</b><br/> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 4a71267eb4..843222a423 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -374,23 +374,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Resolved as %1 - + Путь разрешен как %1 This is the data-local directory and cannot be disabled - + Это директория data-loсal, и она не может быть отключена This directory is part of OpenMW and cannot be disabled - + Это директория является частью OpenMW и не может быть отключена This directory is enabled in an openmw.cfg other than the user one - + Это директория включена в openmw.cfg, не являющемся пользовательским This archive is enabled in an openmw.cfg other than the user one - + Этот архив включен в openmw.cfg, не являющемся пользовательским From 504a9e7d4372de1ee9000b17a91b068b91125aee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Mar 2024 17:09:49 +0100 Subject: [PATCH 1143/2167] Address feedback --- apps/openmw/mwgui/mapwindow.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index ae6da7766f..02a38fa640 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -96,11 +96,11 @@ namespace viewingDistanceInCells, Constants::CellGridRadius, Settings::map().mMaxLocalViewingDistance.get()); } - ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell* cell, int x, int y) + ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell& cell, int x, int y) { - if (cell->isExterior()) + if (cell.isExterior()) return ESM::Cell::generateIdForCell(true, {}, x, y); - return cell->getId(); + return cell.getId(); } } @@ -260,9 +260,8 @@ namespace MWGui { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); - return MyGUI::IntPoint( - std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mActiveCell->getGridX())) * mapWidgetSize), - std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mActiveCell->getGridY())) * mapWidgetSize)); + return MyGUI::IntPoint(std::round((nX + mCellDistance + cellX - mActiveCell->getGridX()) * mapWidgetSize), + std::round((nY + mCellDistance - cellY + mActiveCell->getGridY()) * mapWidgetSize)); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const @@ -350,7 +349,7 @@ namespace MWGui for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { ESM::RefId cellRefId - = getCellIdInWorldSpace(mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); + = getCellIdInWorldSpace(*mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; @@ -892,7 +891,7 @@ namespace MWGui mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); - ESM::RefId clickedId = getCellIdInWorldSpace(mActiveCell, x, y); + ESM::RefId clickedId = getCellIdInWorldSpace(*mActiveCell, x, y); mEditingMarker.mCell = clickedId; From 84adb0a148a664bd0fbd641e021eac4dea959e0e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 14:10:25 +0100 Subject: [PATCH 1144/2167] Make VFS::Path::Normalized constructor from std::string_view explicit --- apps/openmw/mwlua/vfsbindings.cpp | 3 ++- components/resource/stats.cpp | 2 +- components/vfs/pathutil.hpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index c9b1a45fe2..34a84221f8 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -328,7 +328,8 @@ namespace MWLua }, [](const sol::object&) -> sol::object { return sol::nil; }); - api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; + api["fileExists"] + = [vfs](std::string_view fileName) -> bool { return vfs->exists(VFS::Path::Normalized(fileName)); }; api["pathsWithPrefix"] = [vfs](std::string_view prefix) { auto iterator = vfs->getRecursiveDirectoryIterator(prefix); return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional { diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 6ff2112381..0542ffef28 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -29,7 +29,7 @@ namespace Resource static bool collectStatUpdate = false; static bool collectStatEngine = false; - constexpr std::string_view sFontName = "Fonts/DejaVuLGCSansMono.ttf"; + static const VFS::Path::Normalized sFontName("Fonts/DejaVuLGCSansMono.ttf"); static void setupStatCollection() { diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 5c5746cf6f..6e5c5843f3 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -130,7 +130,7 @@ namespace VFS::Path public: Normalized() = default; - Normalized(std::string_view value) + explicit Normalized(std::string_view value) : mValue(normalizeFilename(value)) { } From ffbeb5ab9853f6646443629e05f06f22a7334497 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:00:35 +0100 Subject: [PATCH 1145/2167] Build localization path using VFS::Path::Normalized --- apps/openmw_test_suite/vfs/testpathutil.cpp | 7 +++++++ components/l10n/manager.cpp | 12 +++++++----- components/vfs/pathutil.hpp | 10 ++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 7b9c9abfb5..6eb84f97d5 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -72,6 +72,13 @@ namespace VFS::Path EXPECT_EQ(value.value(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportOperatorDivEqualWithStringView) + { + Normalized value("foo/bar"); + value /= std::string_view("BAZ"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) { Normalized value("foo/bar.a"); diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index 10cad81587..77474cd3f5 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -36,11 +36,13 @@ namespace l10n void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang) { - std::string path = "l10n/"; - path.append(name); - path.append("/"); - path.append(lang.getName()); - path.append(".yaml"); + std::string langName(lang.getName()); + langName += ".yaml"; + + VFS::Path::Normalized path("l10n"); + path /= name; + path /= langName; + if (!mVFS->exists(path)) return; diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 6e5c5843f3..07e73acfa9 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -187,6 +187,16 @@ namespace VFS::Path return *this; } + Normalized& operator/=(std::string_view value) + { + mValue.reserve(mValue.size() + value.size() + 1); + mValue += separator; + const std::size_t offset = mValue.size(); + mValue += value; + normalizeFilenameInPlace(mValue.begin() + offset, mValue.end()); + return *this; + } + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } From cc35df9409cdf4060ae9261b697c7bef5b01d7b0 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:18:51 +0100 Subject: [PATCH 1146/2167] Use VFS::Path::Normalized for fx::Technique file path --- components/fx/technique.cpp | 26 ++++++++++++++++---------- components/fx/technique.hpp | 10 ++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index defb581cfc..f6bc881f78 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -37,11 +37,22 @@ namespace namespace fx { + namespace + { + VFS::Path::Normalized makeFilePath(std::string_view name) + { + std::string fileName(name); + fileName += Technique::sExt; + VFS::Path::Normalized result(Technique::sSubdir); + result /= fileName; + return result; + } + } + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, int height, bool ubo, bool supportsNormals) : mName(std::move(name)) - , mFileName(Files::pathToUnicodeString( - (Files::pathFromUnicodeString(Technique::sSubdir) / (mName + Technique::sExt)))) + , mFilePath(makeFilePath(mName)) , mLastModificationTime(std::filesystem::file_time_type::clock::now()) , mWidth(width) , mHeight(height) @@ -98,9 +109,9 @@ namespace fx { clear(); - if (!mVFS.exists(mFileName)) + if (!mVFS.exists(mFilePath)) { - Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; + Log(Debug::Error) << "Could not load technique, file does not exist '" << mFilePath << "'"; mStatus = Status::File_Not_exists; return false; @@ -167,7 +178,7 @@ namespace fx mStatus = Status::Parse_Error; mLastError = "Failed parsing technique '" + getName() + "' " + e.what(); - ; + Log(Debug::Error) << mLastError; } @@ -179,11 +190,6 @@ namespace fx return mName; } - std::string Technique::getFileName() const - { - return mFileName; - } - bool Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp) { const bool isDirty = timeStamp != mLastModificationTime; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index fa66996aeb..01943a2fbe 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -13,6 +13,8 @@ #include #include +#include + #include "lexer.hpp" #include "pass.hpp" #include "types.hpp" @@ -103,8 +105,8 @@ namespace fx using UniformMap = std::vector>; using RenderTargetMap = std::unordered_map; - inline static std::string sExt = ".omwfx"; - inline static std::string sSubdir = "shaders"; + static constexpr std::string_view sExt = ".omwfx"; + static constexpr std::string_view sSubdir = "shaders"; enum class Status { @@ -128,7 +130,7 @@ namespace fx std::string getName() const; - std::string getFileName() const; + const VFS::Path::Normalized& getFileName() const { return mFilePath; } bool setLastModificationTime(std::filesystem::file_time_type timeStamp); @@ -251,7 +253,7 @@ namespace fx std::string mShared; std::string mName; - std::string mFileName; + VFS::Path::Normalized mFilePath; std::string_view mBlockName; std::string_view mAuthor; std::string_view mDescription; From 709c12053a940a2dcac17fd903b7cf3cbb6fda55 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Sat, 9 Mar 2024 09:49:14 +0000 Subject: [PATCH 1147/2167] Bring sv translations up to date --- files/data/l10n/Interface/sv.yaml | 28 +++++++---- files/data/l10n/OMWControls/sv.yaml | 73 +++++++++++++++++++++++++++-- files/data/l10n/OMWEngine/sv.yaml | 42 ++++++++--------- 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/files/data/l10n/Interface/sv.yaml b/files/data/l10n/Interface/sv.yaml index aae63a1941..10a6c50b58 100644 --- a/files/data/l10n/Interface/sv.yaml +++ b/files/data/l10n/Interface/sv.yaml @@ -1,17 +1,25 @@ -Cancel: "Avbryt" -Close: "Stäng" DurationDay: "{days} d " DurationHour: "{hours} tim " DurationMinute: "{minutes} min " -DurationMonth: "{months} må " +DurationMonth: |- + {months, plural, + one{{months} må } + other{{months} må } + } DurationSecond: "{seconds} sek " -DurationYear: "{years} år " +DurationYear: |- + {years, plural, + one{{years} år } + other{{years} år } + } No: "Nej" -None: "Inget" NotAvailableShort: "N/A" -OK: "Ok" -Off: "Av" -On: "På" -Reset: "Återställ" +Reset: "Reset" Yes: "Ja" -#Copy: "Copy" +On: "På" +Off: "Av" +None: "Inget" +OK: "Ok" +Cancel: "Avbryt" +Close: "Stäng" +Copy: "Kopiera" \ No newline at end of file diff --git a/files/data/l10n/OMWControls/sv.yaml b/files/data/l10n/OMWControls/sv.yaml index 73fc5e18dc..59fecd1f35 100644 --- a/files/data/l10n/OMWControls/sv.yaml +++ b/files/data/l10n/OMWControls/sv.yaml @@ -1,7 +1,7 @@ ControlsPage: "OpenMW Kontroller" ControlsPageDescription: "Ytterligare inställningar relaterade till spelarkontroller" -MovementSettings: "Rörelse" +MovementSettings: "Rörelser" alwaysRun: "Spring alltid" alwaysRunDescription: | @@ -14,5 +14,72 @@ toggleSneakDescription: | istället för att att kräva att knappen hålls nedtryckt för att smyga. Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mjuka handkontrollsrörelser" +smoothControllerMovementDescription: | + Aktiverar mjuka styrspaksrörelser för handkontroller. Gör övergången från gående till springande mindre abrubt. + +TogglePOV_name: "Växla perspektiv" +TogglePOV_description: "Växlar mellan första- och tredjepersonsperspektiv. Håll in för att aktivera omloppskamera." + +Zoom3rdPerson_name: "Zooma in/ut" +Zoom3rdPerson_description: "Flyttar kameran närmare / längre bort i tredjepersonsperspektivet." + +MoveForward_name: "Förflyttning framåt" +MoveForward_description: "Kan avbrytas med Förflyttning bakåt." + +MoveBackward_name: "Förflyttning bakåt" +MoveBackward_description: "Kan avbrytas med Förflyttning framåt." + +MoveLeft_name: "Förflyttning vänster" +MoveLeft_description: "Kan avbrytas med Förflyttning höger." + +MoveRight_name: "Förflyttning höger" +MoveRight_description: "Kan avbrytas med Förflyttning vänster." + +Use_name: "Använd" +Use_description: "Attackera med ett vapen eller kasta en besvärjelse beroende på nuvarande hållning." + +Run_name: "Spring" +Run_description: "Håll in för att springa/gå, beroende på Spring alltid-inställningen." + +AlwaysRun_name: "Spring alltid" +AlwaysRun_description: "Aktiverar Spring alltid-funktionen." + +Jump_name: "Hoppa" +Jump_description: "Hoppar när du är på marken." + +AutoMove_name: "Förflytta automatiskt" +AutoMove_description: "Aktiverar konstant förflyttning framåt." + +Sneak_name: "Smyg" +Sneak_description: "Håll in för att smyga, om Växla till smyg-inställningen är av." + +ToggleSneak_name: "Växla till smygläge" +ToggleSneak_description: "Knappen för smyg växlar till smygläge med ett tryck om denna är på." + +ToggleWeapon_name: "Redo vapen" +ToggleWeapon_description: "Gå in i eller lämna vapenposition." + +ToggleSpell_name: "Redo magi" +ToggleSpell_description: "Gå in i eller lämna magiposition." + +Inventory_name: "Lager" +Inventory_description: "Öppna lagret." + +Journal_name: "Dagbok" +Journal_description: "Öppna dagboken." + +QuickKeysMenu_name: "Snabbknappsmeny" +QuickKeysMenu_description: "Öppnar snabbknappsmenyn." + +SmoothMoveForward_name: "Mjuk förflyttning framåt" +SmoothMoveForward_description: "Framåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveBackward_name: "Mjuk förflyttning bakåt" +SmoothMoveBackward_description: "Bakåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveLeft_name: "Mjuk förflyttning vänster" +SmoothMoveLeft_description: "Vänsterförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveRight_name: "Mjuk förflyttning höger" +SmoothMoveRight_description: "Högerförflyttning anpassad för mjuka övergångar från gång till spring." diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index bbc6132f55..15a9afa495 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -22,15 +22,15 @@ LoadingInProgress: "Laddar sparat spel" LoadingRequiresNewVersionError: |- Denna sparfil skapades i en nyare version av OpenMW och stöds därför inte. Uppgradera till den senaste versionen av OpenMW för att ladda filen. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. + LoadingRequiresOldVersionError: |- + Denna sparfil skapades i en äldre version av OpenMW i ett format som inte längre stöds. + Ladda och spara denna sparfil med {version} för att uppgradera den. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" QuitGameConfirmation: "Avsluta spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Misslyckades att spara skärmdump" +ScreenshotMade: "%s har sparats" # Save game menu @@ -44,18 +44,18 @@ MissingContentFilesConfirmation: |- De valda innehållsfilerna matchar inte filerna som används av denna sparfil. Fel kan uppstå vid laddning eller under spel. Vill du fortsätta? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nHittade saknad fil: } + few{\n\nHittade {files} saknade filer:\n} + other{\n\nHittade {files} saknade filer:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nPress Kopiera för att placera namnet i urklipp.} + few{\n\nPress Kopiera för att placera deras namn i urklipp.} + other{\n\nPress Kopiera för att placera deras namn i urklipp.} + } OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?" SelectCharacter: "Välj spelfigur..." @@ -109,10 +109,10 @@ LightsBoundingSphereMultiplier: "Gränssfärsmultiplikator" LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljusens gränssfär.\nHögre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.\n\nPåverkar inte ljusstyrkan." LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Välj intern hantering av ljuskällor.\n\n +# \"Legacy\" använder alltid max 8 ljuskällor per objekt och ger ljussättning lik ett gammaldags spel.\n\n +# \"Shader (compatibility)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n +# \"Shaders\" har alla fördelar som \"Shaders (compatibility)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" From 7cb316f3c90605ece6124bd86ae2bc5612ae372b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Mar 2024 16:35:24 +0100 Subject: [PATCH 1148/2167] Docu fix --- files/data/scripts/omw/skillhandlers.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index db726e8474..dfa9728688 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -8,7 +8,7 @@ local Skill = core.stats.Skill --- -- Table of skill use types defined by morrowind. -- Each entry corresponds to an index into the available skill gain values --- of a @{openmw.types#SkillRecord} +-- of a @{openmw.core#SkillRecord} -- @type SkillUseType -- @field #number Armor_HitByOpponent 0 -- @field #number Block_Success 0 @@ -182,7 +182,7 @@ return { --- Add new skill level up handler for this actor. -- For load order consistency, handlers should be added in the body if your script. -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) - -- will be skipped. Where skillid and source are the parameters passed to @{#skillLevelUp}, and options is + -- will be skipped. Where skillid and source are the parameters passed to @{#SkillProgression.skillLevelUp}, and options is -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- @@ -203,7 +203,7 @@ return { -- For load order consistency, handlers should be added in the body of your script. -- If `handler(skillid, options)` returns false, other handlers (including the default skill progress handler) -- will be skipped. Where options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. - -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#skillUsed}. + -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#SkillProgression.skillUsed}. -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) From f3b01973ce1735a7d65fd188300458e33df73f98 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 9 Mar 2024 17:12:47 +0000 Subject: [PATCH 1149/2167] c h a n g e l o g --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea44cab4b1..d282b603f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly + Bug #6846: Launcher only works with default config paths Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack Bug #6932: Creatures flee from my followers and we have to chase after them From 72cf015401f3b455b9860a617b1bf5d9312c75f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 9 Mar 2024 20:18:41 +0000 Subject: [PATCH 1150/2167] Make ccache viable for Windows Release builds --- CI/before_script.msvc.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index e11ceb499d..fdbd27fb9c 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -528,8 +528,10 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi -if ! [ -z $USE_CCACHE ]; then - add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" +if [ -n "$USE_CCACHE" ] && ([ -n "$NMAKE" ] || [ -n "$NINJA" ]); then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" +elif [ -n "$USE_CCACHE" ]; then + echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" fi # turn on LTO by default From 57d7f5977c8589572ac46f6c0ef7451b41bccdaf Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Mar 2024 21:56:46 +0100 Subject: [PATCH 1151/2167] Bump interface version --- files/data/scripts/omw/skillhandlers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index dfa9728688..e3ca24f9d0 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -177,7 +177,7 @@ return { interface = { --- Interface version -- @field [parent=#SkillProgression] #number version - version = 0, + version = 1, --- Add new skill level up handler for this actor. -- For load order consistency, handlers should be added in the body if your script. From 0f60052bb87c36c5a767f23246b920d1a2bebe86 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 9 Mar 2024 22:27:10 +0100 Subject: [PATCH 1152/2167] Set Element state in Element::create --- components/lua_ui/element.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 6e7fe9ee16..e509847a4c 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -278,6 +278,7 @@ namespace LuaUi mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } } @@ -304,8 +305,8 @@ namespace LuaUi } mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } - mState = Created; } void Element::destroy() From 7b89ca6bb2eadec61e1ade0087a5ac58d8077159 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 01:31:55 +0000 Subject: [PATCH 1153/2167] Make CCache work for MSVC builds with debug symbols --- CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50af98393e..741295acf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,22 @@ if (MSVC) add_compile_options(/bigobj) add_compile_options(/Zc:__cplusplus) + + if (CMAKE_CXX_COMPILER_LAUNCHER OR CMAKE_C_COMPILER_LAUNCHER) + if (CMAKE_GENERATOR MATCHES "Visual Studio") + message(STATUS "A compiler launcher was specified, but will be unused by the current generator (${CMAKE_GENERATOR})") + else() + foreach (config_lower ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${config_lower}" config) + if (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") + endif() + if (CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") + endif() + endforeach() + endif() + endif() endif() # Set up common paths From 6cf0b9990d6390794d78c002e344bbdc029ed1e1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 01:32:38 +0000 Subject: [PATCH 1154/2167] Don't bother setting up CCache for MSBuild builds It can't work as it ignores compiler launchers --- .gitlab-ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2bb4193a79..06c7e63cb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -576,7 +576,7 @@ macOS14_Xcode15_arm64: - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - - ccache --show-stats + - ccache --show-stats -v - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" @@ -666,7 +666,6 @@ macOS14_Xcode15_arm64: - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - - choco install ccache -y - choco install vswhere -y - choco install python -y - choco install awscli -y @@ -689,15 +688,11 @@ macOS14_Xcode15_arm64: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - $env:CCACHE_BASEDIR = Get-Location - - $env:CCACHE_DIR = "$(Get-Location)\ccache" - - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - cd MSVC2019_64 - Get-Volume - cmake --build . --config $config - - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" @@ -729,7 +724,6 @@ macOS14_Xcode15_arm64: cache: key: msbuild-v8 paths: - - ccache - deps - MSVC2019_64/deps/Qt artifacts: From 30f314025afdcbfbfeb0de3a4650d518c369e407 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Mar 2024 22:19:31 +0300 Subject: [PATCH 1155/2167] Log whether shaders or FFP are used for rendering --- apps/openmw/mwrender/renderingmanager.cpp | 55 ++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2bfbf179ea..004b041336 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -329,13 +329,56 @@ namespace MWRender const SceneUtil::LightingMethod lightingMethod = Settings::shaders().mLightingMethod; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - // Shadows and radial fog have problems with fixed-function mode. - bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog - || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ - || mSkyBlending || Stereo::getMultiview(); - resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // Figure out which pipeline must be used by default and inform the user + bool forceShaders = Settings::shaders().mForceShaders; + { + std::vector requesters; + if (!forceShaders) + { + if (Settings::fog().mRadialFog) + requesters.push_back("radial fog"); + if (Settings::fog().mExponentialFog) + requesters.push_back("exponential fog"); + if (mSkyBlending) + requesters.push_back("sky blending"); + if (Settings::shaders().mSoftParticles) + requesters.push_back("soft particles"); + if (Settings::shadows().mEnableShadows) + requesters.push_back("shadows"); + if (lightingMethod != SceneUtil::LightingMethod::FFP) + requesters.push_back("lighting method"); + if (reverseZ) + requesters.push_back("reverse-Z depth buffer"); + if (Stereo::getMultiview()) + requesters.push_back("stereo multiview"); + + if (!requesters.empty()) + forceShaders = true; + } + + if (forceShaders) + { + std::string message = "Using rendering with shaders by default"; + if (requesters.empty()) + { + message += " (forced)"; + } + else + { + message += ", requested by:"; + for (size_t i = 0; i < requesters.size(); i++) + message += "\n - " + requesters[i]; + } + Log(Debug::Info) << message; + } + else + { + Log(Debug::Info) << "Using fixed-function rendering by default"; + } + } + + resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::shaders().mClampLighting); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::shaders().mAutoUseObjectNormalMaps); From f7e5ef74c6b671529c8cca50df4b0540d2ff1b97 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Mar 2024 14:53:55 +0400 Subject: [PATCH 1156/2167] Partially revert 5dcac4c48f54 --- CHANGELOG.md | 1 - apps/openmw/mwgui/windowmanagerimp.cpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbab574d5..953801b345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,7 +157,6 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value - Bug #7866: Alt-tabbing is considered as a resolution change Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b8c92d761e..29b9cb0e84 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1166,9 +1166,6 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { - if (x == Settings::video().mResolutionX && y == Settings::video().mResolutionY) - return; - Settings::video().mResolutionX.set(x); Settings::video().mResolutionY.set(y); From af8662daeebabeab20cf8403867259495de6c3bd Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 10 Mar 2024 14:05:37 +0100 Subject: [PATCH 1157/2167] Detach Lua Elements properly from their parent --- components/lua_ui/element.cpp | 8 ++++---- components/lua_ui/widget.cpp | 6 ++++++ components/lua_ui/widget.hpp | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index e509847a4c..b491acb7b3 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,7 +54,7 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->widget()->detachFromWidget(); + ext->detachFromParent(); } void detachElements(WidgetExtension* ext) @@ -62,14 +62,14 @@ namespace LuaUi for (auto* child : ext->children()) { if (child->isRoot()) - child->widget()->detachFromWidget(); + child->detachFromParent(); else detachElements(child); } for (auto* child : ext->templateChildren()) { if (child->isRoot()) - child->widget()->detachFromWidget(); + child->detachFromParent(); else detachElements(child); } @@ -272,9 +272,9 @@ namespace LuaUi void Element::create(uint64_t depth) { - assert(!mRoot); if (mState == New) { + assert(!mRoot); mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 9550c9de73..be0ea70387 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -112,6 +112,12 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } + void WidgetExtension::detachFromParent() + { + mParent = nullptr; + widget()->detachFromWidget(); + } + WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { for (WidgetExtension* w : mChildren) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 591c885ce9..05359705a1 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -35,6 +35,7 @@ namespace LuaUi bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } + void detachFromParent(); void reset(); From eba4ae94b0350ed2837962d22caa4ff1e5c0e52b Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 10 Mar 2024 14:06:21 +0100 Subject: [PATCH 1158/2167] Fix re-rendering of settings on value changes --- files/data/scripts/omw/settings/menu.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 7d425f684b..4e6971a516 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -378,7 +378,7 @@ local function onSettingChanged(global) local value = common.getSection(global, group.key):get(settingKey) local settingsContent = groupElement.layout.content.settings.content auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) groupElement:update() end) end @@ -408,7 +408,7 @@ local function onGroupRegistered(global, key) local element = groupElements[group.page][group.key] local settingsContent = element.layout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) element:update() end)) end From 0730dc2ebb9751bfbbef4dd5b392ac0c71b45f2d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 18:04:38 +0000 Subject: [PATCH 1159/2167] Get OSG to tell us the plugin filenames it's going to use That way, we don't have issues like the checker getting false positives when the right plugins are present for the wrong OSG version or build config, or false negatives when we've generated the wrong filenames. --- components/CMakeLists.txt | 15 ++++----------- components/misc/osgpluginchecker.cpp.in | 9 ++++++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1c553e855e..7e422506ad 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,17 +46,10 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker # Helpfully, OSG doesn't export this to its CMake config as it doesn't have one -set(OSG_PLUGIN_PREFIX "") -if (CYGWIN) - SET(OSG_PLUGIN_PREFIX "cygwin_") -elseif(MINGW) - SET(OSG_PLUGIN_PREFIX "mingw_") -endif() -list(TRANSFORM USED_OSG_PLUGINS PREPEND "${OSG_PLUGIN_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") -list(JOIN USED_OSG_PLUGIN_FILENAMES_FORMATTED ", " USED_OSG_PLUGIN_FILENAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGINS REPLACE "^osgdb_" "" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES) +list(TRANSFORM USED_OSG_PLUGIN_NAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGIN_NAMES_FORMATTED APPEND "\"") +list(JOIN USED_OSG_PLUGIN_NAMES_FORMATTED ", " USED_OSG_PLUGIN_NAMES_FORMATTED) set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index f519447752..81ae73f9e3 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -30,7 +30,7 @@ namespace Misc namespace { - constexpr auto USED_OSG_PLUGIN_FILENAMES = std::to_array({${USED_OSG_PLUGIN_FILENAMES_FORMATTED}}); + constexpr auto USED_OSG_PLUGIN_NAMES = std::to_array({${USED_OSG_PLUGIN_NAMES_FORMATTED}}); } bool checkRequiredOSGPluginsArePresent() @@ -62,7 +62,7 @@ namespace Misc auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); bool haveAllPlugins = true; - for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) + for (std::string_view plugin : USED_OSG_PLUGIN_NAMES) { if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { @@ -71,7 +71,10 @@ namespace Misc #else std::filesystem::path pluginPath {availablePlugin}; #endif - return pluginPath.filename() == plugin; + return pluginPath.filename() + == std::filesystem::path( + osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin })) + .filename(); }) == availableOSGPlugins.end()) { From 7ec723e9b9a91f2afb6496a2829d85b87fec5f8b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 23:26:45 +0000 Subject: [PATCH 1160/2167] More sensible conditions --- CI/before_script.msvc.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index fdbd27fb9c..269cc44707 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -528,10 +528,12 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi -if [ -n "$USE_CCACHE" ] && ([ -n "$NMAKE" ] || [ -n "$NINJA" ]); then - add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" -elif [ -n "$USE_CCACHE" ]; then - echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" +if [ -n "$USE_CCACHE" ]; then + if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" + else + echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" + fi fi # turn on LTO by default From 6232b4f9e86556b5fcb7516aa672fb051a602639 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 21 Feb 2024 22:54:50 +0300 Subject: [PATCH 1161/2167] Reimplement the Settings window as a normal window (#7845, #7870) --- apps/openmw/mwbase/windowmanager.hpp | 3 ++- apps/openmw/mwgui/mainmenu.cpp | 8 +++++- apps/openmw/mwgui/settingswindow.cpp | 4 +-- apps/openmw/mwgui/settingswindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 27 ++++++++++++++++--- apps/openmw/mwgui/windowmanagerimp.hpp | 3 ++- apps/openmw/mwinput/mousemanager.cpp | 4 +-- files/data/mygui/openmw_layers.xml | 1 + .../data/mygui/openmw_settings_window.layout | 2 +- 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c252e0c490..c511ac313d 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -136,6 +136,7 @@ namespace MWBase virtual bool isConsoleMode() const = 0; virtual bool isPostProcessorHudVisible() const = 0; + virtual bool isSettingsWindowVisible() const = 0; virtual bool isInteractiveMessageBoxActive() const = 0; virtual void toggleVisible(MWGui::GuiWindow wnd) = 0; @@ -157,7 +158,6 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; - virtual MWGui::SettingsWindow* getSettingsWindow() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; @@ -342,6 +342,7 @@ namespace MWBase virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; virtual void togglePostProcessorHud() = 0; + virtual void toggleSettingsWindow() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index d0c55f432e..be3700342a 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -99,7 +99,7 @@ namespace MWGui } else if (name == "options") { - winMgr->getSettingsWindow()->setVisible(true); + winMgr->toggleSettingsWindow(); } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); @@ -212,6 +212,12 @@ namespace MWGui bool MainMenu::exit() { + if (MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible()) + { + MWBase::Environment::get().getWindowManager()->toggleSettingsWindow(); + return false; + } + return MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running; } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 6c6a34595e..b569132141 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -241,7 +241,7 @@ namespace MWGui } SettingsWindow::SettingsWindow() - : WindowModal("openmw_settings_window.layout") + : WindowBase("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { @@ -1042,8 +1042,6 @@ namespace MWGui void SettingsWindow::onOpen() { - WindowModal::onOpen(); - highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 47951ef121..1f96f7de54 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -7,7 +7,7 @@ namespace MWGui { - class SettingsWindow : public WindowModal + class SettingsWindow : public WindowBase { public: SettingsWindow(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b8c92d761e..ab5e23eeac 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -914,6 +914,9 @@ namespace MWGui if (isConsoleMode()) mConsole->onFrame(frameDuration); + if (isSettingsWindowVisible()) + mSettingsWindow->onFrame(frameDuration); + if (!gameRunning) return; @@ -1473,10 +1476,6 @@ namespace MWGui { return mPostProcessorHud; } - MWGui::SettingsWindow* WindowManager::getSettingsWindow() - { - return mSettingsWindow; - } void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) { @@ -1552,6 +1551,11 @@ namespace MWGui return mPostProcessorHud && mPostProcessorHud->isVisible(); } + bool WindowManager::isSettingsWindowVisible() const + { + return mSettingsWindow && mSettingsWindow->isVisible(); + } + bool WindowManager::isInteractiveMessageBoxActive() const { return mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox(); @@ -2133,6 +2137,21 @@ namespace MWGui updateVisible(); } + void WindowManager::toggleSettingsWindow() + { + bool visible = mSettingsWindow->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mSettingsWindow->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); + } + void WindowManager::cycleSpell(bool next) { if (!isGuiMode()) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 3445ebdb9a..ddc9e1c5e0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -162,6 +162,7 @@ namespace MWGui bool isConsoleMode() const override; bool isPostProcessorHudVisible() const override; + bool isSettingsWindowVisible() const override; bool isInteractiveMessageBoxActive() const override; void toggleVisible(GuiWindow wnd) override; @@ -183,7 +184,6 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; MWGui::PostProcessorHud* getPostProcessorHud() override; - MWGui::SettingsWindow* getSettingsWindow() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; @@ -364,6 +364,7 @@ namespace MWGui void toggleConsole() override; void toggleDebugWindow() override; void togglePostProcessorHud() override; + void toggleSettingsWindow() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index f18ec2ac87..9a8cada25b 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -166,9 +166,7 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - const MWGui::SettingsWindow* settingsWindow - = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); - if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + if (!MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible() && !input->controlsDisabled()) { mBindingsManager->mousePressed(arg, id); } diff --git a/files/data/mygui/openmw_layers.xml b/files/data/mygui/openmw_layers.xml index 045fb1cdc2..459db3fcb9 100644 --- a/files/data/mygui/openmw_layers.xml +++ b/files/data/mygui/openmw_layers.xml @@ -13,6 +13,7 @@ + diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 27298b9756..9e2f707ef5 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -1,6 +1,6 @@  - + From cd3c3ebadb8d288741d71d58b13fdfb269e279dd Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:42:21 +0100 Subject: [PATCH 1162/2167] Use VFS::Path::Normalized for ResourceManager cache key --- components/resource/resourcemanager.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index b2427c308a..e2626665c8 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include "objectcache.hpp" namespace VFS @@ -70,11 +72,11 @@ namespace Resource double mExpiryDelay; }; - class ResourceManager : public GenericResourceManager + class ResourceManager : public GenericResourceManager { public: explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) - : GenericResourceManager(vfs, expiryDelay) + : GenericResourceManager(vfs, expiryDelay) { } }; From 79b73e45a12b322f611c7004ef8eb4591c12e904 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:57:43 +0100 Subject: [PATCH 1163/2167] Replace std::filesystem::path by std::string and std::string_view in nif code It's used only for error reporting. --- apps/niftest/niftest.cpp | 4 ++-- components/nif/exception.hpp | 13 ++++++++----- components/nif/niffile.hpp | 12 ++++++------ components/nifbullet/bulletnifloader.cpp | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index fe60238cd5..b37d85d739 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -65,10 +65,10 @@ void readNIF( std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; } - std::filesystem::path fullPath = !source.empty() ? source / path : path; + const std::filesystem::path fullPath = !source.empty() ? source / path : path; try { - Nif::NIFFile file(fullPath); + Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); Nif::Reader reader(file, nullptr); if (vfs != nullptr) reader.parse(vfs->get(pathStr)); diff --git a/components/nif/exception.hpp b/components/nif/exception.hpp index 15f0e76d70..b123d6dc4f 100644 --- a/components/nif/exception.hpp +++ b/components/nif/exception.hpp @@ -1,18 +1,21 @@ #ifndef OPENMW_COMPONENTS_NIF_EXCEPTION_HPP #define OPENMW_COMPONENTS_NIF_EXCEPTION_HPP -#include #include #include -#include - namespace Nif { struct Exception : std::runtime_error { - explicit Exception(const std::string& message, const std::filesystem::path& path) - : std::runtime_error("NIFFile Error: " + message + " when reading " + Files::pathToUnicodeString(path)) + explicit Exception(std::string_view message, std::string_view path) + : std::runtime_error([&] { + std::string result = "NIFFile Error: "; + result += message; + result += " when reading "; + result += path; + return result; + }()) { } }; diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 993e9b7eea..6bff30a225 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -4,7 +4,7 @@ #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP #include -#include +#include #include #include @@ -45,7 +45,7 @@ namespace Nif std::uint32_t mBethVersion = 0; /// File name, used for error messages and opening the file - std::filesystem::path mPath; + std::string mPath; std::string mHash; /// Record list @@ -56,7 +56,7 @@ namespace Nif bool mUseSkinning = false; - explicit NIFFile(const std::filesystem::path& path) + explicit NIFFile(std::string_view path) : mPath(path) { } @@ -77,7 +77,7 @@ namespace Nif std::size_t numRoots() const { return mFile->mRoots.size(); } /// Get the name of the file - const std::filesystem::path& getFilename() const { return mFile->mPath; } + const std::string& getFilename() const { return mFile->mPath; } const std::string& getHash() const { return mFile->mHash; } @@ -104,7 +104,7 @@ namespace Nif std::uint32_t& mBethVersion; /// File name, used for error messages and opening the file - std::filesystem::path& mFilename; + std::string_view mFilename; std::string& mHash; /// Record list @@ -144,7 +144,7 @@ namespace Nif void setUseSkinning(bool skinning); /// Get the name of the file - std::filesystem::path getFilename() const { return mFilename; } + std::string_view getFilename() const { return mFilename; } /// Get the version of the NIF format used std::uint32_t getVersion() const { return mVersion; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 0737d0a165..a57e8b3c06 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -50,7 +50,7 @@ namespace NifBullet if (node) roots.emplace_back(node); } - mShape->mFileName = Files::pathToUnicodeString(nif.getFilename()); + mShape->mFileName = nif.getFilename(); if (roots.empty()) { warn("Found no root nodes in NIF file " + mShape->mFileName); From a98ce7f76a6a0d78857e7a5476bc48f0c8f969fa Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 19:02:41 +0100 Subject: [PATCH 1164/2167] Replace std::filesystem::path by std::string_view in Files::getHash argument --- apps/openmw_test_suite/files/hash.cpp | 9 ++++++--- components/files/hash.cpp | 10 ++++++---- components/files/hash.hpp | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp index 6ad19713dc..32c8380422 100644 --- a/apps/openmw_test_suite/files/hash.cpp +++ b/apps/openmw_test_suite/files/hash.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "../testing_util.hpp" namespace @@ -35,7 +37,8 @@ namespace std::fill_n(std::back_inserter(content), 1, 'a'); std::istringstream stream(content); stream.exceptions(std::ios::failbit | std::ios::badbit); - EXPECT_THAT(getHash(fileName, stream), ElementsAre(9607679276477937801ull, 16624257681780017498ull)); + EXPECT_THAT(getHash(Files::pathToUnicodeString(fileName), stream), + ElementsAre(9607679276477937801ull, 16624257681780017498ull)); } TEST_P(FilesGetHash, shouldReturnHashForStringStream) @@ -44,7 +47,7 @@ namespace std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::istringstream stream(content); - EXPECT_EQ(getHash(fileName, stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(fileName), stream), GetParam().mHash); } TEST_P(FilesGetHash, shouldReturnHashForConstrainedFileStream) @@ -57,7 +60,7 @@ namespace std::fstream(file, std::ios_base::out | std::ios_base::binary) .write(content.data(), static_cast(content.size())); const auto stream = Files::openConstrainedFileStream(file, 0, content.size()); - EXPECT_EQ(getHash(file, *stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(file), *stream), GetParam().mHash); } INSTANTIATE_TEST_SUITE_P(Params, FilesGetHash, diff --git a/components/files/hash.cpp b/components/files/hash.cpp index afb59b2e9e..1f1839ed0c 100644 --- a/components/files/hash.cpp +++ b/components/files/hash.cpp @@ -1,5 +1,4 @@ #include "hash.hpp" -#include "conversion.hpp" #include @@ -10,7 +9,7 @@ namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream) + std::array getHash(std::string_view fileName, std::istream& stream) { std::array hash{ 0, 0 }; try @@ -35,8 +34,11 @@ namespace Files } catch (const std::exception& e) { - throw std::runtime_error( - "Error while reading \"" + Files::pathToUnicodeString(fileName) + "\" to get hash: " + e.what()); + std::string message = "Error while reading \""; + message += fileName; + message += "\" to get hash: "; + message += e.what(); + throw std::runtime_error(message); } return hash; } diff --git a/components/files/hash.hpp b/components/files/hash.hpp index 0e6ce29ab5..48c373b971 100644 --- a/components/files/hash.hpp +++ b/components/files/hash.hpp @@ -3,12 +3,12 @@ #include #include -#include #include +#include namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream); + std::array getHash(std::string_view fileName, std::istream& stream); } #endif From 3ea3eeb6136fc7a98343cbbd87cb8de5da5bc9e9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Mar 2024 01:20:56 +0100 Subject: [PATCH 1165/2167] Use string_view for canOptimize --- components/resource/scenemanager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 787f2e8441..26d719a106 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -778,12 +778,12 @@ namespace Resource } }; - bool canOptimize(const std::string& filename) + static bool canOptimize(std::string_view filename) { - size_t slashpos = filename.find_last_of("\\/"); - if (slashpos != std::string::npos && slashpos + 1 < filename.size()) + const std::string_view::size_type slashpos = filename.find_last_of('/'); + if (slashpos != std::string_view::npos && slashpos + 1 < filename.size()) { - std::string basename = filename.substr(slashpos + 1); + const std::string_view basename = filename.substr(slashpos + 1); // xmesh.nif can not be optimized because there are keyframes added in post if (!basename.empty() && basename[0] == 'x') return false; @@ -796,7 +796,7 @@ namespace Resource // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly // cautious - instead, decide on filename - if (filename.find("vfx_pattern") != std::string::npos) + if (filename.find("vfx_pattern") != std::string_view::npos) return false; return true; } From 859d76592108b083fa3284582e7ec7958bb561b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Mar 2024 01:45:21 +0100 Subject: [PATCH 1166/2167] Use normalized path for NifFileManager::get --- components/resource/bulletshapemanager.cpp | 2 +- components/resource/niffilemanager.cpp | 4 ++-- components/resource/niffilemanager.hpp | 2 +- components/resource/scenemanager.cpp | 19 ++++++++++--------- components/vfs/manager.cpp | 5 +++++ components/vfs/manager.hpp | 2 ++ 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index f817d6b89a..b37e81111d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -110,7 +110,7 @@ namespace Resource osg::ref_ptr BulletShapeManager::getShape(const std::string& name) { - const std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 352d367f9b..0cc48d4247 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -41,14 +41,14 @@ namespace Resource NifFileManager::~NifFileManager() = default; - Nif::NIFFilePtr NifFileManager::get(const std::string& name) + Nif::NIFFilePtr NifFileManager::get(VFS::Path::NormalizedView name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); if (obj) return static_cast(obj.get())->mNifFile; else { - auto file = std::make_shared(name); + auto file = std::make_shared(name.value()); Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index dab4b70748..a5395fef7e 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -26,7 +26,7 @@ namespace Resource /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. - Nif::NIFFilePtr get(const std::string& name); + Nif::NIFFilePtr get(VFS::Path::NormalizedView name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 26d719a106..9ed72d5f05 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -545,9 +545,9 @@ namespace Resource namespace { osg::ref_ptr loadNonNif( - const std::string& normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) + VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -566,7 +566,7 @@ namespace Resource if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - const std::array fileHash = Files::getHash(normalizedFilename, model); + const std::array fileHash = Files::getHash(normalizedFilename.value(), model); osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) @@ -721,10 +721,10 @@ namespace Resource } } - osg::ref_ptr load(const std::string& normalizedFilename, const VFS::Manager* vfs, + osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); else if (ext == "spt") @@ -843,11 +843,12 @@ namespace Resource { try { + VFS::Path::Normalized path("meshes/marker_error.****"); for (const auto meshType : { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }) { - const std::string normalized = "meshes/marker_error." + std::string(meshType); - if (mVFS->exists(normalized)) - return load(normalized, mVFS, mImageManager, mNifFileManager); + path.changeExtension(meshType); + if (mVFS->exists(path)) + return load(path, mVFS, mImageManager, mNifFileManager); } } catch (const std::exception& e) @@ -869,7 +870,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(std::string_view name, bool compile) { - std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index ef5dd495c9..936dd64679 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -43,6 +43,11 @@ namespace VFS return getNormalized(name); } + Files::IStreamPtr Manager::get(Path::NormalizedView name) const + { + return getNormalized(name.value()); + } + Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { assert(Path::isNormalized(normalizedName)); diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 955538627f..59211602de 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -50,6 +50,8 @@ namespace VFS /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(const Path::Normalized& name) const; + Files::IStreamPtr get(Path::NormalizedView name) const; + /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. From cdbe6adfc397ab2c3b454116c865bee67acd6255 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 12 Mar 2024 03:32:43 +0300 Subject: [PATCH 1167/2167] Fix instance selection mode destruction (#7447) --- CHANGELOG.md | 1 + apps/opencs/view/render/instanceselectionmode.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbab574d5..443273c5ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ Bug #7415: Unbreakable lock discrepancies Bug #7416: Modpccrimelevel is different from vanilla Bug #7428: AutoCalc flag is not used to calculate enchantment costs + Bug #7447: OpenMW-CS: Dragging a cell of a different type (from the initial type) into the 3D view crashes OpenMW-CS Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index fa8998747d..d3e2379640 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -58,7 +58,8 @@ namespace CSVRender InstanceSelectionMode::~InstanceSelectionMode() { - mParentNode->removeChild(mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); } void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) From 75d4ea5d5de89022c09fa24706ffd6d519af6144 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 12 Mar 2024 04:03:04 +0300 Subject: [PATCH 1168/2167] Replace readme 1.0 label link with 1.0 milestone link (#7876) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95ca19685d..67ba2ce003 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Font Licenses: Current Status -------------- -The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/-/issues/?milestone_title=openmw-1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. From 59a25291f8a065a5525f46ba3974b43dc0fc0384 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 12 Mar 2024 07:29:48 -0500 Subject: [PATCH 1169/2167] Fix errors --- apps/openmw/mwlua/mwscriptbindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index c04339f28a..6ccb8c80fd 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -65,7 +65,7 @@ namespace MWLua return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); } return 0; - }; + } sol::table initMWScriptBindings(const Context& context) { @@ -146,7 +146,7 @@ namespace MWLua return sol::nullopt; return getGlobalVariableValue(globalId); }, - [](const GlobalStore& store, int index) -> sol::optional { + [](const GlobalStore& store, size_t index) -> sol::optional { if (index < 1 || store.getSize() < index) return sol::nullopt; auto g = store.at(index - 1); @@ -164,7 +164,7 @@ namespace MWLua return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { - int index = 0; + size_t index = 0; return sol::as_function( [index, &store](sol::this_state ts) mutable -> sol::optional> { if (index >= store.getSize()) From b12f98db98dc0b28113bee42cda8b615fcbfd1f7 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 12 Mar 2024 17:46:38 +0100 Subject: [PATCH 1170/2167] Don't destroy root widget for new elements --- components/lua_ui/element.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index b491acb7b3..9d45f6ed7f 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -313,11 +313,14 @@ namespace LuaUi { if (mState != Destroyed) { - destroyRoot(mRoot); - mRoot = nullptr; if (mState != New) - mLayout = sol::make_object(mLayout.lua_state(), sol::nil); - mState = Destroyed; + { + assert(mRoot); + destroyRoot(mRoot); + mRoot = nullptr; + } + mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } + mState = Destroyed; } } From f9da2b6b26ad8737f13732002c491694a32927ce Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Mar 2024 17:14:43 +0100 Subject: [PATCH 1171/2167] Roll for each region sound --- CHANGELOG.md | 1 + apps/openmw/mwsound/regionsoundselector.cpp | 38 +++------------------ apps/openmw/mwsound/regionsoundselector.hpp | 3 +- apps/openmw/mwsound/soundmanagerimp.cpp | 5 +-- 4 files changed, 10 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953801b345..64f0f55fb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7872: Region sounds use wrong odds Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 8fda57596a..89b5526d30 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -4,29 +4,18 @@ #include #include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" namespace MWSound { - namespace - { - int addChance(int result, const ESM::Region::SoundRef& v) - { - return result + v.mChance; - } - } - RegionSoundSelector::RegionSoundSelector() : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) { } - std::optional RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) + ESM::RefId RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) { mTimePassed += duration; @@ -49,28 +38,11 @@ namespace MWSound if (region == nullptr) return {}; - if (mSumChance == 0) + for (const ESM::Region::SoundRef& sound : region->mSoundList) { - mSumChance = std::accumulate(region->mSoundList.begin(), region->mSoundList.end(), 0, addChance); - if (mSumChance == 0) - return {}; + if (Misc::Rng::roll0to99() < sound.mChance) + return sound.mSound; } - - const int r = Misc::Rng::rollDice(std::max(mSumChance, 100)); - int pos = 0; - - const auto isSelected = [&](const ESM::Region::SoundRef& sound) { - if (r - pos < sound.mChance) - return true; - pos += sound.mChance; - return false; - }; - - const auto it = std::find_if(region->mSoundList.begin(), region->mSoundList.end(), isSelected); - - if (it == region->mSoundList.end()) - return {}; - - return it->mSound; + return {}; } } diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 1a9e6e450b..7a7659f56d 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -2,7 +2,6 @@ #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include -#include #include namespace MWBase @@ -15,7 +14,7 @@ namespace MWSound class RegionSoundSelector { public: - std::optional getNextRandom(float duration, const ESM::RefId& regionName); + ESM::RefId getNextRandom(float duration, const ESM::RefId& regionName); RegionSoundSelector(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 3658be4819..56224b4dcb 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -900,8 +900,9 @@ namespace MWSound if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound)) return; - if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion())) - mCurrentRegionSound = playSound(*next, 1.0f, 1.0f); + ESM::RefId next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion()); + if (!next.empty()) + mCurrentRegionSound = playSound(next, 1.0f, 1.0f); } void SoundManager::updateWaterSound() From 1d69d3808179c326255a7728f8e2e9e01dcb0aed Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Mar 2024 17:14:49 +0100 Subject: [PATCH 1172/2167] Add an actual probability column --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 6 ++++-- .../model/world/nestedcoladapterimp.cpp | 21 ++++++++++++++----- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 45759cd234..f487266dbb 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -235,6 +235,7 @@ namespace CSMWorld { ColumnId_RegionSounds, "Sounds" }, { ColumnId_SoundName, "Sound Name" }, { ColumnId_SoundChance, "Chance" }, + { ColumnId_SoundProbability, "Probability" }, { ColumnId_FactionReactions, "Reactions" }, { ColumnId_FactionRanks, "Ranks" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 74e5bdd006..f5a8e446a5 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -349,6 +349,8 @@ namespace CSMWorld ColumnId_SelectionGroupObjects = 316, + ColumnId_SoundProbability = 317, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ba1f1e5ac3..7bee635678 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -301,8 +301,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRegions.addColumn(new NestedParentColumn(Columns::ColumnId_RegionWeather)); index = mRegions.getColumns() - 1; mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter())); - mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_WeatherName, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds @@ -313,6 +313,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_SoundProbability, ColumnBase::Display_Float, ColumnBase::Flag_Dialogue, false)); mBirthsigns.addColumn(new StringIdColumn); mBirthsigns.addColumn(new RecordStateColumn); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 8b8c7b17be..9e5363e606 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -414,20 +414,31 @@ namespace CSMWorld QVariant RegionSoundListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { - ESM::Region region = record.get(); + const ESM::Region& region = record.get(); - std::vector& soundList = region.mSoundList; + const std::vector& soundList = region.mSoundList; - if (subRowIndex < 0 || subRowIndex >= static_cast(soundList.size())) + const size_t index = static_cast(subRowIndex); + if (subRowIndex < 0 || index >= soundList.size()) throw std::runtime_error("index out of range"); - ESM::Region::SoundRef soundRef = soundList[subRowIndex]; + const ESM::Region::SoundRef& soundRef = soundList[subRowIndex]; switch (subColIndex) { case 0: return QString(soundRef.mSound.getRefIdString().c_str()); case 1: return soundRef.mChance; + case 2: + { + float probability = 1.f; + for (size_t i = 0; i < index; ++i) + { + const float p = std::min(soundList[i].mChance / 100.f, 1.f); + probability *= 1.f - p; + } + return probability * std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + } default: throw std::runtime_error("Region sounds subcolumn index out of range"); } @@ -463,7 +474,7 @@ namespace CSMWorld int RegionSoundListAdapter::getColumnsCount(const Record& record) const { - return 2; + return 3; } int RegionSoundListAdapter::getRowsCount(const Record& record) const From c0578613af3c3be9c268cc51db4387461908c29c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Mar 2024 16:58:40 +0100 Subject: [PATCH 1173/2167] Remove superfluous members --- apps/openmw/mwsound/regionsoundselector.cpp | 8 +------- apps/openmw/mwsound/regionsoundselector.hpp | 8 -------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 89b5526d30..cb2ece7f8f 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -26,14 +26,8 @@ namespace MWSound mTimeToNextEnvSound = mMinTimeBetweenSounds + (mMaxTimeBetweenSounds - mMinTimeBetweenSounds) * a; mTimePassed = 0; - if (mLastRegionName != regionName) - { - mLastRegionName = regionName; - mSumChance = 0; - } - const ESM::Region* const region - = MWBase::Environment::get().getESMStore()->get().search(mLastRegionName); + = MWBase::Environment::get().getESMStore()->get().search(regionName); if (region == nullptr) return {}; diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 7a7659f56d..474e1afa06 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -2,12 +2,6 @@ #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include -#include - -namespace MWBase -{ - class World; -} namespace MWSound { @@ -20,8 +14,6 @@ namespace MWSound private: float mTimeToNextEnvSound = 0.0f; - int mSumChance = 0; - ESM::RefId mLastRegionName; float mTimePassed = 0.0; float mMinTimeBetweenSounds; float mMaxTimeBetweenSounds; From 0fdc432eb243c7e9f53f7c329411e65e52d4515b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Mar 2024 22:14:15 +0100 Subject: [PATCH 1174/2167] Format probability --- apps/opencs/model/world/data.cpp | 2 +- apps/opencs/model/world/nestedcoladapterimp.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7bee635678..1f8ff54e89 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -314,7 +314,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( - Columns::ColumnId_SoundProbability, ColumnBase::Display_Float, ColumnBase::Flag_Dialogue, false)); + Columns::ColumnId_SoundProbability, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mBirthsigns.addColumn(new StringIdColumn); mBirthsigns.addColumn(new RecordStateColumn); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 9e5363e606..aa0178fd28 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -437,7 +437,8 @@ namespace CSMWorld const float p = std::min(soundList[i].mChance / 100.f, 1.f); probability *= 1.f - p; } - return probability * std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + probability *= std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + return QString("%1%").arg(probability, 0, 'f', 2); } default: throw std::runtime_error("Region sounds subcolumn index out of range"); From 942eeb54c1a80b5567fd08eb9779d1dcb06835da Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 12 Mar 2024 23:30:11 +0000 Subject: [PATCH 1175/2167] Yet another osgpluginchecker rewrite It turns out that it's possible for OSG plugins to be spread across multiple directories, and OSG doesn't account for this in osgDB::listAllAvailablePlugins(), even though it works when actually loading the plugin. Instead, use code that's much more similar to how OSG actually loads plugin, and therefore less likely to miss anything. Incidentally make things much simpler as we don't need awkwardness from working around osgDB::listAllAvailablePlugins()'s limitations. --- components/misc/osgpluginchecker.cpp.in | 44 +++---------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 81ae73f9e3..8e57d9a5ce 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -35,50 +35,14 @@ namespace Misc bool checkRequiredOSGPluginsArePresent() { - // work around osgDB::listAllAvailablePlugins() not working on some platforms due to a suspected OSG bug - std::filesystem::path pluginDirectoryName = std::string("osgPlugins-") + std::string(osgGetVersion()); - osgDB::FilePathList& filepath = osgDB::getLibraryFilePathList(); - for (const auto& path : filepath) - { -#ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath{ StringUtils::stringToU8String(path) }; -#else - std::filesystem::path osgPath{ path }; -#endif - if (!osgPath.has_filename()) - osgPath = osgPath.parent_path(); - - if (osgPath.filename() == pluginDirectoryName) - { - osgPath = osgPath.parent_path(); -#ifdef OSG_USE_UTF8_FILENAME - std::string extraPath = StringUtils::u8StringToString(osgPath.u8string()); -#else - std::string extraPath = osgPath.string(); -#endif - filepath.emplace_back(std::move(extraPath)); - } - } - - auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); + // osgDB::listAllAvailablePlugins() lies, so don't use it bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_NAMES) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), - [&](std::string_view availablePlugin) { -#ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath{ StringUtils::stringToU8String(availablePlugin) }; -#else - std::filesystem::path pluginPath {availablePlugin}; -#endif - return pluginPath.filename() - == std::filesystem::path( - osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin })) - .filename(); - }) - == availableOSGPlugins.end()) + std::string libraryName = osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin }); + if (osgDB::findLibraryFile(libraryName).empty()) { - Log(Debug::Error) << "Missing OSG plugin: " << plugin; + Log(Debug::Error) << "Missing OSG plugin: " << libraryName; haveAllPlugins = false; } } From 715efe892f8429960258d3b953c4c77781e200c4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 5 Mar 2024 10:07:35 +0400 Subject: [PATCH 1176/2167] Load YAML files via Lua (feature 7590) --- CHANGELOG.md | 1 + CI/file_name_exceptions.txt | 1 + CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 2 + apps/openmw/mwlua/markupbindings.cpp | 32 ++ apps/openmw/mwlua/markupbindings.hpp | 13 + apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/lua/test_yaml.cpp | 354 ++++++++++++++++++ components/CMakeLists.txt | 2 +- components/lua/yamlloader.cpp | 241 ++++++++++++ components/lua/yamlloader.hpp | 50 +++ docs/source/reference/lua-scripting/api.rst | 1 + .../reference/lua-scripting/openmw_markup.rst | 7 + .../lua-scripting/tables/packages.rst | 2 + files/data/scripts/omw/console/global.lua | 1 + files/data/scripts/omw/console/local.lua | 1 + files/data/scripts/omw/console/menu.lua | 1 + files/data/scripts/omw/console/player.lua | 1 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/markup.lua | 37 ++ 21 files changed, 750 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwlua/markupbindings.cpp create mode 100644 apps/openmw/mwlua/markupbindings.hpp create mode 100644 apps/openmw_test_suite/lua/test_yaml.cpp create mode 100644 components/lua/yamlloader.cpp create mode 100644 components/lua/yamlloader.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_markup.rst create mode 100644 files/lua_api/openmw/markup.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index fe081224ce..f1f36e594c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,6 +194,7 @@ Feature #7546: Start the game on Fredas Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music + Feature #7590: [Lua] Ability to deserialize YAML data from scripts Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt index c3bcee8661..dff3527348 100644 --- a/CI/file_name_exceptions.txt +++ b/CI/file_name_exceptions.txt @@ -20,6 +20,7 @@ apps/openmw_test_suite/lua/test_storage.cpp apps/openmw_test_suite/lua/test_ui_content.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp apps/openmw_test_suite/lua/test_inputactions.cpp +apps/openmw_test_suite/lua/test_yaml.cpp apps/openmw_test_suite/misc/test_endianness.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_stringops.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ca25fd05ff..cfcd167cd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 56) +set(OPENMW_LUA_API_REVISION 57) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5fb06881ec..08bf11d194 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings + classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 0de10827e0..553b8af8f6 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -14,6 +14,7 @@ #include "debugbindings.hpp" #include "inputbindings.hpp" #include "localscripts.hpp" +#include "markupbindings.hpp" #include "menuscripts.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" @@ -35,6 +36,7 @@ namespace MWLua { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, + { "openmw.markup", initMarkupPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.vfs", initVFSPackage(context) }, }; diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp new file mode 100644 index 0000000000..997674b45d --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -0,0 +1,32 @@ +#include "markupbindings.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + auto file = vfs->getNormalized(normalizedName); + return LuaUtil::YamlLoader::load(*file, lua->sol()); + }; + api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { + return LuaUtil::YamlLoader::load(std::string(inputData), lua->sol()); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/markupbindings.hpp b/apps/openmw/mwlua/markupbindings.hpp new file mode 100644 index 0000000000..9105ab5edf --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_MARKUPBINDINGS_H +#define MWLUA_MARKUPBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context&); +} + +#endif // MWLUA_MARKUPBINDINGS_H diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 71da2de590..f3f50cea71 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -29,6 +29,7 @@ file(GLOB UNITTEST_SRC_FILES lua/test_storage.cpp lua/test_async.cpp lua/test_inputactions.cpp + lua/test_yaml.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp new file mode 100644 index 0000000000..c7d484cf51 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -0,0 +1,354 @@ +#include "gmock/gmock.h" +#include + +#include + +#include "../testing_util.hpp" + +namespace +{ + template + bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return result.as() == requiredValue; + } + + bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::boolean) + return false; + + return result.as() == requiredValue; + } + + bool checkNil(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + return result == sol::nil; + } + + bool checkNan(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return std::isnan(result.as()); + } + + bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == requiredValue; + } + + bool checkString(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == inputData; + } + + TEST(LuaUtilYamlLoader, ScalarTypeDeduction) + { + sol::state lua; + + ASSERT_TRUE(checkNil(lua, "null")); + ASSERT_TRUE(checkNil(lua, "Null")); + ASSERT_TRUE(checkNil(lua, "NULL")); + ASSERT_TRUE(checkNil(lua, "~")); + ASSERT_TRUE(checkNil(lua, "")); + ASSERT_FALSE(checkNil(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "'null'", "null")); + + ASSERT_TRUE(checkNumber(lua, "017", 17)); + ASSERT_TRUE(checkNumber(lua, "-017", -17)); + ASSERT_TRUE(checkNumber(lua, "+017", 17)); + ASSERT_TRUE(checkNumber(lua, "17", 17)); + ASSERT_TRUE(checkNumber(lua, "-17", -17)); + ASSERT_TRUE(checkNumber(lua, "+17", 17)); + ASSERT_TRUE(checkNumber(lua, "0o17", 15)); + ASSERT_TRUE(checkString(lua, "-0o17")); + ASSERT_TRUE(checkString(lua, "+0o17")); + ASSERT_TRUE(checkString(lua, "0b1")); + ASSERT_TRUE(checkString(lua, "1:00")); + ASSERT_TRUE(checkString(lua, "'17'", "17")); + ASSERT_TRUE(checkNumber(lua, "0x17", 23)); + ASSERT_TRUE(checkString(lua, "'-0x17'", "-0x17")); + ASSERT_TRUE(checkString(lua, "'+0x17'", "+0x17")); + + ASSERT_TRUE(checkNumber(lua, "2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "-2.1e-05", -2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "+2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "-2.1e+5", -210000)); + ASSERT_TRUE(checkNumber(lua, "+2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-0.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, "-2.7", -2.7)); + ASSERT_TRUE(checkNumber(lua, "+2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, ".27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "27.", 27.0)); + ASSERT_TRUE(checkNumber(lua, "-27.", -27.0)); + ASSERT_TRUE(checkNumber(lua, "+27.", 27.0)); + + ASSERT_TRUE(checkNan(lua, ".nan")); + ASSERT_TRUE(checkNan(lua, ".NaN")); + ASSERT_TRUE(checkNan(lua, ".NAN")); + ASSERT_FALSE(checkNan(lua, "nan")); + ASSERT_FALSE(checkNan(lua, ".nAn")); + ASSERT_TRUE(checkString(lua, "'.nan'", ".nan")); + ASSERT_TRUE(checkString(lua, ".nAn")); + + ASSERT_TRUE(checkNumber(lua, "1.7976931348623157E+308", std::numeric_limits::max())); + ASSERT_TRUE(checkNumber(lua, "-1.7976931348623157E+308", std::numeric_limits::lowest())); + ASSERT_TRUE(checkNumber(lua, "2.2250738585072014e-308", std::numeric_limits::min())); + ASSERT_TRUE(checkNumber(lua, ".inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.Inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.INF", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkString(lua, ".INf")); + ASSERT_TRUE(checkString(lua, "-.INf")); + ASSERT_TRUE(checkString(lua, "+.INf")); + + ASSERT_TRUE(checkBool(lua, "true", true)); + ASSERT_TRUE(checkBool(lua, "false", false)); + ASSERT_TRUE(checkBool(lua, "True", true)); + ASSERT_TRUE(checkBool(lua, "False", false)); + ASSERT_TRUE(checkBool(lua, "TRUE", true)); + ASSERT_TRUE(checkBool(lua, "FALSE", false)); + ASSERT_TRUE(checkString(lua, "y")); + ASSERT_TRUE(checkString(lua, "n")); + ASSERT_TRUE(checkString(lua, "On")); + ASSERT_TRUE(checkString(lua, "Off")); + ASSERT_TRUE(checkString(lua, "YES")); + ASSERT_TRUE(checkString(lua, "NO")); + ASSERT_TRUE(checkString(lua, "TrUe")); + ASSERT_TRUE(checkString(lua, "FaLsE")); + ASSERT_TRUE(checkString(lua, "'true'", "true")); + } + + TEST(LuaUtilYamlLoader, DepthLimit) + { + sol::state lua; + + const std::string input = R"( + array1: &array1_alias + [ + <: *array1_alias, + foo + ] + )"; + + bool depthExceptionThrown = false; + try + { + YAML::Node root = YAML::Load(input); + sol::object result = LuaUtil::YamlLoader::load(input, lua); + } + catch (const std::runtime_error& e) + { + ASSERT_EQ(std::string(e.what()), "Maximum layers depth exceeded, probably caused by a circular reference"); + depthExceptionThrown = true; + } + + ASSERT_TRUE(depthExceptionThrown); + } + + TEST(LuaUtilYamlLoader, Collections) + { + sol::state lua; + + sol::object map = LuaUtil::YamlLoader::load("{ x: , y: 2, 4: 5 }", lua); + ASSERT_EQ(map.as()["x"], sol::nil); + ASSERT_EQ(map.as()["y"], 2); + ASSERT_EQ(map.as()[4], 5); + + sol::object array = LuaUtil::YamlLoader::load("[ 3, 4 ]", lua); + ASSERT_EQ(array.as()[1], 3); + + sol::object emptyTable = LuaUtil::YamlLoader::load("{}", lua); + ASSERT_TRUE(emptyTable.as().empty()); + + sol::object emptyArray = LuaUtil::YamlLoader::load("[]", lua); + ASSERT_TRUE(emptyArray.as().empty()); + + ASSERT_THROW(LuaUtil::YamlLoader::load("{ null: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::YamlLoader::load("{ .nan: 1 }", lua), std::runtime_error); + + const std::string scalarArrayInput = R"( + - First Scalar + - 1 + - true)"; + + sol::object scalarArray = LuaUtil::YamlLoader::load(scalarArrayInput, lua); + ASSERT_EQ(scalarArray.as()[1], std::string("First Scalar")); + ASSERT_EQ(scalarArray.as()[2], 1); + ASSERT_EQ(scalarArray.as()[3], true); + + const std::string scalarMapWithCommentsInput = R"( + string: 'str' # String value + integer: 65 # Integer value + float: 0.278 # Float value + bool: false # Boolean value)"; + + sol::object scalarMapWithComments = LuaUtil::YamlLoader::load(scalarMapWithCommentsInput, lua); + ASSERT_EQ(scalarMapWithComments.as()["string"], std::string("str")); + ASSERT_EQ(scalarMapWithComments.as()["integer"], 65); + ASSERT_EQ(scalarMapWithComments.as()["float"], 0.278); + ASSERT_EQ(scalarMapWithComments.as()["bool"], false); + + const std::string mapOfArraysInput = R"( + x: + - 2 + - 7 + - true + y: + - aaa + - false + - 1)"; + + sol::object mapOfArrays = LuaUtil::YamlLoader::load(mapOfArraysInput, lua); + ASSERT_EQ(mapOfArrays.as()["x"][3], true); + ASSERT_EQ(mapOfArrays.as()["y"][1], std::string("aaa")); + + const std::string arrayOfMapsInput = R"( + - + name: Name1 + hr: 65 + avg: 0.278 + - + name: Name2 + hr: 63 + avg: 0.288)"; + + sol::object arrayOfMaps = LuaUtil::YamlLoader::load(arrayOfMapsInput, lua); + ASSERT_EQ(arrayOfMaps.as()[1]["avg"], 0.278); + ASSERT_EQ(arrayOfMaps.as()[2]["name"], std::string("Name2")); + + const std::string arrayOfArraysInput = R"( + - [Name1, 65, 0.278] + - [Name2 , 63, 0.288])"; + + sol::object arrayOfArrays = LuaUtil::YamlLoader::load(arrayOfArraysInput, lua); + ASSERT_EQ(arrayOfArrays.as()[1][2], 65); + ASSERT_EQ(arrayOfArrays.as()[2][1], std::string("Name2")); + + const std::string mapOfMapsInput = R"( + Name1: {hr: 65, avg: 0.278} + Name2 : { + hr: 63, + avg: 0.288, + })"; + + sol::object mapOfMaps = LuaUtil::YamlLoader::load(mapOfMapsInput, lua); + ASSERT_EQ(mapOfMaps.as()["Name1"]["hr"], 65); + ASSERT_EQ(mapOfMaps.as()["Name2"]["avg"], 0.288); + } + + TEST(LuaUtilYamlLoader, Structures) + { + sol::state lua; + + const std::string twoDocumentsInput + = "---\n" + " - First Scalar\n" + " - 2\n" + " - true\n" + "\n" + "---\n" + " - Second Scalar\n" + " - 3\n" + " - false"; + + sol::object twoDocuments = LuaUtil::YamlLoader::load(twoDocumentsInput, lua); + ASSERT_EQ(twoDocuments.as()[1][1], std::string("First Scalar")); + ASSERT_EQ(twoDocuments.as()[2][3], false); + + const std::string anchorInput = R"(--- + x: + - Name1 + # Following node labeled as "a" + - &a Value1 + y: + - *a # Subsequent occurrence + - Name2)"; + + sol::object anchor = LuaUtil::YamlLoader::load(anchorInput, lua); + ASSERT_EQ(anchor.as()["y"][1], std::string("Value1")); + + const std::string compoundKeyInput = R"( + ? - String1 + - String2 + : - 1 + + ? [ String3, + String4 ] + : [ 2, 3, 4 ])"; + + ASSERT_THROW(LuaUtil::YamlLoader::load(compoundKeyInput, lua), std::runtime_error); + + const std::string compactNestedMappingInput = R"( + - item : Item1 + quantity: 2 + - item : Item2 + quantity: 4 + - item : Item3 + quantity: 11)"; + + sol::object compactNestedMapping = LuaUtil::YamlLoader::load(compactNestedMappingInput, lua); + ASSERT_EQ(compactNestedMapping.as()[2]["quantity"], 4); + } + + TEST(LuaUtilYamlLoader, Scalars) + { + sol::state lua; + + const std::string literalScalarInput = R"(--- | + a + b + c)"; + + ASSERT_TRUE(checkString(lua, literalScalarInput, "a\nb\nc")); + + const std::string foldedScalarInput = R"(--- > + a + b + c)"; + + ASSERT_TRUE(checkString(lua, foldedScalarInput, "a b c")); + + const std::string multiLinePlanarScalarsInput = R"( + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n")"; + + sol::object multiLinePlanarScalars = LuaUtil::YamlLoader::load(multiLinePlanarScalarsInput, lua); + ASSERT_TRUE( + multiLinePlanarScalars.as()["plain"] == std::string("This unquoted scalar spans many lines.")); + ASSERT_TRUE(multiLinePlanarScalars.as()["quoted"] == std::string("So does this quoted scalar.\n")); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 01efcd7c05..f593e0f0f2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -65,7 +65,7 @@ list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE} add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box inputactions + shapes/box inputactions yamlloader ) add_component_dir (l10n diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp new file mode 100644 index 0000000000..14553cfac4 --- /dev/null +++ b/components/lua/yamlloader.cpp @@ -0,0 +1,241 @@ +#include "yamlloader.hpp" + +#include +#include + +#include +#include + +namespace LuaUtil +{ + namespace + { + constexpr uint64_t maxDepth = 250; + } + + sol::object YamlLoader::load(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return LuaUtil::YamlLoader::load(rootNodes, lua); + } + + sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); + } + + sol::object YamlLoader::load(const std::vector rootNodes, const sol::state_view& lua) + { + if (rootNodes.empty()) + return sol::nil; + + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) + { + documentsTable.add(getNode(root, lua, 1)); + } + + return documentsTable; + } + + sol::object YamlLoader::getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) + return sol::nil; + + nodeError(node, "An unknown YAML node encountered"); + } + + sol::table YamlLoader::getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& pair : node) + { + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); + + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); + + childTable[key] = getNode(pair.second, lua, depth); + } + + return childTable; + } + + sol::table YamlLoader::getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) + { + childTable.add(getNode(child, lua, depth)); + } + + return childTable; + } + + YamlLoader::ScalarType YamlLoader::getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag'" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; + + return ScalarType::String; + } + + sol::object YamlLoader::getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) + { + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); + + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); + + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); + + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); + } + } + + [[noreturn]] void YamlLoader::nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); + } + +} diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp new file mode 100644 index 0000000000..1ca95223cd --- /dev/null +++ b/components/lua/yamlloader.hpp @@ -0,0 +1,50 @@ +#ifndef COMPONENTS_LUA_YAMLLOADER_H +#define COMPONENTS_LUA_YAMLLOADER_H + +#include +#include +#include +#include + +namespace LuaUtil +{ + + class YamlLoader + { + public: + static sol::object load(const std::string& input, const sol::state_view& lua); + + static sol::object load(std::istream& input, const sol::state_view& lua); + + private: + enum class ScalarType + { + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; + + static sol::object load(const std::vector rootNodes, const sol::state_view& lua); + + static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static ScalarType getScalarType(const YAML::Node& node); + + static sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); + + [[noreturn]] static void nodeError(const YAML::Node& node, const std::string& message); + }; + +} + +#endif // COMPONENTS_LUA_YAMLLOADER_H diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0b700d46a3..fb354a10a7 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -19,6 +19,7 @@ Lua API reference openmw_animation openmw_async openmw_vfs + openmw_markup openmw_world openmw_self openmw_nearby diff --git a/docs/source/reference/lua-scripting/openmw_markup.rst b/docs/source/reference/lua-scripting/openmw_markup.rst new file mode 100644 index 0000000000..b37afec88f --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_markup.rst @@ -0,0 +1,7 @@ +Package openmw.markup +===================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_markup.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 247bd7eacc..fd82608aed 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -19,6 +19,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.markup ` | everywhere | | API to work with markup languages. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.self ` | by local scripts | | Full access to the object the script is attached to. | diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index bba0cbc7b3..d1d5ae423a 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -23,6 +23,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), world = require('openmw.world'), aux_util = require('openmw_aux.util'), diff --git a/files/data/scripts/omw/console/local.lua b/files/data/scripts/omw/console/local.lua index 6962b9e798..1acd18df0c 100644 --- a/files/data/scripts/omw/console/local.lua +++ b/files/data/scripts/omw/console/local.lua @@ -25,6 +25,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/data/scripts/omw/console/menu.lua b/files/data/scripts/omw/console/menu.lua index 9d6dbaf1d7..b6851bc646 100644 --- a/files/data/scripts/omw/console/menu.lua +++ b/files/data/scripts/omw/console/menu.lua @@ -47,6 +47,7 @@ local env = { core = require('openmw.core'), storage = require('openmw.storage'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), ui = require('openmw.ui'), diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index 6d0ee790a9..9d2e372a93 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -72,6 +72,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), nearby = require('openmw.nearby'), diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 0b960ea259..526ee90955 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -17,6 +17,7 @@ set(LUA_API_FILES openmw/debug.lua openmw/input.lua openmw/interfaces.lua + openmw/markup.lua openmw/menu.lua openmw/nearby.lua openmw/postprocessing.lua diff --git a/files/lua_api/openmw/markup.lua b/files/lua_api/openmw/markup.lua new file mode 100644 index 0000000000..c8776281d3 --- /dev/null +++ b/files/lua_api/openmw/markup.lua @@ -0,0 +1,37 @@ +--- +-- `openmw.markup` allows to work with markup languages. +-- @module markup +-- @usage local markup = require('openmw.markup') + + + +--- +-- Convert YAML data to Lua object +-- @function [parent=#markup] decodeYaml +-- @param #string inputData Data to decode. It has such limitations: +-- +-- 1. YAML format of [version 1.2](https://yaml.org/spec/1.2.2) is used. +-- 2. Map keys should be scalar values (strings, booleans, numbers). +-- 3. YAML tag system is not supported. +-- 4. If scalar is quoted, it is treated like a string. +-- Othewise type deduction works according to YAML 1.2 [Core Schema](https://yaml.org/spec/1.2.2/#103-core-schema). +-- 5. Circular dependencies between YAML nodes are not allowed. +-- 6. Lua 5.1 does not have integer numbers - all numeric scalars use a #number type (which use a floating point). +-- 7. Integer scalars numbers values are limited by the "int" range. Use floating point notation for larger number in YAML files. +-- @return #any Lua object (can be table or scalar value). +-- @usage local result = markup.decodeYaml('{ "x": 1 }'); +-- -- prints 1 +-- print(result["x"]) + +--- +-- Load YAML file from VFS to Lua object. Conventions are the same as in @{#markup.decodeYaml}. +-- @function [parent=#markup] loadYaml +-- @param #string fileName YAML file path in VFS. +-- @return #any Lua object (can be table or scalar value). +-- @usage -- file contains '{ "x": 1 }' data +-- local result = markup.loadYaml('test.yaml'); +-- -- prints 1 +-- print(result["x"]) + + +return nil From b52f721318a86765fa1e72384f47e8c459f6dae1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 14 Mar 2024 17:08:23 +0100 Subject: [PATCH 1177/2167] Use getSubComposite to read AMBI --- components/esm3/loadcell.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 3c651fac1a..b1efea1aec 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -158,7 +158,7 @@ namespace ESM mWater = waterLevel; break; case fourCC("AMBI"): - esm.getHT(mAmbi.mAmbient, mAmbi.mSunlight, mAmbi.mFog, mAmbi.mFogDensity); + esm.getSubComposite(mAmbi); mHasAmbi = true; break; case fourCC("RGNN"): From 2f4049106533a99beda42de95a22dde3108e471b Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 14 Mar 2024 18:08:18 +0100 Subject: [PATCH 1178/2167] Fix crash when destroying UI element in the same frame as creating it --- components/lua_ui/element.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9d45f6ed7f..c239335abb 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -313,9 +313,8 @@ namespace LuaUi { if (mState != Destroyed) { - if (mState != New) + if (mRoot != nullptr) { - assert(mRoot); destroyRoot(mRoot); mRoot = nullptr; } From 68ed77181683da3e417341c8cebc24cab056dde7 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 14 Mar 2024 18:08:38 +0100 Subject: [PATCH 1179/2167] Fix element detachment logic --- components/lua_ui/element.cpp | 9 --------- components/lua_ui/widget.cpp | 10 +++++++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index c239335abb..ffd763b40b 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -99,15 +99,6 @@ namespace LuaUi element->create(depth + 1); WidgetExtension* root = element->mRoot; assert(root); - WidgetExtension* parent = root->getParent(); - if (parent) - { - auto children = parent->children(); - std::erase(children, root); - parent->setChildren(children); - root->widget()->detachFromWidget(); - } - root->updateCoord(); return root; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index be0ea70387..e61c36c452 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -100,6 +100,8 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { + if (ext->mParent != this) + ext->detachFromParent(); ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,7 +116,13 @@ namespace LuaUi void WidgetExtension::detachFromParent() { - mParent = nullptr; + if (mParent) + { + auto children = mParent->children(); + std::erase(children, this); + mParent->setChildren(children); + mParent = nullptr; + } widget()->detachFromWidget(); } From 28131fd62ba7fef091e2cb67670529bc280af033 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 Mar 2024 23:39:19 +0000 Subject: [PATCH 1180/2167] Fixes for a whole bunch of warnings These warnings were always enabled, but we didn't see them due to https://gitlab.com/OpenMW/openmw/-/issues/7882. I do not fully understand the cause of 7822 as I can't repro it in a minimal CMake project. Some of these fixes are thought through. Some are sensible best guesses. Some are kind of a stab in the dark as I don't know whether there was a possible bug the warning was telling me about that I've done nothing to help by introducing a static_cast. Nearly all of these warnings were about some kind of narrowing conversion, so I'm not sure why they weren't firing with GCC and Clang, which have -Wall -Wextra -pedantic set, which should imply -Wnarrowing, and they can't have been affected by 7882. There were also some warnings being triggered from Boost code. The vast majority of library headers that do questionable things weren't firing warnings off, but for some reason, /external:I wasn't putting these Boost headers into external mode. We need these warnings dealt with one way or another so we can switch the default Windows CI from MSBuild (which doesn't do ccache) to Ninja (which does). I have the necessary magic for that on a branch, but the branch won't build because of these warnings. --- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++-- apps/openmw/mwgui/messagebox.cpp | 2 +- apps/openmw/mwgui/messagebox.hpp | 2 +- apps/openmw/mwgui/screenfader.cpp | 4 ++-- apps/openmw/mwgui/trainingwindow.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwinput/bindingsmanager.cpp | 4 ++-- apps/openmw/mwinput/bindingsmanager.hpp | 4 ++-- apps/openmw/mwsound/ffmpeg_decoder.cpp | 10 ++++++---- apps/openmw/mwsound/ffmpeg_decoder.hpp | 4 ++-- apps/openmw/mwsound/loudness.cpp | 6 +++--- components/bsa/ba2dx10file.cpp | 13 +++++++++---- components/bsa/ba2gnrlfile.cpp | 9 ++++++--- components/bsa/compressedbsafile.cpp | 9 ++++++--- components/debug/debugdraw.cpp | 6 +++--- components/debug/debugging.cpp | 10 +++++++++- components/detournavigator/collisionshapetype.cpp | 2 +- components/files/windowspath.cpp | 2 +- components/fx/lexer.hpp | 2 +- components/l10n/messagebundles.cpp | 14 +++++++------- components/lua/scriptscontainer.cpp | 2 +- components/lua/utf8.cpp | 7 ++++--- components/misc/utf8stream.hpp | 2 +- components/sdlutil/sdlmappings.cpp | 2 +- 26 files changed, 77 insertions(+), 55 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c252e0c490..9e8cb05d34 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -290,7 +290,7 @@ namespace MWBase virtual void setEnemy(const MWWorld::Ptr& enemy) = 0; - virtual int getMessagesCount() const = 0; + virtual std::size_t getMessagesCount() const = 0; virtual const Translation::Storage& getTranslationDataStorage() const = 0; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 85c7d8ba88..d46a88e580 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -246,12 +246,12 @@ namespace MWGui bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - int index = found - keyFocusList.begin(); + auto index = found - keyFocusList.begin(); index = forward ? (index + 1) : (index - 1); if (wrap) index = (index + keyFocusList.size()) % keyFocusList.size(); else - index = std::clamp(index, 0, keyFocusList.size() - 1); + index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index b27adacd0f..1d6e1511c4 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,7 +28,7 @@ namespace MWGui MessageBoxManager::clear(); } - int MessageBoxManager::getMessagesCount() + std::size_t MessageBoxManager::getMessagesCount() { return mMessageBoxes.size(); } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index bb61bd6bd9..feb717e0ad 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -29,7 +29,7 @@ namespace MWGui bool immediate = false, int defaultFocus = -1); bool isInteractiveMessageBox(); - int getMessagesCount(); + std::size_t getMessagesCount(); const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe.get(); } diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index e22517a360..22c6a803f2 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -97,8 +97,8 @@ namespace MWGui imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); imageBox->setImageCoord( - MyGUI::IntCoord(texCoordOverride.left * imageSize.width, texCoordOverride.top * imageSize.height, - texCoordOverride.width * imageSize.width, texCoordOverride.height * imageSize.height)); + MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), static_cast(texCoordOverride.top * imageSize.height), + static_cast(texCoordOverride.width * imageSize.width), static_cast(texCoordOverride.height * imageSize.height))); } } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index fa4fd266b5..890aa0ba68 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -189,8 +189,8 @@ namespace MWGui mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); - MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2, false, 0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2f); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f, false, 0.2f); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 29b9cb0e84..4678a269e1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1685,9 +1685,9 @@ namespace MWGui mHud->setEnemy(enemy); } - int WindowManager::getMessagesCount() const + std::size_t WindowManager::getMessagesCount() const { - int count = 0; + std::size_t count = 0; if (mMessageBoxManager) count = mMessageBoxManager->getMessagesCount(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 3445ebdb9a..617570b336 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -313,7 +313,7 @@ namespace MWGui void setEnemy(const MWWorld::Ptr& enemy) override; - int getMessagesCount() const override; + std::size_t getMessagesCount() const override; const Translation::Storage& getTranslationDataStorage() const override; diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 3f505896f4..a6bab19673 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -627,12 +627,12 @@ namespace MWInput return mInputBinder->detectingBindingState(); } - void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mousePressed(arg, deviceID); } - void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mouseReleased(arg, deviceID); } diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index a11baf74de..bee9e07cf7 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -47,8 +47,8 @@ namespace MWInput SDL_GameController* getControllerOrNull() const; - void mousePressed(const SDL_MouseButtonEvent& evt, int deviceID); - void mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID); + void mousePressed(const SDL_MouseButtonEvent& evt, Uint8 deviceID); + void mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID); void mouseMoved(const SDLUtil::MouseMotionEvent& arg); void mouseWheelMoved(const SDL_MouseWheelEvent& arg); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index bd63d3de40..a6f3d0336f 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -21,7 +21,9 @@ namespace MWSound std::streamsize count = stream.gcount(); if (count == 0) return AVERROR_EOF; - return count; + if (count > std::numeric_limits::max()) + return AVERROR_BUG; + return static_cast(count); } catch (std::exception&) { @@ -72,7 +74,7 @@ namespace MWSound if (!mStream) return false; - int stream_idx = mStream - mFormatCtx->streams; + std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams; while (av_read_frame(mFormatCtx, &mPacket) >= 0) { /* Check if the packet belongs to this stream */ @@ -427,9 +429,9 @@ namespace MWSound size_t FFmpeg_Decoder::getSampleOffset() { - int delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) + std::size_t delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / av_get_bytes_per_sample(mOutputSampleFormat); - return (int)(mNextPts * mCodecCtx->sample_rate) - delay; + return static_cast(mNextPts * mCodecCtx->sample_rate) - delay; } FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 88dd3316f5..9d15888fcf 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -41,8 +41,8 @@ namespace MWSound AVPacket mPacket; AVFrame* mFrame; - int mFrameSize; - int mFramePos; + std::size_t mFrameSize; + std::size_t mFramePos; double mNextPts; diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index b1c1a3f2af..c99ef15e9f 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -15,8 +15,8 @@ namespace MWSound return; int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); - int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); - int advance = framesToBytes(1, mChannelConfig, mSampleType); + std::size_t numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); + std::size_t advance = framesToBytes(1, mChannelConfig, mSampleType); int segment = 0; int sample = 0; @@ -61,7 +61,7 @@ namespace MWSound if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); + size_t index = std::clamp(static_cast(sec * mSamplesPerSec), 0, mSamples.size() - 1); return mSamples[index]; } diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index aa3f8d0581..946a68fcd5 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -6,16 +6,21 @@ #include + +#if defined(_MSC_VER) +// why is this necessary? These are included with /external:I +#pragma warning(push) +#pragma warning(disable : 4706) +#pragma warning(disable : 4702) #include #include #include - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4706) #include #pragma warning(pop) #else +#include +#include +#include #include #endif diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 02df12593c..436f5cc4bc 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -6,15 +6,18 @@ #include -#include -#include - #if defined(_MSC_VER) +// why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include +#include #include #pragma warning(pop) #else +#include +#include #include #endif diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 99efe7a587..ea39b42540 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -30,15 +30,18 @@ #include -#include -#include #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include +#include #include #pragma warning(pop) #else +#include +#include #include #endif @@ -168,7 +171,7 @@ namespace Bsa name.resize(input.gcount()); if (name.back() != '\0') fail("Failed to read a filename: filename is too long"); - mHeader.mFileNamesLength -= input.gcount(); + mHeader.mFileNamesLength -= static_cast(input.gcount()); file.mName.insert(file.mName.begin(), folder.mName.begin(), folder.mName.end()); file.mName.insert(file.mName.begin() + folder.mName.size(), '\\'); } diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 2bc7358259..cd98fe2b6d 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -124,7 +124,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = (float(i) / float(subdiv)) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = height / 2.; vertices->push_back(pos); @@ -150,7 +150,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = -height / 2.; vertices->push_back(pos); @@ -162,7 +162,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2f, 1.); auto posTop = normal; posTop *= radius; auto posBot = posTop; diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index d170cf1929..e9e50ff836 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -6,7 +6,15 @@ #include #include +#ifdef _MSC_VER +// TODO: why is this necessary? this has /external:I +#pragma warning(push) +#pragma warning(disable : 4702) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include #include @@ -111,7 +119,7 @@ namespace Debug msg = msg.substr(1); char prefix[32]; - int prefixSize; + std::size_t prefixSize; { prefix[0] = '['; const auto now = std::chrono::system_clock::now(); diff --git a/components/detournavigator/collisionshapetype.cpp b/components/detournavigator/collisionshapetype.cpp index b20ae6147f..b68d5cd239 100644 --- a/components/detournavigator/collisionshapetype.cpp +++ b/components/detournavigator/collisionshapetype.cpp @@ -15,7 +15,7 @@ namespace DetourNavigator return static_cast(value); } std::string error("Invalid CollisionShapeType value: \""); - error += value; + error += std::to_string(value); error += '"'; throw std::invalid_argument(error); } diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 9be3d13a46..bbe0325b58 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -101,7 +101,7 @@ namespace Files { // Key existed, let's try to read the install dir std::array buf{}; - DWORD len = buf.size() * sizeof(wchar_t); + DWORD len = static_cast(buf.size() * sizeof(wchar_t)); if (RegQueryValueExW(hKey, L"Installed Path", nullptr, nullptr, reinterpret_cast(buf.data()), &len) == ERROR_SUCCESS) diff --git a/components/fx/lexer.hpp b/components/fx/lexer.hpp index 01b3a3a56a..fc7d4ec9d7 100644 --- a/components/fx/lexer.hpp +++ b/components/fx/lexer.hpp @@ -30,7 +30,7 @@ namespace fx public: struct Block { - int line; + std::size_t line; std::string_view content; }; diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 4656116487..9a3dc5e00f 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -77,7 +77,7 @@ namespace l10n { const auto key = it.first.as(); const auto value = it.second.as(); - icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), value.size())); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); @@ -115,7 +115,7 @@ namespace l10n std::vector argValues; for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), k.size()))); + argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); @@ -160,9 +160,9 @@ namespace l10n if (message) { if (!args.empty() && !argNames.empty()) - message->format(argNames.data(), args.data(), args.size(), result, success); + message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else - message->format(nullptr, nullptr, args.size(), result, success); + message->format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; @@ -174,15 +174,15 @@ namespace l10n } UParseError parseError; icu::MessageFormat defaultMessage( - icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), key.size())), defaultLocale, parseError, success); + icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) - defaultMessage.format(argNames.data(), args.data(), args.size(), result, success); + defaultMessage.format(argNames.data(), args.data(), static_cast(args.size()), result, success); else - defaultMessage.format(nullptr, nullptr, args.size(), result, success); + defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 9b4a119ba4..ff45b963ca 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -590,7 +590,7 @@ namespace LuaUtil updateTimerQueue(mGameTimersQueue, gameTime); } - static constexpr float instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames + static constexpr float instructionCountAvgCoef = 1.0f / 30; // averaging over approximately 30 frames void ScriptsContainer::statsNextFrame() { diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index b486766b6a..2a585dac2d 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -14,7 +14,7 @@ namespace return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); } - inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) + inline std::int64_t getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) { double integer; if (!arg.is()) @@ -25,7 +25,7 @@ namespace throw std::runtime_error( Misc::StringUtils::format("bad argument #%i to '%s' (number has no integer representation)", n, name)); - return integer; + return static_cast(integer); } // If the input 'pos' is negative, it is treated as counting from the end of the string, @@ -104,7 +104,8 @@ namespace LuaUtf8 throw std::runtime_error( "bad argument #" + std::to_string(i + 1) + " to 'char' (value out of range)"); - result += converter.to_bytes(codepoint); + // this feels dodgy if wchar_t is 16-bit as MAXUTF won't fit in sixteen bits + result += converter.to_bytes(static_cast(codepoint)); } return result; }; diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 271376834d..5eb5f99b84 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -75,7 +75,7 @@ public: return std::make_pair(chr, cur); } - int octets; + std::size_t octets; UnicodeChar chr; std::tie(octets, chr) = getOctetCount(*cur++); diff --git a/components/sdlutil/sdlmappings.cpp b/components/sdlutil/sdlmappings.cpp index fe248e6f70..8a82206c33 100644 --- a/components/sdlutil/sdlmappings.cpp +++ b/components/sdlutil/sdlmappings.cpp @@ -83,7 +83,7 @@ namespace SDLUtil Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button) { - Uint8 value = button.getValue() + 1; + Uint8 value = static_cast(button.getValue() + 1); if (value == SDL_BUTTON_RIGHT) value = SDL_BUTTON_MIDDLE; else if (value == SDL_BUTTON_MIDDLE) From ff3ffa13b6d4a40dd95cc8a4d77eec5c6cd6775b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 Mar 2024 23:54:22 +0000 Subject: [PATCH 1181/2167] Auto format --- apps/openmw/mwgui/screenfader.cpp | 7 ++++--- apps/openmw/mwinput/bindingsmanager.cpp | 5 +---- components/bsa/ba2dx10file.cpp | 5 ++--- components/bsa/ba2gnrlfile.cpp | 4 ++-- components/bsa/compressedbsafile.cpp | 5 ++--- components/debug/debugging.cpp | 5 +---- components/l10n/messagebundles.cpp | 12 ++++++++---- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 22c6a803f2..0068ba7960 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -96,9 +96,10 @@ namespace MWGui { imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); - imageBox->setImageCoord( - MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), static_cast(texCoordOverride.top * imageSize.height), - static_cast(texCoordOverride.width * imageSize.width), static_cast(texCoordOverride.height * imageSize.height))); + imageBox->setImageCoord(MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), + static_cast(texCoordOverride.top * imageSize.height), + static_cast(texCoordOverride.width * imageSize.width), + static_cast(texCoordOverride.height * imageSize.height))); } } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index a6bab19673..67e71bd0d0 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -170,10 +170,7 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void setDetectingKeyboard(bool detecting) - { - mDetectingKeyboard = detecting; - } + void setDetectingKeyboard(bool detecting) { mDetectingKeyboard = detecting; } private: ICS::InputControlSystem* mInputBinder; diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 946a68fcd5..82a5ee8473 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -6,22 +6,21 @@ #include - #if defined(_MSC_VER) // why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include +#include #include #include -#include #pragma warning(pop) #else #include +#include #include #include -#include #endif #include diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 436f5cc4bc..da5ad47029 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -12,13 +12,13 @@ #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include -#include #include +#include #pragma warning(pop) #else #include -#include #include +#include #endif #include diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index ea39b42540..14d90f5d91 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -30,19 +30,18 @@ #include - #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include -#include #include +#include #pragma warning(pop) #else #include -#include #include +#include #endif #include diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e9e50ff836..2d43886cab 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -190,10 +190,7 @@ namespace Debug CurrentDebugLevel = Verbose; } - virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) - { - return size; - } + virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { return size; } }; #if defined _WIN32 && defined _DEBUG diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 9a3dc5e00f..a46b05c6f4 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -77,7 +77,8 @@ namespace l10n { const auto key = it.first.as(); const auto value = it.second.as(); - icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), static_cast(value.size()))); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8( + icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); @@ -115,7 +116,8 @@ namespace l10n std::vector argValues; for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); + argNames.push_back( + icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); @@ -174,13 +176,15 @@ namespace l10n } UParseError parseError; icu::MessageFormat defaultMessage( - icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); + icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), + defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) - defaultMessage.format(argNames.data(), args.data(), static_cast(args.size()), result, success); + defaultMessage.format( + argNames.data(), args.data(), static_cast(args.size()), result, success); else defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); From 9638fbabb47467902deec226e448d5e16cdcac83 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:11:19 +0000 Subject: [PATCH 1182/2167] https://www.youtube.com/watch?v=2_6U9gkQeqY --- apps/openmw/mwinput/bindingsmanager.cpp | 5 ++++- components/debug/debugging.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 67e71bd0d0..a6bab19673 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -170,7 +170,10 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void setDetectingKeyboard(bool detecting) { mDetectingKeyboard = detecting; } + void setDetectingKeyboard(bool detecting) + { + mDetectingKeyboard = detecting; + } private: ICS::InputControlSystem* mInputBinder; diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 2d43886cab..e9e50ff836 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -190,7 +190,10 @@ namespace Debug CurrentDebugLevel = Verbose; } - virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { return size; } + virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) + { + return size; + } }; #if defined _WIN32 && defined _DEBUG From a06ab94a209e1123ec8c37eedc51c021f1ee4756 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:42:15 +0000 Subject: [PATCH 1183/2167] Canonicalise resolved representation of data directories --- components/config/gamesettings.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 976d5e20f2..21110562d5 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -35,14 +35,20 @@ void Config::GameSettings::validatePaths() for (const auto& dataDir : paths) { if (QDir(dataDir.value).exists()) - mDataDirs.append(dataDir); + { + SettingValue copy = dataDir; + copy.value = QDir(dataDir.value).canonicalPath(); + mDataDirs.append(copy); + } } // Do the same for data-local const QString& local = mSettings.value(QString("data-local")).value; if (!local.isEmpty() && QDir(local).exists()) - mDataLocal = local; + { + mDataLocal = QDir(local).canonicalPath(); + } } QString Config::GameSettings::getResourcesVfs() const From dd18e17c97d023669d06150634516a8a18b33cb6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:47:01 +0000 Subject: [PATCH 1184/2167] And now Clang's noticed questionable type conversions --- apps/openmw/mwsound/loudness.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index c99ef15e9f..440207910e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -18,8 +18,8 @@ namespace MWSound std::size_t numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); std::size_t advance = framesToBytes(1, mChannelConfig, mSampleType); - int segment = 0; - int sample = 0; + std::size_t segment = 0; + std::size_t sample = 0; while (segment < numSamples / samplesPerSegment) { float sum = 0; From b5f61a119a01bd24e9d5f74ccaeeef96ccf4d6b6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 13:42:28 +0000 Subject: [PATCH 1185/2167] min --- apps/openmw/mwsound/loudness.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index 440207910e..2a6ac5ac8e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -61,7 +61,7 @@ namespace MWSound if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = std::clamp(static_cast(sec * mSamplesPerSec), 0, mSamples.size() - 1); + size_t index = std::min(static_cast(sec * mSamplesPerSec), mSamples.size() - 1); return mSamples[index]; } From 009ccca978c2fbac73cfd5bc3f5d667603c60fcb Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Mar 2024 18:19:44 +0400 Subject: [PATCH 1186/2167] Modify sound API permissions --- CMakeLists.txt | 2 +- apps/openmw/mwlua/soundbindings.cpp | 104 ++++++++++++++++++---------- files/lua_api/openmw/ambient.lua | 22 ++++++ files/lua_api/openmw/core.lua | 42 ++++++----- 4 files changed, 110 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4abe65498..5263d849e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 57) +set(OPENMW_LUA_API_REVISION 58) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index ad4a498153..2023f2b341 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -11,6 +11,7 @@ #include #include "luamanagerimp.hpp" +#include "objectvariant.hpp" namespace { @@ -28,6 +29,27 @@ namespace float mFade = 1.f; }; + MWWorld::Ptr getMutablePtrOrThrow(const MWLua::ObjectVariant& variant) + { + if (variant.isLObject()) + throw std::runtime_error("Local scripts can only modify object they are attached to."); + + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + + MWWorld::Ptr getPtrOrThrow(const MWLua::ObjectVariant& variant) + { + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -121,6 +143,17 @@ namespace MWLua sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); }; + api["say"] + = [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + + api["stopSay"] = []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }; + api["isSayActive"] + = []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }; + api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; @@ -137,64 +170,61 @@ namespace MWLua api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); }; api["playSound3d"] - = [](std::string_view soundId, const Object& object, const sol::optional& options) { + = [](std::string_view soundId, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); ESM::RefId sound = ESM::RefId::deserializeText(soundId); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); MWBase::Environment::get().getSoundManager()->playSound3D( - object.ptr(), sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + ptr, sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; api["playSoundFile3d"] - = [](std::string_view fileName, const Object& object, const sol::optional& options) { + = [](std::string_view fileName, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); - MWBase::Environment::get().getSoundManager()->playSound3D(object.ptr(), fileName, args.mVolume, - args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, fileName, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; - api["stopSound3d"] = [](std::string_view soundId, const Object& object) { + api["stopSound3d"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), sound); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, sound); }; - api["stopSoundFile3d"] = [](std::string_view fileName, const Object& object) { - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), fileName); + api["stopSoundFile3d"] = [](std::string_view fileName, const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, fileName); }; - api["isSoundPlaying"] = [](std::string_view soundId, const Object& object) { + api["isSoundPlaying"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), sound); + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, sound); }; - api["isSoundFilePlaying"] = [](std::string_view fileName, const Object& object) { - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), fileName); + api["isSoundFilePlaying"] = [](std::string_view fileName, const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, fileName); }; - api["say"] = sol::overload( - [luaManager = context.mLuaManager]( - std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), VFS::Path::Normalized(fileName)); - if (text) - luaManager->addUIMessage(*text); - }, - [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); - if (text) - luaManager->addUIMessage(*text); - }); - api["stopSay"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - MWBase::Environment::get().getSoundManager()->stopSay(objPtr); - }, - []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }); - api["isSayActive"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - return MWBase::Environment::get().getSoundManager()->sayActive(objPtr); - }, - []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }); + api["say"] = [luaManager = context.mLuaManager]( + std::string_view fileName, const sol::object& object, sol::optional text) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->say(ptr, VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + api["stopSay"] = [](const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSay(ptr); + }; + api["isSayActive"] = [](const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->sayActive(ptr); + }; using SoundStore = MWWorld::Store; sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index c10e50ff4a..ff776f84fb 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -95,4 +95,26 @@ -- @return #boolean -- @usage local isPlaying = ambient.isMusicPlaying(); +--- +-- Play an ambient voiceover. +-- @function [parent=#ambient] say +-- @param #string fileName Path to sound file in VFS +-- @param #string text Subtitle text (optional) +-- @usage -- play voiceover and print messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") +-- @usage -- play voiceover, without messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3") + +--- +-- Stop an ambient voiceover +-- @function [parent=#ambient] stopSay +-- @param #string fileName Path to sound file in VFS +-- @usage ambient.stopSay(); + +--- +-- Check if an ambient voiceover is playing +-- @function [parent=#Sound] isSayActive +-- @return #boolean +-- @usage local isActive = isSayActive(); + return nil diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..46978ce903 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -714,6 +714,8 @@ --- -- Play a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSound3d -- @param #string soundId ID of Sound record to play -- @param #GameObject object Object to which we attach the sound @@ -733,6 +735,8 @@ --- -- Play a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object to which we attach the sound @@ -752,6 +756,8 @@ --- -- Stop a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSound3d -- @param #string soundId ID of Sound record to stop -- @param #GameObject object Object on which we want to stop sound @@ -759,6 +765,8 @@ --- -- Stop a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object on which we want to stop sound @@ -781,42 +789,32 @@ -- @usage local isPlaying = core.sound.isSoundFilePlaying("Sound\\test.mp3", object); --- --- Play an animated voiceover. Has two overloads: --- --- * With an "object" argument: play sound for given object, with speaking animation if possible --- * Without an "object" argument: play sound globally, without object +-- Play an animated voiceover. +-- In local scripts can be used only on self. -- @function [parent=#Sound] say -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to play an animated voiceover (optional) +-- @param #GameObject object Object on which we want to play an animated voiceover -- @param #string text Subtitle text (optional) -- @usage -- play voiceover for object and print messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object, "Subtitle text") --- @usage -- play voiceover globally and print messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") --- @usage -- play voiceover for object without messagebox +-- @usage -- play voiceover for object, without messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object) --- @usage -- play voiceover globally without messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3") --- --- Stop animated voiceover +-- Stop an animated voiceover +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSay -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to stop an animated voiceover (optional) --- @usage -- stop voice for given object --- core.sound.stopSay(object); --- @usage -- stop global voice --- core.sound.stopSay(); +-- @param #GameObject object Object on which we want to stop an animated voiceover +-- @usage core.sound.stopSay(object); --- --- Check if animated voiceover is playing +-- Check if an animated voiceover is playing -- @function [parent=#Sound] isSayActive --- @param #GameObject object Object on which we want to check an animated voiceover (optional) +-- @param #GameObject object Object on which we want to check an animated voiceover -- @return #boolean --- @usage -- check voice for given object --- local isActive = isSayActive(object); --- @usage -- check global voice --- local isActive = isSayActive(); +-- @usage local isActive = isSayActive(object); --- -- @type SoundRecord From 6da151cf771495bf91ab6aa9406bc3aa35f8cb92 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Mar 2024 20:12:47 +0400 Subject: [PATCH 1187/2167] Fix GCC build --- components/detournavigator/recastmesh.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index ac487d3b68..6d06db0799 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include From ddb2c15bc9d5f9351edccc66e942541f7578e19d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 16:31:02 +0000 Subject: [PATCH 1188/2167] Review --- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index d46a88e580..9d4971951a 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -246,12 +246,12 @@ namespace MWGui bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - auto index = found - keyFocusList.begin(); + std::ptrdiff_t index{ found - keyFocusList.begin() }; index = forward ? (index + 1) : (index - 1); if (wrap) index = (index + keyFocusList.size()) % keyFocusList.size(); else - index = std::clamp(index, 0, keyFocusList.size() - 1); + index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); From 2b53c2335f57bf0d9b5deab7d919a975fb137cb4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 21:26:03 +0100 Subject: [PATCH 1189/2167] Support printing stats table in json format --- scripts/osg_stats.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 3cdd0febae..d898accb10 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -7,6 +7,7 @@ set of keys over given range of frames. import click import collections +import json import matplotlib.pyplot import numpy import operator @@ -43,6 +44,12 @@ import termtables 'between Physics Actors and physics_time_taken. Format: --plot .') @click.option('--stats', type=str, multiple=True, help='Print table with stats for a given metric containing min, max, mean, median etc.') +@click.option('--stats_sum', is_flag=True, + help='Add a row to stats table for a sum per frame of all given stats metrics.') +@click.option('--stats_sort_by', type=str, default=None, multiple=True, + help='Sort stats table by given fields (source, key, sum, min, max etc).') +@click.option('--stats_table_format', type=click.Choice(['markdown', 'json']), default='markdown', + help='Print table with stats in given format.') @click.option('--precision', type=int, help='Format floating point numbers with given precision') @click.option('--timeseries_sum', is_flag=True, @@ -51,8 +58,6 @@ import termtables help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') @click.option('--timeseries_delta_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') -@click.option('--stats_sum', is_flag=True, - help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, @@ -67,14 +72,12 @@ import termtables help='Threshold for hist_over.') @click.option('--show_common_path_prefix', is_flag=True, help='Show common path prefix when applied to multiple files.') -@click.option('--stats_sort_by', type=str, default=None, multiple=True, - help='Sort stats table by given fields (source, key, sum, min, max etc).') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, commulative_timeseries, commulative_timeseries_sum, frame_number_name, hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, - timeseries_delta, timeseries_delta_sum): + timeseries_delta, timeseries_delta_sum, stats_table_format): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} if not show_common_path_prefix and len(sources) > 1: longest_common_prefix = os.path.commonprefix(list(sources.keys())) @@ -109,7 +112,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if plot: draw_plots(sources=frames, plots=plot) if stats: - print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, sort_by=stats_sort_by) + print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, + sort_by=stats_sort_by, table_format=stats_table_format) if hist_threshold: draw_hist_threshold(sources=frames, keys=matching_keys(hist_threshold), begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) @@ -291,7 +295,7 @@ def draw_plots(sources, plots): fig.canvas.manager.set_window_title('plots') -def print_stats(sources, keys, stats_sum, precision, sort_by): +def print_stats(sources, keys, stats_sum, precision, sort_by, table_format): stats = list() for name, frames in sources.items(): for key in keys: @@ -301,11 +305,22 @@ def print_stats(sources, keys, stats_sum, precision, sort_by): metrics = list(stats[0].keys()) if sort_by: stats.sort(key=operator.itemgetter(*sort_by)) - termtables.print( - [list(v.values()) for v in stats], - header=metrics, - style=termtables.styles.markdown, - ) + if table_format == 'markdown': + termtables.print( + [list(v.values()) for v in stats], + header=metrics, + style=termtables.styles.markdown, + ) + elif table_format == 'json': + table = list() + for row in stats: + row_table = dict() + for key, value in zip(metrics, row.values()): + row_table[key] = value + table.append(row_table) + print(json.dumps(table)) + else: + print(f'Unsupported table format: {table_format}') def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value): From 16410d09608ba3b41550b0eda2c2f6029dce068c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 23:52:55 +0100 Subject: [PATCH 1190/2167] Use std::string for ResourceManager cache key Otherwise terrain textures cache has zero hits because it stores not normalized paths. Due to implicit conversion it's possible to add entry with addEntryToObjectCache passing a string that is converted into normalized path. But then getRefFromObjectCache called with original value does not find this entry because it's not converted and overloaded operators are used instead. --- components/resource/niffilemanager.cpp | 2 +- components/resource/resourcemanager.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 0cc48d4247..c66c7de849 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -52,7 +52,7 @@ namespace Resource Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); - mCache->addEntryToObjectCache(name, obj); + mCache->addEntryToObjectCache(name.value(), obj); return file; } } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index e2626665c8..63ec95de63 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -72,7 +72,7 @@ namespace Resource double mExpiryDelay; }; - class ResourceManager : public GenericResourceManager + class ResourceManager : public GenericResourceManager { public: explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) From 9b412bc802a30a91328e2dc288782e84b6e29e08 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 01:55:14 +0100 Subject: [PATCH 1191/2167] Fix benchmark warning: -Wdeprecated-declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: apps/benchmarks/settings/access.cpp: In function ‘void {anonymous}::localStatic(benchmark::State&)’: apps/benchmarks/settings/access.cpp:43:37: warning: ‘typename std::enable_if<(std::is_trivially_copyable<_Tp>::value && (sizeof (Tp) <= sizeof (Tp*)))>::type benchmark::DoNotOptimize(const Tp&) [with Tp = float; typename std::enable_if<(std::is_trivially_copyable<_Tp>::value && (sizeof (Tp) <= sizeof (Tp*)))>::type = void]’ is deprecated: The const-ref version of this method can permit undesired compiler optimizations in benchmarks [-Wdeprecated-declarations] 43 | benchmark::DoNotOptimize(v); | ~~~~~~~~~~~~~~~~~~~~~~~~^~~ --- apps/benchmarks/settings/access.cpp | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/benchmarks/settings/access.cpp b/apps/benchmarks/settings/access.cpp index aecac2dac8..7660d0d55e 100644 --- a/apps/benchmarks/settings/access.cpp +++ b/apps/benchmarks/settings/access.cpp @@ -38,7 +38,7 @@ namespace { for (auto _ : state) { - static const float v = Settings::Manager::getFloat("sky blending start", "Fog"); + static float v = Settings::Manager::getFloat("sky blending start", "Fog"); benchmark::DoNotOptimize(v); } } @@ -47,8 +47,8 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); } @@ -58,9 +58,9 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); - static const int v3 = Settings::Manager::getInt("reflection detail", "Water"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static int v3 = Settings::Manager::getInt("reflection detail", "Water"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); benchmark::DoNotOptimize(v3); @@ -71,7 +71,8 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::fog().mSkyBlendingStart.get()); + float v = Settings::fog().mSkyBlendingStart.get(); + benchmark::DoNotOptimize(v); } } @@ -79,8 +80,10 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); } } @@ -88,9 +91,12 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); - benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + int v3 = Settings::water().mReflectionDetail.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); } } From 9ae7b542c63aaedb29cbc0178ed903268150e4a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 02:34:46 +0100 Subject: [PATCH 1192/2167] Fix warning: -Wmaybe-uninitialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In file included from apps/opencs/model/world/pathgrid.hpp:7, from apps/opencs/model/world/idcollection.hpp:15, from apps/opencs/model/world/idcollection.cpp:1: In constructor ‘constexpr ESM::Pathgrid::Pathgrid(ESM::Pathgrid&&)’, inlined from ‘constexpr CSMWorld::Pathgrid::Pathgrid(CSMWorld::Pathgrid&&)’ at apps/opencs/model/world/pathgrid.hpp:24:12, inlined from ‘constexpr CSMWorld::Record::Record(CSMWorld::Record&&)’ at apps/opencs/model/world/record.hpp:39:12, inlined from ‘std::__detail::__unique_ptr_t<_Tp> std::make_unique(_Args&& ...) [with _Tp = CSMWorld::Record; _Args = {CSMWorld::Record}]’ at /usr/include/c++/13.2.1/bits/unique_ptr.h:1070:30, inlined from ‘std::unique_ptr CSMWorld::Record::modifiedCopy() const [with ESXRecordT = CSMWorld::Pathgrid]’ at apps/opencs/model/world/record.hpp:92:116: components/esm3/loadpgrd.hpp:19:12: warning: ‘.CSMWorld::Record::mBase.CSMWorld::Pathgrid::.ESM::Pathgrid::mData’ may be used uninitialized [-Wmaybe-uninitialized] 19 | struct Pathgrid | ^~~~~~~~ In file included from apps/opencs/model/world/idcollection.hpp:8: apps/opencs/model/world/record.hpp: In member function ‘std::unique_ptr CSMWorld::Record::modifiedCopy() const [with ESXRecordT = CSMWorld::Pathgrid]’: apps/opencs/model/world/record.hpp:92:53: note: ‘’ declared here 92 | return std::make_unique>(Record(State_ModifiedOnly, nullptr, &(this->get()))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- apps/opencs/model/world/record.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index d1f64fbfef..35e4c82a35 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -19,6 +19,11 @@ namespace CSMWorld State mState; + explicit RecordBase(State state) + : mState(state) + { + } + virtual ~RecordBase() = default; virtual std::unique_ptr clone() const = 0; @@ -69,21 +74,18 @@ namespace CSMWorld template Record::Record() - : mBase() + : RecordBase(State_BaseOnly) + , mBase() , mModified() { } template Record::Record(State state, const ESXRecordT* base, const ESXRecordT* modified) + : RecordBase(state) + , mBase(base == nullptr ? ESXRecordT{} : *base) + , mModified(modified == nullptr ? ESXRecordT{} : *modified) { - if (base) - mBase = *base; - - if (modified) - mModified = *modified; - - this->mState = state; } template From aa0c9fb4cbeaba895479dc8c9bb2f188f6e5856e Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 07:45:52 +0000 Subject: [PATCH 1193/2167] Fix: cannot drag region into map, map columns are rectangular --- apps/opencs/view/world/regionmap.cpp | 18 +++++++++++++++++- apps/opencs/view/world/regionmap.hpp | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index a2847848d0..5c3aa11c8e 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -224,6 +224,10 @@ CSVWorld::RegionMap::RegionMap(const CSMWorld::UniversalId& universalId, CSMDoc: addAction(mViewInTableAction); setAcceptDrops(true); + + // Make columns square incase QSizeHint doesnt apply + for (int column = 0; column < this->model()->columnCount(); ++column) + this->setColumnWidth(column, this->rowHeight(0)); } void CSVWorld::RegionMap::selectAll() @@ -358,12 +362,24 @@ std::vector CSVWorld::RegionMap::getDraggedRecords() cons return ids; } +void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event) +{ + QModelIndex index = indexAt(event->pos()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); + if (mime != nullptr && (mime->holdsType(CSMWorld::UniversalId::Type_Region))) + { + event->accept(); + return; + } + + event->ignore(); +} + void CSVWorld::RegionMap::dropEvent(QDropEvent* event) { QModelIndex index = indexAt(event->pos()); bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern); - if (!index.isValid() || !exists) { return; diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b6c5078ea3..137b47ed83 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -59,6 +59,8 @@ namespace CSVWorld void mouseMoveEvent(QMouseEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; public: From 5fca45565c2822a109240fa1fb3bae0f90ec6741 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 07:46:21 +0000 Subject: [PATCH 1194/2167] Feature: display different brush for land vs water --- apps/opencs/model/world/regionmap.cpp | 33 +++++++++++++++++++++------ apps/opencs/model/world/regionmap.hpp | 3 ++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 5c22aedf4d..c27846a890 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,7 +1,9 @@ #include "regionmap.hpp" +#include #include #include +#include #include #include @@ -21,20 +23,36 @@ #include "data.hpp" #include "universalid.hpp" +namespace CSMWorld +{ + float getLandHeight(const CSMWorld::Cell& cell, CSMWorld::Data& data) + { + const IdCollection& lands = data.getLand(); + int landIndex = lands.searchId(cell.mId); + const Land& land = lands.getRecord(landIndex).get(); + + // If any part of land is above water, returns > 0 - otherwise returns < 0 + if (land.getLandData()) + return land.getLandData()->mMaxHeight - cell.mWater; + + return 0.0f; + } +} + CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted(false) { } -CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell) +CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell, float landHeight) { const Cell& cell2 = cell.get(); if (!cell2.isExterior()) throw std::logic_error("Interior cell in region map"); + mMaxLandHeight = landHeight; mDeleted = cell.isDeleted(); - mRegion = cell2.mRegion; mName = cell2.mName; } @@ -92,7 +110,7 @@ void CSMWorld::RegionMap::buildMap() if (cell2.isExterior()) { - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell2, mData)); CellCoordinates index = getIndex(cell2); @@ -140,7 +158,7 @@ void CSMWorld::RegionMap::addCells(int start, int end) { CellCoordinates index = getIndex(cell2); - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell.get(), mData)); addCell(index, description); } @@ -335,10 +353,11 @@ QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const auto iter = mColours.find(cell->second.mRegion); if (iter != mColours.end()) - return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff)); + return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff), + cell->second.mMaxLandHeight > 0 ? Qt::SolidPattern : Qt::CrossPattern); - if (cell->second.mRegion.empty()) - return QBrush(Qt::Dense6Pattern); // no region + if (cell->second.mRegion.empty()) // no region + return QBrush(cell->second.mMaxLandHeight > 0 ? Qt::Dense3Pattern : Qt::Dense6Pattern); return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 3f62c7b61d..e5a4d61337 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -40,13 +40,14 @@ namespace CSMWorld private: struct CellDescription { + float mMaxLandHeight; bool mDeleted; ESM::RefId mRegion; std::string mName; CellDescription(); - CellDescription(const Record& cell); + CellDescription(const Record& cell, float landHeight); }; Data& mData; From a62da201e5516ba677ade73214bb3b161ddd4a05 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 09:02:47 +0000 Subject: [PATCH 1195/2167] check for land index not -1, fix warning Signed-off-by: Sam Hellawell --- apps/opencs/model/world/regionmap.cpp | 4 +++- apps/opencs/view/world/regionmap.cpp | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index c27846a890..f555f0ea32 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -29,9 +29,11 @@ namespace CSMWorld { const IdCollection& lands = data.getLand(); int landIndex = lands.searchId(cell.mId); - const Land& land = lands.getRecord(landIndex).get(); + if (landIndex == -1) + return 0.0f; // If any part of land is above water, returns > 0 - otherwise returns < 0 + const Land& land = lands.getRecord(landIndex).get(); if (land.getLandData()) return land.getLandData()->mMaxHeight - cell.mWater; diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 5c3aa11c8e..17d0016afc 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -364,7 +364,6 @@ std::vector CSVWorld::RegionMap::getDraggedRecords() cons void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event) { - QModelIndex index = indexAt(event->pos()); const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (mime != nullptr && (mime->holdsType(CSMWorld::UniversalId::Type_Region))) { From ee2cc8aeb7b377127ff5b80acc2aa62b226f1b86 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 13:07:56 +0100 Subject: [PATCH 1196/2167] Fix build with MSVC 19.38 components\detournavigator\navigator.hpp(44): error C3861: 'assert': identifier not found --- components/detournavigator/navigator.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index f2acc8c9d6..378af081d0 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H +#include #include #include "heightfieldshape.hpp" From 4520ee465d3d9cf9c5d2da58d79340f58abd0555 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 16:26:26 +0400 Subject: [PATCH 1197/2167] Do not copy vector --- components/lua/yamlloader.cpp | 2 +- components/lua/yamlloader.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 14553cfac4..41b8fb346f 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -25,7 +25,7 @@ namespace LuaUtil return load(rootNodes, lua); } - sol::object YamlLoader::load(const std::vector rootNodes, const sol::state_view& lua) + sol::object YamlLoader::load(const std::vector& rootNodes, const sol::state_view& lua) { if (rootNodes.empty()) return sol::nil; diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 1ca95223cd..2ad9315149 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -30,7 +30,7 @@ namespace LuaUtil String }; - static sol::object load(const std::vector rootNodes, const sol::state_view& lua); + static sol::object load(const std::vector& rootNodes, const sol::state_view& lua); static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); From 8037ad7f001582655cc5136ce3c1148e6bfffc55 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 16:59:48 +0400 Subject: [PATCH 1198/2167] Remove unused includes --- apps/openmw_test_suite/lua/test_yaml.cpp | 1 - components/lua/yamlloader.hpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index c7d484cf51..198cf1d0b5 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,4 +1,3 @@ -#include "gmock/gmock.h" #include #include diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 2ad9315149..0ba2e5a1f1 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -1,9 +1,7 @@ #ifndef COMPONENTS_LUA_YAMLLOADER_H #define COMPONENTS_LUA_YAMLLOADER_H -#include #include -#include #include namespace LuaUtil From b657cb2e4ca138848657408e743f0a7e42b1784e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 17:22:07 +0400 Subject: [PATCH 1199/2167] Simplify code --- apps/openmw/mwlua/markupbindings.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp index 997674b45d..dacdb7ee2c 100644 --- a/apps/openmw/mwlua/markupbindings.cpp +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -19,8 +19,7 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { - auto normalizedName = VFS::Path::normalizeFilename(fileName); - auto file = vfs->getNormalized(normalizedName); + Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName)); return LuaUtil::YamlLoader::load(*file, lua->sol()); }; api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { From 2523afe9c2d5adf066dafba9395322986002217e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 17:22:25 +0400 Subject: [PATCH 1200/2167] Use namespace instead of static class --- apps/openmw_test_suite/lua/test_yaml.cpp | 2 + components/lua/yamlloader.cpp | 442 ++++++++++++----------- components/lua/yamlloader.hpp | 38 +- 3 files changed, 241 insertions(+), 241 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index 198cf1d0b5..759834c5e1 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include "../testing_util.hpp" diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 41b8fb346f..7e2736aa8e 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -3,239 +3,267 @@ #include #include +#include + #include #include namespace LuaUtil { - namespace + namespace YamlLoader { constexpr uint64_t maxDepth = 250; - } - sol::object YamlLoader::load(const std::string& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return LuaUtil::YamlLoader::load(rootNodes, lua); - } - - sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } - - sol::object YamlLoader::load(const std::vector& rootNodes, const sol::state_view& lua) - { - if (rootNodes.empty()) - return sol::nil; - - if (rootNodes.size() == 1) - return getNode(rootNodes[0], lua, 0); - - sol::table documentsTable(lua, sol::create); - for (const auto& root : rootNodes) + enum class ScalarType { - documentsTable.add(getNode(root, lua, 1)); + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; + + sol::object load(const std::vector& rootNodes, const sol::state_view& lua); + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + ScalarType getScalarType(const YAML::Node& node); + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + + sol::object load(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); } - return documentsTable; - } - - sol::object YamlLoader::getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - if (depth >= maxDepth) - throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); - - ++depth; - - if (node.IsMap()) - return getMap(node, lua, depth); - else if (node.IsSequence()) - return getArray(node, lua, depth); - else if (node.IsScalar()) - return getScalar(node, lua); - else if (node.IsNull()) - return sol::nil; - - nodeError(node, "An unknown YAML node encountered"); - } - - sol::table YamlLoader::getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); - - for (const auto& pair : node) + sol::object load(const std::vector& rootNodes, const sol::state_view& lua) { - if (pair.first.IsMap()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); - if (pair.first.IsSequence()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); - if (pair.first.IsNull()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); - - auto key = getNode(pair.first, lua, depth); - if (key.get_type() == sol::type::number && std::isnan(key.as())) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); - - childTable[key] = getNode(pair.second, lua, depth); - } - - return childTable; - } - - sol::table YamlLoader::getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); - - for (const auto& child : node) - { - childTable.add(getNode(child, lua, depth)); - } - - return childTable; - } - - YamlLoader::ScalarType YamlLoader::getScalarType(const YAML::Node& node) - { - const auto& tag = node.Tag(); - const auto& value = node.Scalar(); - if (tag == "!") - return ScalarType::String; - - // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no - // sense in Lua: - // 1. Both integers and floats use the "number" type prior to Lua 5.3 - // 2. Strings can be quoted, which is more readable than "!!str" - // 3. Most of possible conversions are invalid or their result is unclear - // So ignore this feature for now. - if (tag != "?") - nodeError(node, "An invalid tag'" + tag + "' encountered"); - - if (value.empty()) - return ScalarType::Null; - - // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) - static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), boolRegex)) - return ScalarType::Boolean; - - static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), decimalRegex)) - return ScalarType::Decimal; - - static const std::regex floatRegex( - "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), floatRegex)) - return ScalarType::Float; - - static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), octalRegex)) - return ScalarType::Octal; - - static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), hexdecimalRegex)) - return ScalarType::Hexadecimal; - - static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), infinityRegex)) - return ScalarType::Infinity; - - static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nanRegex)) - return ScalarType::NotNumber; - - static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nullRegex)) - return ScalarType::Null; - - return ScalarType::String; - } - - sol::object YamlLoader::getScalar(const YAML::Node& node, const sol::state_view& lua) - { - auto type = getScalarType(node); - const auto& value = node.Scalar(); - - switch (type) - { - case ScalarType::Null: + if (rootNodes.empty()) return sol::nil; - case ScalarType::String: - return sol::make_object(lua, value); - case ScalarType::NotNumber: - return sol::make_object(lua, std::nan("")); - case ScalarType::Infinity: + + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) { - if (!value.empty() && value[0] == '-') - return sol::make_object(lua, -std::numeric_limits::infinity()); - - return sol::make_object(lua, std::numeric_limits::infinity()); + documentsTable.add(getNode(root, lua, 1)); } - case ScalarType::Boolean: + + return documentsTable; + } + + sol::object load(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); + } + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) + return sol::nil; + + nodeError(node, "An unknown YAML node encountered"); + } + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& pair : node) { - if (Misc::StringUtils::lowerCase(value) == "true") - return sol::make_object(lua, true); + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); - if (Misc::StringUtils::lowerCase(value) == "false") - return sol::make_object(lua, false); + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); - nodeError(node, "Can not read a boolean value '" + value + "'"); + childTable[key] = getNode(pair.second, lua, depth); } - case ScalarType::Decimal: + + return childTable; + } + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) { - int offset = 0; - - // std::from_chars does not support "+" sign - if (!value.empty() && value[0] == '+') - ++offset; - - int result = 0; - const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); - if (status.ec == std::errc()) - return sol::make_object(lua, result); - - nodeError(node, "Can not read a decimal value '" + value + "'"); + childTable.add(getNode(child, lua, depth)); } - case ScalarType::Float: + + return childTable; + } + + ScalarType getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag'" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; + + return ScalarType::String; + } + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) { - // Not all compilers support std::from_chars for floats - double result = 0.0; - bool success = YAML::convert::decode(node, result); - if (success) - return sol::make_object(lua, result); + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); - nodeError(node, "Can not read a float value '" + value + "'"); - } - case ScalarType::Hexadecimal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); - nodeError(node, "Can not read a hexadecimal value '" + value + "'"); - } - case ScalarType::Octal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); - nodeError(node, "Can not read an octal value '" + value + "'"); + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); } - default: - nodeError(node, "An unknown scalar '" + value + "' encountered"); + } + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); } } - - [[noreturn]] void YamlLoader::nodeError(const YAML::Node& node, const std::string& message) - { - const auto& mark = node.Mark(); - std::string error = Misc::StringUtils::format( - " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); - throw std::runtime_error(message + error); - } - } diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 0ba2e5a1f1..c3339d7eb3 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -2,46 +2,16 @@ #define COMPONENTS_LUA_YAMLLOADER_H #include -#include namespace LuaUtil { - class YamlLoader + namespace YamlLoader { - public: - static sol::object load(const std::string& input, const sol::state_view& lua); + sol::object load(const std::string& input, const sol::state_view& lua); - static sol::object load(std::istream& input, const sol::state_view& lua); - - private: - enum class ScalarType - { - Boolean, - Decimal, - Float, - Hexadecimal, - Infinity, - NotNumber, - Null, - Octal, - String - }; - - static sol::object load(const std::vector& rootNodes, const sol::state_view& lua); - - static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static ScalarType getScalarType(const YAML::Node& node); - - static sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); - - [[noreturn]] static void nodeError(const YAML::Node& node, const std::string& message); - }; + sol::object load(std::istream& input, const sol::state_view& lua); + } } From cb831a59178a065c3f1bc76cea186ca55fd6e252 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 17 Mar 2024 17:22:10 +0400 Subject: [PATCH 1201/2167] Add more includes just for sure --- components/lua/yamlloader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 7e2736aa8e..30323fe116 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -1,7 +1,12 @@ #include "yamlloader.hpp" #include +#include +#include #include +#include +#include +#include #include From 2d3a8ca0fc77f6d1532e9e6086b292b4a1275cf5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 17 Mar 2024 18:15:23 +0400 Subject: [PATCH 1202/2167] Do not use an inner namespace --- apps/openmw/mwlua/markupbindings.cpp | 4 +- apps/openmw_test_suite/lua/test_yaml.cpp | 54 ++++++++++++------------ components/lua/yamlloader.cpp | 36 +++++++++------- components/lua/yamlloader.hpp | 14 +++--- 4 files changed, 57 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp index dacdb7ee2c..f0b9d67a51 100644 --- a/apps/openmw/mwlua/markupbindings.cpp +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -20,10 +20,10 @@ namespace MWLua api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName)); - return LuaUtil::YamlLoader::load(*file, lua->sol()); + return LuaUtil::loadYaml(*file, lua->sol()); }; api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { - return LuaUtil::YamlLoader::load(std::string(inputData), lua->sol()); + return LuaUtil::loadYaml(std::string(inputData), lua->sol()); }; return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index 759834c5e1..fa28889440 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,17 +1,19 @@ #include +#include +#include +#include + #include #include -#include "../testing_util.hpp" - namespace { template bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::number) return false; @@ -20,7 +22,7 @@ namespace bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::boolean) return false; @@ -29,13 +31,13 @@ namespace bool checkNil(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); return result == sol::nil; } bool checkNan(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::number) return false; @@ -44,7 +46,7 @@ namespace bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::string) return false; @@ -53,7 +55,7 @@ namespace bool checkString(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::string) return false; @@ -165,7 +167,7 @@ namespace try { YAML::Node root = YAML::Load(input); - sol::object result = LuaUtil::YamlLoader::load(input, lua); + sol::object result = LuaUtil::loadYaml(input, lua); } catch (const std::runtime_error& e) { @@ -180,29 +182,29 @@ namespace { sol::state lua; - sol::object map = LuaUtil::YamlLoader::load("{ x: , y: 2, 4: 5 }", lua); + sol::object map = LuaUtil::loadYaml("{ x: , y: 2, 4: 5 }", lua); ASSERT_EQ(map.as()["x"], sol::nil); ASSERT_EQ(map.as()["y"], 2); ASSERT_EQ(map.as()[4], 5); - sol::object array = LuaUtil::YamlLoader::load("[ 3, 4 ]", lua); + sol::object array = LuaUtil::loadYaml("[ 3, 4 ]", lua); ASSERT_EQ(array.as()[1], 3); - sol::object emptyTable = LuaUtil::YamlLoader::load("{}", lua); + sol::object emptyTable = LuaUtil::loadYaml("{}", lua); ASSERT_TRUE(emptyTable.as().empty()); - sol::object emptyArray = LuaUtil::YamlLoader::load("[]", lua); + sol::object emptyArray = LuaUtil::loadYaml("[]", lua); ASSERT_TRUE(emptyArray.as().empty()); - ASSERT_THROW(LuaUtil::YamlLoader::load("{ null: 1 }", lua), std::runtime_error); - ASSERT_THROW(LuaUtil::YamlLoader::load("{ .nan: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml("{ null: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml("{ .nan: 1 }", lua), std::runtime_error); const std::string scalarArrayInput = R"( - First Scalar - 1 - true)"; - sol::object scalarArray = LuaUtil::YamlLoader::load(scalarArrayInput, lua); + sol::object scalarArray = LuaUtil::loadYaml(scalarArrayInput, lua); ASSERT_EQ(scalarArray.as()[1], std::string("First Scalar")); ASSERT_EQ(scalarArray.as()[2], 1); ASSERT_EQ(scalarArray.as()[3], true); @@ -213,7 +215,7 @@ namespace float: 0.278 # Float value bool: false # Boolean value)"; - sol::object scalarMapWithComments = LuaUtil::YamlLoader::load(scalarMapWithCommentsInput, lua); + sol::object scalarMapWithComments = LuaUtil::loadYaml(scalarMapWithCommentsInput, lua); ASSERT_EQ(scalarMapWithComments.as()["string"], std::string("str")); ASSERT_EQ(scalarMapWithComments.as()["integer"], 65); ASSERT_EQ(scalarMapWithComments.as()["float"], 0.278); @@ -229,7 +231,7 @@ namespace - false - 1)"; - sol::object mapOfArrays = LuaUtil::YamlLoader::load(mapOfArraysInput, lua); + sol::object mapOfArrays = LuaUtil::loadYaml(mapOfArraysInput, lua); ASSERT_EQ(mapOfArrays.as()["x"][3], true); ASSERT_EQ(mapOfArrays.as()["y"][1], std::string("aaa")); @@ -243,7 +245,7 @@ namespace hr: 63 avg: 0.288)"; - sol::object arrayOfMaps = LuaUtil::YamlLoader::load(arrayOfMapsInput, lua); + sol::object arrayOfMaps = LuaUtil::loadYaml(arrayOfMapsInput, lua); ASSERT_EQ(arrayOfMaps.as()[1]["avg"], 0.278); ASSERT_EQ(arrayOfMaps.as()[2]["name"], std::string("Name2")); @@ -251,7 +253,7 @@ namespace - [Name1, 65, 0.278] - [Name2 , 63, 0.288])"; - sol::object arrayOfArrays = LuaUtil::YamlLoader::load(arrayOfArraysInput, lua); + sol::object arrayOfArrays = LuaUtil::loadYaml(arrayOfArraysInput, lua); ASSERT_EQ(arrayOfArrays.as()[1][2], 65); ASSERT_EQ(arrayOfArrays.as()[2][1], std::string("Name2")); @@ -262,7 +264,7 @@ namespace avg: 0.288, })"; - sol::object mapOfMaps = LuaUtil::YamlLoader::load(mapOfMapsInput, lua); + sol::object mapOfMaps = LuaUtil::loadYaml(mapOfMapsInput, lua); ASSERT_EQ(mapOfMaps.as()["Name1"]["hr"], 65); ASSERT_EQ(mapOfMaps.as()["Name2"]["avg"], 0.288); } @@ -282,7 +284,7 @@ namespace " - 3\n" " - false"; - sol::object twoDocuments = LuaUtil::YamlLoader::load(twoDocumentsInput, lua); + sol::object twoDocuments = LuaUtil::loadYaml(twoDocumentsInput, lua); ASSERT_EQ(twoDocuments.as()[1][1], std::string("First Scalar")); ASSERT_EQ(twoDocuments.as()[2][3], false); @@ -295,7 +297,7 @@ namespace - *a # Subsequent occurrence - Name2)"; - sol::object anchor = LuaUtil::YamlLoader::load(anchorInput, lua); + sol::object anchor = LuaUtil::loadYaml(anchorInput, lua); ASSERT_EQ(anchor.as()["y"][1], std::string("Value1")); const std::string compoundKeyInput = R"( @@ -307,7 +309,7 @@ namespace String4 ] : [ 2, 3, 4 ])"; - ASSERT_THROW(LuaUtil::YamlLoader::load(compoundKeyInput, lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml(compoundKeyInput, lua), std::runtime_error); const std::string compactNestedMappingInput = R"( - item : Item1 @@ -317,7 +319,7 @@ namespace - item : Item3 quantity: 11)"; - sol::object compactNestedMapping = LuaUtil::YamlLoader::load(compactNestedMappingInput, lua); + sol::object compactNestedMapping = LuaUtil::loadYaml(compactNestedMappingInput, lua); ASSERT_EQ(compactNestedMapping.as()[2]["quantity"], 4); } @@ -347,7 +349,7 @@ namespace quoted: "So does this quoted scalar.\n")"; - sol::object multiLinePlanarScalars = LuaUtil::YamlLoader::load(multiLinePlanarScalarsInput, lua); + sol::object multiLinePlanarScalars = LuaUtil::loadYaml(multiLinePlanarScalarsInput, lua); ASSERT_TRUE( multiLinePlanarScalars.as()["plain"] == std::string("This unquoted scalar spans many lines.")); ASSERT_TRUE(multiLinePlanarScalars.as()["quoted"] == std::string("So does this quoted scalar.\n")); diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 30323fe116..df83af6253 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include #include @@ -15,7 +18,7 @@ namespace LuaUtil { - namespace YamlLoader + namespace { constexpr uint64_t maxDepth = 250; @@ -32,7 +35,7 @@ namespace LuaUtil String }; - sol::object load(const std::vector& rootNodes, const sol::state_view& lua); + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua); sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); @@ -45,14 +48,23 @@ namespace LuaUtil sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + } - sol::object load(const std::string& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } + sol::object loadYaml(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } - sol::object load(const std::vector& rootNodes, const sol::state_view& lua) + sol::object loadYaml(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } + + namespace + { + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua) { if (rootNodes.empty()) return sol::nil; @@ -69,12 +81,6 @@ namespace LuaUtil return documentsTable; } - sol::object load(std::istream& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } - sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) { if (depth >= maxDepth) @@ -143,7 +149,7 @@ namespace LuaUtil // 3. Most of possible conversions are invalid or their result is unclear // So ignore this feature for now. if (tag != "?") - nodeError(node, "An invalid tag'" + tag + "' encountered"); + nodeError(node, "An invalid tag '" + tag + "' encountered"); if (value.empty()) return ScalarType::Null; diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index c3339d7eb3..6f28da66ce 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -1,18 +1,16 @@ #ifndef COMPONENTS_LUA_YAMLLOADER_H #define COMPONENTS_LUA_YAMLLOADER_H -#include +#include +#include + +#include namespace LuaUtil { + sol::object loadYaml(const std::string& input, const sol::state_view& lua); - namespace YamlLoader - { - sol::object load(const std::string& input, const sol::state_view& lua); - - sol::object load(std::istream& input, const sol::state_view& lua); - } - + sol::object loadYaml(std::istream& input, const sol::state_view& lua); } #endif // COMPONENTS_LUA_YAMLLOADER_H From fcff1a673967d6313ccba94fb9157c6dce992cb7 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 15 Mar 2024 22:23:14 -0500 Subject: [PATCH 1203/2167] Fix #7887, use actual instead of reported size for script data --- CHANGELOG.md | 1 + components/esm3/loadscpt.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d9bd158c..a89eaacaaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds + Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index f79f4989ef..2eb272fe8b 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -149,6 +149,8 @@ namespace ESM if (!hasHeader) esm.fail("Missing SCHD subrecord"); + // Reported script data size is not always trustworthy, so override it with actual data size + mData.mScriptDataSize = mScriptData.size(); } void Script::save(ESMWriter& esm, bool isDeleted) const From 080245aa2643bc7112544894baa7f3ade5d6fca3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 21:26:51 +0100 Subject: [PATCH 1204/2167] Do not align arrays by duplicating last value To produce the same stats for single and multiple sources. If there are multiple sources with different number of frames, leave the number of values per each metric as is. For example: source 1: [1, None, 2] source 2: [3, None, 4, 5] before this change becomes: source 1: [1, 1, 2, 2] source 2: [3, 3, 4, 5] and after this change: source 1: [1, 1, 2] source 2: [3, 3, 4, 5] --- scripts/osg_stats.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d898accb10..e42d62452a 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -149,17 +149,18 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): for key in keys: result[name][key] = [None] * (end_frame - begin_frame) for name, frames in sources.items(): + max_index = 0 for frame in frames: number = frame[frame_number_name] if begin_frame <= number < end_frame: index = number - begin_frame + max_index = max(max_index, index) for key in keys: if key in frame: result[name][key][index] = frame[key] - for name in result.keys(): for key in keys: prev = 0.0 - values = result[name][key] + values = result[name][key][:max_index + 1] for i in range(len(values)): if values[i] is not None: prev = values[i] @@ -183,9 +184,11 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, frames[key], label=f'{key}:{name}') + y = frames[key] + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}', linestyle='--') + y = numpy.sum(list(frames[k] for k in keys), axis=0) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries') @@ -196,10 +199,11 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') + y = numpy.cumsum(frames[key]) + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('commulative_timeseries') @@ -210,10 +214,11 @@ def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame + 1, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.diff(frames[key]), label=f'{key}:{name}') + y = numpy.diff(frames[key]) + ax.plot(x[:len(y)], numpy.diff(frames[key]), label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries_delta') @@ -312,12 +317,7 @@ def print_stats(sources, keys, stats_sum, precision, sort_by, table_format): style=termtables.styles.markdown, ) elif table_format == 'json': - table = list() - for row in stats: - row_table = dict() - for key, value in zip(metrics, row.values()): - row_table[key] = value - table.append(row_table) + table = [dict(zip(metrics, row.values())) for row in stats] print(json.dumps(table)) else: print(f'Unsupported table format: {table_format}') From 6b860caa3e2b0db7c5fd6fec1a55cb465099f8c4 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 18 Mar 2024 01:26:43 +0100 Subject: [PATCH 1205/2167] Fix spelling --- scripts/osg_stats.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index e42d62452a..20fae2cac8 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -23,11 +23,11 @@ import termtables help='Print a list of all present keys in the input file.') @click.option('--regexp_match', is_flag=True, help='Use all metric that match given key. ' - 'Can be used with stats, timeseries, commulative_timeseries, hist, hist_threshold') + 'Can be used with stats, timeseries, cumulative_timeseries, hist, hist_threshold') @click.option('--timeseries', type=str, multiple=True, help='Show a graph for given metric over time.') -@click.option('--commulative_timeseries', type=str, multiple=True, - help='Show a graph for commulative sum of a given metric over time.') +@click.option('--cumulative_timeseries', type=str, multiple=True, + help='Show a graph for cumulative sum of a given metric over time.') @click.option('--timeseries_delta', type=str, multiple=True, help='Show a graph for delta between neighbouring frames of a given metric over time.') @click.option('--hist', type=str, multiple=True, @@ -54,8 +54,8 @@ import termtables help='Format floating point numbers with given precision') @click.option('--timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') -@click.option('--commulative_timeseries_sum', is_flag=True, - help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') +@click.option('--cumulative_timeseries_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given cumulative timeseries.') @click.option('--timeseries_delta_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') @click.option('--begin_frame', type=int, default=0, @@ -75,7 +75,7 @@ import termtables @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum, frame_number_name, + cumulative_timeseries, cumulative_timeseries_sum, frame_number_name, hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, timeseries_delta, timeseries_delta_sum, stats_table_format): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} @@ -97,8 +97,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if timeseries: draw_timeseries(sources=frames, keys=matching_keys(timeseries), add_sum=timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) - if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, + if cumulative_timeseries: + draw_cumulative_timeseries(sources=frames, keys=matching_keys(cumulative_timeseries), add_sum=cumulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if timeseries_delta: draw_timeseries_delta(sources=frames, keys=matching_keys(timeseries_delta), add_sum=timeseries_delta_sum, @@ -194,7 +194,7 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig.canvas.manager.set_window_title('timeseries') -def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): +def draw_cumulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): @@ -206,7 +206,7 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() - fig.canvas.manager.set_window_title('commulative_timeseries') + fig.canvas.manager.set_window_title('cumulative_timeseries') def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): From 6b93479bd3a9412ba9ba2ea9c6974cd5451a7750 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 18 Mar 2024 03:21:57 +0300 Subject: [PATCH 1206/2167] Get rid of ESM4::SubRecordTypes All my homies hate ESM4::SubRecordTypes --- components/esm4/common.hpp | 681 ----------------------------------- components/esm4/loadachr.cpp | 94 ++--- components/esm4/loadacti.cpp | 86 ++--- components/esm4/loadalch.cpp | 72 ++-- components/esm4/loadaloc.cpp | 34 +- components/esm4/loadammo.cpp | 74 ++-- components/esm4/loadanio.cpp | 18 +- components/esm4/loadappa.cpp | 22 +- components/esm4/loadarma.cpp | 92 ++--- components/esm4/loadarmo.cpp | 140 +++---- components/esm4/loadaspc.cpp | 18 +- components/esm4/loadbook.cpp | 70 ++-- components/esm4/loadbptd.cpp | 48 +-- components/esm4/loadcell.cpp | 80 ++-- components/esm4/loadclas.cpp | 14 +- components/esm4/loadclfm.cpp | 10 +- components/esm4/loadclot.cpp | 42 +-- components/esm4/loadcont.cpp | 70 ++-- components/esm4/loadcrea.cpp | 88 ++--- components/esm4/loaddial.cpp | 30 +- components/esm4/loaddobj.cpp | 6 +- components/esm4/loaddoor.cpp | 62 ++-- components/esm4/loadeyes.cpp | 8 +- components/esm4/loadflor.cpp | 62 ++-- components/esm4/loadflst.cpp | 6 +- components/esm4/loadfurn.cpp | 106 +++--- components/esm4/loadglob.cpp | 6 +- components/esm4/loadgmst.cpp | 4 +- components/esm4/loadgras.cpp | 18 +- components/esm4/loadhair.cpp | 14 +- components/esm4/loadhdpt.cpp | 32 +- components/esm4/loadidle.cpp | 30 +- components/esm4/loadidlm.cpp | 28 +- components/esm4/loadimod.cpp | 48 +-- components/esm4/loadinfo.cpp | 116 +++--- components/esm4/loadingr.cpp | 62 ++-- components/esm4/loadkeym.cpp | 56 +-- components/esm4/loadland.cpp | 18 +- components/esm4/loadlgtm.cpp | 6 +- components/esm4/loadligh.cpp | 64 ++-- components/esm4/loadltex.cpp | 16 +- components/esm4/loadlvlc.cpp | 14 +- components/esm4/loadlvli.cpp | 26 +- components/esm4/loadlvln.cpp | 30 +- components/esm4/loadmato.cpp | 16 +- components/esm4/loadmisc.cpp | 64 ++-- components/esm4/loadmset.cpp | 58 +-- components/esm4/loadmstt.cpp | 50 +-- components/esm4/loadmusc.cpp | 12 +- components/esm4/loadnavi.cpp | 12 +- components/esm4/loadnavm.cpp | 28 +- components/esm4/loadnote.cpp | 42 +-- components/esm4/loadnpc.cpp | 232 ++++++------ components/esm4/loadotft.cpp | 4 +- components/esm4/loadpack.cpp | 110 +++--- components/esm4/loadpgrd.cpp | 12 +- components/esm4/loadpgre.cpp | 86 ++--- components/esm4/loadpwat.cpp | 8 +- components/esm4/loadqust.cpp | 144 ++++---- components/esm4/loadrace.cpp | 278 +++++++------- components/esm4/loadrefr.cpp | 214 +++++------ components/esm4/loadregn.cpp | 42 +-- components/esm4/loadroad.cpp | 4 +- components/esm4/loadsbsp.cpp | 4 +- components/esm4/loadscol.cpp | 24 +- components/esm4/loadscpt.cpp | 16 +- components/esm4/loadscrl.cpp | 38 +- components/esm4/loadsgst.cpp | 22 +- components/esm4/loadslgm.cpp | 28 +- components/esm4/loadsndr.cpp | 36 +- components/esm4/loadsoun.cpp | 22 +- components/esm4/loadstat.cpp | 36 +- components/esm4/loadtact.cpp | 48 +-- components/esm4/loadterm.cpp | 94 ++--- components/esm4/loadtes4.cpp | 22 +- components/esm4/loadtree.cpp | 30 +- components/esm4/loadtxst.cpp | 26 +- components/esm4/loadweap.cpp | 206 +++++------ components/esm4/loadwrld.cpp | 80 ++-- components/esm4/reader.cpp | 2 +- components/esm4/reader.hpp | 2 +- 81 files changed, 2046 insertions(+), 2727 deletions(-) diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index 8f37bfb8d4..a50151b10a 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -176,687 +176,6 @@ namespace ESM4 REC_MSET = fourCC("MSET") // Media Set }; - enum SubRecordTypes - { - SUB_ACBS = fourCC("ACBS"), - SUB_ACEC = fourCC("ACEC"), // TES5 Dawnguard - SUB_ACEP = fourCC("ACEP"), // TES5 Dawnguard - SUB_ACID = fourCC("ACID"), // TES5 Dawnguard - SUB_ACPR = fourCC("ACPR"), // TES5 - SUB_ACSR = fourCC("ACSR"), // TES5 Dawnguard - SUB_ACTV = fourCC("ACTV"), // FO4 - SUB_ACUN = fourCC("ACUN"), // TES5 Dawnguard - SUB_AHCF = fourCC("AHCF"), - SUB_AHCM = fourCC("AHCM"), - SUB_AIDT = fourCC("AIDT"), - SUB_ALCA = fourCC("ALCA"), // TES5 - SUB_ALCC = fourCC("ALCC"), // FO4 - SUB_ALCL = fourCC("ALCL"), // TES5 - SUB_ALCO = fourCC("ALCO"), // TES5 - SUB_ALCS = fourCC("ALCS"), // FO4 - SUB_ALDI = fourCC("ALDI"), // FO4 - SUB_ALDN = fourCC("ALDN"), // TES5 - SUB_ALEA = fourCC("ALEA"), // TES5 - SUB_ALED = fourCC("ALED"), // TES5 - SUB_ALEQ = fourCC("ALEQ"), // TES5 - SUB_ALFA = fourCC("ALFA"), // TES5 - SUB_ALFC = fourCC("ALFC"), // TES5 - SUB_ALFD = fourCC("ALFD"), // TES5 - SUB_ALFE = fourCC("ALFE"), // TES5 - SUB_ALFI = fourCC("ALFI"), // TES5 - SUB_ALFL = fourCC("ALFL"), // TES5 - SUB_ALFR = fourCC("ALFR"), // TES5 - SUB_ALFV = fourCC("ALFV"), // FO4 - SUB_ALID = fourCC("ALID"), // TES5 - SUB_ALLA = fourCC("ALLA"), // FO4 - SUB_ALLS = fourCC("ALLS"), // TES5 - SUB_ALMI = fourCC("ALMI"), // FO4 - SUB_ALNA = fourCC("ALNA"), // TES5 - SUB_ALNT = fourCC("ALNT"), // TES5 - SUB_ALPC = fourCC("ALPC"), // TES5 - SUB_ALRT = fourCC("ALRT"), // TES5 - SUB_ALSP = fourCC("ALSP"), // TES5 - SUB_ALST = fourCC("ALST"), // TES5 - SUB_ALUA = fourCC("ALUA"), // TES5 - SUB_ANAM = fourCC("ANAM"), - SUB_AOR2 = fourCC("AOR2"), // FO4 - SUB_APPR = fourCC("APPR"), // FO4 - SUB_ATKD = fourCC("ATKD"), - SUB_ATKE = fourCC("ATKE"), - SUB_ATKR = fourCC("ATKR"), - SUB_ATKS = fourCC("ATKS"), // FO4 - SUB_ATKT = fourCC("ATKT"), // FO4 - SUB_ATKW = fourCC("ATKW"), // FO4 - SUB_ATTN = fourCC("ATTN"), // FO4 - SUB_ATTR = fourCC("ATTR"), - SUB_ATTX = fourCC("ATTX"), // FO4 - SUB_ATXT = fourCC("ATXT"), - SUB_AVFL = fourCC("AVFL"), // FO4 - SUB_AVSK = fourCC("AVSK"), // TES5 - SUB_BAMT = fourCC("BAMT"), - SUB_BCLF = fourCC("BCLF"), // FO4 - SUB_BIDS = fourCC("BIDS"), - SUB_BIPL = fourCC("BIPL"), // FO3 - SUB_BMCT = fourCC("BMCT"), - SUB_BMDT = fourCC("BMDT"), - SUB_BMMP = fourCC("BMMP"), // FO4 - SUB_BNAM = fourCC("BNAM"), - SUB_BOD2 = fourCC("BOD2"), - SUB_BODT = fourCC("BODT"), - SUB_BPND = fourCC("BPND"), - SUB_BPNI = fourCC("BPNI"), - SUB_BPNN = fourCC("BPNN"), - SUB_BPNT = fourCC("BPNT"), - SUB_BPTN = fourCC("BPTN"), - SUB_BRUS = fourCC("BRUS"), // FONV - SUB_BSIZ = fourCC("BSIZ"), // FO4 - SUB_BSMB = fourCC("BSMB"), // FO4 - SUB_BSMP = fourCC("BSMP"), // FO4 - SUB_BSMS = fourCC("BSMS"), // FO4 - SUB_BTXT = fourCC("BTXT"), - SUB_CDIX = fourCC("CDIX"), // FO4 - SUB_CIS1 = fourCC("CIS1"), // TES5 - SUB_CIS2 = fourCC("CIS2"), // TES5 - SUB_CITC = fourCC("CITC"), // TES5 - SUB_CLSZ = fourCC("CLSZ"), // FO4 - SUB_CNAM = fourCC("CNAM"), - SUB_CNTO = fourCC("CNTO"), - SUB_COCT = fourCC("COCT"), - SUB_COED = fourCC("COED"), - SUB_CRDT = fourCC("CRDT"), - SUB_CRGR = fourCC("CRGR"), // TES5 - SUB_CRIF = fourCC("CRIF"), - SUB_CRIS = fourCC("CRIS"), // FO4 - SUB_CRVA = fourCC("CRVA"), // TES5 - SUB_CS2D = fourCC("CS2D"), // FO4 - SUB_CS2E = fourCC("CS2E"), // FO4 - SUB_CS2F = fourCC("CS2F"), // FO4 - SUB_CS2H = fourCC("CS2H"), // FO4 - SUB_CS2K = fourCC("CS2K"), // FO4 - SUB_CSCR = fourCC("CSCR"), - SUB_CSCV = fourCC("CSCV"), // FO4 - SUB_CSDC = fourCC("CSDC"), - SUB_CSDI = fourCC("CSDI"), - SUB_CSDT = fourCC("CSDT"), - SUB_CSFL = fourCC("CSFL"), // TES5 - SUB_CSGD = fourCC("CSGD"), // TES5 - SUB_CSLR = fourCC("CSLR"), // TES5 - SUB_CSMD = fourCC("CSMD"), // TES5 - SUB_CSME = fourCC("CSME"), // TES5 - SUB_CSRA = fourCC("CSRA"), // FO4 - SUB_CTDA = fourCC("CTDA"), - SUB_CTDT = fourCC("CTDT"), - SUB_CUSD = fourCC("CUSD"), // FO4 - SUB_CVPA = fourCC("CVPA"), // FO4 - SUB_DALC = fourCC("DALC"), // FO3 - SUB_DAMA = fourCC("DAMA"), // FO4 - SUB_DAMC = fourCC("DAMC"), // FO4 - SUB_DAT2 = fourCC("DAT2"), // FONV - SUB_DATA = fourCC("DATA"), - SUB_DELE = fourCC("DELE"), - SUB_DEMO = fourCC("DEMO"), // TES5 - SUB_DESC = fourCC("DESC"), - SUB_DEST = fourCC("DEST"), - SUB_DEVA = fourCC("DEVA"), // TES5 - SUB_DFTF = fourCC("DFTF"), - SUB_DFTM = fourCC("DFTM"), - SUB_DMAX = fourCC("DMAX"), // TES5 - SUB_DMDC = fourCC("DMDC"), // FO4 - SUB_DMDL = fourCC("DMDL"), - SUB_DMDS = fourCC("DMDS"), - SUB_DMDT = fourCC("DMDT"), - SUB_DMIN = fourCC("DMIN"), // TES5 - SUB_DNAM = fourCC("DNAM"), - SUB_DODT = fourCC("DODT"), - SUB_DOFT = fourCC("DOFT"), - SUB_DPLT = fourCC("DPLT"), - SUB_DSTA = fourCC("DSTA"), // FO4 - SUB_DSTD = fourCC("DSTD"), - SUB_DSTF = fourCC("DSTF"), - SUB_DTGT = fourCC("DTGT"), // FO4 - SUB_DTID = fourCC("DTID"), // FO4 - SUB_EAMT = fourCC("EAMT"), - SUB_ECOR = fourCC("ECOR"), - SUB_EDID = fourCC("EDID"), - SUB_EFID = fourCC("EFID"), - SUB_EFIT = fourCC("EFIT"), - SUB_EFSD = fourCC("EFSD"), // FONV DeadMoney - SUB_EITM = fourCC("EITM"), - SUB_ENAM = fourCC("ENAM"), - SUB_ENIT = fourCC("ENIT"), - SUB_EPF2 = fourCC("EPF2"), - SUB_EPF3 = fourCC("EPF3"), - SUB_EPFB = fourCC("EPFB"), // FO4 - SUB_EPFD = fourCC("EPFD"), - SUB_EPFT = fourCC("EPFT"), - SUB_ESCE = fourCC("ESCE"), - SUB_ETYP = fourCC("ETYP"), - SUB_FCHT = fourCC("FCHT"), // TES5 - SUB_FCPL = fourCC("FCPL"), // FO4 - SUB_FFFF = fourCC("FFFF"), - SUB_FGGA = fourCC("FGGA"), - SUB_FGGS = fourCC("FGGS"), - SUB_FGTS = fourCC("FGTS"), - SUB_FIMD = fourCC("FIMD"), // FO4 - SUB_FLMV = fourCC("FLMV"), - SUB_FLTR = fourCC("FLTR"), // TES5 - SUB_FLTV = fourCC("FLTV"), - SUB_FMIN = fourCC("FMIN"), // FO4 - SUB_FMRI = fourCC("FMRI"), // FO4 - SUB_FMRN = fourCC("FMRN"), // FO4 - SUB_FMRS = fourCC("FMRS"), // FO4 - SUB_FNAM = fourCC("FNAM"), - SUB_FNMK = fourCC("FNMK"), - SUB_FNPR = fourCC("FNPR"), - SUB_FPRT = fourCC("FPRT"), // TES5 - SUB_FTSF = fourCC("FTSF"), - SUB_FTSM = fourCC("FTSM"), - SUB_FTST = fourCC("FTST"), - SUB_FTYP = fourCC("FTYP"), // FO4 - SUB_FULL = fourCC("FULL"), - SUB_FVPA = fourCC("FVPA"), // FO4 - SUB_GNAM = fourCC("GNAM"), - SUB_GREE = fourCC("GREE"), // FO4 - SUB_GWOR = fourCC("GWOR"), // TES5 - SUB_HCLF = fourCC("HCLF"), - SUB_HCLR = fourCC("HCLR"), - SUB_HEAD = fourCC("HEAD"), - SUB_HEDR = fourCC("HEDR"), - SUB_HLTX = fourCC("HLTX"), // FO4 - SUB_HNAM = fourCC("HNAM"), - SUB_HTID = fourCC("HTID"), // TES5 - SUB_ICO2 = fourCC("ICO2"), - SUB_ICON = fourCC("ICON"), - SUB_IDLA = fourCC("IDLA"), - SUB_IDLB = fourCC("IDLB"), // FO3 - SUB_IDLC = fourCC("IDLC"), - SUB_IDLF = fourCC("IDLF"), - SUB_IDLT = fourCC("IDLT"), - SUB_IMPF = fourCC("IMPF"), // FO3 Anchorage - SUB_IMPS = fourCC("IMPS"), // FO3 Anchorage - SUB_IMSP = fourCC("IMSP"), // TES5 - SUB_INAM = fourCC("INAM"), - SUB_INCC = fourCC("INCC"), - SUB_INDX = fourCC("INDX"), - SUB_INFC = fourCC("INFC"), // FONV - SUB_INFX = fourCC("INFX"), // FONV - SUB_INRD = fourCC("INRD"), // FO4 - SUB_INTT = fourCC("INTT"), // FO4 - SUB_INTV = fourCC("INTV"), - SUB_IOVR = fourCC("IOVR"), // FO4 - SUB_ISIZ = fourCC("ISIZ"), // FO4 - SUB_ITID = fourCC("ITID"), // FO4 - SUB_ITMC = fourCC("ITMC"), // FO4 - SUB_ITME = fourCC("ITME"), // FO4 - SUB_ITMS = fourCC("ITMS"), // FO4 - SUB_ITXT = fourCC("ITXT"), - SUB_JAIL = fourCC("JAIL"), // TES5 - SUB_JNAM = fourCC("JNAM"), // FONV - SUB_JOUT = fourCC("JOUT"), // TES5 - SUB_KFFZ = fourCC("KFFZ"), - SUB_KNAM = fourCC("KNAM"), - SUB_KSIZ = fourCC("KSIZ"), - SUB_KWDA = fourCC("KWDA"), - SUB_LCEC = fourCC("LCEC"), // TES5 - SUB_LCEP = fourCC("LCEP"), // TES5 - SUB_LCID = fourCC("LCID"), // TES5 - SUB_LCPR = fourCC("LCPR"), // TES5 - SUB_LCSR = fourCC("LCSR"), // TES5 - SUB_LCUN = fourCC("LCUN"), // TES5 - SUB_LFSD = fourCC("LFSD"), // FO4 - SUB_LFSP = fourCC("LFSP"), // FO4 - SUB_LLCT = fourCC("LLCT"), - SUB_LLKC = fourCC("LLKC"), // FO4 - SUB_LNAM = fourCC("LNAM"), - SUB_LTMP = fourCC("LTMP"), - SUB_LTPC = fourCC("LTPC"), // FO4 - SUB_LTPT = fourCC("LTPT"), // FO4 - SUB_LVLD = fourCC("LVLD"), - SUB_LVLF = fourCC("LVLF"), - SUB_LVLG = fourCC("LVLG"), // FO3 - SUB_LVLM = fourCC("LVLM"), // FO4 - SUB_LVLO = fourCC("LVLO"), - SUB_LVSG = fourCC("LVSG"), // FO4 - SUB_MASE = fourCC("MASE"), // FO4 - SUB_MAST = fourCC("MAST"), - SUB_MCHT = fourCC("MCHT"), // TES5 - SUB_MDOB = fourCC("MDOB"), - SUB_MHDT = fourCC("MHDT"), - SUB_MIC2 = fourCC("MIC2"), - SUB_MICO = fourCC("MICO"), - SUB_MLSI = fourCC("MLSI"), // FO4 - SUB_MMRK = fourCC("MMRK"), // FONV - SUB_MNAM = fourCC("MNAM"), - SUB_MO2B = fourCC("MO2B"), - SUB_MO2C = fourCC("MO2C"), // FO4 - SUB_MO2F = fourCC("MO2F"), // FO4 - SUB_MO2S = fourCC("MO2S"), - SUB_MO2T = fourCC("MO2T"), - SUB_MO3B = fourCC("MO3B"), - SUB_MO3C = fourCC("MO3C"), // FO4 - SUB_MO3F = fourCC("MO3F"), // FO4 - SUB_MO3S = fourCC("MO3S"), // FO3 - SUB_MO3T = fourCC("MO3T"), - SUB_MO4B = fourCC("MO4B"), - SUB_MO4C = fourCC("MO4C"), // FO4 - SUB_MO4F = fourCC("MO4F"), // FO4 - SUB_MO4S = fourCC("MO4S"), - SUB_MO4T = fourCC("MO4T"), - SUB_MO5C = fourCC("MO5C"), // FO4 - SUB_MO5F = fourCC("MO5F"), // FO4 - SUB_MO5S = fourCC("MO5S"), // TES5 - SUB_MO5T = fourCC("MO5T"), - SUB_MOD2 = fourCC("MOD2"), - SUB_MOD3 = fourCC("MOD3"), - SUB_MOD4 = fourCC("MOD4"), - SUB_MOD5 = fourCC("MOD5"), - SUB_MODB = fourCC("MODB"), - SUB_MODC = fourCC("MODC"), // FO4 - SUB_MODD = fourCC("MODD"), // FO3 - SUB_MODF = fourCC("MODF"), // FO4 - SUB_MODL = fourCC("MODL"), - SUB_MODQ = fourCC("MODQ"), // FO4 - SUB_MODS = fourCC("MODS"), - SUB_MODT = fourCC("MODT"), - SUB_MOSD = fourCC("MOSD"), // FO3 - SUB_MPAI = fourCC("MPAI"), - SUB_MPAV = fourCC("MPAV"), - SUB_MPCD = fourCC("MPCD"), // FO4 - SUB_MPGN = fourCC("MPGN"), // FO4 - SUB_MPGS = fourCC("MPGS"), // FO4 - SUB_MPPC = fourCC("MPPC"), // FO4 - SUB_MPPF = fourCC("MPPF"), // FO4 - SUB_MPPI = fourCC("MPPI"), // FO4 - SUB_MPPK = fourCC("MPPK"), // FO4 - SUB_MPPM = fourCC("MPPM"), // FO4 - SUB_MPPN = fourCC("MPPN"), // FO4 - SUB_MPPT = fourCC("MPPT"), // FO4 - SUB_MPRT = fourCC("MPRT"), // TES5 - SUB_MRSV = fourCC("MRSV"), // FO4 - SUB_MSDK = fourCC("MSDK"), // FO4 - SUB_MSDV = fourCC("MSDV"), // FO4 - SUB_MSID = fourCC("MSID"), // FO4 - SUB_MSM0 = fourCC("MSM0"), // FO4 - SUB_MSM1 = fourCC("MSM1"), // FO4 - SUB_MTNM = fourCC("MTNM"), - SUB_MTYP = fourCC("MTYP"), - SUB_MWD1 = fourCC("MWD1"), // FONV - SUB_MWD2 = fourCC("MWD2"), // FONV - SUB_MWD3 = fourCC("MWD3"), // FONV - SUB_MWD4 = fourCC("MWD4"), // FONV - SUB_MWD5 = fourCC("MWD5"), // FONV - SUB_MWD6 = fourCC("MWD6"), // FONV - SUB_MWD7 = fourCC("MWD7"), // FONV - SUB_MWGT = fourCC("MWGT"), // FO4 - SUB_NAM0 = fourCC("NAM0"), - SUB_NAM1 = fourCC("NAM1"), - SUB_NAM2 = fourCC("NAM2"), - SUB_NAM3 = fourCC("NAM3"), - SUB_NAM4 = fourCC("NAM4"), - SUB_NAM5 = fourCC("NAM5"), - SUB_NAM6 = fourCC("NAM6"), - SUB_NAM7 = fourCC("NAM7"), - SUB_NAM8 = fourCC("NAM8"), - SUB_NAM9 = fourCC("NAM9"), - SUB_NAMA = fourCC("NAMA"), - SUB_NAME = fourCC("NAME"), - SUB_NETO = fourCC("NETO"), // FO4 - SUB_NEXT = fourCC("NEXT"), // FO3 - SUB_NIFT = fourCC("NIFT"), - SUB_NIFZ = fourCC("NIFZ"), - SUB_NNAM = fourCC("NNAM"), - SUB_NNGS = fourCC("NNGS"), // FO4 - SUB_NNGT = fourCC("NNGT"), // FO4 - SUB_NNUS = fourCC("NNUS"), // FO4 - SUB_NNUT = fourCC("NNUT"), // FO4 - SUB_NONE = fourCC("NONE"), // FO4 - SUB_NPOS = fourCC("NPOS"), // FO4 - SUB_NPOT = fourCC("NPOT"), // FO4 - SUB_NQUS = fourCC("NQUS"), // FO4 - SUB_NQUT = fourCC("NQUT"), // FO4 - SUB_NTOP = fourCC("NTOP"), // FO4 - SUB_NTRM = fourCC("NTRM"), // FO4 - SUB_NULL = fourCC("NULL"), - SUB_NVCA = fourCC("NVCA"), // FO3 - SUB_NVCI = fourCC("NVCI"), // FO3 - SUB_NVDP = fourCC("NVDP"), // FO3 - SUB_NVER = fourCC("NVER"), - SUB_NVEX = fourCC("NVEX"), // FO3 - SUB_NVGD = fourCC("NVGD"), // FO3 - SUB_NVMI = fourCC("NVMI"), - SUB_NVNM = fourCC("NVNM"), - SUB_NVPP = fourCC("NVPP"), - SUB_NVSI = fourCC("NVSI"), - SUB_NVTR = fourCC("NVTR"), // FO3 - SUB_NVVX = fourCC("NVVX"), // FO3 - SUB_OBND = fourCC("OBND"), - SUB_OBTE = fourCC("OBTE"), // FO4 - SUB_OBTF = fourCC("OBTF"), // FO4 - SUB_OBTS = fourCC("OBTS"), // FO4 - SUB_OCOR = fourCC("OCOR"), // TES5 - SUB_OFST = fourCC("OFST"), // TES4 only? - SUB_ONAM = fourCC("ONAM"), - SUB_PCMB = fourCC("PCMB"), // FO4 - SUB_PDTO = fourCC("PDTO"), - SUB_PFIG = fourCC("PFIG"), - SUB_PFO2 = fourCC("PFO2"), // TES5 - SUB_PFOR = fourCC("PFOR"), // TES5 - SUB_PFPC = fourCC("PFPC"), - SUB_PFRN = fourCC("PFRN"), // FO4 - SUB_PGAG = fourCC("PGAG"), - SUB_PGRI = fourCC("PGRI"), - SUB_PGRL = fourCC("PGRL"), - SUB_PGRP = fourCC("PGRP"), - SUB_PGRR = fourCC("PGRR"), - SUB_PHTN = fourCC("PHTN"), - SUB_PHWT = fourCC("PHWT"), - SUB_PKAM = fourCC("PKAM"), // FO3 - SUB_PKC2 = fourCC("PKC2"), // TES5 - SUB_PKCU = fourCC("PKCU"), // TES5 - SUB_PKD2 = fourCC("PKD2"), // FO3 - SUB_PKDD = fourCC("PKDD"), // FO3 - SUB_PKDT = fourCC("PKDT"), - SUB_PKE2 = fourCC("PKE2"), // FO3 - SUB_PKED = fourCC("PKED"), // FO3 - SUB_PKFD = fourCC("PKFD"), // FO3 - SUB_PKID = fourCC("PKID"), - SUB_PKPT = fourCC("PKPT"), // FO3 - SUB_PKW3 = fourCC("PKW3"), // FO3 - SUB_PLCN = fourCC("PLCN"), // TES5 - SUB_PLD2 = fourCC("PLD2"), // FO3 - SUB_PLDT = fourCC("PLDT"), - SUB_PLVD = fourCC("PLVD"), // TES5 - SUB_PNAM = fourCC("PNAM"), - SUB_POBA = fourCC("POBA"), // FO3 - SUB_POCA = fourCC("POCA"), // FO3 - SUB_POEA = fourCC("POEA"), // FO3 - SUB_PRCB = fourCC("PRCB"), // TES5 - SUB_PRKC = fourCC("PRKC"), - SUB_PRKE = fourCC("PRKE"), - SUB_PRKF = fourCC("PRKF"), - SUB_PRKR = fourCC("PRKR"), - SUB_PRKZ = fourCC("PRKZ"), - SUB_PRPS = fourCC("PRPS"), // FO4 - SUB_PSDT = fourCC("PSDT"), - SUB_PTD2 = fourCC("PTD2"), // FO3 - SUB_PTDA = fourCC("PTDA"), // TES5 - SUB_PTDT = fourCC("PTDT"), - SUB_PTOP = fourCC("PTOP"), // FO4 - SUB_PTRN = fourCC("PTRN"), // FO4 - SUB_PUID = fourCC("PUID"), // FO3 - SUB_QNAM = fourCC("QNAM"), - SUB_QOBJ = fourCC("QOBJ"), // FO3 - SUB_QSDT = fourCC("QSDT"), - SUB_QSTA = fourCC("QSTA"), - SUB_QSTI = fourCC("QSTI"), - SUB_QSTR = fourCC("QSTR"), - SUB_QTGL = fourCC("QTGL"), // TES5 - SUB_QTOP = fourCC("QTOP"), // FO4 - SUB_QUAL = fourCC("QUAL"), - SUB_RADR = fourCC("RADR"), // FO4 - SUB_RAGA = fourCC("RAGA"), - SUB_RBPC = fourCC("RBPC"), // FO4 - SUB_RCEC = fourCC("RCEC"), // TES5 - SUB_RCIL = fourCC("RCIL"), // FONV - SUB_RCLR = fourCC("RCLR"), - SUB_RCPR = fourCC("RCPR"), // TES5 Dawnguard - SUB_RCSR = fourCC("RCSR"), // TES5 - SUB_RCUN = fourCC("RCUN"), // TES5 - SUB_RDAT = fourCC("RDAT"), - SUB_RDGS = fourCC("RDGS"), - SUB_RDID = fourCC("RDID"), // FONV - SUB_RDMD = fourCC("RDMD"), // TES4 only? - SUB_RDMO = fourCC("RDMO"), - SUB_RDMP = fourCC("RDMP"), - SUB_RDOT = fourCC("RDOT"), - SUB_RDSA = fourCC("RDSA"), - SUB_RDSB = fourCC("RDSB"), // FONV - SUB_RDSD = fourCC("RDSD"), // TES4 only? - SUB_RDSI = fourCC("RDSI"), // FONV - SUB_RDWT = fourCC("RDWT"), - SUB_REPL = fourCC("REPL"), // FO3 - SUB_REPT = fourCC("REPT"), // FO4 - SUB_RLDM = fourCC("RLDM"), // FO4 - SUB_RNAM = fourCC("RNAM"), - SUB_RNMV = fourCC("RNMV"), - SUB_RPLD = fourCC("RPLD"), - SUB_RPLI = fourCC("RPLI"), - SUB_RPRF = fourCC("RPRF"), - SUB_RPRM = fourCC("RPRM"), - SUB_RVIS = fourCC("RVIS"), // FO4 - SUB_SADD = fourCC("SADD"), // FO4 - SUB_SAKD = fourCC("SAKD"), // FO4 - SUB_SAPT = fourCC("SAPT"), // FO4 - SUB_SCDA = fourCC("SCDA"), - SUB_SCHD = fourCC("SCHD"), - SUB_SCHR = fourCC("SCHR"), - SUB_SCIT = fourCC("SCIT"), - SUB_SCQS = fourCC("SCQS"), // FO4 - SUB_SCRI = fourCC("SCRI"), - SUB_SCRN = fourCC("SCRN"), - SUB_SCRO = fourCC("SCRO"), - SUB_SCRV = fourCC("SCRV"), // FONV - SUB_SCTX = fourCC("SCTX"), - SUB_SCVR = fourCC("SCVR"), // FONV - SUB_SDSC = fourCC("SDSC"), - SUB_SGNM = fourCC("SGNM"), // FO4 - SUB_SHRT = fourCC("SHRT"), - SUB_SLCP = fourCC("SLCP"), - SUB_SLSD = fourCC("SLSD"), // FONV - SUB_SNAM = fourCC("SNAM"), - SUB_SNDD = fourCC("SNDD"), - SUB_SNDX = fourCC("SNDX"), - SUB_SNMV = fourCC("SNMV"), - SUB_SOFT = fourCC("SOFT"), - SUB_SOUL = fourCC("SOUL"), - SUB_SPCT = fourCC("SPCT"), - SUB_SPED = fourCC("SPED"), - SUB_SPIT = fourCC("SPIT"), - SUB_SPLO = fourCC("SPLO"), - SUB_SPMV = fourCC("SPMV"), // TES5 - SUB_SPOR = fourCC("SPOR"), - SUB_SRAC = fourCC("SRAC"), // FO4 - SUB_SRAF = fourCC("SRAF"), // FO4 - SUB_SSPN = fourCC("SSPN"), // FO4 - SUB_STCP = fourCC("STCP"), // FO4 - SUB_STKD = fourCC("STKD"), // FO4 - SUB_STOL = fourCC("STOL"), // TES5 - SUB_STOP = fourCC("STOP"), // FO4 - SUB_STSC = fourCC("STSC"), // FO4 - SUB_SWMV = fourCC("SWMV"), - SUB_TCFU = fourCC("TCFU"), // FONV - SUB_TCLF = fourCC("TCLF"), - SUB_TCLT = fourCC("TCLT"), - SUB_TDUM = fourCC("TDUM"), // FONV - SUB_TEND = fourCC("TEND"), // FO4 - SUB_TETI = fourCC("TETI"), // FO4 - SUB_TIAS = fourCC("TIAS"), - SUB_TIFC = fourCC("TIFC"), // TES5 - SUB_TINC = fourCC("TINC"), - SUB_TIND = fourCC("TIND"), - SUB_TINI = fourCC("TINI"), - SUB_TINL = fourCC("TINL"), - SUB_TINP = fourCC("TINP"), - SUB_TINT = fourCC("TINT"), - SUB_TINV = fourCC("TINV"), - SUB_TIQS = fourCC("TIQS"), // FO4 - SUB_TIRS = fourCC("TIRS"), - SUB_TNAM = fourCC("TNAM"), - SUB_TPIC = fourCC("TPIC"), - SUB_TPLT = fourCC("TPLT"), - SUB_TPTA = fourCC("TPTA"), // FO4 - SUB_TRDA = fourCC("TRDA"), // FO4 - SUB_TRDT = fourCC("TRDT"), - SUB_TSCE = fourCC("TSCE"), // FO4 - SUB_TTEB = fourCC("TTEB"), // FO4 - SUB_TTEC = fourCC("TTEC"), // FO4 - SUB_TTED = fourCC("TTED"), // FO4 - SUB_TTEF = fourCC("TTEF"), // FO4 - SUB_TTET = fourCC("TTET"), // FO4 - SUB_TTGE = fourCC("TTGE"), // FO4 - SUB_TTGP = fourCC("TTGP"), // FO4 - SUB_TVDT = fourCC("TVDT"), - SUB_TWAT = fourCC("TWAT"), // TES5 - SUB_TX00 = fourCC("TX00"), - SUB_TX01 = fourCC("TX01"), - SUB_TX02 = fourCC("TX02"), - SUB_TX03 = fourCC("TX03"), - SUB_TX04 = fourCC("TX04"), - SUB_TX05 = fourCC("TX05"), - SUB_TX06 = fourCC("TX06"), - SUB_TX07 = fourCC("TX07"), - SUB_UNAM = fourCC("UNAM"), - SUB_UNES = fourCC("UNES"), - SUB_UNWP = fourCC("UNWP"), // FO4 - SUB_VANM = fourCC("VANM"), // FONV - SUB_VATS = fourCC("VATS"), // FONV - SUB_VCLR = fourCC("VCLR"), - SUB_VENC = fourCC("VENC"), // TES5 - SUB_VEND = fourCC("VEND"), // TES5 - SUB_VENV = fourCC("VENV"), // TES5 - SUB_VHGT = fourCC("VHGT"), - SUB_VISI = fourCC("VISI"), // FO4 - SUB_VMAD = fourCC("VMAD"), - SUB_VNAM = fourCC("VNAM"), - SUB_VNML = fourCC("VNML"), - SUB_VTCK = fourCC("VTCK"), - SUB_VTEX = fourCC("VTEX"), - SUB_VTXT = fourCC("VTXT"), - SUB_WAIT = fourCC("WAIT"), // TES5 - SUB_WAMD = fourCC("WAMD"), // FO4 - SUB_WBDT = fourCC("WBDT"), - SUB_WCTR = fourCC("WCTR"), - SUB_WGDR = fourCC("WGDR"), // FO4 - SUB_WKMV = fourCC("WKMV"), - SUB_WLEV = fourCC("WLEV"), // FO4 - SUB_WLST = fourCC("WLST"), - SUB_WMAP = fourCC("WMAP"), // FO4 - SUB_WMI1 = fourCC("WMI1"), // FONV - SUB_WMI2 = fourCC("WMI2"), // FONV - SUB_WMI3 = fourCC("WMI3"), // FONV - SUB_WMS1 = fourCC("WMS1"), // FONV - SUB_WMS2 = fourCC("WMS2"), // FONV - SUB_WNAM = fourCC("WNAM"), - SUB_WNM1 = fourCC("WNM1"), // FONV - SUB_WNM2 = fourCC("WNM2"), // FONV - SUB_WNM3 = fourCC("WNM3"), // FONV - SUB_WNM4 = fourCC("WNM4"), // FONV - SUB_WNM5 = fourCC("WNM5"), // FONV - SUB_WNM6 = fourCC("WNM6"), // FONV - SUB_WNM7 = fourCC("WNM7"), // FONV - SUB_WZMD = fourCC("WZMD"), // FO4 - SUB_XACT = fourCC("XACT"), - SUB_XALP = fourCC("XALP"), - SUB_XAMC = fourCC("XAMC"), // FO3 - SUB_XAMT = fourCC("XAMT"), // FO3 - SUB_XAPD = fourCC("XAPD"), - SUB_XAPR = fourCC("XAPR"), - SUB_XASP = fourCC("XASP"), // FO4 - SUB_XATO = fourCC("XATO"), // FONV - SUB_XATP = fourCC("XATP"), // FO4 - SUB_XATR = fourCC("XATR"), - SUB_XBSD = fourCC("XBSD"), // FO4 - SUB_XCAS = fourCC("XCAS"), - SUB_XCCM = fourCC("XCCM"), - SUB_XCCP = fourCC("XCCP"), - SUB_XCET = fourCC("XCET"), // FO3 - SUB_XCGD = fourCC("XCGD"), - SUB_XCHG = fourCC("XCHG"), // thievery.exp - SUB_XCIM = fourCC("XCIM"), - SUB_XCLC = fourCC("XCLC"), - SUB_XCLL = fourCC("XCLL"), - SUB_XCLP = fourCC("XCLP"), // FO3 - SUB_XCLR = fourCC("XCLR"), - SUB_XCLW = fourCC("XCLW"), - SUB_XCMO = fourCC("XCMO"), - SUB_XCMT = fourCC("XCMT"), // TES4 only? - SUB_XCNT = fourCC("XCNT"), - SUB_XCRI = fourCC("XCRI"), // FO4 - SUB_XCVL = fourCC("XCVL"), - SUB_XCVR = fourCC("XCVR"), - SUB_XCWT = fourCC("XCWT"), - SUB_XCZA = fourCC("XCZA"), - SUB_XCZC = fourCC("XCZC"), - SUB_XCZR = fourCC("XCZR"), // TES5 - SUB_XDCR = fourCC("XDCR"), // FO3 - SUB_XEMI = fourCC("XEMI"), - SUB_XESP = fourCC("XESP"), - SUB_XEZN = fourCC("XEZN"), - SUB_XFVC = fourCC("XFVC"), - SUB_XGDR = fourCC("XGDR"), // FO4 - SUB_XGLB = fourCC("XGLB"), - SUB_XHLP = fourCC("XHLP"), // FO3 - SUB_XHLT = fourCC("XHLT"), // Unofficial Oblivion Patch - SUB_XHOR = fourCC("XHOR"), - SUB_XHRS = fourCC("XHRS"), - SUB_XHTW = fourCC("XHTW"), - SUB_XIBS = fourCC("XIBS"), // FO3 - SUB_XILL = fourCC("XILL"), - SUB_XILW = fourCC("XILW"), // FO4 - SUB_XIS2 = fourCC("XIS2"), - SUB_XLCM = fourCC("XLCM"), - SUB_XLCN = fourCC("XLCN"), - SUB_XLIB = fourCC("XLIB"), - SUB_XLIG = fourCC("XLIG"), - SUB_XLKR = fourCC("XLKR"), - SUB_XLKT = fourCC("XLKT"), // FO4 - SUB_XLOC = fourCC("XLOC"), - SUB_XLOD = fourCC("XLOD"), - SUB_XLRL = fourCC("XLRL"), - SUB_XLRM = fourCC("XLRM"), - SUB_XLRT = fourCC("XLRT"), - SUB_XLTW = fourCC("XLTW"), - SUB_XLYR = fourCC("XLYR"), // FO4 - SUB_XMBO = fourCC("XMBO"), - SUB_XMBP = fourCC("XMBP"), - SUB_XMBR = fourCC("XMBR"), - SUB_XMRC = fourCC("XMRC"), - SUB_XMRK = fourCC("XMRK"), - SUB_XMSP = fourCC("XMSP"), // FO4 - SUB_XNAM = fourCC("XNAM"), - SUB_XNDP = fourCC("XNDP"), - SUB_XOCP = fourCC("XOCP"), - SUB_XORD = fourCC("XORD"), // FO3 - SUB_XOWN = fourCC("XOWN"), - SUB_XPCI = fourCC("XPCI"), - SUB_XPDD = fourCC("XPDD"), // FO4 - SUB_XPLK = fourCC("XPLK"), // FO4 - SUB_XPOD = fourCC("XPOD"), - SUB_XPPA = fourCC("XPPA"), - SUB_XPRD = fourCC("XPRD"), - SUB_XPRI = fourCC("XPRI"), // FO4 - SUB_XPRM = fourCC("XPRM"), - SUB_XPTL = fourCC("XPTL"), - SUB_XPWR = fourCC("XPWR"), - SUB_XRAD = fourCC("XRAD"), // FO3 - SUB_XRDO = fourCC("XRDO"), // FO3 - SUB_XRDS = fourCC("XRDS"), - SUB_XRFG = fourCC("XRFG"), // FO4 - SUB_XRGB = fourCC("XRGB"), - SUB_XRGD = fourCC("XRGD"), - SUB_XRMR = fourCC("XRMR"), - SUB_XRNK = fourCC("XRNK"), // TES4 only? - SUB_XRTM = fourCC("XRTM"), - SUB_XSCL = fourCC("XSCL"), - SUB_XSED = fourCC("XSED"), - SUB_XSPC = fourCC("XSPC"), - SUB_XSRD = fourCC("XSRD"), // FONV - SUB_XSRF = fourCC("XSRF"), // FONV - SUB_XTEL = fourCC("XTEL"), - SUB_XTNM = fourCC("XTNM"), - SUB_XTRG = fourCC("XTRG"), - SUB_XTRI = fourCC("XTRI"), - SUB_XWCN = fourCC("XWCN"), - SUB_XWCS = fourCC("XWCS"), - SUB_XWCU = fourCC("XWCU"), - SUB_XWEM = fourCC("XWEM"), - SUB_XWPG = fourCC("XWPG"), // FO4 - SUB_XWPN = fourCC("XWPN"), // FO4 - SUB_XXXX = fourCC("XXXX"), - SUB_YNAM = fourCC("YNAM"), - SUB_ZNAM = fourCC("ZNAM"), - }; - // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records enum RecordFlag { diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index dc181dda4b..6ccdc0a8c7 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -42,22 +42,22 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): reader.getFormId(mBaseObj); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -78,57 +78,57 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): reader.getFormId(mEsp.parent); reader.get(mEsp.flags); break; - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } - case ESM4::SUB_XRGD: // ragdoll - case ESM4::SUB_XRGB: // ragdoll biped - case ESM4::SUB_XHRS: // horse formId - case ESM4::SUB_XMRC: // merchant container formId + case ESM::fourCC("XRGD"): // ragdoll + case ESM::fourCC("XRGB"): // ragdoll biped + case ESM::fourCC("XHRS"): // horse formId + case ESM::fourCC("XMRC"): // merchant container formId // TES5 - case ESM4::SUB_XAPD: // activation parent - case ESM4::SUB_XAPR: // active parent - case ESM4::SUB_XEZN: // encounter zone - case ESM4::SUB_XHOR: - case ESM4::SUB_XLCM: // levelled creature - case ESM4::SUB_XLCN: // location - case ESM4::SUB_XLKR: // location route? - case ESM4::SUB_XLRT: // location type + case ESM::fourCC("XAPD"): // activation parent + case ESM::fourCC("XAPR"): // active parent + case ESM::fourCC("XEZN"): // encounter zone + case ESM::fourCC("XHOR"): + case ESM::fourCC("XLCM"): // levelled creature + case ESM::fourCC("XLCN"): // location + case ESM::fourCC("XLKR"): // location route? + case ESM::fourCC("XLRT"): // location type // - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): // - case ESM4::SUB_XIS2: - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLOD: - case ESM4::SUB_VMAD: - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XRDS: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XEMI: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHLT: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMBR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XRNK: // FO4 + case ESM::fourCC("XIS2"): + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLOD"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XRDS"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XEMI"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHLT"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMBR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XRNK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index 74eaff2dab..0609e4e1bf 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -41,69 +41,69 @@ void ESM4::Activator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mActivationSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRadioStation); break; - case ESM4::SUB_XATO: + case ESM::fourCC("XATO"): reader.getZString(mActivationPrompt); break; // FONV - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WNAM: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CITC: - case ESM4::SUB_NVNM: - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_RADR: // FO4 - case ESM4::SUB_STCP: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CITC"): + case ESM::fourCC("NVNM"): + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("RADR"): // FO4 + case ESM::fourCC("STCP"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp index 1ecfda25e8..4a289ab760 100644 --- a/components/esm4/loadalch.cpp +++ b/components/esm4/loadalch.cpp @@ -42,35 +42,35 @@ void ESM4::Potion::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): if (subHdr.dataSize == 8) // TES4 { reader.get(&mItem, 8); @@ -82,36 +82,36 @@ void ESM4::Potion::load(ESM4::Reader& reader) reader.adjustFormId(mItem.withdrawl); reader.adjustFormId(mItem.sound); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_CTDA: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DESC: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_CUSD: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DESC"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("CUSD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp index 690684df7c..1b3bfd7e5b 100644 --- a/components/esm4/loadaloc.cpp +++ b/components/esm4/loadaloc.cpp @@ -42,34 +42,34 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mBattleSets.emplace_back()); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mLocationSets.emplace_back()); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mEnemySets.emplace_back()); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mNeutralSets.emplace_back()); break; - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): reader.getFormId(mFriendSets.emplace_back()); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mAllySets.emplace_back()); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mConditionalFaction); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): { reader.get(mMediaFlags); std::uint8_t flags = mMediaFlags.loopingOptions; @@ -77,21 +77,21 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data break; } - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.get(mLocationDelay); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.get(mRetriggerDelay); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.get(mDayStart); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.get(mNightStart); break; - case ESM4::SUB_NAM2: // always 0? 4 bytes - case ESM4::SUB_NAM3: // always 0? 4 bytes - case ESM4::SUB_FNAM: // always 0? 4 bytes + case ESM::fourCC("NAM2"): // always 0? 4 bytes + case ESM::fourCC("NAM3"): // always 0? 4 bytes + case ESM::fourCC("FNAM"): // always 0? 4 bytes { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp index 8c5bc45c85..39c42fc83f 100644 --- a/components/esm4/loadammo.cpp +++ b/components/esm4/loadammo.cpp @@ -41,13 +41,13 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): switch (subHdr.dataSize) { case 18: // TES4 @@ -86,7 +86,7 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) break; } break; - case ESM4::SUB_DAT2: + case ESM::fourCC("DAT2"): if (subHdr.dataSize == 20) { reader.get(mData.mProjPerShot); @@ -100,71 +100,71 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getFormId(mData.mProjectile); reader.get(mData.mFlags); mData.mFlags &= 0xFF; reader.get(mData.mDamage); reader.get(mData.mHealth); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getLocalizedString(mShortName); break; - case ESM4::SUB_QNAM: // FONV + case ESM::fourCC("QNAM"): // FONV reader.getLocalizedString(mAbbrev); break; - case ESM4::SUB_RCIL: + case ESM::fourCC("RCIL"): reader.getFormId(mAmmoEffects.emplace_back()); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScript); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_NAM1: // FO4 casing model data - case ESM4::SUB_NAM2: // + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("NAM1"): // FO4 casing model data + case ESM::fourCC("NAM2"): // reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp index fa440f5ace..a8156eef2b 100644 --- a/components/esm4/loadanio.cpp +++ b/components/esm4/loadanio.cpp @@ -41,25 +41,25 @@ void ESM4::AnimObject::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getZString(mUnloadEvent); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mIdleAnim); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp index 45e12739b9..8c74d020a6 100644 --- a/components/esm4/loadappa.cpp +++ b/components/esm4/loadappa.cpp @@ -41,13 +41,13 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) { reader.get(mData.value); @@ -61,24 +61,24 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) reader.get(mData.quality); } break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_MODT: - case ESM4::SUB_OBND: - case ESM4::SUB_QUAL: + case ESM::fourCC("MODT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("QUAL"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 2bb6240ee8..a1a1a10845 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -43,39 +43,39 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: - case ESM4::SUB_MOD5: + case ESM::fourCC("MOD4"): + case ESM::fourCC("MOD5"): { std::string model; reader.getZString(model); break; } - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getFormId(mTextureMale); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getFormId(mTextureFemale); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRacePrimary); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 reader.getFormId(mRaces.emplace_back()); else reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV break; - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); reader.get(mBodyTemplate.unknown1); // probably padding @@ -83,7 +83,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.unknown3); // probably padding reader.get(mBodyTemplate.type); break; - case ESM4::SUB_BOD2: // TES5+ + case ESM::fourCC("BOD2"): // TES5+ reader.get(mBodyTemplate.bodyPart); mBodyTemplate.flags = 0; mBodyTemplate.unknown1 = 0; // probably padding @@ -94,7 +94,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.type); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): if (subHdr.dataSize == 12) { std::uint16_t unknownInt16; @@ -111,40 +111,40 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) else reader.skipSubRecordData(); break; - case ESM4::SUB_MO2T: // FIXME: should group with MOD2 - case ESM4::SUB_MO2S: // FIXME: should group with MOD2 - case ESM4::SUB_MO2C: // FIXME: should group with MOD2 - case ESM4::SUB_MO2F: // FIXME: should group with MOD2 - case ESM4::SUB_MO3T: // FIXME: should group with MOD3 - case ESM4::SUB_MO3S: // FIXME: should group with MOD3 - case ESM4::SUB_MO3C: // FIXME: should group with MOD3 - case ESM4::SUB_MO3F: // FIXME: should group with MOD3 - case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3 - case ESM4::SUB_MO4T: // FIXME: should group with MOD4 - case ESM4::SUB_MO4S: // FIXME: should group with MOD4 - case ESM4::SUB_MO4C: // FIXME: should group with MOD4 - case ESM4::SUB_MO4F: // FIXME: should group with MOD4 - case ESM4::SUB_MO5T: - case ESM4::SUB_MO5S: - case ESM4::SUB_MO5C: - case ESM4::SUB_MO5F: - case ESM4::SUB_NAM2: // txst formid male - case ESM4::SUB_NAM3: // txst formid female - case ESM4::SUB_SNDD: // footset sound formid - case ESM4::SUB_BMDT: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_FULL: // FO3 - case ESM4::SUB_ICO2: // FO3 // female - case ESM4::SUB_ICON: // FO3 // male - case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("MO2T"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2S"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2C"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2F"): // FIXME: should group with MOD2 + case ESM::fourCC("MO3T"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3S"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3C"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3F"): // FIXME: should group with MOD3 + case ESM::fourCC("MOSD"): // FO3 // FIXME: should group with MOD3 + case ESM::fourCC("MO4T"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4S"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4C"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4F"): // FIXME: should group with MOD4 + case ESM::fourCC("MO5T"): + case ESM::fourCC("MO5S"): + case ESM::fourCC("MO5C"): + case ESM::fourCC("MO5F"): + case ESM::fourCC("NAM2"): // txst formid male + case ESM::fourCC("NAM3"): // txst formid female + case ESM::fourCC("SNDD"): // footset sound formid + case ESM::fourCC("BMDT"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("FULL"): // FO3 + case ESM::fourCC("ICO2"): // FO3 // female + case ESM::fourCC("ICON"): // FO3 // male + case ESM::fourCC("MODT"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODS"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODD"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp index 572cbd6ecd..dc926f7a01 100644 --- a/components/esm4/loadarmo.cpp +++ b/components/esm4/loadarmo.cpp @@ -41,13 +41,13 @@ void ESM4::Armor::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -69,12 +69,12 @@ void ESM4::Armor::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INDX: // FO4 + case ESM::fourCC("INDX"): // FO4 { reader.get(currentIndex); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (subHdr.dataSize == 4) { @@ -97,28 +97,28 @@ void ESM4::Armor::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MIC2: + case ESM::fourCC("MIC2"): reader.getZString(mMiniIconFemale); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): if (subHdr.dataSize == 8) // FO3 { reader.get(mArmorFlags); @@ -133,7 +133,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES4; } break; - case ESM4::SUB_BODT: + case ESM::fourCC("BODT"): { reader.get(mArmorFlags); uint32_t flags = 0; @@ -146,7 +146,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES5; break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): // FO4, TES5 if (subHdr.dataSize == 4 || subHdr.dataSize == 8) { @@ -163,75 +163,75 @@ void ESM4::Armor::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO2S: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_OBND: - case ESM4::SUB_RNAM: // race formid - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_TNAM: - case ESM4::SUB_DNAM: - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_ETYP: - case ESM4::SUB_BMCT: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_VMAD: - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_MODS: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_SNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO2S"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("OBND"): + case ESM::fourCC("RNAM"): // race formid + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("BMCT"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("MODS"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("SNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp index d79df9d8ef..e8fe9d3b34 100644 --- a/components/esm4/loadaspc.cpp +++ b/components/esm4/loadaspc.cpp @@ -41,35 +41,35 @@ void ESM4::AcousticSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnvironmentType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mAmbientLoopSounds.emplace_back()); break; - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.getFormId(mSoundRegion); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mIsInterior); break; - case ESM4::SUB_XTRI: + case ESM::fourCC("XTRI"): std::uint8_t isInterior; reader.get(isInterior); mIsInterior = isInterior; break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // usually 0 for FONV (maybe # of close Actors for Walla to trigger) (4 bytes) // Weather attenuation in FO4 (2 bytes) reader.skipSubRecordData(); break; } - case ESM4::SUB_BNAM: // TES5 reverb formid - case ESM4::SUB_OBND: + case ESM::fourCC("BNAM"): // TES5 reverb formid + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp index f2842e5313..cef942074a 100644 --- a/components/esm4/loadbook.cpp +++ b/components/esm4/loadbook.cpp @@ -42,16 +42,16 @@ void ESM4::Book::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -82,53 +82,53 @@ void ESM4::Book::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: - case ESM4::SUB_INAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp index 509eadfcf1..5ff3b5908b 100644 --- a/components/esm4/loadbptd.cpp +++ b/components/esm4/loadbptd.cpp @@ -56,56 +56,56 @@ void ESM4::BodyPartData::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BPTN: + case ESM::fourCC("BPTN"): reader.getLocalizedString(bodyPart.mPartName); break; - case ESM4::SUB_BPNN: + case ESM::fourCC("BPNN"): reader.getZString(bodyPart.mNodeName); break; - case ESM4::SUB_BPNT: + case ESM::fourCC("BPNT"): reader.getZString(bodyPart.mVATSTarget); break; - case ESM4::SUB_BPNI: + case ESM::fourCC("BPNI"): reader.getZString(bodyPart.mIKStartNode); break; - case ESM4::SUB_BPND: + case ESM::fourCC("BPND"): if (subHdr.dataSize == sizeof(bodyPart.mData)) reader.get(bodyPart.mData); // FIXME: FO4 else reader.skipSubRecordData(); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(bodyPart.mLimbReplacementModel); break; - case ESM4::SUB_NAM4: // FIXME: assumed occurs last + case ESM::fourCC("NAM4"): // FIXME: assumed occurs last reader.getZString(bodyPart.mGoreEffectsTarget); // target bone mBodyParts.push_back(bodyPart); // FIXME: possible without copying? bodyPart.clear(); break; - case ESM4::SUB_NAM5: - case ESM4::SUB_RAGA: // ragdoll - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BNAM: // FO4 - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_ENAM: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_JNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 + case ESM::fourCC("NAM5"): + case ESM::fourCC("RAGA"): // ragdoll + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BNAM"): // FO4 + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("ENAM"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("JNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 0091ab0bd6..8106c1637f 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -78,7 +78,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { if (!reader.getZString(mEditorId)) throw std::runtime_error("CELL EDID data read error"); @@ -89,7 +89,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_XCLC: + case ESM::fourCC("XCLC"): { //(X, Y) grid location of the cell followed by flags. Always in // exterior cells and never in interior cells. @@ -114,10 +114,10 @@ void ESM4::Cell::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) reader.get(mCellFlags); @@ -131,7 +131,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCLR: // for exterior cells + case ESM::fourCC("XCLR"): // for exterior cells { mRegions.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) @@ -145,7 +145,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -166,19 +166,19 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; // Oblivion only? - case ESM4::SUB_XCCM: + case ESM::fourCC("XCCM"): reader.getFormId(mClimate); break; - case ESM4::SUB_XCWT: + case ESM::fourCC("XCWT"): reader.getFormId(mWater); break; - case ESM4::SUB_XCLW: + case ESM::fourCC("XCLW"): reader.get(mWaterHeight); break; - case ESM4::SUB_XCLL: + case ESM::fourCC("XCLL"): { switch (subHdr.dataSize) { @@ -197,45 +197,45 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCMT: + case ESM::fourCC("XCMT"): reader.get(mMusicType); break; // Oblivion only? - case ESM4::SUB_LTMP: + case ESM::fourCC("LTMP"): reader.getFormId(mLightingTemplate); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP - case ESM4::SUB_XCMO: + case ESM::fourCC("XCMO"): reader.getFormId(mMusic); break; - case ESM4::SUB_XCAS: + case ESM::fourCC("XCAS"): reader.getFormId(mAcousticSpace); break; - case ESM4::SUB_TVDT: - case ESM4::SUB_MHDT: - case ESM4::SUB_XCGD: - case ESM4::SUB_XNAM: - case ESM4::SUB_XLCN: - case ESM4::SUB_XWCS: - case ESM4::SUB_XWCU: - case ESM4::SUB_XWCN: - case ESM4::SUB_XCIM: - case ESM4::SUB_XEZN: - case ESM4::SUB_XWEM: - case ESM4::SUB_XILL: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCET: // FO3 - case ESM4::SUB_IMPF: // FO3 Zeta - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_PCMB: // FO4 - case ESM4::SUB_RVIS: // FO4 - case ESM4::SUB_VISI: // FO4 - case ESM4::SUB_XGDR: // FO4 - case ESM4::SUB_XILW: // FO4 - case ESM4::SUB_XCRI: // FO4 - case ESM4::SUB_XPRI: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("TVDT"): + case ESM::fourCC("MHDT"): + case ESM::fourCC("XCGD"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XWCS"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XCIM"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("XILL"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCET"): // FO3 + case ESM::fourCC("IMPF"): // FO3 Zeta + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("PCMB"): // FO4 + case ESM::fourCC("RVIS"): // FO4 + case ESM::fourCC("VISI"): // FO4 + case ESM::fourCC("XGDR"): // FO4 + case ESM::fourCC("XILW"): // FO4 + case ESM::fourCC("XCRI"): // FO4 + case ESM::fourCC("XPRI"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp index 7d232a0aa1..e80b36849e 100644 --- a/components/esm4/loadclas.cpp +++ b/components/esm4/loadclas.cpp @@ -41,21 +41,21 @@ void ESM4::Class::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mDesc); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: - case ESM4::SUB_ATTR: - case ESM4::SUB_PRPS: + case ESM::fourCC("DATA"): + case ESM::fourCC("ATTR"): + case ESM::fourCC("PRPS"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp index bc887cd15c..b7157877c7 100644 --- a/components/esm4/loadclfm.cpp +++ b/components/esm4/loadclfm.cpp @@ -41,22 +41,22 @@ void ESM4::Colour::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mColour.red); reader.get(mColour.green); reader.get(mColour.blue); reader.get(mColour.custom); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mPlayable); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp index c67ac3df6b..48999adb18 100644 --- a/components/esm4/loadclot.cpp +++ b/components/esm4/loadclot.cpp @@ -41,55 +41,55 @@ void ESM4::Clothing::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): reader.get(mClothingFlags); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index d650678093..a41b06cdd8 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -41,17 +41,17 @@ void ESM4::Container::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mDataFlags); reader.get(mWeight); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -59,47 +59,47 @@ void ESM4::Container::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_QNAM: + case ESM::fourCC("QNAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_VMAD: // TES5 only - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_COCT: // TES5 only - case ESM4::SUB_COED: // TES5 only - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_ONAM: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_TNAM: - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("VMAD"): // TES5 only + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("COCT"): // TES5 only + case ESM::fourCC("COED"): // TES5 only + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("TNAM"): + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 0c07eb92e3..532bd5acdb 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -45,16 +45,16 @@ void ESM4::Creature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -62,76 +62,76 @@ void ESM4::Creature::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mFaction); reader.adjustFormId(mFaction.faction); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): if (subHdr.dataSize == 20) // FO3 reader.skipSubRecordData(); else reader.get(mAIData); // 12 bytes break; - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): // if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) if (subHdr.dataSize == 24) reader.get(mBaseConfig); else reader.get(&mBaseConfig, 16); // TES4 break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 17) // FO3 reader.skipSubRecordData(); else reader.get(mData); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mBaseScale); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.get(mTurningSpeed); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.get(mFootWeight); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getZString(mBloodSpray); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(mBloodDecal); break; - case ESM4::SUB_NIFZ: + case ESM::fourCC("NIFZ"): if (!reader.getZeroTerminatedStringArray(mNif)) throw std::runtime_error("CREA NIFZ data read error"); break; - case ESM4::SUB_NIFT: + case ESM::fourCC("NIFT"): { if (subHdr.dataSize != 4) // FIXME: FO3 { @@ -147,33 +147,33 @@ void ESM4::Creature::load(ESM4::Reader& reader) Log(Debug::Verbose) << "CREA NIFT " << mId << ", non-zero " << nift; break; } - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): if (!reader.getZeroTerminatedStringArray(mKf)) throw std::runtime_error("CREA KFFZ data read error"); break; - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; // FO3 - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mBodyParts.emplace_back()); break; - case ESM4::SUB_MODT: - case ESM4::SUB_RNAM: - case ESM4::SUB_CSDT: - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_NAM5: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_LNAM: // FO3 - case ESM4::SUB_EITM: // FO3 - case ESM4::SUB_DEST: // FO3 - case ESM4::SUB_DSTD: // FO3 - case ESM4::SUB_DSTF: // FO3 - case ESM4::SUB_DMDL: // FO3 - case ESM4::SUB_DMDT: // FO3 - case ESM4::SUB_COED: // FO3 + case ESM::fourCC("MODT"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("NAM5"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("LNAM"): // FO3 + case ESM::fourCC("EITM"): // FO3 + case ESM::fourCC("DEST"): // FO3 + case ESM::fourCC("DSTD"): // FO3 + case ESM::fourCC("DSTF"): // FO3 + case ESM::fourCC("DMDL"): // FO3 + case ESM::fourCC("DMDT"): // FO3 + case ESM::fourCC("COED"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp index 19e1099482..3ed9b79e0a 100644 --- a/components/esm4/loaddial.cpp +++ b/components/esm4/loaddial.cpp @@ -42,19 +42,19 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mTopicName); break; - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuests.emplace_back()); break; - case ESM4::SUB_QSTR: // Seems never used in TES4 + case ESM::fourCC("QSTR"): // Seems never used in TES4 reader.getFormId(mQuestsRemoved.emplace_back()); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 4) // TES5 { @@ -74,20 +74,20 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mPriority); break; // FO3/FONV - case ESM4::SUB_TDUM: + case ESM::fourCC("TDUM"): reader.getZString(mTextDumb); break; // FONV - case ESM4::SUB_SCRI: - case ESM4::SUB_INFC: // FONV info connection - case ESM4::SUB_INFX: // FONV info index - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_KNAM: // FO4 + case ESM::fourCC("SCRI"): + case ESM::fourCC("INFC"): // FONV info connection + case ESM::fourCC("INFX"): // FONV info index + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("KNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp index 50135fc7a1..9c0c193f81 100644 --- a/components/esm4/loaddobj.cpp +++ b/components/esm4/loaddobj.cpp @@ -44,10 +44,10 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; // "DefaultObjectManager" - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mData.stimpack); reader.getFormId(mData.superStimpack); reader.getFormId(mData.radX); @@ -87,7 +87,7 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) reader.getFormId(mData.cateyeMobileEffectNYI); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp index 7fe38b6b7a..10171085c3 100644 --- a/components/esm4/loaddoor.cpp +++ b/components/esm4/loaddoor.cpp @@ -41,57 +41,57 @@ void ESM4::Door::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mDoorFlags); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mRandomTeleport); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_ONAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("ONAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp index 28f6d33c6e..7e356889d9 100644 --- a/components/esm4/loadeyes.cpp +++ b/components/esm4/loadeyes.cpp @@ -41,16 +41,16 @@ void ESM4::Eyes::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; default: diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp index 69f1c82b13..164f97eff1 100644 --- a/components/esm4/loadflor.cpp +++ b/components/esm4/loadflor.cpp @@ -41,53 +41,53 @@ void ESM4::Flora::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PFIG: + case ESM::fourCC("PFIG"): reader.getFormId(mIngredient); break; - case ESM4::SUB_PFPC: + case ESM::fourCC("PFPC"): reader.get(mPercentHarvest); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_FNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_PNAM: - case ESM4::SUB_RNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_ATTX: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("FNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("ATTX"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp index 9da17bc84b..4acf4d28d2 100644 --- a/components/esm4/loadflst.cpp +++ b/components/esm4/loadflst.cpp @@ -41,13 +41,13 @@ void ESM4::FormIdList::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mObjects.emplace_back()); break; default: diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 41ddca07a2..75dc3751a6 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -41,10 +41,10 @@ void ESM4::Furniture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { std::string name; reader.getLocalizedString(name); @@ -53,65 +53,65 @@ void ESM4::Furniture::load(ESM4::Reader& reader) mFullName = std::move(name); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mActiveMarkerFlags); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_ENAM: - case ESM4::SUB_FNAM: - case ESM4::SUB_FNMK: - case ESM4::SUB_FNPR: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WBDT: - case ESM4::SUB_XMRK: - case ESM4::SUB_PRPS: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_CITC: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_COED: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NAM1: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("ENAM"): + case ESM::fourCC("FNAM"): + case ESM::fourCC("FNMK"): + case ESM::fourCC("FNPR"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WBDT"): + case ESM::fourCC("XMRK"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("CITC"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("COED"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NAM1"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 436f3e34ae..4349bcb072 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -41,16 +41,16 @@ void ESM4::GlobalVariable::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mType); break; - case ESM4::SUB_FLTV: + case ESM::fourCC("FLTV"): reader.get(mValue); break; case ESM::fourCC("NTWK"): // FO76 diff --git a/components/esm4/loadgmst.cpp b/components/esm4/loadgmst.cpp index f22ed5848d..0b2df075f2 100644 --- a/components/esm4/loadgmst.cpp +++ b/components/esm4/loadgmst.cpp @@ -67,10 +67,10 @@ namespace ESM4 const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): mData = readData(mId, mEditorId, reader); break; default: diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp index ebcdde04a1..8514b3aa0a 100644 --- a/components/esm4/loadgras.cpp +++ b/components/esm4/loadgras.cpp @@ -41,23 +41,23 @@ void ESM4::Grass::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp index 3ab983d6b6..f3e5a8a1c3 100644 --- a/components/esm4/loadhair.cpp +++ b/components/esm4/loadhair.cpp @@ -41,25 +41,25 @@ void ESM4::Hair::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index c560ff5fac..9b7e27bdf9 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -45,32 +45,32 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mExtraParts.emplace_back()); break; - case ESM4::SUB_NAM0: // TES5 + case ESM::fourCC("NAM0"): // TES5 { std::uint32_t value; reader.get(value); type = value; break; } - case ESM4::SUB_NAM1: // TES5 + case ESM::fourCC("NAM1"): // TES5 { std::string file; reader.getZString(file); @@ -87,29 +87,29 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) mTriFile[*type] = std::move(file); break; } - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mBaseTexture); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mColor); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mValidRaces.emplace_back()); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mType); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): case ESM::fourCC("ENLM"): case ESM::fourCC("XFLG"): case ESM::fourCC("ENLT"): case ESM::fourCC("ENLS"): case ESM::fourCC("AUUV"): case ESM::fourCC("MODD"): // Model data end - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp index 310c43b2e1..18a408f053 100644 --- a/components/esm4/loadidle.cpp +++ b/components/esm4/loadidle.cpp @@ -41,16 +41,16 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getZString(mCollision); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getZString(mEvent); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): { switch (subHdr.dataSize) { @@ -74,21 +74,21 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_CTDA: // formId - case ESM4::SUB_CTDT: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_DATA: - case ESM4::SUB_MODD: - case ESM4::SUB_MODS: - case ESM4::SUB_MODT: - case ESM4::SUB_GNAM: // FO4 + case ESM::fourCC("CTDA"): // formId + case ESM::fourCC("CTDT"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODD"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODT"): + case ESM::fourCC("GNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp index 3f1ed9518c..0aec281c6c 100644 --- a/components/esm4/loadidlm.cpp +++ b/components/esm4/loadidlm.cpp @@ -43,13 +43,13 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_IDLF: + case ESM::fourCC("IDLF"): reader.get(mIdleFlags); break; - case ESM4::SUB_IDLC: + case ESM::fourCC("IDLC"): if (subHdr.dataSize != 1) // FO3 can have 4? { reader.skipSubRecordData(); @@ -58,10 +58,10 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.get(mIdleCount); break; - case ESM4::SUB_IDLT: + case ESM::fourCC("IDLT"): reader.get(mIdleTimer); break; - case ESM4::SUB_IDLA: + case ESM::fourCC("IDLA"): { bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; if (esmVer == ESM::VER_094 || isFONV) // FO3? 4 or 8 bytes @@ -75,17 +75,17 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.getFormId(value); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_QNAM: + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("QNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 0359f6d23b..76f51357a3 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -43,53 +43,53 @@ void ESM4::ItemMod::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.mValue); reader.get(mData.mWeight); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODD: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("OBND"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODD"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 1b001c1665..266bdb086c 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -49,13 +49,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuest); break; // FormId quest id - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): reader.getFormId(mSound); break; // FO3 (not used in FONV?) - case ESM4::SUB_TRDT: + case ESM::fourCC("TRDT"): { if (subHdr.dataSize == 16) // TES4 reader.get(&mResponseData, 16); @@ -70,16 +70,16 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getLocalizedString(mResponse); break; // response text - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mNotes); break; // actor notes - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mEdits); break; // not in TES4 - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 reader.get(&mTargetCondition, 24); @@ -105,7 +105,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { if (!ignore) reader.get(mScript.scriptHeader); @@ -114,16 +114,16 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { localVar.clear(); reader.get(localVar.index); @@ -136,7 +136,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD + case ESM::fourCC("SCVR"): // assumed always pair with SLSD { reader.getZString(localVar.variableName); @@ -144,7 +144,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); @@ -153,13 +153,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NEXT: // FO3/FONV marker for next script header + case ESM::fourCC("NEXT"): // FO3/FONV marker for next script header { ignore = true; break; } - case ESM4::SUB_DATA: // always 3 for TES4 ? + case ESM::fourCC("DATA"): // always 3 for TES4 ? { if (subHdr.dataSize == 4) // FO3/FONV { @@ -171,48 +171,48 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) reader.skipSubRecordData(); // FIXME break; } - case ESM4::SUB_NAME: // FormId add topic (not always present) - case ESM4::SUB_CTDT: // older version of CTDA? 20 bytes - case ESM4::SUB_SCHD: // 28 bytes - case ESM4::SUB_TCLT: // FormId choice - case ESM4::SUB_TCLF: // FormId - case ESM4::SUB_PNAM: // TES4 DLC - case ESM4::SUB_TPIC: // TES4 DLC - case ESM4::SUB_ANAM: // FO3 speaker formid - case ESM4::SUB_DNAM: // FO3 speech challenge - case ESM4::SUB_KNAM: // FO3 formid - case ESM4::SUB_LNAM: // FONV - case ESM4::SUB_TCFU: // FONV - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_TWAT: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_EDID: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_ONAM: // TES5 - case ESM4::SUB_QNAM: // TES5 for mScript - case ESM4::SUB_RNAM: // TES5 - case ESM4::SUB_ALFA: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GREE: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_INCC: // FO4 - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_IOVR: // FO4 - case ESM4::SUB_MODQ: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_NAM4: // FO4 - case ESM4::SUB_NAM9: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_TIQS: // FO4 - case ESM4::SUB_TNAM: // FO4 - case ESM4::SUB_TRDA: // FO4 - case ESM4::SUB_TSCE: // FO4 - case ESM4::SUB_WZMD: // FO4 + case ESM::fourCC("NAME"): // FormId add topic (not always present) + case ESM::fourCC("CTDT"): // older version of CTDA? 20 bytes + case ESM::fourCC("SCHD"): // 28 bytes + case ESM::fourCC("TCLT"): // FormId choice + case ESM::fourCC("TCLF"): // FormId + case ESM::fourCC("PNAM"): // TES4 DLC + case ESM::fourCC("TPIC"): // TES4 DLC + case ESM::fourCC("ANAM"): // FO3 speaker formid + case ESM::fourCC("DNAM"): // FO3 speech challenge + case ESM::fourCC("KNAM"): // FO3 formid + case ESM::fourCC("LNAM"): // FONV + case ESM::fourCC("TCFU"): // FONV + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("TWAT"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("EDID"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("ONAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 for mScript + case ESM::fourCC("RNAM"): // TES5 + case ESM::fourCC("ALFA"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GREE"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("INCC"): // FO4 + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("IOVR"): // FO4 + case ESM::fourCC("MODQ"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("NAM4"): // FO4 + case ESM::fourCC("NAM9"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("TIQS"): // FO4 + case ESM::fourCC("TNAM"): // FO4 + case ESM::fourCC("TRDA"): // FO4 + case ESM::fourCC("TSCE"): // FO4 + case ESM::fourCC("WZMD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp index d0b81fd4a1..64103058e5 100644 --- a/components/esm4/loadingr.cpp +++ b/components/esm4/loadingr.cpp @@ -42,10 +42,10 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -64,7 +64,7 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 @@ -74,49 +74,49 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): reader.get(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_YNAM: - case ESM4::SUB_ZNAM: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("YNAM"): + case ESM::fourCC("ZNAM"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp index 9b0c280b8b..b430f7ce3d 100644 --- a/components/esm4/loadkeym.cpp +++ b/components/esm4/loadkeym.cpp @@ -41,54 +41,54 @@ void ESM4::Key::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 2215b56dd1..53fb1de083 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -65,18 +65,18 @@ void ESM4::Land::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mLandFlags); break; } - case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VNML"): // vertex normals, 33x33x(1+1+1) = 3267 { reader.get(mVertNorm); mDataTypes |= LAND_VNML; break; } - case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 + case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { #if 0 reader.get(mHeightMap.heightOffset); @@ -88,13 +88,13 @@ void ESM4::Land::load(ESM4::Reader& reader) mDataTypes |= LAND_VHGT; break; } - case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VCLR"): // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 { reader.get(mVertColr); mDataTypes |= LAND_VCLR; break; } - case ESM4::SUB_BTXT: + case ESM::fourCC("BTXT"): { BTXT base; if (reader.getExact(base)) @@ -112,7 +112,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ATXT: + case ESM::fourCC("ATXT"): { if (currentAddQuad != -1) { @@ -144,7 +144,7 @@ void ESM4::Land::load(ESM4::Reader& reader) currentAddQuad = layer.texture.quadrant; break; } - case ESM4::SUB_VTXT: + case ESM::fourCC("VTXT"): { if (currentAddQuad == -1) throw std::runtime_error("VTXT without ATXT found"); @@ -177,7 +177,7 @@ void ESM4::Land::load(ESM4::Reader& reader) // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } - case ESM4::SUB_VTEX: // only in Oblivion? + case ESM::fourCC("VTEX"): // only in Oblivion? { const std::uint16_t count = reader.subRecordHeader().dataSize / sizeof(ESM::FormId32); if ((reader.subRecordHeader().dataSize % sizeof(ESM::FormId32)) != 0) @@ -191,7 +191,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MPCD: // FO4 + case ESM::fourCC("MPCD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp index 0959be10a2..ce895ea5b8 100644 --- a/components/esm4/loadlgtm.cpp +++ b/components/esm4/loadlgtm.cpp @@ -44,10 +44,10 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 36) // TES4 reader.get(&mLighting, 36); if (subHdr.dataSize == 40) // FO3/FONV @@ -60,7 +60,7 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) else reader.skipSubRecordData(); // throw? break; - case ESM4::SUB_DALC: // TES5 + case ESM::fourCC("DALC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp index 0848ee8435..a0d467bafc 100644 --- a/components/esm4/loadligh.cpp +++ b/components/esm4/loadligh.cpp @@ -40,13 +40,13 @@ void ESM4::Light::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize != 32 && subHdr.dataSize != 48 && subHdr.dataSize != 64) { @@ -78,47 +78,47 @@ void ESM4::Light::load(ESM4::Reader& reader) reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mFade); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_WGDR: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("WGDR"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 9b2d12034f..a8b6c9ec81 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -41,10 +41,10 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { switch (subHdr.dataSize) { @@ -61,22 +61,22 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mTextureFile); break; // Oblivion only? - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mTextureSpecular); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mGrass.emplace_back()); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTexture); break; // TES5, FO4 - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getFormId(mMaterial); break; // TES5, FO4 - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mMaterialFlags); break; // SSE default: diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index b1a0a0f241..8ce2497bcc 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -41,22 +41,22 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTemplate); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlCreaFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -83,7 +83,7 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_OBND: // FO3 + case ESM::fourCC("OBND"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index cab8db4a21..51e3e33a55 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -41,20 +41,20 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -76,14 +76,14 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_LLCT: - case ESM4::SUB_OBND: // FO3/FONV - case ESM4::SUB_COED: // FO3/FONV - case ESM4::SUB_LVLG: // FO3/FONV - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLM: // FO4 - case ESM4::SUB_LVSG: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("LLCT"): + case ESM::fourCC("OBND"): // FO3/FONV + case ESM::fourCC("COED"): // FO3/FONV + case ESM::fourCC("LVLG"): // FO3/FONV + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLM"): // FO4 + case ESM::fourCC("LVSG"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index febdcbeca9..6633d6ad7b 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -42,22 +42,22 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_LLCT: + case ESM::fourCC("LLCT"): reader.get(mListCount); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlActorFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -89,15 +89,15 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_COED: // owner - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLG: // FO4 - case ESM4::SUB_LVLM: // FO4 + case ESM::fourCC("COED"): // owner + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLG"): // FO4 + case ESM::fourCC("LVLM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp index 13d5e7d83d..6d45b689ba 100644 --- a/components/esm4/loadmato.cpp +++ b/components/esm4/loadmato.cpp @@ -41,18 +41,18 @@ void ESM4::Material::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DNAM: - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("DNAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp index 6dfd69148d..b27e38f055 100644 --- a/components/esm4/loadmisc.cpp +++ b/components/esm4/loadmisc.cpp @@ -41,58 +41,58 @@ void ESM4::MiscItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_CDIX: // FO4 - case ESM4::SUB_CVPA: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("CDIX"): // FO4 + case ESM::fourCC("CVPA"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp index e15c508bc1..f7c088c47f 100644 --- a/components/esm4/loadmset.cpp +++ b/components/esm4/loadmset.cpp @@ -41,91 +41,91 @@ void ESM4::MediaSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.get(mSetType); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mEnabled); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mSet2); break; - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mSet3); break; - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.getZString(mSet4); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.getZString(mSet5); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.getZString(mSet6); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.getZString(mSet7); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mSoundIntro); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mSoundOutro); break; - case ESM4::SUB_NAM8: + case ESM::fourCC("NAM8"): reader.get(mLevel8); break; - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): reader.get(mLevel9); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.get(mLevel0); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mLevelA); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mLevelB); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mLevelC); break; - case ESM4::SUB_JNAM: + case ESM::fourCC("JNAM"): reader.get(mBoundaryDayOuter); break; - case ESM4::SUB_KNAM: + case ESM::fourCC("KNAM"): reader.get(mBoundaryDayMiddle); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mBoundaryDayInner); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mBoundaryNightOuter); break; - case ESM4::SUB_NNAM: + case ESM::fourCC("NNAM"): reader.get(mBoundaryNightMiddle); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.get(mBoundaryNightInner); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mTime1); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.get(mTime2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mTime3); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.get(mTime4); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp index 14091e96f0..3e0cc9ea2d 100644 --- a/components/esm4/loadmstt.cpp +++ b/components/esm4/loadmstt.cpp @@ -41,41 +41,41 @@ void ESM4::MovableStatic::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_MODB: - case ESM4::SUB_PRPS: - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("MODB"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp index 47ed71b2cf..a06b4dc81c 100644 --- a/components/esm4/loadmusc.cpp +++ b/components/esm4/loadmusc.cpp @@ -43,16 +43,16 @@ void ESM4::Music::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mMusicFile); break; - case ESM4::SUB_ANAM: // FONV float (attenuation in db? loop if positive?) - case ESM4::SUB_WNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_TNAM: // TES5 + case ESM::fourCC("ANAM"): // FONV float (attenuation in db? loop if positive?) + case ESM::fourCC("WNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("TNAM"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 5b73af606e..47befbf268 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -241,13 +241,13 @@ void ESM4::Navigation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: // seems to be unused? + case ESM::fourCC("EDID"): // seems to be unused? { if (!reader.getZString(mEditorId)) throw std::runtime_error("NAVI EDID data read error"); break; } - case ESM4::SUB_NVPP: + case ESM::fourCC("NVPP"): { // FIXME: FO4 updates the format if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) @@ -330,14 +330,14 @@ void ESM4::Navigation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_NVER: + case ESM::fourCC("NVER"): { std::uint32_t version; // always the same? (0x0c) reader.get(version); // TODO: store this or use it for merging? // std::cout << "NAVI version " << std::dec << version << std::endl; break; } - case ESM4::SUB_NVMI: // multiple + case ESM::fourCC("NVMI"): // multiple { // Can only read TES4 navmesh data // Note FO4 FIXME above @@ -353,8 +353,8 @@ void ESM4::Navigation::load(ESM4::Reader& reader) mNavMeshInfo.push_back(nvmi); break; } - case ESM4::SUB_NVSI: // from Dawnguard onwards - case ESM4::SUB_NVCI: // FO3 + case ESM::fourCC("NVSI"): // from Dawnguard onwards + case ESM::fourCC("NVCI"): // FO3 { reader.skipSubRecordData(); // FIXME: break; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 828fd77ca1..ebe6a7dbbb 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -209,7 +209,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_NVNM: + case ESM::fourCC("NVNM"): { // See FIXME in ESM4::Navigation::load. // FO4 updates the format @@ -224,19 +224,19 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) mData.push_back(nvnm); // FIXME try swap break; } - case ESM4::SUB_ONAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_NNAM: - case ESM4::SUB_NVER: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_NVVX: // FO3 - case ESM4::SUB_NVTR: // FO3 - case ESM4::SUB_NVCA: // FO3 - case ESM4::SUB_NVDP: // FO3 - case ESM4::SUB_NVGD: // FO3 - case ESM4::SUB_NVEX: // FO3 - case ESM4::SUB_EDID: // FO3 - case ESM4::SUB_MNAM: // FO4 + case ESM::fourCC("ONAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("NNAM"): + case ESM::fourCC("NVER"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("NVVX"): // FO3 + case ESM::fourCC("NVTR"): // FO3 + case ESM::fourCC("NVCA"): // FO3 + case ESM::fourCC("NVDP"): // FO3 + case ESM::fourCC("NVGD"): // FO3 + case ESM::fourCC("NVEX"): // FO3 + case ESM::fourCC("EDID"): // FO3 + case ESM::fourCC("MNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp index 9c1b4b3140..aee7909e88 100644 --- a/components/esm4/loadnote.cpp +++ b/components/esm4/loadnote.cpp @@ -41,41 +41,41 @@ void ESM4::Note::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 885263d67b..9b8a1679ef 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -48,16 +48,16 @@ void ESM4::Npc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; // not for TES5, see Race - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -65,13 +65,13 @@ void ESM4::Npc::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): { // FO4, FO76 if (subHdr.dataSize == 5) @@ -81,27 +81,27 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.adjustFormId(mFaction.faction); break; } - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRace); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClass); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mHair); break; // not for TES5 - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEyes); break; // - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; // - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): { if (subHdr.dataSize != 12) { @@ -112,7 +112,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mAIData); // TES4 break; } - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): { switch (subHdr.dataSize) { @@ -129,7 +129,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 0) break; @@ -140,19 +140,19 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // FIXME: should be read into mWornArmor for FO4 if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -161,10 +161,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mFootWeight); break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): { // Seems to be only below 3, and only happens 3 times while loading TES4: // Forward_SheogorathWithCane.kf @@ -174,10 +174,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) throw std::runtime_error("NPC_ KFFZ data read error"); break; } - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mHairLength); break; - case ESM4::SUB_HCLR: + case ESM::fourCC("HCLR"): { reader.get(mHairColour.red); reader.get(mHairColour.green); @@ -186,10 +186,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { mSymShapeModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -197,7 +197,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { mAsymShapeModeCoefficients.resize(30); for (std::size_t i = 0; i < 30; ++i) @@ -205,7 +205,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { mSymTextureModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -213,122 +213,122 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { reader.get(mFgRace); // std::cout << "race " << mEditorId << " " << mRace << std::endl; // FIXME // std::cout << "fg race " << mEditorId << " " << mFgRace << std::endl; // FIXME break; } - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mHeadParts.emplace_back()); break; - case ESM4::SUB_HCLF: // TES5 hair colour + case ESM::fourCC("HCLF"): // TES5 hair colour { reader.getFormId(mHairColourId); break; } - case ESM4::SUB_BCLF: + case ESM::fourCC("BCLF"): { reader.getFormId(mBeardColourId); break; } - case ESM4::SUB_COCT: // TES5 + case ESM::fourCC("COCT"): // TES5 { std::uint32_t count; reader.get(count); break; } - case ESM4::SUB_DOFT: + case ESM::fourCC("DOFT"): reader.getFormId(mDefaultOutfit); break; - case ESM4::SUB_SOFT: + case ESM::fourCC("SOFT"): reader.getFormId(mSleepOutfit); break; - case ESM4::SUB_DPLT: + case ESM::fourCC("DPLT"): reader.getFormId(mDefaultPkg); break; // AI package list - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_NAM6: // height mult - case ESM4::SUB_NAM7: // weight mult - case ESM4::SUB_ATKR: - case ESM4::SUB_CRIF: - case ESM4::SUB_CSDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_ECOR: - case ESM4::SUB_ANAM: - case ESM4::SUB_ATKD: - case ESM4::SUB_ATKE: - case ESM4::SUB_FTST: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM5: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_NAMA: - case ESM4::SUB_OBND: - case ESM4::SUB_PRKR: - case ESM4::SUB_PRKZ: - case ESM4::SUB_QNAM: - case ESM4::SUB_SPCT: - case ESM4::SUB_TIAS: - case ESM4::SUB_TINC: - case ESM4::SUB_TINI: - case ESM4::SUB_TINV: - case ESM4::SUB_VMAD: - case ESM4::SUB_VTCK: - case ESM4::SUB_GNAM: - case ESM4::SUB_SHRT: - case ESM4::SUB_SPOR: - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_COED: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_LTPT: // FO4 - case ESM4::SUB_LTPC: // FO4 - case ESM4::SUB_MWGT: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PFRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TEND: // FO4 - case ESM4::SUB_TPTA: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: // - case ESM4::SUB_OBTS: // - case ESM4::SUB_STOP: // FO4 object template end - case ESM4::SUB_OCOR: // FO4 new package lists start - case ESM4::SUB_GWOR: // - case ESM4::SUB_FCPL: // - case ESM4::SUB_RCLR: // FO4 new package lists end - case ESM4::SUB_CS2D: // FO4 actor sound subrecords - case ESM4::SUB_CS2E: // - case ESM4::SUB_CS2F: // - case ESM4::SUB_CS2H: // - case ESM4::SUB_CS2K: // FO4 actor sound subrecords end - case ESM4::SUB_MSDK: // FO4 morph subrecords start - case ESM4::SUB_MSDV: // - case ESM4::SUB_MRSV: // - case ESM4::SUB_FMRI: // - case ESM4::SUB_FMRS: // - case ESM4::SUB_FMIN: // FO4 morph subrecords end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("NAM6"): // height mult + case ESM::fourCC("NAM7"): // weight mult + case ESM::fourCC("ATKR"): + case ESM::fourCC("CRIF"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("ECOR"): + case ESM::fourCC("ANAM"): + case ESM::fourCC("ATKD"): + case ESM::fourCC("ATKE"): + case ESM::fourCC("FTST"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM5"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PRKR"): + case ESM::fourCC("PRKZ"): + case ESM::fourCC("QNAM"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("TIAS"): + case ESM::fourCC("TINC"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINV"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VTCK"): + case ESM::fourCC("GNAM"): + case ESM::fourCC("SHRT"): + case ESM::fourCC("SPOR"): + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("COED"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("LTPT"): // FO4 + case ESM::fourCC("LTPC"): // FO4 + case ESM::fourCC("MWGT"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PFRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TEND"): // FO4 + case ESM::fourCC("TPTA"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): // + case ESM::fourCC("OBTS"): // + case ESM::fourCC("STOP"): // FO4 object template end + case ESM::fourCC("OCOR"): // FO4 new package lists start + case ESM::fourCC("GWOR"): // + case ESM::fourCC("FCPL"): // + case ESM::fourCC("RCLR"): // FO4 new package lists end + case ESM::fourCC("CS2D"): // FO4 actor sound subrecords + case ESM::fourCC("CS2E"): // + case ESM::fourCC("CS2F"): // + case ESM::fourCC("CS2H"): // + case ESM::fourCC("CS2K"): // FO4 actor sound subrecords end + case ESM::fourCC("MSDK"): // FO4 morph subrecords start + case ESM::fourCC("MSDV"): // + case ESM::fourCC("MRSV"): // + case ESM::fourCC("FMRI"): // + case ESM::fourCC("FMRS"): // + case ESM::fourCC("FMIN"): // FO4 morph subrecords end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp index b980de4a8c..a5fec9b002 100644 --- a/components/esm4/loadotft.cpp +++ b/components/esm4/loadotft.cpp @@ -41,10 +41,10 @@ void ESM4::Outfit::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): { mInventory.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& formId : mInventory) diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index ab75598121..5d81e38f43 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -42,10 +42,10 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_PKDT: + case ESM::fourCC("PKDT"): { if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) { @@ -60,7 +60,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PSDT: // reader.get(mSchedule); break; + case ESM::fourCC("PSDT"): // reader.get(mSchedule); break; { if (subHdr.dataSize != sizeof(mSchedule)) reader.skipSubRecordData(); // FIXME: @@ -69,7 +69,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PLDT: + case ESM::fourCC("PLDT"): { if (subHdr.dataSize != sizeof(mLocation)) reader.skipSubRecordData(); // FIXME: @@ -82,7 +82,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PTDT: + case ESM::fourCC("PTDT"): { if (subHdr.dataSize != sizeof(mTarget)) reader.skipSubRecordData(); // FIXME: FO3 @@ -95,7 +95,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): { if (subHdr.dataSize != sizeof(CTDA)) { @@ -112,55 +112,55 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDT: // always 20 for TES4 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_POBA: // FO3 - case ESM4::SUB_POCA: // FO3 - case ESM4::SUB_POEA: // FO3 - case ESM4::SUB_SCTX: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_SCRO: // FO3 - case ESM4::SUB_IDLA: // FO3 - case ESM4::SUB_IDLC: // FO3 - case ESM4::SUB_IDLF: // FO3 - case ESM4::SUB_IDLT: // FO3 - case ESM4::SUB_PKDD: // FO3 - case ESM4::SUB_PKD2: // FO3 - case ESM4::SUB_PKPT: // FO3 - case ESM4::SUB_PKED: // FO3 - case ESM4::SUB_PKE2: // FO3 - case ESM4::SUB_PKAM: // FO3 - case ESM4::SUB_PUID: // FO3 - case ESM4::SUB_PKW3: // FO3 - case ESM4::SUB_PTD2: // FO3 - case ESM4::SUB_PLD2: // FO3 - case ESM4::SUB_PKFD: // FO3 - case ESM4::SUB_SLSD: // FO3 - case ESM4::SUB_SCVR: // FO3 - case ESM4::SUB_SCRV: // FO3 - case ESM4::SUB_IDLB: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_UNAM: // TES5 - case ESM4::SUB_XNAM: // TES5 - case ESM4::SUB_PDTO: // TES5 - case ESM4::SUB_PTDA: // TES5 - case ESM4::SUB_PFOR: // TES5 - case ESM4::SUB_PFO2: // TES5 - case ESM4::SUB_PRCB: // TES5 - case ESM4::SUB_PKCU: // TES5 - case ESM4::SUB_PKC2: // TES5 - case ESM4::SUB_CITC: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_TPIC: // TES5 + case ESM::fourCC("CTDT"): // always 20 for TES4 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("POBA"): // FO3 + case ESM::fourCC("POCA"): // FO3 + case ESM::fourCC("POEA"): // FO3 + case ESM::fourCC("SCTX"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("SCRO"): // FO3 + case ESM::fourCC("IDLA"): // FO3 + case ESM::fourCC("IDLC"): // FO3 + case ESM::fourCC("IDLF"): // FO3 + case ESM::fourCC("IDLT"): // FO3 + case ESM::fourCC("PKDD"): // FO3 + case ESM::fourCC("PKD2"): // FO3 + case ESM::fourCC("PKPT"): // FO3 + case ESM::fourCC("PKED"): // FO3 + case ESM::fourCC("PKE2"): // FO3 + case ESM::fourCC("PKAM"): // FO3 + case ESM::fourCC("PUID"): // FO3 + case ESM::fourCC("PKW3"): // FO3 + case ESM::fourCC("PTD2"): // FO3 + case ESM::fourCC("PLD2"): // FO3 + case ESM::fourCC("PKFD"): // FO3 + case ESM::fourCC("SLSD"): // FO3 + case ESM::fourCC("SCVR"): // FO3 + case ESM::fourCC("SCRV"): // FO3 + case ESM::fourCC("IDLB"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("UNAM"): // TES5 + case ESM::fourCC("XNAM"): // TES5 + case ESM::fourCC("PDTO"): // TES5 + case ESM::fourCC("PTDA"): // TES5 + case ESM::fourCC("PFOR"): // TES5 + case ESM::fourCC("PFO2"): // TES5 + case ESM::fourCC("PRCB"): // TES5 + case ESM::fourCC("PKCU"): // TES5 + case ESM::fourCC("PKC2"): // TES5 + case ESM::fourCC("CITC"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("TPIC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 12cbf6f28b..4246e7517e 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -44,10 +44,10 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); if (numNodes != std::size_t(mData)) // keep gcc quiet @@ -66,7 +66,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { static PGRR link; @@ -91,7 +91,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRI: + case ESM::fourCC("PGRI"): { std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); mForeign.resize(numForeign); @@ -103,7 +103,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRL: + case ESM::fourCC("PGRL"): { static PGRL objLink; reader.getFormId(objLink.object); @@ -118,7 +118,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGAG: + case ESM::fourCC("PGAG"): { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp index 4e473bd47a..123d2c967a 100644 --- a/components/esm4/loadpgre.cpp +++ b/components/esm4/loadpgre.cpp @@ -43,51 +43,51 @@ void ESM4::PlacedGrenade::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_NAME: - case ESM4::SUB_XEZN: - case ESM4::SUB_XRGD: - case ESM4::SUB_XRGB: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XOWN: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCNT: - case ESM4::SUB_XRDS: - case ESM4::SUB_XHLP: - case ESM4::SUB_XPWR: - case ESM4::SUB_XDCR: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XCLP: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XATO: - case ESM4::SUB_XESP: - case ESM4::SUB_XEMI: - case ESM4::SUB_XMBR: - case ESM4::SUB_XIBS: - case ESM4::SUB_XSCL: - case ESM4::SUB_DATA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XAMC: // FO4 - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XIS2: // FO4 - case ESM4::SUB_XLOD: // FO4 - case ESM4::SUB_XLRL: // FO4 - case ESM4::SUB_XLRT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XRFG: // FO4 + case ESM::fourCC("NAME"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XRGD"): + case ESM::fourCC("XRGB"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XOWN"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCNT"): + case ESM::fourCC("XRDS"): + case ESM::fourCC("XHLP"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XDCR"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XCLP"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XATO"): + case ESM::fourCC("XESP"): + case ESM::fourCC("XEMI"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XIBS"): + case ESM::fourCC("XSCL"): + case ESM::fourCC("DATA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XAMC"): // FO4 + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XIS2"): // FO4 + case ESM::fourCC("XLOD"): // FO4 + case ESM::fourCC("XLRL"): // FO4 + case ESM::fourCC("XLRT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XRFG"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp index 339ae63daf..33a2c86546 100644 --- a/components/esm4/loadpwat.cpp +++ b/components/esm4/loadpwat.cpp @@ -43,12 +43,12 @@ void ESM4::PlaceableWater::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: - case ESM4::SUB_DNAM: + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index b7f9b33db9..27c23d92f1 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -42,16 +42,16 @@ void ESM4::Quest::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mQuestName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) // TES4 { @@ -66,10 +66,10 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mQuestScript); break; - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 { @@ -95,80 +95,80 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): reader.get(mScript.scriptHeader); break; - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_INDX: - case ESM4::SUB_QSDT: - case ESM4::SUB_CNAM: - case ESM4::SUB_QSTA: - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_QOBJ: // FO3 - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_DNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_NEXT: // TES5 - case ESM4::SUB_ALCA: // TES5 - case ESM4::SUB_ALCL: // TES5 - case ESM4::SUB_ALCO: // TES5 - case ESM4::SUB_ALDN: // TES5 - case ESM4::SUB_ALEA: // TES5 - case ESM4::SUB_ALED: // TES5 - case ESM4::SUB_ALEQ: // TES5 - case ESM4::SUB_ALFA: // TES5 - case ESM4::SUB_ALFC: // TES5 - case ESM4::SUB_ALFD: // TES5 - case ESM4::SUB_ALFE: // TES5 - case ESM4::SUB_ALFI: // TES5 - case ESM4::SUB_ALFL: // TES5 - case ESM4::SUB_ALFR: // TES5 - case ESM4::SUB_ALID: // TES5 - case ESM4::SUB_ALLS: // TES5 - case ESM4::SUB_ALNA: // TES5 - case ESM4::SUB_ALNT: // TES5 - case ESM4::SUB_ALPC: // TES5 - case ESM4::SUB_ALRT: // TES5 - case ESM4::SUB_ALSP: // TES5 - case ESM4::SUB_ALST: // TES5 - case ESM4::SUB_ALUA: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNTO: // TES5 - case ESM4::SUB_COCT: // TES5 - case ESM4::SUB_ECOR: // TES5 - case ESM4::SUB_FLTR: // TES5 - case ESM4::SUB_KNAM: // TES5 - case ESM4::SUB_KSIZ: // TES5 - case ESM4::SUB_KWDA: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_QTGL: // TES5 - case ESM4::SUB_SPOR: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_VTCK: // TES5 - case ESM4::SUB_ALCC: // FO4 - case ESM4::SUB_ALCS: // FO4 - case ESM4::SUB_ALDI: // FO4 - case ESM4::SUB_ALFV: // FO4 - case ESM4::SUB_ALLA: // FO4 - case ESM4::SUB_ALMI: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GWOR: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 - case ESM4::SUB_OCOR: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_XNAM: // FO4 + case ESM::fourCC("INDX"): + case ESM::fourCC("QSDT"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("QSTA"): + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("QOBJ"): // FO3 + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("DNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("NEXT"): // TES5 + case ESM::fourCC("ALCA"): // TES5 + case ESM::fourCC("ALCL"): // TES5 + case ESM::fourCC("ALCO"): // TES5 + case ESM::fourCC("ALDN"): // TES5 + case ESM::fourCC("ALEA"): // TES5 + case ESM::fourCC("ALED"): // TES5 + case ESM::fourCC("ALEQ"): // TES5 + case ESM::fourCC("ALFA"): // TES5 + case ESM::fourCC("ALFC"): // TES5 + case ESM::fourCC("ALFD"): // TES5 + case ESM::fourCC("ALFE"): // TES5 + case ESM::fourCC("ALFI"): // TES5 + case ESM::fourCC("ALFL"): // TES5 + case ESM::fourCC("ALFR"): // TES5 + case ESM::fourCC("ALID"): // TES5 + case ESM::fourCC("ALLS"): // TES5 + case ESM::fourCC("ALNA"): // TES5 + case ESM::fourCC("ALNT"): // TES5 + case ESM::fourCC("ALPC"): // TES5 + case ESM::fourCC("ALRT"): // TES5 + case ESM::fourCC("ALSP"): // TES5 + case ESM::fourCC("ALST"): // TES5 + case ESM::fourCC("ALUA"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNTO"): // TES5 + case ESM::fourCC("COCT"): // TES5 + case ESM::fourCC("ECOR"): // TES5 + case ESM::fourCC("FLTR"): // TES5 + case ESM::fourCC("KNAM"): // TES5 + case ESM::fourCC("KSIZ"): // TES5 + case ESM::fourCC("KWDA"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("QTGL"): // TES5 + case ESM::fourCC("SPOR"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("VTCK"): // TES5 + case ESM::fourCC("ALCC"): // FO4 + case ESM::fourCC("ALCS"): // FO4 + case ESM::fourCC("ALDI"): // FO4 + case ESM::fourCC("ALFV"): // FO4 + case ESM::fourCC("ALLA"): // FO4 + case ESM::fourCC("ALMI"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GWOR"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 + case ESM::fourCC("OCOR"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("XNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 7434a7f87f..02f6f953b4 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -52,7 +52,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); // TES4 @@ -73,10 +73,10 @@ void ESM4::Race::load(ESM4::Reader& reader) // Imperial 0x00000907 break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): { if (subHdr.dataSize == 1) // FO3? { @@ -87,10 +87,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getLocalizedString(mDesc); break; } - case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) + case ESM::fourCC("SPLO"): // bonus spell formid (TES5 may have SPCT and multiple SPLO) reader.getFormId(mBonusSpells.emplace_back()); break; - case ESM4::SUB_DATA: // ?? different length for TES5 + case ESM::fourCC("DATA"): // ?? different length for TES5 { // DATA:size 128 // 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 @@ -210,14 +210,14 @@ void ESM4::Race::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.getFormId(mDefaultHair[0]); // male reader.getFormId(mDefaultHair[1]); // female break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): // CNAM SNAM VNAM // Sheogorath 0x0 0000 98 2b 10011000 00101011 // Golden Saint 0x3 0011 26 46 00100110 01000110 @@ -238,13 +238,13 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f - case ESM4::SUB_UNAM: + case ESM::fourCC("UNAM"): reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f - case ESM4::SUB_ATTR: // Only in TES4? + case ESM::fourCC("ATTR"): // Only in TES4? { if (subHdr.dataSize == 2) // FO3? { @@ -276,7 +276,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // | | // +-------------+ // - case ESM4::SUB_NAM0: // start marker head data /* 1 */ + case ESM::fourCC("NAM0"): // start marker head data /* 1 */ { curr_part = 0; // head part @@ -296,7 +296,7 @@ void ESM4::Race::load(ESM4::Reader& reader) currentIndex = 0xffffffff; break; } - case ESM4::SUB_INDX: + case ESM::fourCC("INDX"): { reader.get(currentIndex); // FIXME: below check is rather useless @@ -313,7 +313,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (currentIndex == 0xffffffff) { @@ -350,10 +350,10 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.skipSubRecordData(); break; // always 0x0000? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): { if (currentIndex == 0xffffffff) { @@ -379,7 +379,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_NAM1: // start marker body data /* 4 */ + case ESM::fourCC("NAM1"): // start marker body data /* 4 */ { if (isFO3 || isFONV) @@ -406,14 +406,14 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): isMale = true; break; /* 2, 5, 7 */ - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): isMale = false; break; /* 3, 6, 8 */ // - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { // FIXME: this is a texture name in FO4 if (subHdr.dataSize % sizeof(ESM::FormId32) != 0) @@ -428,7 +428,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): { std::size_t numEyeChoices = subHdr.dataSize / sizeof(ESM::FormId32); mEyeChoices.resize(numEyeChoices); @@ -437,7 +437,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { if (isMale || isTES4) { @@ -454,7 +454,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { if (isMale || isTES4) { @@ -471,7 +471,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { if (isMale || isTES4) { @@ -489,12 +489,12 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_SNAM: // skipping...2 // only in TES4? + case ESM::fourCC("SNAM"): // skipping...2 // only in TES4? { reader.skipSubRecordData(); break; } - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): { ESM::FormId race; std::int32_t adjustment; @@ -504,7 +504,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): { if (subHdr.dataSize == 8) // TES4 { @@ -528,7 +528,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_ANAM: // TES5 + case ESM::fourCC("ANAM"): // TES5 { if (isMale) reader.getZString(mModelMale); @@ -536,10 +536,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getZString(mModelFemale); break; } - case ESM4::SUB_KSIZ: + case ESM::fourCC("KSIZ"): reader.get(mNumKeywords); break; - case ESM4::SUB_KWDA: + case ESM::fourCC("KWDA"): { ESM::FormId formid; for (unsigned int i = 0; i < mNumKeywords; ++i) @@ -547,13 +547,13 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_WNAM: // ARMO FormId + case ESM::fourCC("WNAM"): // ARMO FormId { reader.getFormId(mSkin); // std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME break; } - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template { reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); @@ -564,7 +564,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): { if (subHdr.dataSize == 8 || subHdr.dataSize == 4) // TES5, FO4 { @@ -584,7 +584,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_HEAD: // TES5 + case ESM::fourCC("HEAD"): // TES5 { ESM::FormId formId; reader.getFormId(formId); @@ -611,7 +611,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM3: // start of hkx model + case ESM::fourCC("NAM3"): // start of hkx model { curr_part = 3; // for TES5 NAM3 indicates the start of hkx model @@ -651,7 +651,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // ManakinRace // ManakinRace // ManakinRace FX0 - case ESM4::SUB_NAME: // TES5 biped object names (x32) + case ESM::fourCC("NAME"): // TES5 biped object names (x32) { std::string name; reader.getZString(name); @@ -659,112 +659,112 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MTNM: // movement type - case ESM4::SUB_ATKD: // attack data - case ESM4::SUB_ATKE: // attach event - case ESM4::SUB_GNAM: // body part data - case ESM4::SUB_NAM4: // material type - case ESM4::SUB_NAM5: // unarmed impact? - case ESM4::SUB_LNAM: // close loot sound - case ESM4::SUB_QNAM: // equipment slot formid - case ESM4::SUB_HCLF: // default hair colour - case ESM4::SUB_UNES: // unarmed equipment slot formid - case ESM4::SUB_TINC: - case ESM4::SUB_TIND: - case ESM4::SUB_TINI: - case ESM4::SUB_TINL: - case ESM4::SUB_TINP: - case ESM4::SUB_TINT: - case ESM4::SUB_TINV: - case ESM4::SUB_TIRS: - case ESM4::SUB_PHWT: - case ESM4::SUB_AHCF: - case ESM4::SUB_AHCM: - case ESM4::SUB_MPAI: - case ESM4::SUB_MPAV: - case ESM4::SUB_DFTF: - case ESM4::SUB_DFTM: - case ESM4::SUB_FLMV: - case ESM4::SUB_FTSF: - case ESM4::SUB_FTSM: - case ESM4::SUB_MTYP: - case ESM4::SUB_NAM7: - case ESM4::SUB_NAM8: - case ESM4::SUB_PHTN: - case ESM4::SUB_RNAM: - case ESM4::SUB_RNMV: - case ESM4::SUB_RPRF: - case ESM4::SUB_RPRM: - case ESM4::SUB_SNMV: - case ESM4::SUB_SPCT: - case ESM4::SUB_SPED: - case ESM4::SUB_SWMV: - case ESM4::SUB_WKMV: - case ESM4::SUB_SPMV: - case ESM4::SUB_ATKR: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MTNM"): // movement type + case ESM::fourCC("ATKD"): // attack data + case ESM::fourCC("ATKE"): // attach event + case ESM::fourCC("GNAM"): // body part data + case ESM::fourCC("NAM4"): // material type + case ESM::fourCC("NAM5"): // unarmed impact? + case ESM::fourCC("LNAM"): // close loot sound + case ESM::fourCC("QNAM"): // equipment slot formid + case ESM::fourCC("HCLF"): // default hair colour + case ESM::fourCC("UNES"): // unarmed equipment slot formid + case ESM::fourCC("TINC"): + case ESM::fourCC("TIND"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINL"): + case ESM::fourCC("TINP"): + case ESM::fourCC("TINT"): + case ESM::fourCC("TINV"): + case ESM::fourCC("TIRS"): + case ESM::fourCC("PHWT"): + case ESM::fourCC("AHCF"): + case ESM::fourCC("AHCM"): + case ESM::fourCC("MPAI"): + case ESM::fourCC("MPAV"): + case ESM::fourCC("DFTF"): + case ESM::fourCC("DFTM"): + case ESM::fourCC("FLMV"): + case ESM::fourCC("FTSF"): + case ESM::fourCC("FTSM"): + case ESM::fourCC("MTYP"): + case ESM::fourCC("NAM7"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("PHTN"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("RNMV"): + case ESM::fourCC("RPRF"): + case ESM::fourCC("RPRM"): + case ESM::fourCC("SNMV"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("SPED"): + case ESM::fourCC("SWMV"): + case ESM::fourCC("WKMV"): + case ESM::fourCC("SPMV"): + case ESM::fourCC("ATKR"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end // - case ESM4::SUB_YNAM: // FO3 - case ESM4::SUB_NAM2: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_ONAM: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_BMMP: // FO4 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 + case ESM::fourCC("YNAM"): // FO3 + case ESM::fourCC("NAM2"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("ONAM"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("BMMP"): // FO4 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 - case ESM4::SUB_FMRI: // FO4 - case ESM4::SUB_FMRN: // FO4 - case ESM4::SUB_HLTX: // FO4 - case ESM4::SUB_MLSI: // FO4 - case ESM4::SUB_MPGN: // FO4 - case ESM4::SUB_MPGS: // FO4 - case ESM4::SUB_MPPC: // FO4 - case ESM4::SUB_MPPF: // FO4 - case ESM4::SUB_MPPI: // FO4 - case ESM4::SUB_MPPK: // FO4 - case ESM4::SUB_MPPM: // FO4 - case ESM4::SUB_MPPN: // FO4 - case ESM4::SUB_MPPT: // FO4 - case ESM4::SUB_MSID: // FO4 - case ESM4::SUB_MSM0: // FO4 - case ESM4::SUB_MSM1: // FO4 - case ESM4::SUB_NNAM: // FO4 - case ESM4::SUB_NTOP: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTOP: // FO4 - case ESM4::SUB_QSTI: // FO4 - case ESM4::SUB_RBPC: // FO4 - case ESM4::SUB_SADD: // FO4 - case ESM4::SUB_SAKD: // FO4 - case ESM4::SUB_SAPT: // FO4 - case ESM4::SUB_SGNM: // FO4 - case ESM4::SUB_SRAC: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_STKD: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TTEB: // FO4 - case ESM4::SUB_TTEC: // FO4 - case ESM4::SUB_TTED: // FO4 - case ESM4::SUB_TTEF: // FO4 - case ESM4::SUB_TTET: // FO4 - case ESM4::SUB_TTGE: // FO4 - case ESM4::SUB_TTGP: // FO4 - case ESM4::SUB_UNWP: // FO4 - case ESM4::SUB_WMAP: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("FMRI"): // FO4 + case ESM::fourCC("FMRN"): // FO4 + case ESM::fourCC("HLTX"): // FO4 + case ESM::fourCC("MLSI"): // FO4 + case ESM::fourCC("MPGN"): // FO4 + case ESM::fourCC("MPGS"): // FO4 + case ESM::fourCC("MPPC"): // FO4 + case ESM::fourCC("MPPF"): // FO4 + case ESM::fourCC("MPPI"): // FO4 + case ESM::fourCC("MPPK"): // FO4 + case ESM::fourCC("MPPM"): // FO4 + case ESM::fourCC("MPPN"): // FO4 + case ESM::fourCC("MPPT"): // FO4 + case ESM::fourCC("MSID"): // FO4 + case ESM::fourCC("MSM0"): // FO4 + case ESM::fourCC("MSM1"): // FO4 + case ESM::fourCC("NNAM"): // FO4 + case ESM::fourCC("NTOP"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTOP"): // FO4 + case ESM::fourCC("QSTI"): // FO4 + case ESM::fourCC("RBPC"): // FO4 + case ESM::fourCC("SADD"): // FO4 + case ESM::fourCC("SAKD"): // FO4 + case ESM::fourCC("SAPT"): // FO4 + case ESM::fourCC("SGNM"): // FO4 + case ESM::fourCC("SRAC"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("STKD"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TTEB"): // FO4 + case ESM::fourCC("TTEC"): // FO4 + case ESM::fourCC("TTED"): // FO4 + case ESM::fourCC("TTEF"): // FO4 + case ESM::fourCC("TTET"): // FO4 + case ESM::fourCC("TTGE"): // FO4 + case ESM::fourCC("TTGP"): // FO4 + case ESM::fourCC("UNWP"): // FO4 + case ESM::fourCC("WMAP"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index fb26e39546..5ac3a5f077 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -46,26 +46,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): { ESM::FormId BaseId; reader.getFormId(BaseId); mBaseObj = BaseId; break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -86,13 +86,13 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; - case ESM4::SUB_XRNK: + case ESM::fourCC("XRNK"): reader.get(mFactionRank); break; - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): { reader.getFormId(mEsp.parent); reader.get(mEsp.flags); @@ -100,7 +100,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // << ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME break; } - case ESM4::SUB_XTEL: + case ESM::fourCC("XTEL"): { switch (subHdr.dataSize) { @@ -125,7 +125,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XSED: + case ESM::fourCC("XSED"): { // 1 or 4 bytes if (subHdr.dataSize == 1) @@ -147,7 +147,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XLOD: + case ESM::fourCC("XLOD"): { // 12 bytes if (subHdr.dataSize == 12) @@ -168,7 +168,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XACT: + case ESM::fourCC("XACT"): { if (subHdr.dataSize == 4) { @@ -182,7 +182,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XRTM: // formId + case ESM::fourCC("XRTM"): // formId { // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" // e.g. some are doors (prob. quest related) @@ -199,7 +199,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME break; } - case ESM4::SUB_TNAM: // reader.get(mMapMarker); break; + case ESM::fourCC("TNAM"): // reader.get(mMapMarker); break; { if (subHdr.dataSize != sizeof(mMapMarker)) // reader.skipSubRecordData(); // FIXME: FO3 @@ -209,26 +209,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XMRK: + case ESM::fourCC("XMRK"): mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { // std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_XTRG: // formId + case ESM::fourCC("XTRG"): // formId { reader.getFormId(mTargetRef); // std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mAudioLocation); break; // FONV - case ESM4::SUB_XRDO: // FO3 + case ESM::fourCC("XRDO"): // FO3 { // FIXME: completely different meaning in FO4 reader.get(mRadio.rangeRadius); @@ -238,14 +238,14 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRO: // FO3 + case ESM::fourCC("SCRO"): // FO3 { reader.getFormId(sid); // if (mFormId == 0x0016b74B) // std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME break; } - case ESM4::SUB_XLOC: + case ESM::fourCC("XLOC"): { mIsLocked = true; std::int8_t dummy; // FIXME: very poor code @@ -268,97 +268,97 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } // lighting - case ESM4::SUB_LNAM: // lighting template formId - case ESM4::SUB_XLIG: // struct, FOV, fade, etc - case ESM4::SUB_XEMI: // LIGH formId - case ESM4::SUB_XRDS: // Radius or Radiance - case ESM4::SUB_XRGB: - case ESM4::SUB_XRGD: // tangent data? - case ESM4::SUB_XALP: // alpha cutoff + case ESM::fourCC("LNAM"): // lighting template formId + case ESM::fourCC("XLIG"): // struct, FOV, fade, etc + case ESM::fourCC("XEMI"): // LIGH formId + case ESM::fourCC("XRDS"): // Radius or Radiance + case ESM::fourCC("XRGB"): + case ESM::fourCC("XRGD"): // tangent data? + case ESM::fourCC("XALP"): // alpha cutoff // - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLCM: - case ESM4::SUB_ONAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_XPRM: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCTX: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XCVL: - case ESM4::SUB_XCZA: - case ESM4::SUB_XCZC: - case ESM4::SUB_XEZN: - case ESM4::SUB_XFVC: - case ESM4::SUB_XHTW: - case ESM4::SUB_XIS2: - case ESM4::SUB_XLCN: - case ESM4::SUB_XLIB: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLRM: - case ESM4::SUB_XLRT: - case ESM4::SUB_XLTW: - case ESM4::SUB_XMBO: - case ESM4::SUB_XMBP: - case ESM4::SUB_XMBR: - case ESM4::SUB_XNDP: - case ESM4::SUB_XOCP: - case ESM4::SUB_XPOD: - case ESM4::SUB_XPTL: - case ESM4::SUB_XPPA: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPWR: - case ESM4::SUB_XRMR: - case ESM4::SUB_XSPC: - case ESM4::SUB_XTNM: - case ESM4::SUB_XTRI: - case ESM4::SUB_XWCN: - case ESM4::SUB_XWCU: - case ESM4::SUB_XATR: - case ESM4::SUB_XHLT: // Unofficial Oblivion Patch - case ESM4::SUB_XCHG: // thievery.exp - case ESM4::SUB_XHLP: // FO3 - case ESM4::SUB_XAMT: // FO3 - case ESM4::SUB_XAMC: // FO3 - case ESM4::SUB_XRAD: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_XORD: // FO3 - case ESM4::SUB_XCLP: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_RCLR: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_MMRK: // FONV - case ESM4::SUB_MNAM: // FONV - case ESM4::SUB_NNAM: // FONV - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_SCRV: // FONV - case ESM4::SUB_SCVR: // FONV - case ESM4::SUB_SLSD: // FONV - case ESM4::SUB_XSRF: // FONV - case ESM4::SUB_XSRD: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XBSD: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XCZR: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPDD: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XWPG: // FO4 - case ESM4::SUB_XWPN: // FO4 + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLCM"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XPRM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XCVL"): + case ESM::fourCC("XCZA"): + case ESM::fourCC("XCZC"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XFVC"): + case ESM::fourCC("XHTW"): + case ESM::fourCC("XIS2"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XLIB"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLRM"): + case ESM::fourCC("XLRT"): + case ESM::fourCC("XLTW"): + case ESM::fourCC("XMBO"): + case ESM::fourCC("XMBP"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XNDP"): + case ESM::fourCC("XOCP"): + case ESM::fourCC("XPOD"): + case ESM::fourCC("XPTL"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XRMR"): + case ESM::fourCC("XSPC"): + case ESM::fourCC("XTNM"): + case ESM::fourCC("XTRI"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XATR"): + case ESM::fourCC("XHLT"): // Unofficial Oblivion Patch + case ESM::fourCC("XCHG"): // thievery.exp + case ESM::fourCC("XHLP"): // FO3 + case ESM::fourCC("XAMT"): // FO3 + case ESM::fourCC("XAMC"): // FO3 + case ESM::fourCC("XRAD"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("XORD"): // FO3 + case ESM::fourCC("XCLP"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("RCLR"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("MMRK"): // FONV + case ESM::fourCC("MNAM"): // FONV + case ESM::fourCC("NNAM"): // FONV + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("SCRV"): // FONV + case ESM::fourCC("SCVR"): // FONV + case ESM::fourCC("SLSD"): // FONV + case ESM::fourCC("XSRF"): // FONV + case ESM::fourCC("XSRD"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XBSD"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XCZR"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPDD"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XWPG"): // FO4 + case ESM::fourCC("XWPN"): // FO4 // if (mFormId == 0x0007e90f) // XPRM XPOD // if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents reader.skipSubRecordData(); diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 2f10ea22d8..c8cc9663d9 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -41,22 +41,22 @@ void ESM4::Region::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_RCLR: + case ESM::fourCC("RCLR"): reader.get(mColour); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mWorldId); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mShader); break; - case ESM4::SUB_RPLI: + case ESM::fourCC("RPLI"): reader.get(mEdgeFalloff); break; - case ESM4::SUB_RPLD: + case ESM::fourCC("RPLD"): { mRPLD.resize(subHdr.dataSize / sizeof(std::uint32_t)); for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) @@ -71,10 +71,10 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.get(mData); break; - case ESM4::SUB_RDMP: + case ESM::fourCC("RDMP"): { if (mData.type != RDAT_Map) throw std::runtime_error("REGN unexpected data type"); @@ -83,7 +83,7 @@ void ESM4::Region::load(ESM4::Reader& reader) } // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) // FONV none - case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon + case ESM::fourCC("RDMD"): // music type; 0 default, 1 public, 2 dungeon { #if 0 int dummy; @@ -94,14 +94,14 @@ void ESM4::Region::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_RDMO: // not seen in FO3/FONV? + case ESM::fourCC("RDMO"): // not seen in FO3/FONV? { // std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_RDSD: // Possibly the same as RDSA + case ESM::fourCC("RDSD"): // Possibly the same as RDSA { if (mData.type != RDAT_Sound) throw std::runtime_error( @@ -114,16 +114,16 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId - case ESM4::SUB_RDSA: - case ESM4::SUB_RDWT: // formId - case ESM4::SUB_RDOT: // formId - case ESM4::SUB_RDID: // FONV - case ESM4::SUB_RDSB: // FONV - case ESM4::SUB_RDSI: // FONV - case ESM4::SUB_NVMI: // TES5 - case ESM4::SUB_ANAM: // FO4 - case ESM4::SUB_RLDM: // FO4 + case ESM::fourCC("RDGS"): // Only in Oblivion? (ToddTestRegion1) // formId + case ESM::fourCC("RDSA"): + case ESM::fourCC("RDWT"): // formId + case ESM::fourCC("RDOT"): // formId + case ESM::fourCC("RDID"): // FONV + case ESM::fourCC("RDSB"): // FONV + case ESM::fourCC("RDSI"): // FONV + case ESM::fourCC("NVMI"): // TES5 + case ESM::fourCC("ANAM"): // FO4 + case ESM::fourCC("RLDM"): // FO4 // RDAT skipping... following is a map // RDMP skipping... map name // diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index 8a33ab1c1d..3e33acbc7b 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -45,7 +45,7 @@ void ESM4::Road::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); @@ -57,7 +57,7 @@ void ESM4::Road::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { static PGRR link; static RDRP linkPt; diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp index a874331dab..c9450492b8 100644 --- a/components/esm4/loadsbsp.cpp +++ b/components/esm4/loadsbsp.cpp @@ -41,10 +41,10 @@ void ESM4::SubSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.get(mDimension.x); reader.get(mDimension.y); diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp index dea900fe17..00775edaa5 100644 --- a/components/esm4/loadscol.cpp +++ b/components/esm4/loadscol.cpp @@ -43,22 +43,22 @@ void ESM4::StaticCollection::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_DATA: - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index b4071ed21d..12953b4609 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -43,12 +43,12 @@ void ESM4::Script::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { // For debugging only #if 0 @@ -73,12 +73,12 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); // if (mEditorId == "CTrapLogs01SCRIPT") // std::cout << mScript.scriptSource << std::endl; break; - case ESM4::SUB_SCDA: // compiled script data + case ESM::fourCC("SCDA"): // compiled script data { // For debugging only #if 0 @@ -112,10 +112,10 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { localVar.clear(); reader.get(localVar.index); @@ -128,11 +128,11 @@ void ESM4::Script::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD + case ESM::fourCC("SCVR"): // assumed always pair with SLSD reader.getZString(localVar.variableName); mScript.localVarData.push_back(localVar); break; - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp index 954ddf4e36..f88854f8db 100644 --- a/components/esm4/loadscrl.cpp +++ b/components/esm4/loadscrl.cpp @@ -41,40 +41,40 @@ void ESM4::Scroll::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.value); reader.get(mData.weight); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - // case ESM4::SUB_MODB: reader.get(mBoundRadius); break; - case ESM4::SUB_OBND: - case ESM4::SUB_CTDA: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MDOB: - case ESM4::SUB_MODT: - case ESM4::SUB_SPIT: - case ESM4::SUB_CIS2: + // case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; + case ESM::fourCC("OBND"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MDOB"): + case ESM::fourCC("MODT"): + case ESM::fourCC("SPIT"): + case ESM::fourCC("CIS2"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp index c4284eb9bc..d93709f537 100644 --- a/components/esm4/loadsgst.cpp +++ b/components/esm4/loadsgst.cpp @@ -42,10 +42,10 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -62,34 +62,34 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mData.uses); reader.get(mData.value); reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: + case ESM::fourCC("MODT"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp index 635b73312e..cb16f36857 100644 --- a/components/esm4/loadslgm.cpp +++ b/components/esm4/loadslgm.cpp @@ -41,38 +41,38 @@ void ESM4::SoulGem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SOUL: + case ESM::fourCC("SOUL"): reader.get(mSoul); break; - case ESM4::SUB_SLCP: + case ESM::fourCC("SLCP"): reader.get(mSoulCapacity); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp index 830cdfdc54..21ed9f93e4 100644 --- a/components/esm4/loadsndr.cpp +++ b/components/esm4/loadsndr.cpp @@ -41,10 +41,10 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.get(&mTargetCondition, 20); reader.get(mTargetCondition.runOn); reader.get(mTargetCondition.reference); @@ -52,22 +52,22 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.adjustFormId(mTargetCondition.reference); reader.skipSubRecordData(4); // unknown break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mSoundCategory); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSoundId); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getFormId(mOutputModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLoopInfo); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): { if (subHdr.dataSize == 6) reader.get(mData); @@ -77,16 +77,16 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CNAM: // CRC32 hash - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FNAM: // unknown - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_ITMC: // FO4 - case ESM4::SUB_ITME: // FO4 - case ESM4::SUB_ITMS: // FO4 - case ESM4::SUB_NNAM: // FO4 + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CNAM"): // CRC32 hash + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FNAM"): // unknown + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("ITMC"): // FO4 + case ESM::fourCC("ITME"): // FO4 + case ESM::fourCC("ITMS"): // FO4 + case ESM::fourCC("NNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp index a5ae005fe2..4fc3b6609f 100644 --- a/components/esm4/loadsoun.cpp +++ b/components/esm4/loadsoun.cpp @@ -41,16 +41,16 @@ void ESM4::Sound::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_SNDX: + case ESM::fourCC("SNDX"): reader.get(mData); break; - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): if (subHdr.dataSize == 8) reader.get(&mData, 8); else @@ -59,13 +59,13 @@ void ESM4::Sound::load(ESM4::Reader& reader) reader.get(mExtra); } break; - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_SDSC: // TES5 only - case ESM4::SUB_ANAM: // FO3 - case ESM4::SUB_GNAM: // FO3 - case ESM4::SUB_HNAM: // FO3 - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_REPT: // FO4 + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("SDSC"): // TES5 only + case ESM::fourCC("ANAM"): // FO3 + case ESM::fourCC("GNAM"): // FO3 + case ESM::fourCC("HNAM"): // FO3 + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("REPT"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index e3b51633cf..9d85374ae4 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -41,19 +41,19 @@ void ESM4::Static::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): { // version is only availabe in TES5 (seems to be 27 or 28?) // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -72,7 +72,7 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { for (std::string& level : mLOD) { @@ -84,18 +84,18 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODC: // More model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_DNAM: - case ESM4::SUB_BRUS: // FONV - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_VMAD: // FO4 + case ESM::fourCC("MODC"): // More model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BRUS"): // FONV + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("VMAD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp index ad5efb9fbf..453df85504 100644 --- a/components/esm4/loadtact.cpp +++ b/components/esm4/loadtact.cpp @@ -41,44 +41,44 @@ void ESM4::TalkingActivator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mVoiceType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp index 39f26391e8..1fb8ef117d 100644 --- a/components/esm4/loadterm.cpp +++ b/components/esm4/loadterm.cpp @@ -41,73 +41,73 @@ void ESM4::Terminal::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.getFormId(mPasswordNote); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): if (subHdr.dataSize == 4) reader.getFormId(mSound); // FIXME: FO4 sound marker params else reader.skipSubRecordData(); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getZString(mResultText); break; - case ESM4::SUB_DNAM: // difficulty - case ESM4::SUB_ANAM: // flags - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_INAM: - case ESM4::SUB_ITXT: // Menu Item - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_SCDA: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCRO: - case ESM4::SUB_SCRV: - case ESM4::SUB_SCTX: - case ESM4::SUB_SCVR: - case ESM4::SUB_SLSD: - case ESM4::SUB_TNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_BSIZ: // FO4 - case ESM4::SUB_BTXT: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_ISIZ: // FO4 - case ESM4::SUB_ITID: // FO4 - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_UNAM: // FO4 - case ESM4::SUB_VNAM: // FO4 - case ESM4::SUB_WBDT: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_XMRK: // FO4 + case ESM::fourCC("DNAM"): // difficulty + case ESM::fourCC("ANAM"): // flags + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("INAM"): + case ESM::fourCC("ITXT"): // Menu Item + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("SCDA"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCRO"): + case ESM::fourCC("SCRV"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("SCVR"): + case ESM::fourCC("SLSD"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("BSIZ"): // FO4 + case ESM::fourCC("BTXT"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("ISIZ"): // FO4 + case ESM::fourCC("ITID"): // FO4 + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("UNAM"): // FO4 + case ESM::fourCC("VNAM"): // FO4 + case ESM::fourCC("WBDT"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("XMRK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 0cbf91c52e..19db9b9d09 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -41,7 +41,7 @@ void ESM4::Header::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_HEDR: + case ESM::fourCC("HEDR"): { if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) @@ -51,13 +51,13 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 HEDR data size mismatch"); break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getZString(mAuthor); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getZString(mDesc); break; - case ESM4::SUB_MAST: // multiple + case ESM::fourCC("MAST"): // multiple { ESM::MasterData m; if (!reader.getZString(m.name)) @@ -68,7 +68,7 @@ void ESM4::Header::load(ESM4::Reader& reader) mMaster.push_back(m); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (mMaster.empty()) throw std::runtime_error( @@ -78,7 +78,7 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 DATA data read error"); break; } - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): { mOverrides.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& mOverride : mOverrides) @@ -95,11 +95,11 @@ void ESM4::Header::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INTV: - case ESM4::SUB_INCC: - case ESM4::SUB_OFST: // Oblivion only? - case ESM4::SUB_DELE: // Oblivion only? - case ESM4::SUB_TNAM: // Fallout 4 (CK only) + case ESM::fourCC("INTV"): + case ESM::fourCC("INCC"): + case ESM::fourCC("OFST"): // Oblivion only? + case ESM::fourCC("DELE"): // Oblivion only? + case ESM::fourCC("TNAM"): // Fallout 4 (CK only) case ESM::fourCC("MMSB"): // Fallout 76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index 9290ae79c4..c433f11564 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -41,29 +41,29 @@ void ESM4::Tree::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mLeafTexture); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_CNAM: - case ESM4::SUB_BNAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_FULL: - case ESM4::SUB_OBND: - case ESM4::SUB_PFIG: - case ESM4::SUB_PFPC: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("CNAM"): + case ESM::fourCC("BNAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("FULL"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PFIG"): + case ESM::fourCC("PFPC"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 3b5f04f265..69d48cc049 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -41,37 +41,37 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("FLTR"): // FO76 reader.getZString(mFilter); break; - case ESM4::SUB_TX00: + case ESM::fourCC("TX00"): reader.getZString(mDiffuse); break; - case ESM4::SUB_TX01: + case ESM::fourCC("TX01"): reader.getZString(mNormalMap); break; - case ESM4::SUB_TX02: + case ESM::fourCC("TX02"): // This is a "wrinkle map" in FO4/76 reader.getZString(mEnvMask); break; - case ESM4::SUB_TX03: + case ESM::fourCC("TX03"): // This is a glow map in FO4/76 reader.getZString(mToneMap); break; - case ESM4::SUB_TX04: + case ESM::fourCC("TX04"): // This is a height map in FO4/76 reader.getZString(mDetailMap); break; - case ESM4::SUB_TX05: + case ESM::fourCC("TX05"): reader.getZString(mEnvMap); break; - case ESM4::SUB_TX06: + case ESM::fourCC("TX06"): reader.getZString(mMultiLayer); break; - case ESM4::SUB_TX07: + case ESM::fourCC("TX07"): // This is a "smooth specular" map in FO4/76 reader.getZString(mSpecular); break; @@ -84,14 +84,14 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) case ESM::fourCC("TX10"): // FO76 reader.getZString(mFlow); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mDataFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getZString(mMaterial); break; - case ESM4::SUB_DODT: // Decal data - case ESM4::SUB_OBND: // object bounds + case ESM::fourCC("DODT"): // Decal data + case ESM::fourCC("OBND"): // object bounds case ESM::fourCC("OPDS"): // Object placement defaults, FO76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp index 2b80305690..81b0d4286a 100644 --- a/components/esm4/loadweap.cpp +++ b/components/esm4/loadweap.cpp @@ -43,13 +43,13 @@ void ESM4::Weapon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 @@ -79,126 +79,126 @@ void ESM4::Weapon::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_INAM: - case ESM4::SUB_CNAM: - case ESM4::SUB_CRDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_OBND: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_VNAM: - case ESM4::SUB_WNAM: - case ESM4::SUB_XNAM: // Dawnguard only? - case ESM4::SUB_NNAM: - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_MOD2: // FO3 - case ESM4::SUB_MO2T: // FO3 - case ESM4::SUB_MO2S: // FO3 - case ESM4::SUB_NAM6: // FO3 - case ESM4::SUB_MOD4: // First person model data - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_MO4C: - case ESM4::SUB_MO4F: // First person model data end - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_NAM7: // FO3 - case ESM4::SUB_MOD3: // FO3 - case ESM4::SUB_MO3T: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_MODD: // FO3 - // case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_VATS: // FONV - case ESM4::SUB_VANM: // FONV - case ESM4::SUB_MWD1: // FONV - case ESM4::SUB_MWD2: // FONV - case ESM4::SUB_MWD3: // FONV - case ESM4::SUB_MWD4: // FONV - case ESM4::SUB_MWD5: // FONV - case ESM4::SUB_MWD6: // FONV - case ESM4::SUB_MWD7: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_WMI2: // FONV - case ESM4::SUB_WMI3: // FONV - case ESM4::SUB_WMS1: // FONV - case ESM4::SUB_WMS2: // FONV - case ESM4::SUB_WNM1: // FONV - case ESM4::SUB_WNM2: // FONV - case ESM4::SUB_WNM3: // FONV - case ESM4::SUB_WNM4: // FONV - case ESM4::SUB_WNM5: // FONV - case ESM4::SUB_WNM6: // FONV - case ESM4::SUB_WNM7: // FONV - case ESM4::SUB_EFSD: // FONV DeadMoney - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MASE: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_WAMD: // FO4 - case ESM4::SUB_WZMD: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("INAM"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("CRDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("OBND"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VNAM"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("XNAM"): // Dawnguard only? + case ESM::fourCC("NNAM"): + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("MOD2"): // FO3 + case ESM::fourCC("MO2T"): // FO3 + case ESM::fourCC("MO2S"): // FO3 + case ESM::fourCC("NAM6"): // FO3 + case ESM::fourCC("MOD4"): // First person model data + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("MO4C"): + case ESM::fourCC("MO4F"): // First person model data end + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("NAM7"): // FO3 + case ESM::fourCC("MOD3"): // FO3 + case ESM::fourCC("MO3T"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("MODD"): // FO3 + // case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("VATS"): // FONV + case ESM::fourCC("VANM"): // FONV + case ESM::fourCC("MWD1"): // FONV + case ESM::fourCC("MWD2"): // FONV + case ESM::fourCC("MWD3"): // FONV + case ESM::fourCC("MWD4"): // FONV + case ESM::fourCC("MWD5"): // FONV + case ESM::fourCC("MWD6"): // FONV + case ESM::fourCC("MWD7"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("WMI2"): // FONV + case ESM::fourCC("WMI3"): // FONV + case ESM::fourCC("WMS1"): // FONV + case ESM::fourCC("WMS2"): // FONV + case ESM::fourCC("WNM1"): // FONV + case ESM::fourCC("WNM2"): // FONV + case ESM::fourCC("WNM3"): // FONV + case ESM::fourCC("WNM4"): // FONV + case ESM::fourCC("WNM5"): // FONV + case ESM::fourCC("WNM6"): // FONV + case ESM::fourCC("WNM7"): // FONV + case ESM::fourCC("EFSD"): // FONV DeadMoney + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MASE"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("WAMD"): // FO4 + case ESM::fourCC("WZMD"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index b29cb37eb5..d9bae15385 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -56,46 +56,46 @@ void ESM4::World::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_WCTR: // TES5+ + case ESM::fourCC("WCTR"): // TES5+ reader.get(mCenterCell); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mParent); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mSound); break; // sound, Oblivion only? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mMapFile); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClimate); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getFormId(mWater); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): { reader.get(mMinX); reader.get(mMinY); break; } - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): { reader.get(mMaxX); reader.get(mMaxY); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mWorldFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { reader.get(mMap.width); reader.get(mMap.height); @@ -113,7 +113,7 @@ void ESM4::World::load(ESM4::Reader& reader) break; } - case ESM4::SUB_DNAM: // defaults + case ESM::fourCC("DNAM"): // defaults { reader.get(mLandLevel); // -2700.f for TES5 reader.get(mWaterLevel); // -14000.f for TES5 @@ -135,37 +135,37 @@ void ESM4::World::load(ESM4::Reader& reader) // 00119D2E freeside\freeside_01.mp3 0012D94D FreesideNorthWorld (Freeside) // 00119D2E freeside\freeside_01.mp3 0012D94E FreesideFortWorld (Old Mormon Fort) // NOTE: FONV DefaultObjectManager has 00090908 "explore" as the default music - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mMusic); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mParentUseFlags); break; - case ESM4::SUB_OFST: - case ESM4::SUB_RNAM: // multiple - case ESM4::SUB_MHDT: - case ESM4::SUB_LTMP: - case ESM4::SUB_XEZN: - case ESM4::SUB_XLCN: - case ESM4::SUB_NAM3: - case ESM4::SUB_NAM4: - case ESM4::SUB_NAMA: - case ESM4::SUB_ONAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_XWEM: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_XNAM: // FO3 - case ESM4::SUB_IMPS: // FO3 Anchorage - case ESM4::SUB_IMPF: // FO3 Anchorage - case ESM4::SUB_CLSZ: // FO4 - case ESM4::SUB_WLEV: // FO4 + case ESM::fourCC("OFST"): + case ESM::fourCC("RNAM"): // multiple + case ESM::fourCC("MHDT"): + case ESM::fourCC("LTMP"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("NAM3"): + case ESM::fourCC("NAM4"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("XNAM"): // FO3 + case ESM::fourCC("IMPS"): // FO3 Anchorage + case ESM::fourCC("IMPF"): // FO3 Anchorage + case ESM::fourCC("CLSZ"): // FO4 + case ESM::fourCC("WLEV"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index a3ea438d65..9811cf6103 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -588,7 +588,7 @@ namespace ESM4 // Extended storage subrecord redefines the following subrecord's size. // Would need to redesign the loader to support that, so skip over both subrecords. - if (result && mCtx.subRecordHeader.typeId == ESM4::SUB_XXXX) + if (result && mCtx.subRecordHeader.typeId == ESM::fourCC("XXXX")) { std::uint32_t extDataSize; get(extDataSize); diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 92dc00b96d..914fa4a647 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -335,7 +335,7 @@ namespace ESM4 // Get a subrecord of a particular type and data type template - bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + bool getSubRecord(const std::uint32_t type, T& t) { ESM4::SubRecordHeader hdr; if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) From 974415addf059fc37b41a7ee054c1440ed8090d8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Mar 2024 09:57:36 +0300 Subject: [PATCH 1207/2167] Allow weapon equip/unequip animations to intersect (#7886) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a89eaacaaa..2d276774b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds + Bug #7886: Equip and unequip animations can't share the animation track section Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c0f6111a79..91daaa1fd1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1060,17 +1060,23 @@ namespace MWMechanics std::string_view action = evt.substr(groupname.size() + 2); if (action == "equip attach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(true); - else - mAnimation->showWeapons(true); + if (mUpperBodyState == UpperBodyState::Equipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(true); + else + mAnimation->showWeapons(true); + } } else if (action == "unequip detach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(false); - else - mAnimation->showWeapons(false); + if (mUpperBodyState == UpperBodyState::Unequipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(false); + else + mAnimation->showWeapons(false); + } } else if (action == "chop hit" || action == "slash hit" || action == "thrust hit" || action == "hit") { From 0da8b29a8830b06062420dddcc2fb3eabb4657d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 19 Mar 2024 22:14:53 +0100 Subject: [PATCH 1208/2167] Remove static modifier from local variables used to store temporary loading results They make the code thread unsafe because different threads will use the same memory to write and read using different instances of the loaded objects. --- components/esm4/loadcont.cpp | 2 +- components/esm4/loadcrea.cpp | 2 +- components/esm4/loadinfo.cpp | 2 +- components/esm4/loadlvlc.cpp | 2 +- components/esm4/loadlvli.cpp | 2 +- components/esm4/loadlvln.cpp | 2 +- components/esm4/loadnpc.cpp | 2 +- components/esm4/loadpack.cpp | 2 +- components/esm4/loadpgrd.cpp | 4 ++-- components/esm4/loadroad.cpp | 4 ++-- components/esm4/loadscpt.cpp | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index d650678093..7c90472d29 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -53,7 +53,7 @@ void ESM4::Container::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 0c07eb92e3..cb587b091d 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -56,7 +56,7 @@ void ESM4::Creature::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 1b001c1665..1acc419ada 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -41,7 +41,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) mEditorId = ESM::RefId(mId).serializeText(); // FIXME: quick workaround to use existing code - static ScriptLocalVariableData localVar; + ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index b1a0a0f241..8f045b6038 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -58,7 +58,7 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index cab8db4a21..6253db5272 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -56,7 +56,7 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index febdcbeca9..3dff37614d 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -59,7 +59,7 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 885263d67b..7c91da747e 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -59,7 +59,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index ab75598121..34ef000934 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -103,7 +103,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - static CTDA condition; + CTDA condition; reader.get(condition); // FIXME: how to "unadjust" if not FormId? // adjustFormId(condition.param1); diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 12cbf6f28b..d88cf41e8a 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -68,7 +68,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) } case ESM4::SUB_PGRR: { - static PGRR link; + PGRR link; for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet { @@ -105,7 +105,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) } case ESM4::SUB_PGRL: { - static PGRL objLink; + PGRL objLink; reader.getFormId(objLink.object); // object linkedNode std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index 8a33ab1c1d..2cf5b2ed90 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -59,8 +59,8 @@ void ESM4::Road::load(ESM4::Reader& reader) } case ESM4::SUB_PGRR: { - static PGRR link; - static RDRP linkPt; + PGRR link; + RDRP linkPt; for (std::size_t i = 0; i < mNodes.size(); ++i) { diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index b4071ed21d..841bfbc839 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -36,7 +36,7 @@ void ESM4::Script::load(ESM4::Reader& reader) mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; - static ScriptLocalVariableData localVar; + ScriptLocalVariableData localVar; while (reader.getSubRecordHeader()) { From f49d270c26b7bec6a734046d65c98b271ab35038 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 00:54:12 +0000 Subject: [PATCH 1209/2167] Don't throw away user-provided shadow map resolutions Resolves https://gitlab.com/OpenMW/openmw/-/issues/7891 I think this is better than just adding 8192 as an allowed option as the vast majority of GPUs would be too slow given what we know about the cost if that setting (maybe that'll change if we get rid of the unconditional conditional discard I suspect is the cause of the slowness that's there for no good reason since the shadowsbin already moves most drawables to a known alpha-free stateset). --- apps/launcher/settingspage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 93a724909e..1641fa07d7 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -246,6 +246,11 @@ bool Launcher::SettingsPage::loadSettings() int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); + else + { + shadowResolutionComboBox->addItem(QString::number(shadowRes)); + shadowResolutionComboBox->setCurrentIndex(shadowResolutionComboBox->count() - 1); + } connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); From b15f7857c07e788e93a2b78e2da09e12462b0832 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 14:34:23 +0000 Subject: [PATCH 1210/2167] currentDir.value is already canonicalised --- apps/launcher/datafilespage.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d87073df02..87667bda37 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -293,10 +293,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) std::unordered_set visitedDirectories; for (const Config::SettingValue& currentDir : directories) { - // normalize user supplied directories: resolve symlink, convert to native separator - const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir.value)).canonicalPath(); - - if (!visitedDirectories.insert(canonicalDirPath).second) + if (!visitedDirectories.insert(currentDir.value).second) continue; // add new achives files presents in current directory @@ -305,7 +302,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) QStringList tooltip; // add content files presents in current directory - mSelector->addFiles(currentDir.value, mNewDataDirs.contains(canonicalDirPath)); + mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value)); // add current directory to list ui.directoryListWidget->addItem(currentDir.originalRepresentation); @@ -317,7 +314,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) tooltip << tr("Resolved as %1").arg(currentDir.value); // Display new content with custom formatting - if (mNewDataDirs.contains(canonicalDirPath)) + if (mNewDataDirs.contains(currentDir.value)) { tooltip << tr("Will be added to the current profile"); QFont font = item->font(); From 0371791cce91a6bbc7ec8a8da94fb319cffc6728 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 23:12:19 +0000 Subject: [PATCH 1211/2167] Break --- apps/launcher/maindialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index aca8a64e31..178b254545 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -373,7 +373,10 @@ bool Launcher::MainDialog::setupGameData() << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) + { foundData = true; + break; + } } if (!foundData) From 37b695a0cfc50a23f975d74d57e411239879be82 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 14:12:02 +0100 Subject: [PATCH 1212/2167] Cleanup includes --- apps/openmw/mwlua/animationbindings.cpp | 5 ++--- apps/openmw/mwlua/animationbindings.hpp | 2 ++ apps/openmw/mwlua/birthsignbindings.cpp | 6 ++---- apps/openmw/mwlua/camerabindings.cpp | 1 + apps/openmw/mwlua/classbindings.cpp | 9 ++------- apps/openmw/mwlua/debugbindings.cpp | 1 + apps/openmw/mwlua/factionbindings.cpp | 7 +------ apps/openmw/mwlua/inputbindings.cpp | 1 + apps/openmw/mwlua/itemdata.cpp | 4 +--- apps/openmw/mwlua/objectlists.cpp | 1 - apps/openmw/mwlua/racebindings.cpp | 5 ++--- apps/openmw/mwlua/vfsbindings.cpp | 4 +++- 12 files changed, 18 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 5c4ccf7212..fb3e64ba73 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,3 +1,5 @@ +#include "animationbindings.hpp" + #include #include #include @@ -18,9 +20,6 @@ #include "luamanagerimp.hpp" #include "objectvariant.hpp" -#include "animationbindings.hpp" -#include - namespace MWLua { using BlendMask = MWRender::Animation::BlendMask; diff --git a/apps/openmw/mwlua/animationbindings.hpp b/apps/openmw/mwlua/animationbindings.hpp index d28dda9208..251de42ee8 100644 --- a/apps/openmw/mwlua/animationbindings.hpp +++ b/apps/openmw/mwlua/animationbindings.hpp @@ -5,6 +5,8 @@ namespace MWLua { + struct Context; + sol::table initAnimationPackage(const Context& context); sol::table initCoreVfxBindings(const Context& context); } diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 6993ae0105..f65d50bc5a 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -1,15 +1,13 @@ +#include "birthsignbindings.hpp" + #include #include #include #include #include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" -#include "birthsignbindings.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" #include "types/types.hpp" namespace sol diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index e3470eb853..ed75b4b198 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,3 +1,4 @@ +#include "camerabindings.hpp" #include #include diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index c9d5a9fb7b..84864781d2 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,14 +1,9 @@ +#include "classbindings.hpp" + #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "classbindings.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" -#include "stats.hpp" #include "types/types.hpp" namespace sol diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 0aa1f4ace5..97ca080e5c 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -1,4 +1,5 @@ #include "debugbindings.hpp" + #include "context.hpp" #include "luamanagerimp.hpp" diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index e4c65386bf..b606d1a6f9 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -3,14 +3,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/store.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" namespace { diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index ae54061cb6..e9ed4fe485 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -11,6 +11,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" + #include "luamanagerimp.hpp" namespace sol diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index d7ced755ea..3e2b755af8 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -1,13 +1,11 @@ #include "itemdata.hpp" #include "context.hpp" - #include "luamanagerimp.hpp" +#include "objectvariant.hpp" #include "../mwworld/class.hpp" -#include "objectvariant.hpp" - namespace { using SelfObject = MWLua::SelfObject; diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index e7b3bb2e06..d0bda5a644 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -7,7 +7,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwclass/container.hpp" diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index e2e2ae2a8a..ea23e883e1 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -1,14 +1,13 @@ +#include "racebindings.hpp" + #include #include #include #include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" -#include "racebindings.hpp" #include "types/types.hpp" namespace diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index 34a84221f8..0e13c07ef9 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -1,6 +1,9 @@ #include "vfsbindings.hpp" +#include + #include +#include #include #include #include @@ -10,7 +13,6 @@ #include "../mwbase/environment.hpp" #include "context.hpp" -#include "luamanagerimp.hpp" namespace MWLua { From 3721a69747b388fa9d144e4867cae33241a6ca36 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Mar 2024 18:16:11 +0300 Subject: [PATCH 1213/2167] ESM4: Make script local variable loading more reliable --- components/esm4/loadinfo.cpp | 11 ++++++----- components/esm4/loadscpt.cpp | 12 +++++++----- components/esm4/script.hpp | 7 ------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 40e366d5d7..d3339d350a 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -41,7 +41,6 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) mEditorId = ESM::RefId(mId).serializeText(); // FIXME: quick workaround to use existing code - ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) @@ -125,22 +124,24 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } case ESM::fourCC("SCVR"): // assumed always pair with SLSD { - reader.getZString(localVar.variableName); - - mScript.localVarData.push_back(localVar); + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); break; } diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index 7eaef816c8..dbc75b75d6 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -36,8 +36,6 @@ void ESM4::Script::load(ESM4::Reader& reader) mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; - ScriptLocalVariableData localVar; - while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); @@ -117,20 +115,24 @@ void ESM4::Script::load(ESM4::Reader& reader) break; case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } case ESM::fourCC("SCVR"): // assumed always pair with SLSD - reader.getZString(localVar.variableName); - mScript.localVarData.push_back(localVar); + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); + break; case ESM::fourCC("SCRV"): { diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp index 57dd85367b..3715ea55d4 100644 --- a/components/esm4/script.hpp +++ b/components/esm4/script.hpp @@ -365,13 +365,6 @@ namespace ESM4 std::uint32_t unknown4; // SCVR std::string variableName; - - void clear() - { - index = 0; - type = 0; - variableName.clear(); - } }; struct ScriptDefinition From da8150e2e4dcfca3e7fed213b402ed3fa7a6ae3a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 21 Mar 2024 15:51:29 +0000 Subject: [PATCH 1214/2167] Even more MSVC-specific warnings that evaded detection in CI --- apps/openmw/mwstate/character.cpp | 8 ++++---- components/esm3/loadland.cpp | 4 ++-- components/esm3/loadlevlist.cpp | 2 +- components/esm3/loadscpt.cpp | 2 +- components/esm4/reader.cpp | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 9a3bc46742..a486ff4bec 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -132,9 +132,9 @@ const MWState::Slot* MWState::Character::createSlot(const ESM::SavedGame& profil void MWState::Character::deleteSlot(const Slot* slot) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); @@ -147,9 +147,9 @@ void MWState::Character::deleteSlot(const Slot* slot) const MWState::Slot* MWState::Character::updateSlot(const Slot* slot, const ESM::SavedGame& profile) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 98e07b530f..006510f21b 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -94,7 +94,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), mWnam.size()); + esm.getHExact(mWnam.data(), static_cast(mWnam.size())); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): @@ -206,7 +206,7 @@ namespace ESM mLandData = std::make_unique(); mLandData->mHeightOffset = 0; - std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0); + std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0.0f); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; for (int i = 0; i < LAND_NUM_VERTS; ++i) diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 627edbadce..766fd42054 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -87,7 +87,7 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", static_cast(mList.size())); for (const auto& item : mList) { diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index 2eb272fe8b..ae56a7b4f4 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -150,7 +150,7 @@ namespace ESM if (!hasHeader) esm.fail("Missing SCHD subrecord"); // Reported script data size is not always trustworthy, so override it with actual data size - mData.mScriptDataSize = mScriptData.size(); + mData.mScriptDataSize = static_cast(mScriptData.size()); } void Script::save(ESMWriter& esm, bool isDeleted) const diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index 9811cf6103..2d9a929bb2 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -83,8 +83,8 @@ namespace ESM4 stream.next_in = reinterpret_cast(compressed.data()); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_in = compressed.size(); - stream.avail_out = decompressed.size(); + stream.avail_in = static_cast(compressed.size()); + stream.avail_out = static_cast(decompressed.size()); if (const int ec = inflateInit(&stream); ec != Z_OK) return getError("inflateInit error", ec, stream.msg); @@ -112,9 +112,9 @@ namespace ESM4 const auto prevTotalIn = stream.total_in; const auto prevTotalOut = stream.total_out; stream.next_in = reinterpret_cast(compressed.data()); - stream.avail_in = std::min(blockSize, compressed.size()); + stream.avail_in = static_cast(std::min(blockSize, compressed.size())); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_out = std::min(blockSize, decompressed.size()); + stream.avail_out = static_cast(std::min(blockSize, decompressed.size())); const int ec = inflate(&stream, Z_NO_FLUSH); if (ec == Z_STREAM_END) break; From 818a99a8707fc9b69530fc4809d654f5edce6b2e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 21 Mar 2024 16:18:18 +0000 Subject: [PATCH 1215/2167] Review --- components/esm3/esmreader.cpp | 10 +++++----- components/esm3/esmreader.hpp | 6 +++--- components/esm3/loadland.cpp | 4 ++-- components/esm3/loadlevlist.cpp | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 92a04fb487..c52e739f5f 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -244,16 +244,16 @@ namespace ESM skipHString(); } - void ESMReader::getHExact(void* p, int size) + void ESMReader::getHExact(void* p, std::size_t size) { getSubHeader(); - if (size != static_cast(mCtx.leftSub)) + if (size != mCtx.leftSub) reportSubSizeMismatch(size, mCtx.leftSub); getExact(p, size); } // Read the given number of bytes from a named subrecord - void ESMReader::getHNExact(void* p, int size, NAME name) + void ESMReader::getHNExact(void* p, std::size_t size, NAME name) { getSubNameIs(name); getHExact(p, size); @@ -326,10 +326,10 @@ namespace ESM skip(mCtx.leftSub); } - void ESMReader::skipHSubSize(int size) + void ESMReader::skipHSubSize(std::size_t size) { skipHSub(); - if (static_cast(mCtx.leftSub) != size) + if (mCtx.leftSub != size) reportSubSizeMismatch(mCtx.leftSub, size); } diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 7d0b9b980c..b67cc0f8bb 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -238,10 +238,10 @@ namespace ESM void skipHRefId(); // Read the given number of bytes from a subrecord - void getHExact(void* p, int size); + void getHExact(void* p, std::size_t size); // Read the given number of bytes from a named subrecord - void getHNExact(void* p, int size, NAME name); + void getHNExact(void* p, std::size_t size, NAME name); ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR"); @@ -275,7 +275,7 @@ namespace ESM void skipHSub(); // Skip sub record and check its size - void skipHSubSize(int size); + void skipHSubSize(std::size_t size); // Skip all subrecords until the given subrecord or no more subrecords remaining void skipHSubUntil(NAME name); diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 006510f21b..8b8a8f90fa 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -25,7 +25,7 @@ namespace ESM // Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise // including the case when data is already loaded. - bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size) + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, std::size_t size) { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { @@ -94,7 +94,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), static_cast(mWnam.size())); + esm.getHExact(mWnam.data(), mWnam.size()); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 766fd42054..f37009d6f9 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -87,7 +87,7 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", static_cast(mList.size())); + esm.writeHNT("INDX", static_cast(mList.size())); for (const auto& item : mList) { From 098396822ff78796ca78a4b8a07090d1f05e26e0 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Thu, 21 Mar 2024 17:48:30 -0700 Subject: [PATCH 1216/2167] add choice widget type to postprocessing uniforms --- apps/openmw/mwgui/postprocessorhud.cpp | 12 +- apps/openmw/mwgui/postprocessorhud.hpp | 2 + apps/openmw/mwrender/pingpongcanvas.cpp | 2 + components/fx/technique.cpp | 135 ++++++++++----- components/fx/technique.hpp | 6 + components/fx/types.hpp | 15 +- components/fx/widgets.cpp | 154 ++++++++++++------ components/fx/widgets.hpp | 112 ++++++------- .../source/reference/postprocessing/omwfx.rst | 45 +++-- .../mygui/openmw_postprocessor_hud.skin.xml | 10 ++ 10 files changed, 321 insertions(+), 172 deletions(-) diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index 6f73c5a9fd..8f5b20ba98 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -168,10 +168,11 @@ namespace MWGui if (static_cast(index) != selected) { auto technique = *mActiveList->getItemDataAt>(selected); - if (technique->getDynamic()) + if (technique->getDynamic() || technique->getInternal()) return; - if (processor->enableTechnique(std::move(technique), index) != MWRender::PostProcessor::Status_Error) + if (processor->enableTechnique(std::move(technique), index - mOffset) + != MWRender::PostProcessor::Status_Error) processor->saveChain(); } } @@ -444,10 +445,16 @@ namespace MWGui } } + mOffset = 0; for (auto technique : processor->getTechniques()) { if (!technique->getHidden()) + { mActiveList->addItem(technique->getName(), technique); + + if (technique->getInternal()) + mOffset++; + } } auto tryFocus = [this](ListWrapper* widget, const std::string& hint) { @@ -484,6 +491,7 @@ namespace MWGui factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); + factory.registerFactory("Widget"); factory.registerFactory("Widget"); } } diff --git a/apps/openmw/mwgui/postprocessorhud.hpp b/apps/openmw/mwgui/postprocessorhud.hpp index e08d20e358..20e27bac3a 100644 --- a/apps/openmw/mwgui/postprocessorhud.hpp +++ b/apps/openmw/mwgui/postprocessorhud.hpp @@ -96,6 +96,8 @@ namespace MWGui Gui::AutoSizedEditBox* mShaderInfo; std::string mOverrideHint; + + int mOffset = 0; }; } diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 3af937045f..285dde884a 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -1,5 +1,7 @@ #include "pingpongcanvas.hpp" +#include + #include #include #include diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index f6bc881f78..73355de9a5 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -498,6 +498,37 @@ namespace fx mDefinedUniforms.emplace_back(std::move(uniform)); } + template + SrcT Technique::getUniformValue() + { + constexpr bool isVec + = std::is_same_v || std::is_same_v || std::is_same_v; + constexpr bool isFloat = std::is_same_v; + constexpr bool isInt = std::is_same_v; + constexpr bool isBool = std::is_same_v; + + static_assert(isVec || isFloat || isInt || isBool, "Unsupported type"); + + if constexpr (isVec) + { + return parseVec(); + } + else if constexpr (isFloat) + { + return parseFloat(); + } + else if constexpr (isInt) + { + return parseInteger(); + } + else if constexpr (isBool) + { + return parseBool(); + } + + error(Misc::StringUtils::format("failed setting uniform type")); + } + template void Technique::parseUniform() { @@ -515,28 +546,13 @@ namespace fx expect("error parsing config for uniform block"); - constexpr bool isVec = std::is_same_v || std::is_same_v || std::is_same_v; - constexpr bool isFloat = std::is_same_v; - constexpr bool isInt = std::is_same_v; - constexpr bool isBool = std::is_same_v; - - static_assert(isVec || isFloat || isInt || isBool, "Unsupported type"); - if (key == "default") { - if constexpr (isVec) - data.mDefault = parseVec(); - else if constexpr (isFloat) - data.mDefault = parseFloat(); - else if constexpr (isInt) - data.mDefault = parseInteger(); - else if constexpr (isBool) - data.mDefault = parseBool(); + data.mDefault = getUniformValue(); } else if (key == "size") { - if constexpr (isBool) + if constexpr (std::is_same_v) error("bool arrays currently unsupported"); int size = parseInteger(); @@ -545,25 +561,11 @@ namespace fx } else if (key == "min") { - if constexpr (isVec) - data.mMin = parseVec(); - else if constexpr (isFloat) - data.mMin = parseFloat(); - else if constexpr (isInt) - data.mMin = parseInteger(); - else if constexpr (isBool) - data.mMin = parseBool(); + data.mMin = getUniformValue(); } else if (key == "max") { - if constexpr (isVec) - data.mMax = parseVec(); - else if constexpr (isFloat) - data.mMax = parseFloat(); - else if constexpr (isInt) - data.mMax = parseInteger(); - else if constexpr (isBool) - data.mMax = parseBool(); + data.mMax = getUniformValue(); } else if (key == "step") uniform->mStep = parseFloat(); @@ -571,18 +573,19 @@ namespace fx uniform->mStatic = parseBool(); else if (key == "description") { - expect(); - uniform->mDescription = std::get(mToken).value; + uniform->mDescription = parseString(); } else if (key == "header") { - expect(); - uniform->mHeader = std::get(mToken).value; + uniform->mHeader = parseString(); } else if (key == "display_name") { - expect(); - uniform->mDisplayName = std::get(mToken).value; + uniform->mDisplayName = parseString(); + } + else if (key == "widget_type") + { + parseWidgetType(data); } else error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key })); @@ -1001,6 +1004,60 @@ namespace fx error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{ asLiteral() })); } + template + void Technique::parseWidgetType(Types::Uniform& uniform) + { + expect(); + + if (asLiteral() == "choice") + { + /* Example usage + + widget_type = choice( + "Option A": , + "Option B": , + "Option C": + ); + + */ + expect(); + + std::vector> choices; + + while (!isNext()) + { + fx::Types::Choice choice; + choice.mLabel = parseString(); + expect(); + choice.mValue = getUniformValue(); + choices.push_back(choice); + + if (isNext()) + { + mToken = mLexer->next(); + + // Handle leading comma + if (isNext()) + { + break; + } + + continue; + } + + break; + } + + uniform.mChoices = std::move(choices); + + expect(); + } + else + { + error(Misc::StringUtils::format("unrecognized widget type '%s'", std::string{ asLiteral() })); + } + } + bool Technique::parseBool() { mToken = mLexer->next(); diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 01943a2fbe..2778763a9a 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -228,6 +228,12 @@ namespace fx int parseSourceFormat(); + template + void parseWidgetType(Types::Uniform& uniform); + + template + SrcT getUniformValue(); + osg::BlendEquation::Equation parseBlendEquation(); osg::BlendFunc::BlendFuncMode parseBlendFuncMode(); diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 829bf176b7..596b54c217 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -4,15 +4,9 @@ #include #include -#include -#include -#include -#include #include #include -#include - #include #include #include @@ -56,6 +50,13 @@ namespace fx osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); }; + template + struct Choice + { + std::string mLabel; + T mValue; + }; + template struct Uniform { @@ -66,6 +67,8 @@ namespace fx T mMin = std::numeric_limits::lowest(); T mMax = std::numeric_limits::max(); + std::vector> mChoices; + using value_type = T; bool isArray() const { return mArray.has_value(); } diff --git a/components/fx/widgets.cpp b/components/fx/widgets.cpp index f8d12fdba3..8382ca2d56 100644 --- a/components/fx/widgets.cpp +++ b/components/fx/widgets.cpp @@ -28,35 +28,20 @@ namespace fx { void EditBool::setValue(bool value) { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - mCheckbutton->setCaptionWithReplacing(value ? "#{Interface:On}" : "#{Interface:Off}"); mFill->setVisible(value); - uniform->setValue(value); + mUniform->setValue(value); } void EditBool::setValueFromUniform() { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - - setValue(uniform->template getValue()); + setValue(mUniform->template getValue()); } void EditBool::toDefault() { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - - setValue(uniform->getDefault()); + setValue(mUniform->getDefault()); } void EditBool::initialiseOverride() @@ -71,12 +56,75 @@ namespace fx void EditBool::notifyMouseButtonClick(MyGUI::Widget* sender) { - auto uniform = mUniform.lock(); + setValue(!mUniform->getValue()); + } - if (!uniform) - return; + template + void EditChoice::setValue(const T& value) + { + // Update the combo view + for (size_t i = 0; i < this->mChoices->getItemCount(); i++) + { + if (*this->mChoices->getItemDataAt(i) == value) + { + this->mChoices->setIndexSelected(i); + break; + } + } - setValue(!uniform->getValue()); + mUniform->template setValue(value); + } + + void EditChoice::notifyComboBoxChanged(MyGUI::ComboBox* sender, size_t pos) + { + std::visit( + [this, sender, pos](auto&& data) { + using T = typename std::decay_t::value_type; + setValue(*sender->getItemDataAt(pos)); + }, + mUniform->mData); + } + + void EditChoice::setValueFromUniform() + { + std::visit( + [this](auto&& data) { + using T = typename std::decay_t::value_type; + size_t index = 0; + for (const auto& choice : data.mChoices) + { + this->mChoices->addItem(choice.mLabel, choice.mValue); + + if (choice.mValue == mUniform->template getValue()) + { + this->mChoices->setIndexSelected(index); + } + + index++; + } + + setValue(mUniform->template getValue()); + }, + mUniform->mData); + } + + void EditChoice::toDefault() + { + std::visit( + [this](auto&& data) { + using T = typename std::decay_t::value_type; + setValue(mUniform->template getDefault()); + }, + mUniform->mData); + } + + void EditChoice::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mChoices, "Choices"); + + mChoices->eventComboChangePosition += MyGUI::newDelegate(this, &EditChoice::notifyComboBoxChanged); } void UniformBase::init(const std::shared_ptr& uniform) @@ -101,38 +149,48 @@ namespace fx [this, &uniform](auto&& arg) { using T = typename std::decay_t::value_type; - if constexpr (std::is_same_v) + if (arg.mChoices.size() > 0) { - createVectorWidget(uniform, mClient, this); - } - else if constexpr (std::is_same_v) - { - createVectorWidget(uniform, mClient, this); - } - else if constexpr (std::is_same_v) - { - createVectorWidget(uniform, mClient, this); - } - else if constexpr (std::is_same_v) - { - auto* widget = mClient->createWidget("MW_ValueEditNumber", + auto* widget = mClient->createWidget("MW_ValueEditChoice", { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); widget->setData(uniform); mBases.emplace_back(widget); } - else if constexpr (std::is_same_v) + else { - auto* widget = mClient->createWidget("MW_ValueEditNumber", - { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); - widget->setData(uniform); - mBases.emplace_back(widget); - } - else if constexpr (std::is_same_v) - { - auto* widget = mClient->createWidget("MW_ValueEditBool", - { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); - widget->setData(uniform); - mBases.emplace_back(widget); + if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + createVectorWidget(uniform, mClient, this); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", + { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditNumber", + { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } + else if constexpr (std::is_same_v) + { + auto* widget = mClient->createWidget("MW_ValueEditBool", + { 0, 0, mClient->getWidth(), mClient->getHeight() }, MyGUI::Align::Stretch); + widget->setData(uniform); + mBases.emplace_back(widget); + } } mReset->eventMouseButtonClick += MyGUI::newDelegate(this, &UniformBase::notifyResetClicked); diff --git a/components/fx/widgets.hpp b/components/fx/widgets.hpp index baee2d6df4..6217af7fee 100644 --- a/components/fx/widgets.hpp +++ b/components/fx/widgets.hpp @@ -57,7 +57,7 @@ namespace fx virtual void toDefault() = 0; protected: - std::weak_ptr mUniform; + std::shared_ptr mUniform; Index mIndex; }; @@ -95,22 +95,19 @@ namespace fx float range = 0.f; float min = 0.f; - if (auto uniform = mUniform.lock()) + if constexpr (std::is_fundamental_v) { - if constexpr (std::is_fundamental_v) - { - uniform->template setValue(mValue); - range = uniform->template getMax() - uniform->template getMin(); - min = uniform->template getMin(); - } - else - { - UType uvalue = uniform->template getValue(); - uvalue[mIndex] = mValue; - uniform->template setValue(uvalue); - range = uniform->template getMax()[mIndex] - uniform->template getMin()[mIndex]; - min = uniform->template getMin()[mIndex]; - } + mUniform->template setValue(mValue); + range = mUniform->template getMax() - mUniform->template getMin(); + min = mUniform->template getMin(); + } + else + { + UType uvalue = mUniform->template getValue(); + uvalue[mIndex] = mValue; + mUniform->template setValue(uvalue); + range = mUniform->template getMax()[mIndex] - mUniform->template getMin()[mIndex]; + min = mUniform->template getMin()[mIndex]; } float fill = (range == 0.f) ? 1.f : (mValue - min) / range; @@ -119,28 +116,22 @@ namespace fx void setValueFromUniform() override { - if (auto uniform = mUniform.lock()) - { - T value; + T value; - if constexpr (std::is_fundamental_v) - value = uniform->template getValue(); - else - value = uniform->template getValue()[mIndex]; + if constexpr (std::is_fundamental_v) + value = mUniform->template getValue(); + else + value = mUniform->template getValue()[mIndex]; - setValue(value); - } + setValue(value); } void toDefault() override { - if (auto uniform = mUniform.lock()) - { - if constexpr (std::is_fundamental_v) - setValue(uniform->template getDefault()); - else - setValue(uniform->template getDefault()[mIndex]); - } + if constexpr (std::is_fundamental_v) + setValue(mUniform->template getDefault()); + else + setValue(mUniform->template getDefault()[mIndex]); } private: @@ -164,15 +155,10 @@ namespace fx void notifyMouseWheel(MyGUI::Widget* sender, int rel) { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - if (rel > 0) - increment(uniform->mStep); + increment(mUniform->mStep); else - increment(-uniform->mStep); + increment(-mUniform->mStep); } void notifyMouseButtonDragged(MyGUI::Widget* sender, int left, int top, MyGUI::MouseButton id) @@ -180,24 +166,19 @@ namespace fx if (id != MyGUI::MouseButton::Left) return; - auto uniform = mUniform.lock(); - - if (!uniform) - return; - int delta = left - mLastPointerX; // allow finer tuning when shift is pressed constexpr double scaling = 20.0; T step - = MyGUI::InputManager::getInstance().isShiftPressed() ? uniform->mStep / scaling : uniform->mStep; + = MyGUI::InputManager::getInstance().isShiftPressed() ? mUniform->mStep / scaling : mUniform->mStep; if (step == 0) { if constexpr (std::is_integral_v) step = 1; else - step = uniform->mStep; + step = mUniform->mStep; } if (delta > 0) @@ -218,30 +199,20 @@ namespace fx void increment(T step) { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - if constexpr (std::is_fundamental_v) - setValue(std::clamp(uniform->template getValue() + step, - uniform->template getMin(), uniform->template getMax())); + setValue(std::clamp(mUniform->template getValue() + step, + mUniform->template getMin(), mUniform->template getMax())); else - setValue(std::clamp(uniform->template getValue()[mIndex] + step, - uniform->template getMin()[mIndex], uniform->template getMax()[mIndex])); + setValue(std::clamp(mUniform->template getValue()[mIndex] + step, + mUniform->template getMin()[mIndex], mUniform->template getMax()[mIndex])); } void notifyButtonClicked(MyGUI::Widget* sender) { - auto uniform = mUniform.lock(); - - if (!uniform) - return; - if (sender == mButtonDecrease) - increment(-uniform->mStep); + increment(-mUniform->mStep); else if (sender == mButtonIncrease) - increment(uniform->mStep); + increment(mUniform->mStep); } MyGUI::Button* mButtonDecrease{ nullptr }; @@ -275,6 +246,23 @@ namespace fx MYGUI_RTTI_DERIVED(EditNumberInt) }; + class EditChoice : public EditBase, public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(EditChoice) + + public: + template + void setValue(const T& value); + void setValueFromUniform() override; + void toDefault() override; + + private: + void initialiseOverride() override; + void notifyComboBoxChanged(MyGUI::ComboBox* sender, size_t pos); + + MyGUI::ComboBox* mChoices{ nullptr }; + }; + class UniformBase final : public MyGUI::Widget { MYGUI_RTTI_DERIVED(UniformBase) diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index b47e509925..f9335e5c88 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -451,21 +451,21 @@ To use the sampler, define the appropriately named `sampler2D` in any of your pa It is possible to define settings for your shaders that can be adjusted by either users or a Lua script. -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -| Block | default | min | max | static | step | description | display_name | header | -+=================+==========+==========+==========+=========+==========+==============+===================+=========+ -|``uniform_bool`` | boolean | x | x | boolean | x | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -|``uniform_float``| float | float | float | boolean | float | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -|``uniform_int`` | integer | integer | integer | boolean | integer | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -|``uniform_vec2`` | vec2 | vec2 | vec2 | boolean | vec2 | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -|``uniform_vec3`` | vec3 | vec3 | vec3 | boolean | vec3 | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ -|``uniform_vec4`` | vec4 | vec4 | vec4 | boolean | vec4 | string | string | string | -+-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+ ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +| Block | default | min | max | static | step | description | display_name | header | widget_type | ++=================+==========+==========+==========+=========+==========+==============+===================+=========+==============+ +|``uniform_bool`` | boolean | x | x | boolean | x | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +|``uniform_float``| float | float | float | boolean | float | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +|``uniform_int`` | integer | integer | integer | boolean | integer | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +|``uniform_vec2`` | vec2 | vec2 | vec2 | boolean | vec2 | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +|``uniform_vec3`` | vec3 | vec3 | vec3 | boolean | vec3 | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ +|``uniform_vec4`` | vec4 | vec4 | vec4 | boolean | vec4 | string | string | string | choice(...) | ++-----------------+----------+----------+----------+---------+----------+--------------+-------------------+---------+--------------+ The ``description`` field is used to display a toolip when viewed in the in-game HUD. The ``header`` field field can be used to organize uniforms into groups in the HUD. The ``display_name`` field can be used to create a @@ -509,6 +509,21 @@ These uniform blocks must be defined with the new ``size`` parameter. size = 10; } +You may also define a dropdown list for users to select specific values from instead of the default sliders using the ``widget_type`` field. +Each item in the dropdown has an associated display name, which can be a localized string. + +.. code-block:: none + + uniform_int uStrength { + default = 2; + display_name = "Strength"; + widget_type = choice( + "Low" = 1, + "Medium" = 2, + "High" = 3 + ); + } + ``render_target`` ***************** diff --git a/files/data/mygui/openmw_postprocessor_hud.skin.xml b/files/data/mygui/openmw_postprocessor_hud.skin.xml index 8da8df2050..f6c6522a41 100644 --- a/files/data/mygui/openmw_postprocessor_hud.skin.xml +++ b/files/data/mygui/openmw_postprocessor_hud.skin.xml @@ -1,6 +1,16 @@ + + + + + + + + + + From 79039f88df4f0390cd62bd76c3211dda2760197a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Mar 2024 04:25:32 +0300 Subject: [PATCH 1217/2167] Use the right ID for magic effect verifier messages (#7894) --- apps/opencs/model/tools/magiceffectcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index a9ad4023fc..e44119bb67 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -58,7 +58,7 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa return; ESM::MagicEffect effect = record.get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect)); if (effect.mDescription.empty()) { From c20a23b694bf2d294e74ddd972661c6025654f22 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 22 Mar 2024 03:13:04 +0000 Subject: [PATCH 1218/2167] Remove unused regionmap CellDescription constructor --- apps/opencs/model/world/regionmap.cpp | 5 ----- apps/opencs/model/world/regionmap.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index f555f0ea32..79a0d5474d 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -41,11 +41,6 @@ namespace CSMWorld } } -CSMWorld::RegionMap::CellDescription::CellDescription() - : mDeleted(false) -{ -} - CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell, float landHeight) { const Cell& cell2 = cell.get(); diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index e5a4d61337..96281ba49c 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -45,8 +45,6 @@ namespace CSMWorld ESM::RefId mRegion; std::string mName; - CellDescription(); - CellDescription(const Record& cell, float landHeight); }; From d6241dd1c5956c28068c66bfbe5218aa820321fc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 17:41:12 -0500 Subject: [PATCH 1219/2167] Add back new_index --- apps/openmw/mwlua/mwscriptbindings.cpp | 37 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 6ccb8c80fd..1f0a081fe4 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -67,6 +67,19 @@ namespace MWLua return 0; } + void setGlobalVariableValue(const std::string_view globalId, float value) + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, value); + } + else if (varType == 's' || varType == 'l') + { + MWBase::Environment::get().getWorld()->setGlobalInt(globalId, value); + } + } + sol::table initMWScriptBindings(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -155,14 +168,22 @@ namespace MWLua std::string globalId = g->mId.serializeText(); return getGlobalVariableValue(globalId); }); - - globalStoreT[sol::meta_function::new_index] - = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); - return getGlobalVariableValue(globalId); - }); + globalStoreT[sol::meta_function::new_index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId, float val) -> void { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); + setGlobalVariableValue(globalId, val); + }, + [](const GlobalStore& store, size_t index, float val) { + if (index < 1 || store.getSize() < index) + return; + auto g = store.at(index - 1); + if (g == nullptr) + return; + std::string globalId = g->mId.serializeText(); + setGlobalVariableValue(globalId, val); + }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { size_t index = 0; return sol::as_function( From 4634c7dba95e9b0cfe2d9db38d09fb337710c53d Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 18:56:15 -0500 Subject: [PATCH 1220/2167] Add iteration global tests --- example-suite | 1 + .../integration_tests/test_lua_api/test.lua | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 160000 example-suite diff --git a/example-suite b/example-suite new file mode 160000 index 0000000000..f0c62b7e46 --- /dev/null +++ b/example-suite @@ -0,0 +1 @@ +Subproject commit f0c62b7e4637badb324e782c97169560e8171032 diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 2ec9f09b97..775f3ba499 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -64,6 +65,28 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end +local function testMWScript() + local variableStoreCount = 18 + local variableStore = world.mwscript.getGlobalVariables(player) + testing.expectEqual(variableStoreCount,#variableStore) + + variableStore.year = variableStoreCount + testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 1 + local indexCheck = 0 + for index, value in ipairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) + indexCheck = 0 + for index, value in pairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -101,6 +124,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'mwscript', testMWScript}, } return { From b8c8e304319c5ea9e853f5b48b3d604cbd7aea32 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:06:00 -0500 Subject: [PATCH 1221/2167] Revert "Add iteration global tests" This reverts commit 4634c7dba95e9b0cfe2d9db38d09fb337710c53d. --- example-suite | 1 - .../integration_tests/test_lua_api/test.lua | 24 ------------------- 2 files changed, 25 deletions(-) delete mode 160000 example-suite diff --git a/example-suite b/example-suite deleted file mode 160000 index f0c62b7e46..0000000000 --- a/example-suite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f0c62b7e4637badb324e782c97169560e8171032 diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 775f3ba499..2ec9f09b97 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,7 +2,6 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') -local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -65,28 +64,6 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end -local function testMWScript() - local variableStoreCount = 18 - local variableStore = world.mwscript.getGlobalVariables(player) - testing.expectEqual(variableStoreCount,#variableStore) - - variableStore.year = variableStoreCount - testing.expectEqual(variableStoreCount,variableStore.year) - variableStore.year = 1 - local indexCheck = 0 - for index, value in ipairs(variableStore) do - testing.expectEqual(variableStore[index],value) - indexCheck = indexCheck + 1 - end - testing.expectEqual(variableStoreCount,indexCheck) - indexCheck = 0 - for index, value in pairs(variableStore) do - testing.expectEqual(variableStore[index],value) - indexCheck = indexCheck + 1 - end - testing.expectEqual(variableStoreCount,indexCheck) -end - local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -124,7 +101,6 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, - {'mwscript', testMWScript}, } return { From b51891cbcdd6ed639b58ea8cf65d3f30e2deca68 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:13:39 -0500 Subject: [PATCH 1222/2167] Add lua global var test back --- .../integration_tests/test_lua_api/test.lua | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 2ec9f09b97..0eead01ff0 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -64,6 +65,28 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end +local function testMWScript() + local variableStoreCount = 18 + local variableStore = world.mwscript.getGlobalVariables(player) + testing.expectEqual(variableStoreCount,#variableStore) + + variableStore.year = variableStoreCount + testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 1 + local indexCheck = 0 + for index, value in ipairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) + indexCheck = 0 + for index, value in pairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -101,6 +124,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'mwscript', testMWScript}, } return { @@ -109,4 +133,4 @@ return { onPlayerAdded = function(p) player = p end, }, eventHandlers = testing.eventHandlers, -} +} \ No newline at end of file From 7d1f52451f953be5842c35e90b2d9741a5c69ed7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:14:28 -0500 Subject: [PATCH 1223/2167] Re-add new line --- scripts/data/integration_tests/test_lua_api/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 0eead01ff0..775f3ba499 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -133,4 +133,4 @@ return { onPlayerAdded = function(p) player = p end, }, eventHandlers = testing.eventHandlers, -} \ No newline at end of file +} From 1aff88e6a3fc5637f65c840686898a0008e77284 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Mar 2024 00:33:50 +0000 Subject: [PATCH 1224/2167] Even more warning fixes --- apps/openmw/mwdialogue/keywordsearch.hpp | 2 +- apps/openmw_test_suite/openmw/options.cpp | 2 +- apps/openmw_test_suite/sqlite3/request.cpp | 4 ++-- apps/openmw_test_suite/toutf8/toutf8.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3b784cd59c..c93a52e43e 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -87,7 +87,7 @@ namespace MWDialogue // some keywords might be longer variations of other keywords, so we definitely need a list of // candidates the first element in the pair is length of the match, i.e. depth from the first character // on - std::vector> candidates; + std::vector> candidates; while ((j + 1) != end) { diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index fc89264f8c..fe319f64fa 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -36,7 +36,7 @@ namespace (result.emplace_back(makeString(args)), ...); for (int i = 1; i <= std::numeric_limits::max(); ++i) if (i != '&' && i != '"' && i != ' ' && i != '\n') - result.push_back(std::string(1, i)); + result.push_back(std::string(1, static_cast(i))); return result; } diff --git a/apps/openmw_test_suite/sqlite3/request.cpp b/apps/openmw_test_suite/sqlite3/request.cpp index 23efe9dc2e..c299493952 100644 --- a/apps/openmw_test_suite/sqlite3/request.cpp +++ b/apps/openmw_test_suite/sqlite3/request.cpp @@ -151,7 +151,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetAll("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } @@ -205,7 +205,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp index f189294cf2..9a259c69ab 100644 --- a/apps/openmw_test_suite/toutf8/toutf8.cpp +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -47,7 +47,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result.data(), input.data()); @@ -99,7 +99,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result.data(), input.data()); From 7c857559503acc06fc403a325a731330a9447776 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Mar 2024 02:34:57 +0000 Subject: [PATCH 1225/2167] Warning that doesn't fire with MSVC 2022 Hopefully this fixes it. I've only tried MSVC 2022 locally, so can't verify this fix. --- apps/openmw/mwdialogue/keywordsearch.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index c93a52e43e..2c98eac218 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -148,11 +148,11 @@ namespace MWDialogue // resolve overlapping keywords while (!matches.empty()) { - int longestKeywordSize = 0; + std::size_t longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { - int size = it->mEnd - it->mBeg; + std::size_t size = it->mEnd - it->mBeg; if (size > longestKeywordSize) { longestKeywordSize = size; @@ -199,7 +199,7 @@ namespace MWDialogue void seed_impl(std::string_view keyword, value_t value, size_t depth, Entry& entry) { - int ch = Misc::StringUtils::toLower(keyword.at(depth)); + auto ch = Misc::StringUtils::toLower(keyword.at(depth)); typename Entry::childen_t::iterator j = entry.mChildren.find(ch); From 5a0aed3a78edeca440dd2b3c50120bb0bb3a3f19 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Mar 2024 12:15:09 +0100 Subject: [PATCH 1226/2167] Use more decomposition, string_view, and implicit sizes in ESM code --- apps/esmtool/record.cpp | 3 - apps/essimporter/converter.cpp | 2 +- apps/essimporter/importcellref.cpp | 34 ++++++--- apps/essimporter/importer.cpp | 2 +- components/esm/luascripts.cpp | 2 +- components/esm3/esmreader.cpp | 29 ++------ components/esm3/esmreader.hpp | 8 +-- components/esm3/esmwriter.cpp | 20 +++--- components/esm3/esmwriter.hpp | 21 +++--- components/esm3/landrecorddata.hpp | 13 ++-- components/esm3/loadland.cpp | 108 ++++++++++++++++------------- components/esm3/loadland.hpp | 24 ++----- 12 files changed, 124 insertions(+), 142 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 912ad0d683..b83c711476 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -896,9 +896,6 @@ namespace EsmTool if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes)) { std::cout << " Height Offset: " << data->mHeightOffset << std::endl; - // Lots of missing members. - std::cout << " Unknown1: " << data->mUnk1 << std::endl; - std::cout << " Unknown2: " << static_cast(data->mUnk2) << std::endl; } mData.unloadData(); std::cout << " Deleted: " << mIsDeleted << std::endl; diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4c4bd1e438..ebb0c9d281 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -232,7 +232,7 @@ namespace ESSImport esm.skip(4); } - esm.getExact(nam8, 32); + esm.getT(nam8); newcell.mFogOfWar.reserve(16 * 16); for (int x = 0; x < 16; ++x) diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 56e888d3f6..9e8e9a6948 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,10 +1,30 @@ #include "importcellref.hpp" #include +#include + #include namespace ESSImport { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown, v.mFlags, v.mBreathMeter, v.mUnknown2, v.mDynamic, v.mUnknown3, v.mAttributes, v.mMagicEffects, + v.mUnknown4, v.mGoldPool, v.mCountDown, v.mUnknown5); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown1, v.mFlags, v.mUnknown2, v.mCorpseClearCountdown, v.mUnknown3); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGroupIndex, v.mUnknown, v.mTime); + } void CellRef::load(ESM::ESMReader& esm) { @@ -45,14 +65,9 @@ namespace ESSImport bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); - mActorData.mHasACDT - = esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, - mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, - mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, - mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); + mActorData.mHasACDT = esm.getOptionalComposite("ACDT", mActorData.mACDT); - mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, - mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); + mActorData.mHasACSC = esm.getOptionalComposite("ACSC", mActorData.mACSC); if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); @@ -127,8 +142,7 @@ namespace ESSImport if (esm.isNextSub("ND3D")) esm.skipHSub(); - mActorData.mHasANIS - = esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); + mActorData.mHasANIS = esm.getOptionalComposite("ANIS", mActorData.mANIS); if (esm.isNextSub("LVCR")) { @@ -146,7 +160,7 @@ namespace ESSImport // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess for (int i = 0; i < 2; ++i) - esm.getHNOT("DATA", mPos.pos, mPos.rot); + esm.getOptionalComposite("DATA", mPos); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 76b685c8a3..5cc9a8259b 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -135,7 +135,7 @@ namespace ESSImport sub.mFileOffset = esm.getFileOffset(); sub.mName = esm.retSubName().toString(); sub.mData.resize(esm.getSubSize()); - esm.getExact(&sub.mData[0], sub.mData.size()); + esm.getExact(sub.mData.data(), sub.mData.size()); rec.mSubrecords.push_back(sub); } file.mRecords.push_back(rec); diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 8f2048d8a7..71e2ce6dc1 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -38,7 +38,7 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm) { esm.getSubHeader(); data.resize(esm.getSubSize()); - esm.getExact(data.data(), static_cast(data.size())); + esm.getExact(data.data(), data.size()); } return data; } diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index c52e739f5f..4f69b8edef 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -153,7 +153,7 @@ namespace ESM { if (isNextSub(name)) return getHString(); - return ""; + return {}; } ESM::RefId ESMReader::getHNORefId(NAME name) @@ -244,21 +244,6 @@ namespace ESM skipHString(); } - void ESMReader::getHExact(void* p, std::size_t size) - { - getSubHeader(); - if (size != mCtx.leftSub) - reportSubSizeMismatch(size, mCtx.leftSub); - getExact(p, size); - } - - // Read the given number of bytes from a named subrecord - void ESMReader::getHNExact(void* p, std::size_t size, NAME name) - { - getSubNameIs(name); - getHExact(p, size); - } - FormId ESMReader::getFormId(bool wide, NAME tag) { FormId res; @@ -316,7 +301,7 @@ namespace ESM // reading the subrecord data anyway. const std::size_t subNameSize = decltype(mCtx.subName)::sCapacity; - getExact(mCtx.subName.mData, static_cast(subNameSize)); + getExact(mCtx.subName.mData, subNameSize); mCtx.leftRec -= static_cast(subNameSize); } @@ -506,7 +491,7 @@ namespace ESM case RefIdType::Generated: { std::uint64_t generated{}; - getExact(&generated, sizeof(std::uint64_t)); + getT(generated); return RefId::generated(generated); } case RefIdType::Index: @@ -514,14 +499,14 @@ namespace ESM RecNameInts recordType{}; getExact(&recordType, sizeof(std::uint32_t)); std::uint32_t index{}; - getExact(&index, sizeof(std::uint32_t)); + getT(index); return RefId::index(recordType, index); } case RefIdType::ESM3ExteriorCell: { int32_t x, y; - getExact(&x, sizeof(std::int32_t)); - getExact(&y, sizeof(std::int32_t)); + getT(x); + getT(y); return RefId::esm3ExteriorCell(x, y); } } @@ -529,7 +514,7 @@ namespace ESM fail("Unsupported RefIdType: " + std::to_string(static_cast(refIdType))); } - [[noreturn]] void ESMReader::fail(const std::string& msg) + [[noreturn]] void ESMReader::fail(std::string_view msg) { std::stringstream ss; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index b67cc0f8bb..5af5e75573 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -237,12 +237,6 @@ namespace ESM void skipHRefId(); - // Read the given number of bytes from a subrecord - void getHExact(void* p, std::size_t size); - - // Read the given number of bytes from a named subrecord - void getHNExact(void* p, std::size_t size, NAME name); - ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR"); /************************************************************************* @@ -354,7 +348,7 @@ namespace ESM } /// Used for error handling - [[noreturn]] void fail(const std::string& msg); + [[noreturn]] void fail(std::string_view msg); /// Sets font encoder for ESM strings void setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index ad64ced0a4..47c861e3ca 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -97,14 +97,14 @@ namespace ESM mHeader.mData.type = type; } - void ESMWriter::setAuthor(const std::string& auth) + void ESMWriter::setAuthor(std::string_view auth) { - mHeader.mData.author.assign(auth); + mHeader.mData.author = auth; } - void ESMWriter::setDescription(const std::string& desc) + void ESMWriter::setDescription(std::string_view desc) { - mHeader.mData.desc.assign(desc); + mHeader.mData.desc = desc; } void ESMWriter::setRecordCount(int count) @@ -122,7 +122,7 @@ namespace ESM mHeader.mMaster.clear(); } - void ESMWriter::addMaster(const std::string& name, uint64_t size) + void ESMWriter::addMaster(std::string_view name, uint64_t size) { Header::MasterData d; d.name = name; @@ -208,14 +208,14 @@ namespace ESM endRecord(NAME(name)); } - void ESMWriter::writeHNString(NAME name, const std::string& data) + void ESMWriter::writeHNString(NAME name, std::string_view data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(NAME name, const std::string& data, size_t size) + void ESMWriter::writeHNString(NAME name, std::string_view data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -278,9 +278,9 @@ namespace ESM write(string.c_str(), string.size()); } - void ESMWriter::writeHString(const std::string& data) + void ESMWriter::writeHString(std::string_view data) { - if (data.size() == 0) + if (data.empty()) write("\0", 1); else { @@ -291,7 +291,7 @@ namespace ESM } } - void ESMWriter::writeHCString(const std::string& data) + void ESMWriter::writeHCString(std::string_view data) { writeHString(data); if (data.size() > 0 && data[data.size() - 1] != '\0') diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 101246fe43..96445bcdae 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -38,8 +38,8 @@ namespace ESM void setVersion(unsigned int ver = 0x3fa66666); void setType(int type); void setEncoder(ToUTF8::Utf8Encoder* encoding); - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); + void setAuthor(std::string_view author); + void setDescription(std::string_view desc); void setHeader(const Header& value) { mHeader = value; } // Set the record count for writing it in the file header @@ -54,7 +54,7 @@ namespace ESM void clearMaster(); - void addMaster(const std::string& name, uint64_t size); + void addMaster(std::string_view name, uint64_t size); void save(std::ostream& file); ///< Start saving a file by writing the TES3 header. @@ -62,20 +62,20 @@ namespace ESM void close(); ///< \note Does not close the stream. - void writeHNString(NAME name, const std::string& data); - void writeHNString(NAME name, const std::string& data, size_t size); - void writeHNCString(NAME name, const std::string& data) + void writeHNString(NAME name, std::string_view data); + void writeHNString(NAME name, std::string_view data, size_t size); + void writeHNCString(NAME name, std::string_view data) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(NAME name, const std::string& data) + void writeHNOString(NAME name, std::string_view data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(NAME name, const std::string& data) + void writeHNOCString(NAME name, std::string_view data) { if (!data.empty()) writeHNCString(name, data); @@ -140,6 +140,7 @@ namespace ESM // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. void writeHNT(NAME name, const std::string& data) = delete; + void writeHNT(NAME name, std::string_view data) = delete; void writeT(NAME data) = delete; @@ -181,8 +182,8 @@ namespace ESM void endRecord(NAME name); void endRecord(uint32_t name); void writeMaybeFixedSizeString(const std::string& data, std::size_t size); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); + void writeHString(std::string_view data); + void writeHCString(std::string_view data); void writeMaybeFixedSizeRefId(RefId value, std::size_t size); diff --git a/components/esm3/landrecorddata.hpp b/components/esm3/landrecorddata.hpp index e7db0d9f3a..ca2a2b74ad 100644 --- a/components/esm3/landrecorddata.hpp +++ b/components/esm3/landrecorddata.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H #define OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H +#include #include namespace ESM @@ -22,24 +23,20 @@ namespace ESM // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset = 0; // Height in world space for each vertex - float mHeights[sLandNumVerts]; + std::array mHeights; float mMinHeight = 0; float mMaxHeight = 0; // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. - std::int8_t mNormals[sLandNumVerts * 3]; + std::array mNormals; // 2D array of texture indices. An index can be used to look up an LandTexture, // but to do so you must subtract 1 from the index first! // An index of 0 indicates the default texture. - std::uint16_t mTextures[sLandNumTextures]; + std::array mTextures; // 24-bit RGB color for each vertex - std::uint8_t mColours[3 * sLandNumVerts]; - - // ??? - std::uint16_t mUnk1 = 0; - std::uint8_t mUnk2 = 0; + std::array mColours; int mDataLoaded = 0; }; diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 8b8a8f90fa..74edf30498 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -5,7 +5,9 @@ #include #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -13,27 +15,43 @@ namespace ESM { namespace { + struct VHGT + { + float mHeightOffset; + std::int8_t mHeightData[LandRecordData::sLandNumVerts]; + }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHeightOffset, v.mHeightData, padding); + } + void transposeTextureData(const std::uint16_t* in, std::uint16_t* out) { - int readPos = 0; // bit ugly, but it works - for (int y1 = 0; y1 < 4; y1++) - for (int x1 = 0; x1 < 4; x1++) - for (int y2 = 0; y2 < 4; y2++) - for (int x2 = 0; x2 < 4; x2++) + size_t readPos = 0; // bit ugly, but it works + for (size_t y1 = 0; y1 < 4; y1++) + for (size_t x1 = 0; x1 < 4; x1++) + for (size_t y2 = 0; y2 < 4; y2++) + for (size_t x2 = 0; x2 < 4; x2++) out[(y1 * 4 + y2) * 16 + (x1 * 4 + x2)] = in[readPos++]; } // Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise // including the case when data is already loaded. - bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, std::size_t size) + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, auto& in) { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { - reader.getHExact(ptr, size); + if constexpr (std::is_same_v, VHGT>) + reader.getSubComposite(in); + else + reader.getHT(in); targetFlags |= dataFlag; return true; } - reader.skipHSubSize(size); + reader.skipHSub(); return false; } } @@ -50,11 +68,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - esm.getSubHeader(); - if (esm.getSubSize() != 8) - esm.fail("Subrecord size is not equal to 8"); - esm.getT(mX); - esm.getT(mY); + esm.getHT(mX, mY); hasLocation = true; break; case fourCC("DATA"): @@ -94,7 +108,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), mWnam.size()); + esm.getHT(mWnam); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): @@ -137,12 +151,10 @@ namespace ESM { VHGT offsets; offsets.mHeightOffset = mLandData->mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mLandData->mUnk1; - offsets.mUnk2 = mLandData->mUnk2; float prevY = mLandData->mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) + size_t number = 0; // avoid multiplication + for (unsigned i = 0; i < LandRecordData::sLandSize; ++i) { float diff = (mLandData->mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -151,7 +163,7 @@ namespace ESM float prevX = prevY = mLandData->mHeights[number]; ++number; - for (int j = 1; j < LAND_SIZE; ++j) + for (unsigned j = 1; j < LandRecordData::sLandSize; ++j) { diff = (mLandData->mHeights[number] - prevX) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -161,7 +173,7 @@ namespace ESM ++number; } } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + esm.writeNamedComposite("VHGT", offsets); } if (mDataTypes & Land::DATA_WNAM) { @@ -169,13 +181,15 @@ namespace ESM std::int8_t wnam[LAND_GLOBAL_MAP_LOD_SIZE]; constexpr float max = std::numeric_limits::max(); constexpr float min = std::numeric_limits::min(); - constexpr float vertMult = static_cast(Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; - for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) + constexpr float vertMult + = static_cast(LandRecordData::sLandSize - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + for (unsigned row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) { - for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) + for (unsigned col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) { - float height = mLandData->mHeights[static_cast(row * vertMult) * Land::LAND_SIZE - + static_cast(col * vertMult)]; + float height + = mLandData->mHeights[static_cast(row * vertMult) * LandRecordData::sLandSize + + static_cast(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; height = std::clamp(height, min, max); wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); @@ -189,8 +203,8 @@ namespace ESM } if (mDataTypes & Land::DATA_VTEX) { - uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mLandData->mTextures, vtex); + uint16_t vtex[LandRecordData::sLandNumTextures]; + transposeTextureData(mLandData->mTextures.data(), vtex); esm.writeHNT("VTEX", vtex); } } @@ -200,25 +214,23 @@ namespace ESM { setPlugin(0); - std::fill(std::begin(mWnam), std::end(mWnam), 0); + mWnam.fill(0); if (mLandData == nullptr) mLandData = std::make_unique(); mLandData->mHeightOffset = 0; - std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0.0f); + mLandData->mHeights.fill(0); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; - for (int i = 0; i < LAND_NUM_VERTS; ++i) + for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i) { mLandData->mNormals[i * 3 + 0] = 0; mLandData->mNormals[i * 3 + 1] = 0; mLandData->mNormals[i * 3 + 2] = 127; } - std::fill(std::begin(mLandData->mTextures), std::end(mLandData->mTextures), 0); - std::fill(std::begin(mLandData->mColours), std::end(mLandData->mColours), 255); - mLandData->mUnk1 = 0; - mLandData->mUnk2 = 0; + mLandData->mTextures.fill(0); + mLandData->mColours.fill(255); mLandData->mDataLoaded = Land::DATA_VNML | Land::DATA_VHGT | Land::DATA_WNAM | Land::DATA_VCLR | Land::DATA_VTEX; mDataTypes = mLandData->mDataLoaded; @@ -259,32 +271,32 @@ namespace ESM if (reader.isNextSub("VNML")) { - condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals, sizeof(data.mNormals)); + condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals); } if (reader.isNextSub("VHGT")) { VHGT vhgt; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) + if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, vhgt)) { data.mMinHeight = std::numeric_limits::max(); data.mMaxHeight = -std::numeric_limits::max(); float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) + for (unsigned y = 0; y < LandRecordData::sLandSize; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + rowOffset += vhgt.mHeightData[y * LandRecordData::sLandSize]; - data.mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + data.mHeights[y * LandRecordData::sLandSize] = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE < data.mMinHeight) data.mMinHeight = rowOffset * HEIGHT_SCALE; float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) + for (unsigned x = 1; x < LandRecordData::sLandSize; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - data.mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + colOffset += vhgt.mHeightData[y * LandRecordData::sLandSize + x]; + data.mHeights[x + y * LandRecordData::sLandSize] = colOffset * HEIGHT_SCALE; if (colOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = colOffset * HEIGHT_SCALE; @@ -292,8 +304,6 @@ namespace ESM data.mMinHeight = colOffset * HEIGHT_SCALE; } } - data.mUnk1 = vhgt.mUnk1; - data.mUnk2 = vhgt.mUnk2; } } @@ -301,13 +311,13 @@ namespace ESM reader.skipHSub(); if (reader.isNextSub("VCLR")) - condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours, 3 * LAND_NUM_VERTS); + condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours); if (reader.isNextSub("VTEX")) { - uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) + uint16_t vtex[LandRecordData::sLandNumTextures]; + if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex)) { - transposeTextureData(vtex, data.mTextures); + transposeTextureData(vtex, data.mTextures.data()); } } } diff --git a/components/esm3/loadland.hpp b/components/esm3/loadland.hpp index 0d32407a5d..510f1790a8 100644 --- a/components/esm3/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -86,19 +86,9 @@ namespace ESM // total number of textures per land static constexpr int LAND_NUM_TEXTURES = LandRecordData::sLandNumTextures; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE = 81; + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE = 81; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; - -#pragma pack(push, 1) - struct VHGT - { - float mHeightOffset; - std::int8_t mHeightData[LAND_NUM_VERTS]; - std::uint16_t mUnk1; - std::uint8_t mUnk2; - }; -#pragma pack(pop) + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; using LandData = ESM::LandRecordData; @@ -128,16 +118,10 @@ namespace ESM const LandData* getLandData(int flags) const; /// Return land data without loading first anything. Can return a 0-pointer. - const LandData* getLandData() const - { - return mLandData.get(); - } + const LandData* getLandData() const { return mLandData.get(); } /// Return land data without loading first anything. Can return a 0-pointer. - LandData* getLandData() - { - return mLandData.get(); - } + LandData* getLandData() { return mLandData.get(); } /// \attention Must not be called on objects that aren't fully loaded. /// From 24913687cde83ecd6ee6b1b72dd8e8d0e77cfc0f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 09:15:12 +0300 Subject: [PATCH 1227/2167] Exterior cell naming corrections Use the ID for anonymous regions Try to use the name of the worldspace for ESM4 --- apps/openmw/mwbase/world.hpp | 2 -- apps/openmw/mwworld/worldimp.cpp | 39 ++++++++++++++++++-------------- apps/openmw/mwworld/worldimp.hpp | 1 - 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fe8b5cc13a..b800311eca 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -183,8 +183,6 @@ namespace MWBase /// generate a name. virtual std::string_view getCellName(const MWWorld::Cell& cell) const = 0; - virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - virtual void removeRefScript(const MWWorld::CellRef* ref) = 0; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0468b36a2f..ed61cf5bde 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -662,25 +663,29 @@ namespace MWWorld if (!cell.isExterior() || !cell.getDisplayName().empty()) return cell.getDisplayName(); - return ESM::visit(ESM::VisitOverload{ - [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, - [&](const ESM4::Cell& cellIn) -> std::string_view { - return mStore.get().find("sDefaultCellname")->mValue.getString(); - }, - }, - cell); - } - - std::string_view World::getCellName(const ESM::Cell* cell) const - { - if (cell) + if (!cell.getRegion().empty()) { - if (!cell->isExterior() || !cell->mName.empty()) - return cell->mName; - - if (const ESM::Region* region = mStore.get().search(cell->mRegion)) - return region->mName; + std::string_view regionName + = ESM::visit(ESM::VisitOverload{ + [&](const ESM::Cell& cellIn) -> std::string_view { + if (const ESM::Region* region = mStore.get().search(cell.getRegion())) + return !region->mName.empty() ? region->mName : region->mId.getRefIdString(); + return {}; + }, + [&](const ESM4::Cell& cellIn) -> std::string_view { return {}; }, + }, + cell); + if (!regionName.empty()) + return regionName; } + + if (!cell.getWorldSpace().empty() && ESM::isEsm4Ext(cell.getWorldSpace())) + { + if (const ESM4::World* worldspace = mStore.get().search(cell.getWorldSpace())) + if (!worldspace->mFullName.empty()) + return worldspace->mFullName; + } + return mStore.get().find("sDefaultCellname")->mValue.getString(); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4e36419e7f..b7db68214d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -273,7 +273,6 @@ namespace MWWorld /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. std::string_view getCellName(const MWWorld::Cell& cell) const override; - std::string_view getCellName(const ESM::Cell* cell) const override; void removeRefScript(const MWWorld::CellRef* ref) override; //< Remove the script attached to ref from mLocalScripts From c5c80936a0220ed41ca199eab881976968359d05 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 23 Mar 2024 13:27:53 -0500 Subject: [PATCH 1228/2167] Space after , --- .../data/integration_tests/test_lua_api/test.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 775f3ba499..53262dd168 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -68,23 +68,23 @@ end local function testMWScript() local variableStoreCount = 18 local variableStore = world.mwscript.getGlobalVariables(player) - testing.expectEqual(variableStoreCount,#variableStore) + testing.expectEqual(variableStoreCount, #variableStore) - variableStore.year = variableStoreCount - testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 5 + testing.expectEqual(5, variableStore.year) variableStore.year = 1 local indexCheck = 0 for index, value in ipairs(variableStore) do - testing.expectEqual(variableStore[index],value) + testing.expectEqual(variableStore[index], value) indexCheck = indexCheck + 1 end - testing.expectEqual(variableStoreCount,indexCheck) + testing.expectEqual(variableStoreCount, indexCheck) indexCheck = 0 for index, value in pairs(variableStore) do - testing.expectEqual(variableStore[index],value) + testing.expectEqual(variableStore[index], value) indexCheck = indexCheck + 1 end - testing.expectEqual(variableStoreCount,indexCheck) + testing.expectEqual(variableStoreCount, indexCheck) end local function initPlayer() From a4dd9224df6cb49ee9848ddf6284f21981ace72f Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sat, 23 Mar 2024 21:56:30 +0000 Subject: [PATCH 1229/2167] Restructure colormasks at higher level --- apps/openmw/mwrender/animation.cpp | 4 +--- apps/openmw/mwrender/npcanimation.cpp | 11 ++--------- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ apps/openmw/mwrender/sky.cpp | 1 + apps/openmw/mwrender/skyutil.cpp | 4 +--- components/resource/scenemanager.cpp | 8 ++++++++ components/resource/scenemanager.hpp | 2 ++ 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9cdbb19a98..6d4456699b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -1594,8 +1593,7 @@ namespace MWRender // Morrowind has a white ambient light attached to the root VFX node of the scenegraph node->getOrCreateStateSet()->setAttributeAndModes( getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) - node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); + mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(node->getOrCreateStateSet(), false); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 61260e687e..b9ad471bf5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -312,9 +312,8 @@ namespace MWRender class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: - DepthClearCallback(Resource::ResourceSystem* resourceSystem) + DepthClearCallback() { - mPassNormals = resourceSystem->getSceneManager()->getSupportsNormalsRT(); mDepth = new SceneUtil::AutoDepth; mDepth->setWriteMask(true); @@ -335,11 +334,6 @@ namespace MWRender unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); - if (mPassNormals) - { - state->get()->glColorMaski(1, true, true, true, true); - state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); - } glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); @@ -360,7 +354,6 @@ namespace MWRender state->checkGLErrors("after DepthClearCallback::drawImplementation"); } - bool mPassNormals; osg::ref_ptr mDepth; osg::ref_ptr mStateSet; }; @@ -409,7 +402,7 @@ namespace MWRender if (!prototypeAdded) { osg::ref_ptr depthClearBin(new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem)); + depthClearBin->setDrawCallback(new DepthClearCallback()); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 004b041336..acc8976219 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -550,6 +550,8 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); + resourceSystem->getSceneManager()->setUpNormalsRTForStateSet(sceneRoot->getOrCreateStateSet(), true); + mFog = std::make_unique(); mSky = std::make_unique( diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 231f90fd78..c75849d532 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -274,6 +274,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); + mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false); SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 3274b8c6b0..53baf36416 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -794,8 +793,7 @@ namespace MWRender // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); stateset->setAttributeAndModes(colormask); - if (sceneManager.getSupportsNormalsRT()) - stateset->setAttributeAndModes(new osg::ColorMaski(1, false, false, false, false)); + sceneManager.setUpNormalsRTForStateSet(stateset, false); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9ed72d5f05..45c84f093f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -511,6 +512,13 @@ namespace Resource return mCache->checkInObjectCache(VFS::Path::normalizeFilename(name), timeStamp); } + void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled) + { + if (!getSupportsNormalsRT()) + return; + stateset->setAttributeAndModes(new osg::ColorMaski(1, enabled, enabled, enabled, enabled)); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 12900441de..3ad8a24892 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -224,6 +224,8 @@ namespace Resource void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } + void setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled); + void setSoftParticles(bool enabled) { mSoftParticles = enabled; } bool getSoftParticles() const { return mSoftParticles; } From 0f7b4fc6e693b34ecfa5b3191cead42a717ac504 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 13:40:34 +0300 Subject: [PATCH 1230/2167] Consistently avoid null pointer dereferencing in postprocessor (#7587) --- CHANGELOG.md | 1 + apps/openmw/mwrender/postprocessor.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..1395c1cc40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind + Bug #7587: Quick load related crash Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 42b2e4e1ee..1c0702879d 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -346,7 +346,7 @@ namespace MWRender for (auto& technique : mTechniques) { - if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); @@ -564,7 +564,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique->isValid()) + if (!technique || !technique->isValid()) continue; if (technique->getGLSLVersion() > mGLSLVersion) @@ -718,7 +718,7 @@ namespace MWRender PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) { - if (technique->getLocked()) + if (!technique || technique->getLocked()) return Status_Error; auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); @@ -734,6 +734,9 @@ namespace MWRender bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const { + if (!technique) + return false; + if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) return false; @@ -815,7 +818,7 @@ namespace MWRender void PostProcessor::disableDynamicShaders() { for (auto& technique : mTechniques) - if (technique->getDynamic()) + if (technique && technique->getDynamic()) disableTechnique(technique); } From ba69e1737c46eb7aecb7e0dd09b9a9a76a463777 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 13:45:46 +0300 Subject: [PATCH 1231/2167] Use the right shader for 360-degree screenshots Doesn't fix #7720 --- apps/openmw/mwrender/screenshotmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index a23d242a15..f478229daa 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -243,7 +243,7 @@ namespace MWRender osg::ref_ptr stateset = quad->getOrCreateStateSet(); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); + stateset->setAttributeAndModes(shaderMgr.getProgram("s360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); From 6515fdd73fbd42f094b34300499d233a8955b6c6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 19:58:22 +0300 Subject: [PATCH 1232/2167] Handle zero length Lua storage files more gracefully (#7823) --- CHANGELOG.md | 1 + components/lua/storage.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..7f70ea9f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka + Bug #7823: Game crashes when launching it. Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index db81b6e172..063dbf0d10 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -239,8 +239,11 @@ namespace LuaUtil assert(mData.empty()); // Shouldn't be used before loading try { - Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) - << " bytes)"; + std::uintmax_t fileSize = std::filesystem::file_size(path); + Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << fileSize << " bytes)"; + if (fileSize == 0) + throw std::runtime_error("Storage file has zero length"); + std::ifstream fin(path, std::fstream::binary); std::string serializedData((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); sol::table data = deserialize(mLua, serializedData); @@ -253,7 +256,7 @@ namespace LuaUtil } catch (std::exception& e) { - Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what(); + Log(Debug::Error) << "Cannot read \"" << path << "\": " << e.what(); } } From c59d097ab2366c44a69b1380781876ba86c4e7db Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 24 Mar 2024 16:24:49 -0500 Subject: [PATCH 1233/2167] FIX(#7898): Limit scale for references TES3 values --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..4df27121bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,7 @@ Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save + Bug #7898: Editor: Invalid reference scales are allowed Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 0cd95b0ed2..53e0ba07cf 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -971,7 +971,7 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mScale = data.toFloat(); + record2.mScale = std::clamp(data.toFloat(), 0.5f, 2.0f); record.setModified(record2); } From 0e2f28156da92b9a6b959c614f88606b32b40fa6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Mar 2024 23:48:37 +0000 Subject: [PATCH 1234/2167] Restore logging of openmw.cfg paths in launcher Removed here https://gitlab.com/OpenMW/openmw/-/merge_requests/2650/diffs#be09c16519a3f26f4306b920c50e0e4215dffaee_329_328 --- apps/launcher/maindialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 178b254545..5424d4010b 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -351,6 +351,7 @@ bool Launcher::MainDialog::setupGameSettings() for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { + Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); if (!loadFile(path, &Config::GameSettings::readFile)) return false; } From 6d529835aeb2a7f7ae2f7e76ba5013f80e0c820a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 25 Mar 2024 13:46:23 +0000 Subject: [PATCH 1235/2167] Lua: Standardize record stores --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/corebindings.cpp | 3 +- apps/openmw/mwlua/factionbindings.cpp | 28 +-- apps/openmw/mwlua/factionbindings.hpp | 2 +- apps/openmw/mwlua/magicbindings.cpp | 57 ++---- apps/openmw/mwlua/recordstore.hpp | 63 ++++++ apps/openmw/mwlua/soundbindings.cpp | 20 +- apps/openmw/mwlua/stats.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 46 +---- files/lua_api/openmw/core.lua | 67 ++++--- files/lua_api/openmw/types.lua | 180 +++++++++++++++--- .../integration_tests/test_lua_api/test.lua | 41 ++++ 12 files changed, 329 insertions(+), 182 deletions(-) create mode 100644 apps/openmw/mwlua/recordstore.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e69bb5f240..f92e8a0bc1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,7 +63,7 @@ add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings - postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings + postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 8d8e97ed07..b212d4d01c 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -97,8 +97,7 @@ namespace MWLua api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); - initCoreFactionBindings(context); - api["factions"] = &MWBase::Environment::get().getESMStore()->get(); + api["factions"] = initCoreFactionBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index b606d1a6f9..83b9cfc5e8 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -1,4 +1,5 @@ #include "factionbindings.hpp" +#include "recordstore.hpp" #include #include @@ -32,10 +33,6 @@ namespace sol { }; template <> - struct is_automagical> : std::false_type - { - }; - template <> struct is_automagical> : std::false_type { }; @@ -43,27 +40,11 @@ namespace sol namespace MWLua { - using FactionStore = MWWorld::Store; - - void initCoreFactionBindings(const Context& context) + sol::table initCoreFactionBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); - sol::usertype factionStoreT = lua.new_usertype("ESM3_FactionStore"); - factionStoreT[sol::meta_function::to_string] = [](const FactionStore& store) { - return "ESM3_FactionStore{" + std::to_string(store.getSize()) + " factions}"; - }; - factionStoreT[sol::meta_function::length] = [](const FactionStore& store) { return store.getSize(); }; - factionStoreT[sol::meta_function::index] = sol::overload( - [](const FactionStore& store, size_t index) -> const ESM::Faction* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const FactionStore& store, std::string_view factionId) -> const ESM::Faction* { - return store.search(ESM::RefId::deserializeText(factionId)); - }); - factionStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - factionStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + sol::table factions(lua, sol::create); + addRecordFunctionBinding(factions, context); // Faction record auto factionT = lua.new_usertype("ESM3_Faction"); factionT[sol::meta_function::to_string] @@ -113,5 +94,6 @@ namespace MWLua res.add(rec.mAttribute2); return res; }); + return LuaUtil::makeReadOnly(factions); } } diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp index fe37133dbe..0dc06ceaf2 100644 --- a/apps/openmw/mwlua/factionbindings.hpp +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - void initCoreFactionBindings(const Context& context); + sol::table initCoreFactionBindings(const Context& context); } #endif // MWLUA_FACTIONBINDINGS_H diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 1e3cb2ab69..9dae9f085f 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -31,6 +31,7 @@ #include "luamanagerimp.hpp" #include "object.hpp" #include "objectvariant.hpp" +#include "recordstore.hpp" namespace MWLua { @@ -135,10 +136,6 @@ namespace MWLua namespace sol { - template - struct is_automagical> : std::false_type - { - }; template <> struct is_automagical : std::false_type { @@ -228,50 +225,18 @@ namespace MWLua } // Spell store - using SpellStore = MWWorld::Store; - const SpellStore* spellStore = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype spellStoreT = lua.new_usertype("ESM3_SpellStore"); - spellStoreT[sol::meta_function::to_string] - = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; - spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; - spellStoreT[sol::meta_function::index] = sol::overload( - [](const SpellStore& store, size_t index) -> const ESM::Spell* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { - return store.search(ESM::RefId::deserializeText(spellId)); - }); - spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["spells"] = spellStore; + sol::table spells(lua, sol::create); + addRecordFunctionBinding(spells, context); + magicApi["spells"] = LuaUtil::makeReadOnly(spells); // Enchantment store - using EnchantmentStore = MWWorld::Store; - const EnchantmentStore* enchantmentStore - = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype enchantmentStoreT = lua.new_usertype("ESM3_EnchantmentStore"); - enchantmentStoreT[sol::meta_function::to_string] = [](const EnchantmentStore& store) { - return "ESM3_EnchantmentStore{" + std::to_string(store.getSize()) + " enchantments}"; - }; - enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); }; - enchantmentStoreT[sol::meta_function::index] = sol::overload( - [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* { - return store.search(ESM::RefId::deserializeText(enchantmentId)); - }); - enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["enchantments"] = enchantmentStore; + sol::table enchantments(lua, sol::create); + addRecordFunctionBinding(enchantments, context); + magicApi["enchantments"] = LuaUtil::makeReadOnly(enchantments); // MagicEffect store + sol::table magicEffects(lua, sol::create); + magicApi["effects"] = LuaUtil::makeReadOnly(magicEffects); using MagicEffectStore = MWWorld::Store; const MagicEffectStore* magicEffectStore = &MWBase::Environment::get().getWorld()->getStore().get(); @@ -303,8 +268,10 @@ namespace MWLua }; magicEffectStoreT[sol::meta_function::pairs] = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; + magicEffectStoreT[sol::meta_function::ipairs] + = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; - magicApi["effects"] = magicEffectStore; + magicEffects["records"] = magicEffectStore; // Spell record auto spellT = lua.new_usertype("ESM3_Spell"); diff --git a/apps/openmw/mwlua/recordstore.hpp b/apps/openmw/mwlua/recordstore.hpp new file mode 100644 index 0000000000..3d04de396b --- /dev/null +++ b/apps/openmw/mwlua/recordstore.hpp @@ -0,0 +1,63 @@ +#ifndef MWLUA_RECORDSTORE_H +#define MWLUA_RECORDSTORE_H + +#include + +#include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwworld/store.hpp" + +#include "context.hpp" +#include "object.hpp" + +namespace sol +{ + // Ensure sol does not try to create the automatic Container or usertype bindings for Store. + // They include write operations and we want the store to be read-only. + template + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + template + void addRecordFunctionBinding( + sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) + { + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); + + table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, + [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); + + // Define a custom user type for the store. + // Provide the interface of a read-only array. + using StoreT = MWWorld::Store; + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); + storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { + return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; + }; + storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; + storeT[sol::meta_function::index] = sol::overload( + [](const StoreT& store, size_t index) -> const T* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); // Translate from Lua's 1-based indexing. + }, + [](const StoreT& store, std::string_view id) -> const T* { + return store.search(ESM::RefId::deserializeText(id)); + }); + storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + + // Provide access to the store. + table["records"] = &store; + } +} +#endif // MWLUA_RECORDSTORE_H diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index ad4a498153..11ad22873a 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -1,4 +1,5 @@ #include "soundbindings.hpp" +#include "recordstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -196,24 +197,7 @@ namespace MWLua }, []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }); - using SoundStore = MWWorld::Store; - sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); - soundStoreT[sol::meta_function::to_string] - = [](const SoundStore& store) { return "ESM3_SoundStore{" + std::to_string(store.getSize()) + " sounds}"; }; - soundStoreT[sol::meta_function::length] = [](const SoundStore& store) { return store.getSize(); }; - soundStoreT[sol::meta_function::index] = sol::overload( - [](const SoundStore& store, size_t index) -> const ESM::Sound* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SoundStore& store, std::string_view soundId) -> const ESM::Sound* { - return store.search(ESM::RefId::deserializeText(soundId)); - }); - soundStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - soundStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - api["sounds"] = &MWBase::Environment::get().getWorld()->getStore().get(); + addRecordFunctionBinding(api, context); // Sound record auto soundT = lua.new_usertype("ESM3_Sound"); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index eaa1f89d97..ad0f585207 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -22,7 +22,7 @@ #include "../mwworld/esmstore.hpp" #include "objectvariant.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace { diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index b52846508a..76bd2848e0 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -6,22 +6,8 @@ #include #include -#include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwworld/esmstore.hpp" -#include "apps/openmw/mwworld/store.hpp" - #include "../context.hpp" -#include "../object.hpp" - -namespace sol -{ - // Ensure sol does not try to create the automatic Container or usertype bindings for Store. - // They include write operations and we want the store to be read-only. - template - struct is_automagical> : std::false_type - { - }; -} +#include "../recordstore.hpp" namespace MWLua { @@ -68,36 +54,6 @@ namespace MWLua void addESM4DoorBindings(sol::table door, const Context& context); void addESM4TerminalBindings(sol::table term, const Context& context); - - template - void addRecordFunctionBinding( - sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) - { - const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); - - table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, - [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); - - // Define a custom user type for the store. - // Provide the interface of a read-only array. - using StoreT = MWWorld::Store; - sol::state_view& lua = context.mLua->sol(); - sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); - storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { - return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; - }; - storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; - storeT[sol::meta_function::index] = [](const StoreT& store, size_t index) -> const T* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); // Translate from Lua's 1-based indexing. - }; - storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - // Provide access to the store. - table["records"] = &store; - } } #endif // MWLUA_TYPES_H diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..86f95c4079 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -10,10 +10,6 @@ -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. See the actual value at the top of the page. -- @field [parent=#core] #number API_REVISION ---- --- A read-only list of all @{#FactionRecord}s in the world database. --- @field [parent=#core] #list<#FactionRecord> factions - --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit @@ -622,41 +618,52 @@ -- @field #number Curse Curse -- @field #number Power Power, can be used once a day +--- @{#Spells}: Spells +-- @field [parent=#Magic] #Spells spells --- List of all @{#Spell}s. --- @field [parent=#Magic] #list<#Spell> spells --- @usage local spell = core.magic.spells['thunder fist'] -- get by id --- @usage local spell = core.magic.spells[1] -- get by index +-- @field [parent=#Spells] #list<#Spell> records A read-only list of all @{#Spell} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #Spell. +-- @usage local spell = core.magic.spells.records['thunder fist'] -- get by id +-- @usage local spell = core.magic.spells.records[1] -- get by index -- @usage -- Print all powers --- for _, spell in pairs(core.magic.spells) do +-- for _, spell in pairs(core.magic.spells.records) do -- if spell.types == core.magic.SPELL_TYPE.Power then -- print(spell.name) -- end -- end +--- @{#Effects}: Magic Effects +-- @field [parent=#Magic] #Effects effects + --- Map from @{#MagicEffectId} to @{#MagicEffect} --- @field [parent=#Magic] #map<#number, #MagicEffect> effects +-- @field [parent=#Effects] #map<#number, #MagicEffect> records -- @usage -- Print all harmful effects --- for _, effect in pairs(core.magic.effects) do +-- for _, effect in pairs(core.magic.effects.records) do -- if effect.harmful then -- print(effect.name) -- end -- end -- @usage -- Look up the record of a specific effect and print its icon --- local mgef = core.magic.effects[core.magic.EFFECT_TYPE.Reflect] +-- local mgef = core.magic.effects.records[core.magic.EFFECT_TYPE.Reflect] -- print('Reflect Icon: '..tostring(mgef.icon)) ---- List of all @{#Enchantment}s. --- @field [parent=#Magic] #list<#Enchantment> enchantments --- @usage local enchantment = core.magic.enchantments['marara's boon'] -- get by id --- @usage local enchantment = core.magic.enchantments[1] -- get by index +--- @{#Enchantments}: Enchantments +-- @field [parent=#Magic] #Enchantments enchantments + +--- A read-only list of all @{#Enchantment} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) and [iterables#Map](iterables.html#map-iterable) of #Enchantment. +-- @field [parent=#Enchantments] #list<#Enchantment> records +-- @usage local enchantment = core.magic.enchantments.records['marara's boon'] -- get by id +-- @usage local enchantment = core.magic.enchantments.records[1] -- get by index -- @usage -- Print all enchantments with constant effect --- for _, ench in pairs(core.magic.enchantments) do +-- for _, ench in pairs(core.magic.enchantments.records) do -- if ench.type == core.magic.ENCHANTMENT_TYPE.ConstantEffect then -- print(ench.id) -- end -- end + --- -- @type Spell -- @field #string id Spell id @@ -827,11 +834,12 @@ -- @field #number maxRange Raw maximal range value, from 0 to 255 --- List of all @{#SoundRecord}s. --- @field [parent=#Sound] #list<#SoundRecord> sounds --- @usage local sound = core.sound.sounds['Ashstorm'] -- get by id --- @usage local sound = core.sound.sounds[1] -- get by index +-- @field [parent=#Sound] #list<#SoundRecord> records A read-only list of all @{#SoundRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SoundRecord. +-- @usage local sound = core.sound.records['Ashstorm'] -- get by id +-- @usage local sound = core.sound.records[1] -- get by index -- @usage -- Print all sound files paths --- for _, sound in pairs(core.sound.sounds) do +-- for _, sound in pairs(core.sound.records) do -- print(sound.fileName) -- end @@ -844,7 +852,10 @@ --- `core.stats.Attribute` -- @type Attribute --- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database. +-- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #AttributeRecord. +-- @usage local record = core.stats.Attribute.records['example_recordid'] +-- @usage local record = core.stats.Attribute.records[1] --- -- Returns a read-only @{#AttributeRecord} @@ -857,7 +868,10 @@ --- `core.stats.Skill` -- @type Skill --- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database. +-- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SkillRecord. +-- @usage local record = core.stats.Skill.records['example_recordid'] +-- @usage local record = core.stats.Skill.records[1] --- -- Returns a read-only @{#SkillRecord} @@ -892,6 +906,15 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound +--- @{#Factions}: Factions +-- @field [parent=#core] #Factions factions + +--- +-- A read-only list of all @{#FactionRecord}s in the world database. +-- @field [parent=#Factions] #list<#FactionRecord> records +-- @usage local record = core.factions.records['example_recordid'] +-- @usage local record = core.factions.records[1] + --- -- Faction data record -- @type FactionRecord diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 60f3e79628..32b073783b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -718,7 +718,13 @@ -- @type Creature -- @extends #Actor -- @field #Actor baseType @{#Actor} --- @field #list<#CreatureRecord> records A read-only list of all @{#CreatureRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #CreatureRecord. +-- @field [parent=#Creature] #list<#CreatureRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is a creature. @@ -772,7 +778,13 @@ -- @extends #Actor -- @field #Actor baseType @{#Actor} -- @field [parent=#NPC] #NpcStats stats --- @field #list<#NpcRecord> records A read-only list of all @{#NpcRecord}s in the world database. + +--- +-- A read-only list of all @{#NpcRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #NpcRecord. +-- @field [parent=#NPC] #map<#NpcRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is an NPC or a Player. @@ -925,8 +937,11 @@ -- @field [parent=#NPC] #Classes classes --- --- A read-only list of all @{#ClassRecord}s in the world database. +-- A read-only list of all @{#ClassRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #ClassRecord. -- @field [parent=#Classes] #list<#ClassRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#ClassRecord} @@ -963,7 +978,10 @@ --- -- A read-only list of all @{#RaceRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RaceRecord. -- @field [parent=#Races] #list<#RaceRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#RaceRecord} @@ -1120,7 +1138,10 @@ --- -- A read-only list of all @{#BirthSignRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BirthSignRecord. -- @field [parent=#BirthSigns] #list<#BirthSignRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#BirthSignRecord} @@ -1152,7 +1173,6 @@ -- @type Armor -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ArmorRecord> records A read-only list of all @{#ArmorRecord}s in the world database. --- -- Whether the object is an Armor. @@ -1174,6 +1194,13 @@ -- @field #number LBracer -- @field #number RBracer +--- +-- A read-only list of all @{#ArmorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ArmorRecord. +-- @field [parent=#Armor] #list<#ArmorRecord> records +-- @usage local record = types.Armor.records['example_recordid'] +-- @usage local record = types.Armor.records[1] + --- @{#ArmorTYPE} -- @field [parent=#Armor] #ArmorTYPE TYPE @@ -1219,7 +1246,13 @@ -- @type Book -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#BookRecord> records A read-only list of all @{#BookRecord}s in the world database. + +--- +-- A read-only list of all @{#BookRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BookRecord. +-- @field [parent=#Book] #list<#BookRecord> records +-- @usage local record = types.Book.records['example_recordid'] +-- @usage local record = types.Book.records[1] --- -- Whether the object is a Book. @@ -1295,7 +1328,13 @@ -- @type Clothing -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ClothingRecord> records A read-only list of all @{#ClothingRecord}s in the world database. + +--- +-- A read-only list of all @{#ClothingRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ClothingRecord. +-- @field [parent=#Clothing] #list<#ClothingRecord> records +-- @usage local record = types.Clothing.records['example_recordid'] +-- @usage local record = types.Clothing.records[1] --- -- Whether the object is a Clothing. @@ -1361,7 +1400,13 @@ -- @type Ingredient -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#IngredientRecord> records A read-only list of all @{#IngredientRecord}s in the world database. + +--- +-- A read-only list of all @{#IngredientRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #IngredientRecord. +-- @field [parent=#Ingredient] #list<#IngredientRecord> records +-- @usage local record = types.Ingredient.records['example_recordid'] +-- @usage local record = types.Ingredient.records[1] --- -- Whether the object is an Ingredient. @@ -1448,7 +1493,13 @@ -- @type Light -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LightRecord> records A read-only list of all @{#LightRecord}s in the world database. + +--- +-- A read-only list of all @{#LightRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LightRecord. +-- @field [parent=#Light] #list<#LightRecord> records +-- @usage local record = types.Light.records['example_recordid'] +-- @usage local record = types.Light.records[1] --- -- Whether the object is a Light. @@ -1486,7 +1537,13 @@ -- @type Miscellaneous -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#MiscellaneousRecord> records A read-only list of all @{#MiscellaneousRecord}s in the world database. + +--- +-- A read-only list of all @{#MiscellaneousRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #MiscellaneousRecord. +-- @field [parent=#Miscellaneous] #list<#MiscellaneousRecord> records +-- @usage local record = types.Miscellaneous.records['example_recordid'] +-- @usage local record = types.Miscellaneous.records[1] --- -- Whether the object is a Miscellaneous. @@ -1537,7 +1594,13 @@ -- @type Potion -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#PotionRecord> records A read-only list of all @{#PotionRecord}s in the world database. + +--- +-- A read-only list of all @{#PotionRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #PotionRecord. +-- @field [parent=#Potion] #list<#PotionRecord> records +-- @usage local record = types.Potion.records['example_recordid'] +-- @usage local record = types.Potion.records[1] --- -- Whether the object is a Potion. @@ -1578,7 +1641,13 @@ -- @type Weapon -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#WeaponRecord> records A read-only list of all @{#WeaponRecord}s in the world database. + +--- +-- A read-only list of all @{#WeaponRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #WeaponRecord. +-- @field [parent=#Weapon] #list<#WeaponRecord> records +-- @usage local record = types.Weapon.records['example_recordid'] +-- @usage local record = types.Weapon.records[1] --- -- Whether the object is a Weapon. @@ -1650,7 +1719,14 @@ -- @type Apparatus -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ApparatusRecord> records A read-only list of all @{#ApparatusRecord}s in the world database. + + +--- +-- A read-only list of all @{#ApparatusRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ApparatusRecord. +-- @field [parent=#Apparatus] #list<#ApparatusRecord> records +-- @usage local record = types.Apparatus.records['example_recordid'] +-- @usage local record = types.Apparatus.records[1] --- -- Whether the object is an Apparatus. @@ -1693,7 +1769,13 @@ -- @type Lockpick -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LockpickRecord> records A read-only list of all @{#LockpickRecord}s in the world database. + +--- +-- A read-only list of all @{#LockpickRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LockpickRecord. +-- @field [parent=#Lockpick] #list<#LockpickRecord> records +-- @usage local record = types.Lockpick.records['example_recordid'] +-- @usage local record = types.Lockpick.records[1] --- -- Whether the object is a Lockpick. @@ -1726,7 +1808,13 @@ -- @type Probe -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ProbeRecord> records A read-only list of all @{#ProbeRecord}s in the world database. + +--- +-- A read-only list of all @{#ProbeRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ProbeRecord. +-- @field [parent=#Probe] #list<#ProbeRecord> records +-- @usage local record = types.Probe.records['example_recordid'] +-- @usage local record = types.Probe.records[1] --- -- Whether the object is a Probe. @@ -1759,7 +1847,13 @@ -- @type Repair -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#RepairRecord> records A read-only list of all @{#RepairRecord}s in the world database. + +--- +-- A read-only list of all @{#RepairRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RepairRecord. +-- @field [parent=#Repair] #list<#RepairRecord> records +-- @usage local record = types.Repair.records['example_recordid'] +-- @usage local record = types.Repair.records[1] --- -- Whether the object is a Repair. @@ -1790,7 +1884,13 @@ --- -- @type Activator --- @field #list<#ActivatorRecord> records A read-only list of all @{#ActivatorRecord}s in the world database. + +--- +-- A read-only list of all @{#ActivatorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ActivatorRecord. +-- @field [parent=#Activator] #list<#ActivatorRecord> records +-- @usage local record = types.Activator.records['example_recordid'] +-- @usage local record = types.Activator.records[1] --- -- Whether the object is an Activator. @@ -1827,7 +1927,13 @@ -- @type Container -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#ContainerRecord> records A read-only list of all @{#ContainerRecord}s in the world database. + +--- +-- A read-only list of all @{#ContainerRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ContainerRecord. +-- @field [parent=#Container] #list<#ContainerRecord> records +-- @usage local record = types.Container.records['example_recordid'] +-- @usage local record = types.Container.records[1] --- -- Container content. @@ -1882,7 +1988,13 @@ -- @type Door -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#DoorRecord> records A read-only list of all @{#DoorRecord}s in the world database. + +--- +-- A read-only list of all @{#DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #DoorRecord. +-- @field [parent=#Door] #list<#DoorRecord> records +-- @usage local record = types.Door.records['example_recordid'] +-- @usage local record = types.Door.records[1] --- -- Whether the object is a Door. @@ -1936,7 +2048,13 @@ --- -- @type Static --- @field #list<#StaticRecord> records A read-only list of all @{#StaticRecord}s in the world database. + +--- +-- A read-only list of all @{#StaticRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #StaticRecord. +-- @field [parent=#Static] #list<#StaticRecord> records +-- @usage local record = types.Static.records['example_recordid'] +-- @usage local record = types.Static.records[1] --- -- Whether the object is a Static. @@ -1961,7 +2079,13 @@ --- -- @type CreatureLevelledList --- @field #list<#CreatureLevelledListRecord> records A read-only list of all @{#CreatureLevelledListRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureLevelledListRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #CreatureLevelledListRecord. +-- @field [parent=#CreatureLevelledList] #list<#CreatureLevelledListRecord> records +-- @usage local record = types.CreatureLevelledList.records['example_recordid'] +-- @usage local record = types.CreatureLevelledList.records[1] --- -- Whether the object is a CreatureLevelledList. @@ -2045,7 +2169,13 @@ --- -- @type ESM4Terminal --- @field #list<#ESM4TerminalRecord> records A read-only list of all @{#ESM4TerminalRecord}s in the world database. + +--- +-- A read-only list of all @{#ESM4TerminalRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4TerminalRecord. +-- @field [parent=#ESM4Terminal] #list<#ESM4TerminalRecord> records +-- @usage local record = types.ESM4Terminal.records['example_recordid'] +-- @usage local record = types.ESM4Terminal.records[1] --- -- Whether the object is a ESM4Terminal. @@ -2110,9 +2240,11 @@ -- @return #ESM4DoorRecord --- --- Returns a read-only list of all @{#ESM4DoorRecord}s in the world database. --- @function [parent=#ESM4Door] records --- @return #list<#ESM4DoorRecord> +-- A read-only list of all @{#ESM4DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4DoorRecord. +-- @field [parent=#ESM4Door] #list<#ESM4DoorRecord> records +-- @usage local record = types.ESM4Door.records['example_recordid'] +-- @usage local record = types.ESM4Door.records[1] --- -- @type ESM4DoorRecord diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 53262dd168..863cdd0f57 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local types = require('openmw.types') local world = require('openmw.world') local function testTimers() @@ -87,6 +88,45 @@ local function testMWScript() testing.expectEqual(variableStoreCount, indexCheck) end +local function testRecordStore(store,storeName,skipPairs) + testing.expect(store.records) + local firstRecord = store.records[1] + if not firstRecord then return end + testing.expectEqual(firstRecord.id,store.records[firstRecord.id].id) + local status, _ = pcall(function() + for index, value in ipairs(store.records) do + if value.id == firstRecord.id then + testing.expectEqual(index,1,storeName) + break + end + end + end) + + testing.expectEqual(status,true,storeName) + +end + +local function testRecordStores() + for key, type in pairs(types) do + if type.records then + testRecordStore(type,key) + end + end + testRecordStore(core.magic.enchantments,"enchantments") + testRecordStore(core.magic.effects,"effects",true) + testRecordStore(core.magic.spells,"spells") + + testRecordStore(core.stats.Attribute,"Attribute") + testRecordStore(core.stats.Skill,"Skill") + + testRecordStore(core.sound,"sound") + testRecordStore(core.factions,"factions") + + testRecordStore(types.NPC.classes,"classes") + testRecordStore(types.NPC.races,"races") + testRecordStore(types.Player.birthSigns,"birthSigns") +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -124,6 +164,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'recordStores', testRecordStores}, {'mwscript', testMWScript}, } From 320d8ef01441c2a9f3e7cc2a93644f5206fcac28 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 25 Mar 2024 13:50:23 +0000 Subject: [PATCH 1236/2167] Spellcast related Lua API + spellcasting/activespell refactor --- apps/esmtool/record.cpp | 27 +- apps/opencs/model/tools/enchantmentcheck.cpp | 20 +- .../model/world/nestedcoladapterimp.hpp | 39 +-- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 3 +- apps/openmw/mwgui/spellcreationdialog.cpp | 13 +- apps/openmw/mwgui/spellmodel.cpp | 6 +- apps/openmw/mwgui/tooltips.cpp | 18 +- apps/openmw/mwgui/widgets.cpp | 34 +- apps/openmw/mwlua/magicbindings.cpp | 306 +++++++++++++++--- apps/openmw/mwlua/types/ingredient.cpp | 19 +- apps/openmw/mwlua/types/potion.cpp | 7 +- apps/openmw/mwmechanics/activespells.cpp | 139 +++++--- apps/openmw/mwmechanics/activespells.hpp | 25 +- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/aicast.cpp | 8 +- apps/openmw/mwmechanics/aicast.hpp | 4 +- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombataction.cpp | 12 +- apps/openmw/mwmechanics/alchemy.cpp | 12 +- apps/openmw/mwmechanics/autocalcspell.cpp | 20 +- apps/openmw/mwmechanics/character.cpp | 30 +- apps/openmw/mwmechanics/character.hpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 22 +- .../mwmechanics/mechanicsmanagerimp.cpp | 10 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 168 ++++------ apps/openmw/mwmechanics/spellcasting.hpp | 10 +- apps/openmw/mwmechanics/spelleffects.cpp | 47 ++- apps/openmw/mwmechanics/spellpriority.cpp | 24 +- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwmechanics/spellutil.cpp | 77 ++++- apps/openmw/mwmechanics/spellutil.hpp | 6 + apps/openmw/mwrender/animation.cpp | 7 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 27 +- apps/openmw/mwworld/esmstore.hpp | 5 +- apps/openmw/mwworld/magiceffects.cpp | 51 ++- apps/openmw/mwworld/projectilemanager.cpp | 15 +- apps/openmw/mwworld/worldimp.cpp | 18 +- apps/openmw_test_suite/esm3/testsaveload.cpp | 37 +-- components/esm3/activespells.cpp | 39 ++- components/esm3/activespells.hpp | 44 ++- components/esm3/effectlist.cpp | 27 +- components/esm3/effectlist.hpp | 13 +- components/esm3/formatversion.hpp | 3 +- files/lua_api/openmw/core.lua | 27 +- files/lua_api/openmw/types.lua | 50 ++- 55 files changed, 974 insertions(+), 527 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 912ad0d683..fd51560f80 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -180,22 +180,23 @@ namespace void printEffectList(const ESM::EffectList& effects) { int i = 0; - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID - << ")" << std::endl; - if (effect.mSkill != -1) - std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" + std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " (" + << effect.mData.mEffectID << ")" << std::endl; + if (effect.mData.mSkill != -1) + std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")" << std::endl; - if (effect.mAttribute != -1) - std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute - << ")" << std::endl; - std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; + if (effect.mData.mAttribute != -1) + std::cout << " Attribute: " << attributeLabel(effect.mData.mAttribute) << " (" + << (int)effect.mData.mAttribute << ")" << std::endl; + std::cout << " Range: " << rangeTypeLabel(effect.mData.mRange) << " (" << effect.mData.mRange << ")" + << std::endl; // Area is always zero if range type is "Self" - if (effect.mRange != ESM::RT_Self) - std::cout << " Area: " << effect.mArea << std::endl; - std::cout << " Duration: " << effect.mDuration << std::endl; - std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; + if (effect.mData.mRange != ESM::RT_Self) + std::cout << " Area: " << effect.mData.mArea << std::endl; + std::cout << " Duration: " << effect.mData.mDuration << std::endl; + std::cout << " Magnitude: " << effect.mData.mMagnMin << "-" << effect.mData.mMagnMax << std::endl; i++; } } diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp index d6cb22b738..48cee579be 100644 --- a/apps/opencs/model/tools/enchantmentcheck.cpp +++ b/apps/opencs/model/tools/enchantmentcheck.cpp @@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa } else { - std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); + std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) { const std::string number = std::to_string(i); // At the time of writing this effects, attributes and skills are hardcoded - if (effect->mEffectID < 0 || effect->mEffectID > 142) + if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > 142) { messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error); ++effect; continue; } - if (effect->mSkill < -1 || effect->mSkill > 26) + if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26) messages.add( id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mAttribute < -1 || effect->mAttribute > 7) + if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > 7) messages.add( id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mRange < 0 || effect->mRange > 2) + if (effect->mData.mRange < 0 || effect->mData.mRange > 2) messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mArea < 0) + if (effect->mData.mArea < 0) messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mDuration < 0) + if (effect->mData.mDuration < 0) messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin < 0) + if (effect->mData.mMagnMin < 0) messages.add( id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMax < 0) + if (effect->mData.mMagnMax < 0) messages.add( id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin > effect->mMagnMax) + if (effect->mData.mMagnMin > effect->mData.mMagnMax) messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", CSMDoc::Message::Severity_Error); ++effect; diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 46928973fe..e18cda9611 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -255,20 +255,22 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; // blank row - ESM::ENAMstruct effect; - effect.mEffectID = 0; - effect.mSkill = -1; - effect.mAttribute = -1; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mIndex = position; + effect.mData.mEffectID = 0; + effect.mData.mSkill = -1; + effect.mData.mAttribute = -1; + effect.mData.mRange = 0; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; effectsList.insert(effectsList.begin() + position, effect); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -277,12 +279,13 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (rowToRemove < 0 || rowToRemove >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); effectsList.erase(effectsList.begin() + rowToRemove); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -292,7 +295,7 @@ namespace CSMWorld ESXRecordT magic = record.get(); magic.mEffects.mList - = static_cast>&>(nestedTable).mNestedTable; + = static_cast>&>(nestedTable).mNestedTable; record.setModified(magic); } @@ -300,19 +303,19 @@ namespace CSMWorld NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mEffects.mList); + return new NestedTableWrapper>(record.get().mEffects.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -374,12 +377,12 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -438,7 +441,7 @@ namespace CSMWorld throw std::runtime_error("Magic Effects subcolumn index out of range"); } - magic.mEffects.mList[subRowIndex] = effect; + magic.mEffects.mList[subRowIndex].mData = effect; record.setModified(magic); } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index c8e353acc9..37586ed33a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -265,7 +265,7 @@ namespace MWBase virtual bool isReadyToBlock(const MWWorld::Ptr& ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const = 0; - virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) = 0; + virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) = 0; virtual void processChangedSettings(const std::set>& settings) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fe8b5cc13a..85e83b7f8a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -463,7 +463,7 @@ namespace MWBase */ virtual MWWorld::SpellCastState startSpellCast(const MWWorld::Ptr& actor) = 0; - virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0; + virtual void castSpell(const MWWorld::Ptr& actor, bool scriptedSpell = false) = 0; virtual void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, ESM::RefNum item) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 7509fbf71f..c5cdc60cec 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -136,7 +136,7 @@ namespace MWClass const ESM::MagicEffect* effect = store.get().find(ESM::MagicEffect::Telekinesis); animation->addSpellCastGlow( - effect, 1); // 1 second glow to match the time taken for a door opening or closing + effect->getColor(), 1); // 1 second glow to match the time taken for a door opening or closing } } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 8264dd60b6..af4a3e8ce3 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -273,7 +273,7 @@ namespace MWGui void EnchantingDialog::notifyEffectsChanged() { - mEffectList.mList = mEffects; + mEffectList.populate(mEffects); mEnchanting.setEffect(mEffectList); updateLabels(); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1c8aad5447..0ee341c5c2 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -427,7 +427,7 @@ namespace MWGui { // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - spell->mEffects.mList.front().mEffectID); + spell->mEffects.mList.front().mData.mEffectID); std::string icon = effect->mIcon; std::replace(icon.begin(), icon.end(), '/', '\\'); size_t slashPos = icon.rfind('\\'); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 204fa00492..df7236242f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -299,7 +299,8 @@ namespace MWGui mSelected->button->setUserString("Spell", spellId.serialize()); // use the icon of the first effect - const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* effect + = esmStore.get().find(spell->mEffects.mList.front().mData.mEffectID); std::string path = effect->mIcon; std::replace(path.begin(), path.end(), '/', '\\'); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index d668db1dec..d8302df87c 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -470,9 +470,7 @@ namespace MWGui y *= 1.5; } - ESM::EffectList effectList; - effectList.mList = mEffects; - mSpell.mEffects = std::move(effectList); + mSpell.mEffects.populate(mEffects); mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; @@ -528,10 +526,11 @@ namespace MWGui if (spell->mData.mType != ESM::Spell::ST_Spell) continue; - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { + int16_t effectId = effectInfo.mData.mEffectID; const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(effectInfo.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effectId); // skip effects that do not allow spellmaking/enchanting int requiredFlags @@ -539,8 +538,8 @@ namespace MWGui if (!(effect->mData.mFlags & requiredFlags)) continue; - if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end()) - knownEffects.push_back(effectInfo.mEffectID); + if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) + knownEffects.push_back(effectId); } } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 385464da24..3d70c391c9 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,14 +48,14 @@ namespace MWGui for (const auto& effect : effects.mList) { - short effectId = effect.mEffectID; + short effectId = effect.mData.mEffectID; if (effectId != -1) { const ESM::MagicEffect* magicEffect = store.get().find(effectId); const ESM::Attribute* attribute - = store.get().search(ESM::Attribute::indexToRefId(effect.mAttribute)); - const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mSkill)); + = store.get().search(ESM::Attribute::indexToRefId(effect.mData.mAttribute)); + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mData.mSkill)); std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0e0c56c194..960a4a5a21 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -222,17 +222,17 @@ namespace MWGui = store->get().find(ESM::RefId::deserialize(focus->getUserString("Spell"))); info.caption = spell->mName; Widgets::SpellEffectList effects; - for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& spellEffect : spell->mEffects.mList) { Widgets::SpellEffectParams params; - params.mEffectID = spellEffect.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(spellEffect.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); - params.mDuration = spellEffect.mDuration; - params.mMagnMin = spellEffect.mMagnMin; - params.mMagnMax = spellEffect.mMagnMax; - params.mRange = spellEffect.mRange; - params.mArea = spellEffect.mArea; + params.mEffectID = spellEffect.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); + params.mDuration = spellEffect.mData.mDuration; + params.mMagnMin = spellEffect.mData.mMagnMin; + params.mMagnMax = spellEffect.mData.mMagnMax; + params.mRange = spellEffect.mData.mRange; + params.mArea = spellEffect.mData.mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index fea6d490c5..6cc5bdfdf5 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -195,18 +195,18 @@ namespace MWGui::Widgets const ESM::Spell* spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); @@ -308,17 +308,17 @@ namespace MWGui::Widgets SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) { SpellEffectList result; - for (const ESM::ENAMstruct& effectInfo : effects->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects->mList) { SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; + params.mArea = effectInfo.mData.mArea; result.push_back(params); } return result; diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 1e3cb2ab69..0e2d5217ae 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -13,12 +13,14 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/spellutil.hpp" @@ -144,7 +146,7 @@ namespace sol { }; template <> - struct is_automagical : std::false_type + struct is_automagical : std::false_type { }; template <> @@ -192,6 +194,26 @@ namespace MWLua return ESM::RefId::deserializeText(LuaUtil::cast(recordOrId)); } + static const ESM::Spell* toSpell(const sol::object& spellOrId) + { + if (spellOrId.is()) + return spellOrId.as(); + else + { + auto& store = MWBase::Environment::get().getWorld()->getStore(); + auto refId = ESM::RefId::deserializeText(LuaUtil::cast(spellOrId)); + return store.get().find(refId); + } + } + + static sol::table effectParamsListToTable(sol::state_view& lua, const std::vector& effects) + { + sol::table res(lua, sol::create); + for (size_t i = 0; i < effects.size(); ++i) + res[i + 1] = effects[i]; // ESM::IndexedENAMstruct (effect params) + return res; + } + sol::table initCoreMagicBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); @@ -314,12 +336,12 @@ namespace MWLua spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; }); spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; }); spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; }); - spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; - }); + spellT["alwaysSucceedFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Always); }); + spellT["autocalcFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Autocalc); }); + spellT["effects"] = sol::readonly_property( + [&lua](const ESM::Spell& rec) -> sol::table { return effectParamsListToTable(lua, rec.mEffects.mList); }); // Enchantment record auto enchantT = lua.new_usertype("ESM3_Enchantment"); @@ -334,46 +356,49 @@ namespace MWLua enchantT["charge"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCharge; }); enchantT["effects"] = sol::readonly_property([&lua](const ESM::Enchantment& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; + return effectParamsListToTable(lua, rec.mEffects.mList); }); // Effect params - auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); - effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { - const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID); + auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); + effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::IndexedENAMstruct& params) { + const ESM::MagicEffect* const rec = magicEffectStore->find(params.mData.mEffectID); return "ESM3_EffectParams[" + ESM::MagicEffect::indexToGmstString(rec->mIndex) + "]"; }; - effectParamsT["effect"] - = sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* { - return magicEffectStore->find(params.mEffectID); - }); + effectParamsT["effect"] = sol::readonly_property( + [magicEffectStore](const ESM::IndexedENAMstruct& params) -> const ESM::MagicEffect* { + return magicEffectStore->find(params.mData.mEffectID); + }); + effectParamsT["id"] = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> std::string { + auto name = ESM::MagicEffect::indexToName(params.mData.mEffectID); + return Misc::StringUtils::lowerCase(name); + }); effectParamsT["affectedSkill"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Skill::indexToRefId(params.mSkill); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["affectedAttribute"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Attribute::indexToRefId(params.mAttribute); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Attribute::indexToRefId(params.mData.mAttribute); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["range"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mRange; }); effectParamsT["area"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mArea; }); effectParamsT["magnitudeMin"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMin; }); effectParamsT["magnitudeMax"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; }); - effectParamsT["duration"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mDuration; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMax; }); + effectParamsT["duration"] = sol::readonly_property( + [](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mDuration; }); + effectParamsT["index"] + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mIndex; }); // MagicEffect record auto magicEffectT = context.mLua->sol().new_usertype("ESM3_MagicEffect"); @@ -394,12 +419,22 @@ namespace MWLua magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool { return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; }); - magicEffectT["castingStatic"] = sol::readonly_property( + magicEffectT["areaSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mAreaSound.serializeText(); }); + magicEffectT["boltSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBoltSound.serializeText(); }); + magicEffectT["castSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mCastSound.serializeText(); }); + magicEffectT["hitSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mHitSound.serializeText(); }); + magicEffectT["areaStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); + magicEffectT["boltStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBolt.serializeText(); }); + magicEffectT["castStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); }); magicEffectT["hitStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); }); - magicEffectT["areaStatic"] = sol::readonly_property( - [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return MWBase::Environment::get() .getWorld() @@ -415,8 +450,20 @@ namespace MWLua magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f); }); + magicEffectT["hasDuration"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoDuration); }); + magicEffectT["hasMagnitude"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoMagnitude); }); + // TODO: Not self-explanatory. Needs either a better name or documentation. The description in + // loadmgef.hpp is uninformative. + magicEffectT["isAppliedOnce"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::AppliedOnce; }); magicEffectT["harmful"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; }); + magicEffectT["casterLinked"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::CasterLinked; }); + magicEffectT["nonRecastable"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::NonRecastable; }); // TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed? // magicEffectT["projectileSpeed"] @@ -430,6 +477,8 @@ namespace MWLua auto name = ESM::MagicEffect::indexToName(effect.mEffectId); return Misc::StringUtils::lowerCase(name); }); + activeSpellEffectT["index"] + = sol::readonly_property([](const ESM::ActiveEffect& effect) -> int { return effect.mEffectIndex; }); activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string { return MWMechanics::EffectKey(effect.mEffectId, effect.getSkillOrAttribute()).toString(); }); @@ -493,12 +542,13 @@ namespace MWLua auto activeSpellT = context.mLua->sol().new_usertype("ActiveSpellParams"); activeSpellT[sol::meta_function::to_string] = [](const ActiveSpell& activeSpell) { - return "ActiveSpellParams[" + activeSpell.mParams.getId().serializeText() + "]"; + return "ActiveSpellParams[" + activeSpell.mParams.getSourceSpellId().serializeText() + "]"; }; activeSpellT["name"] = sol::readonly_property( [](const ActiveSpell& activeSpell) -> std::string_view { return activeSpell.mParams.getDisplayName(); }); - activeSpellT["id"] = sol::readonly_property( - [](const ActiveSpell& activeSpell) -> std::string { return activeSpell.mParams.getId().serializeText(); }); + activeSpellT["id"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getSourceSpellId().serializeText(); + }); activeSpellT["item"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object { auto item = activeSpell.mParams.getItem(); if (!item.isSet()) @@ -535,6 +585,21 @@ namespace MWLua } return res; }); + activeSpellT["fromEquipment"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Equipment); + }); + activeSpellT["temporary"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Temporary); + }); + activeSpellT["affectsBaseValues"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); + }); + activeSpellT["stackable"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Stackable); + }); + activeSpellT["activeSpellId"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getActiveSpellId().serializeText(); + }); auto activeEffectT = context.mLua->sol().new_usertype("ActiveEffect"); @@ -573,6 +638,78 @@ namespace MWLua return LuaUtil::makeReadOnly(magicApi); } + static std::pair> getNameAndMagicEffects( + const MWWorld::Ptr& actor, ESM::RefId id, const sol::table& effects, bool quiet) + { + std::vector effectIndexes; + + for (const auto& entry : effects) + { + if (entry.second.is()) + effectIndexes.push_back(entry.second.as()); + else if (entry.second.is()) + throw std::runtime_error("Error: Adding effects as enam structs is not implemented, use indexes."); + else + throw std::runtime_error("Unexpected entry in 'effects' table while trying to add to active effects"); + } + + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + + auto getEffectsFromIndexes = [&](const ESM::EffectList& effects) { + std::vector enams; + for (auto index : effectIndexes) + enams.push_back(effects.mList.at(index)); + return enams; + }; + + auto getNameAndEffects = [&](auto* record) { + return std::pair>( + record->mName, getEffectsFromIndexes(record->mEffects)); + }; + auto getNameAndEffectsEnch = [&](auto* record) { + auto* enchantment = esmStore.get().find(record->mEnchant); + return std::pair>( + record->mName, getEffectsFromIndexes(enchantment->mEffects)); + }; + switch (esmStore.find(id)) + { + case ESM::REC_ALCH: + return getNameAndEffects(esmStore.get().find(id)); + case ESM::REC_INGR: + { + // Ingredients are a special case as their effect list is calculated on consumption. + const ESM::Ingredient* ingredient = esmStore.get().find(id); + std::vector enams; + quiet = quiet || actor != MWMechanics::getPlayer(); + for (uint32_t i = 0; i < effectIndexes.size(); i++) + { + if (auto effect = MWMechanics::rollIngredientEffect(actor, ingredient, effectIndexes[i])) + enams.push_back(effect->mList[0]); + } + if (enams.empty() && !quiet) + { + // "X has no effect on you" + std::string message = esmStore.get().find("sNotifyMessage50")->mValue.getString(); + message = Misc::StringUtils::format(message, ingredient->mName); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + return { ingredient->mName, std::move(enams) }; + } + case ESM::REC_ARMO: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_BOOK: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_CLOT: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_WEAP: + return getNameAndEffectsEnch(esmStore.get().find(id)); + default: + // esmStore.find doesn't find REC_SPELs + case ESM::REC_SPEL: + return getNameAndEffects(esmStore.get().find(id)); + } + } + void addActorMagicBindings(sol::table& actor, const Context& context) { const MWWorld::Store* spellStore @@ -731,6 +868,16 @@ namespace MWLua }); }; + // types.Actor.spells(o):canUsePower() + spellsT["canUsePower"] = [](const ActorSpells& spells, const sol::object& spellOrId) -> bool { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + auto* spell = toSpell(spellOrId); + if (auto* store = spells.getStore()) + return store->canUsePower(spell); + return false; + }; + // pairs(types.Actor.activeSpells(o)) activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) { sol::state_view lua(ts); @@ -738,7 +885,7 @@ namespace MWLua return sol::as_function([lua, self]() mutable -> std::pair { if (!self.isEnd()) { - auto id = sol::make_object(lua, self.mIterator->getId().serializeText()); + auto id = sol::make_object(lua, self.mIterator->getSourceSpellId().serializeText()); auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator }); self.advance(); return { id, params }; @@ -762,14 +909,97 @@ namespace MWLua }; // types.Actor.activeSpells(o):remove(id) - activeSpellsT["remove"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) { + activeSpellsT["remove"] = [context](const ActorActiveSpells& spells, std::string_view idStr) { + if (spells.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + context.mLuaManager->addAction([spells = spells, id = ESM::RefId::deserializeText(idStr)]() { + if (auto* store = spells.getStore()) + { + auto it = store->getActiveSpellById(id); + if (it != store->end()) + { + if (it->hasFlag(ESM::ActiveSpells::Flag_Temporary)) + store->removeEffectsByActiveSpellId(spells.mActor.ptr(), id); + else + throw std::runtime_error("Can only remove temporary effects."); + } + } + }); + }; + + // types.Actor.activeSpells(o):add(id, spellid, effects, options) + activeSpellsT["add"] = [](const ActorActiveSpells& spells, const sol::table& options) { if (spells.isLObject()) throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); - auto id = toSpellId(spellOrId); if (auto* store = spells.getStore()) { - store->removeEffects(spells.mActor.ptr(), id); + ESM::RefId id = ESM::RefId::deserializeText(options.get("id")); + sol::optional item = options.get>("item"); + ESM::RefNum itemId; + if (item) + itemId = item->id(); + sol::optional caster = options.get>("caster"); + bool stackable = options.get_or("stackable", false); + bool ignoreReflect = options.get_or("ignoreReflect", false); + bool ignoreSpellAbsorption = options.get_or("ignoreSpellAbsorption", false); + bool ignoreResistances = options.get_or("ignoreResistances", false); + sol::table effects = options.get("effects"); + bool quiet = options.get_or("quiet", false); + if (effects.empty()) + throw std::runtime_error("Error: Parameter 'effects': cannot be an empty list/table"); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + auto [name, enams] = getNameAndMagicEffects(spells.mActor.ptr(), id, effects, quiet); + name = options.get_or("name", name); + + MWWorld::Ptr casterPtr; + if (caster) + casterPtr = caster->ptrOrEmpty(); + + bool affectsHealth = false; + MWMechanics::ActiveSpells::ActiveSpellParams params(casterPtr, id, name, itemId); + params.setFlag(ESM::ActiveSpells::Flag_Lua); + params.setFlag(ESM::ActiveSpells::Flag_Temporary); + if (stackable) + params.setFlag(ESM::ActiveSpells::Flag_Stackable); + + for (auto enam : enams) + { + const ESM::MagicEffect* mgef = esmStore.get().find(enam.mData.mEffectID); + MWMechanics::ActiveSpells::ActiveEffect effect; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if (ignoreReflect) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; + if (ignoreSpellAbsorption) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + if (ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + + bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; + + bool appliedOnce = mgef->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); + effect.mTimeLeft = effect.mDuration; + params.getEffects().emplace_back(effect); + + affectsHealth = affectsHealth || mgef->mData.mFlags & ESM::MagicEffect::Harmful + || effect.mEffectId == ESM::MagicEffect::RestoreHealth; + } + store->addSpell(params); + if (affectsHealth && casterPtr == MWMechanics::getPlayer()) + // If player is attempting to cast a harmful spell on or is healing a living target, show the + // target's HP bar. + // TODO: This should be moved to Lua once the HUD has been dehardcoded + MWBase::Environment::get().getWindowManager()->setEnemy(spells.mActor.ptr()); } }; diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index abfd2329ce..f7c3a8a050 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -47,15 +47,16 @@ namespace MWLua { if (rec.mData.mEffectID[i] < 0) continue; - ESM::ENAMstruct effect; - effect.mEffectID = rec.mData.mEffectID[i]; - effect.mSkill = rec.mData.mSkills[i]; - effect.mAttribute = rec.mData.mAttributes[i]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mData.mEffectID = rec.mData.mEffectID[i]; + effect.mData.mSkill = rec.mData.mSkills[i]; + effect.mData.mAttribute = rec.mData.mAttributes[i]; + effect.mData.mRange = ESM::RT_Self; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; + effect.mIndex = i; res[i + 1] = effect; } return res; diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 50aca6d9e7..d686bdb1f7 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -46,7 +46,10 @@ namespace size_t numEffects = effectsTable.size(); potion.mEffects.mList.resize(numEffects); for (size_t i = 0; i < numEffects; ++i) - potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + { + potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + } + potion.mEffects.updateIndexes(); } return potion; } @@ -83,7 +86,7 @@ namespace MWLua record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { sol::table res(context.mLua->sol(), sol::create); for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + res[i + 1] = rec.mEffects.mList[i]; // ESM::IndexedENAMstruct (effect params) return res; }); } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 937e7a6658..2fb7df61c1 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -49,16 +50,15 @@ namespace void addEffects( std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { - int currentEffectIndex = 0; for (const auto& enam : list.mList) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; - effect.mEffectIndex = currentEffectIndex++; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; if (ignoreResistances) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; @@ -82,12 +82,13 @@ namespace MWMechanics mActiveSpells.mIterating = false; } - ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) - : mId(cast.mId) - , mDisplayName(cast.mSourceName) + ActiveSpells::ActiveSpellParams::ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item) + : mSourceSpellId(id) + , mDisplayName(sourceName) , mCasterActorId(-1) - , mItem(cast.mItem) - , mType(cast.mType) + , mItem(item) + , mFlags() , mWorsenings(-1) { if (!caster.isEmpty() && caster.getClass().isActor()) @@ -96,48 +97,52 @@ namespace MWMechanics ActiveSpells::ActiveSpellParams::ActiveSpellParams( const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) - : mId(spell->mId) + : mSourceSpellId(spell->mId) , mDisplayName(spell->mName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability - : ESM::ActiveSpells::Type_Permanent) + , mFlags() , mWorsenings(-1) { assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + setFlag(ESM::ActiveSpells::Flag_SpellStore); + if (spell->mData.mType == ESM::Spell::ST_Ability) + setFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); addEffects(mEffects, spell->mEffects, ignoreResistances); } ActiveSpells::ActiveSpellParams::ActiveSpellParams( const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor) - : mId(item.getCellRef().getRefId()) + : mSourceSpellId(item.getCellRef().getRefId()) , mDisplayName(item.getClass().getName(item)) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(item.getCellRef().getRefNum()) - , mType(ESM::ActiveSpells::Type_Enchantment) + , mFlags() , mWorsenings(-1) { assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); addEffects(mEffects, enchantment->mEffects); + setFlag(ESM::ActiveSpells::Flag_Equipment); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) - : mId(params.mId) + : mActiveSpellId(params.mActiveSpellId) + , mSourceSpellId(params.mSourceSpellId) , mEffects(params.mEffects) , mDisplayName(params.mDisplayName) , mCasterActorId(params.mCasterActorId) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(params.mWorsenings) , mNextWorsening({ params.mNextWorsening }) { } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) - : mId(params.mId) + : mSourceSpellId(params.mSourceSpellId) , mDisplayName(params.mDisplayName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(-1) { } @@ -145,17 +150,23 @@ namespace MWMechanics ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const { ESM::ActiveSpells::ActiveSpellParams params; - params.mId = mId; + params.mActiveSpellId = mActiveSpellId; + params.mSourceSpellId = mSourceSpellId; params.mEffects = mEffects; params.mDisplayName = mDisplayName; params.mCasterActorId = mCasterActorId; params.mItem = mItem; - params.mType = mType; + params.mFlags = mFlags; params.mWorsenings = mWorsenings; params.mNextWorsening = mNextWorsening.toEsm(); return params; } + void ActiveSpells::ActiveSpellParams::setFlag(ESM::ActiveSpells::Flags flag) + { + mFlags = static_cast(mFlags | flag); + } + void ActiveSpells::ActiveSpellParams::worsen() { ++mWorsenings; @@ -178,21 +189,31 @@ namespace MWMechanics { // Enchantment id is not stored directly. Instead the enchanted item is stored. const auto& store = MWBase::Environment::get().getESMStore(); - switch (store->find(mId)) + switch (store->find(mSourceSpellId)) { case ESM::REC_ARMO: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_BOOK: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_CLOT: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_WEAP: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; default: return {}; } } + const ESM::Spell* ActiveSpells::ActiveSpellParams::getSpell() const + { + return MWBase::Environment::get().getESMStore()->get().search(getSourceSpellId()); + } + + bool ActiveSpells::ActiveSpellParams::hasFlag(ESM::ActiveSpells::Flags flags) const + { + return static_cast(mFlags & flags) == flags; + } + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) @@ -203,8 +224,7 @@ namespace MWMechanics // Erase no longer active spells and effects for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - if (spellIt->mType != ESM::ActiveSpells::Type_Temporary - && spellIt->mType != ESM::ActiveSpells::Type_Consumable) + if (!spellIt->hasFlag(ESM::ActiveSpells::Flag_Temporary)) { ++spellIt; continue; @@ -244,7 +264,10 @@ namespace MWMechanics { if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) + { mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } } bool updateSpellWindow = false; @@ -270,8 +293,8 @@ namespace MWMechanics if (std::find_if(mSpells.begin(), mSpells.end(), [&](const ActiveSpellParams& params) { return params.mItem == slot->getCellRef().getRefNum() - && params.mType == ESM::ActiveSpells::Type_Enchantment - && params.mId == slot->getCellRef().getRefId(); + && params.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && params.mSourceSpellId == slot->getCellRef().getRefId(); }) != mSpells.end()) continue; @@ -279,8 +302,8 @@ namespace MWMechanics // invisibility manually purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); - const ActiveSpellParams& params - = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); @@ -350,12 +373,11 @@ namespace MWMechanics continue; bool remove = false; - if (spellIt->mType == ESM::ActiveSpells::Type_Ability - || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) { try { - remove = !spells.hasSpell(spellIt->mId); + remove = !spells.hasSpell(spellIt->mSourceSpellId); } catch (const std::runtime_error& e) { @@ -363,9 +385,9 @@ namespace MWMechanics Log(Debug::Error) << "Removing active effect: " << e.what(); } } - else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment) + else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) { - // Remove constant effect enchantments that have been unequipped + // Remove effects tied to equipment that has been unequipped const auto& store = ptr.getClass().getInventoryStore(ptr); remove = true; for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) @@ -411,11 +433,11 @@ namespace MWMechanics void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - if (spell.mType != ESM::ActiveSpells::Type_Consumable) + if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable)) { auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) { - return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId - && spell.mItem == existing.mItem; + return spell.mSourceSpellId == existing.mSourceSpellId + && spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem; }); if (found != mSpells.end()) { @@ -428,6 +450,7 @@ namespace MWMechanics } } mSpells.emplace_back(spell); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); } ActiveSpells::ActiveSpells() @@ -445,10 +468,19 @@ namespace MWMechanics return mSpells.end(); } + ActiveSpells::TIterator ActiveSpells::getActiveSpellById(const ESM::RefId& id) + { + for (TIterator it = begin(); it != end(); it++) + if (it->getActiveSpellId() == id) + return it; + return end(); + } + bool ActiveSpells::isSpellActive(const ESM::RefId& id) const { - return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return spell.mId == id; }) - != mSpells.end(); + return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { + return spell.mSourceSpellId == id; + }) != mSpells.end(); } bool ActiveSpells::isEnchantmentActive(const ESM::RefId& id) const @@ -557,9 +589,14 @@ namespace MWMechanics return removedCurrentSpell; } - void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id) + void ActiveSpells::removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) { - purge([=](const ActiveSpellParams& params) { return params.mId == id; }, ptr); + purge([=](const ActiveSpellParams& params) { return params.mSourceSpellId == id; }, ptr); + } + + void ActiveSpells::removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) + { + purge([=](const ActiveSpellParams& params) { return params.mActiveSpellId == id; }, ptr); } void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg) @@ -604,19 +641,19 @@ namespace MWMechanics void ActiveSpells::readState(const ESM::ActiveSpells& state) { for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + { mSpells.emplace_back(ActiveSpellParams{ spell }); + // Generate ID for older saves that didn't have any. + if (mSpells.back().getActiveSpellId().empty()) + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) mQueue.emplace_back(ActiveSpellParams{ spell }); } void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) { - purge( - [](const auto& spell) { - return spell.getType() == ESM::ActiveSpells::Type_Consumable - || spell.getType() == ESM::ActiveSpells::Type_Temporary; - }, - ptr); + purge([](const auto& spell) { return spell.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, ptr); mQueue.clear(); } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index a505b8990a..e4fa60ddb6 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -33,12 +33,13 @@ namespace MWMechanics using ActiveEffect = ESM::ActiveEffect; class ActiveSpellParams { - ESM::RefId mId; + ESM::RefId mActiveSpellId; + ESM::RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType; + ESM::ActiveSpells::Flags mFlags; int mWorsenings; MWWorld::TimeStamp mNextWorsening; MWWorld::Ptr mSource; @@ -57,15 +58,17 @@ namespace MWMechanics friend class ActiveSpells; public: - ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); + ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item); - const ESM::RefId& getId() const { return mId; } + ESM::RefId getActiveSpellId() const { return mActiveSpellId; } + void setActiveSpellId(ESM::RefId id) { mActiveSpellId = id; } + + const ESM::RefId& getSourceSpellId() const { return mSourceSpellId; } const std::vector& getEffects() const { return mEffects; } std::vector& getEffects() { return mEffects; } - ESM::ActiveSpells::EffectType getType() const { return mType; } - int getCasterActorId() const { return mCasterActorId; } int getWorsenings() const { return mWorsenings; } @@ -75,6 +78,10 @@ namespace MWMechanics ESM::RefNum getItem() const { return mItem; } ESM::RefId getEnchantment() const; + const ESM::Spell* getSpell() const; + bool hasFlag(ESM::ActiveSpells::Flags flags) const; + void setFlag(ESM::ActiveSpells::Flags flags); + // Increments worsenings count and sets the next timestamp void worsen(); @@ -93,6 +100,8 @@ namespace MWMechanics TIterator end() const; + TIterator getActiveSpellById(const ESM::RefId& id); + void update(const MWWorld::Ptr& ptr, float duration); private: @@ -132,7 +141,9 @@ namespace MWMechanics void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id - void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id); + void removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); + /// Removes the active effects of a specific active spell + void removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); /// Remove all active effects with this effect id void purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg = {}); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 506bf80bc4..addf62df34 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1228,11 +1228,11 @@ namespace MWMechanics } } - void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) const + void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) - iter->second->getCharacterController().castSpell(spellId, manualSpell); + iter->second->getCharacterController().castSpell(spellId, scriptedSpell); } bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 14c55c4e45..2821df43e6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -67,7 +67,7 @@ namespace MWMechanics void resurrect(const MWWorld::Ptr& ptr) const; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) const; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) const; void updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const; ///< Updates an actor with a new Ptr diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 249ca97326..6384d70c06 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -25,11 +25,11 @@ namespace MWMechanics } } -MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell) +MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell) : mTargetId(targetId) , mSpellId(spellId) , mCasting(false) - , mManual(manualSpell) + , mScripted(scriptedSpell) , mDistance(getInitialDistance(spellId)) { } @@ -49,7 +49,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (target.isEmpty()) return true; - if (!mManual + if (!mScripted && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, characterController.getSupportedMovementDirections(), mDistance)) { @@ -85,7 +85,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (!mCasting) { - MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual); + MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mScripted); mCasting = true; return false; } diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 435458cc0f..649c5a4d34 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -15,7 +15,7 @@ namespace MWMechanics class AiCast final : public TypedAiPackage { public: - AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell = false); + AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell = false); bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; @@ -37,7 +37,7 @@ namespace MWMechanics const ESM::RefId mTargetId; const ESM::RefId mSpellId; bool mCasting; - const bool mManual; + const bool mScripted; const float mDistance; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0b3c2b8bd2..2399961a3a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -275,7 +275,7 @@ namespace MWMechanics if (!spellId.empty()) { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); - if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mData.mRange != ESM::RT_Target) canShout = false; } storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 563bd8b8cd..91d2a9bbb8 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -355,14 +355,14 @@ namespace MWMechanics { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(selectedSpellId); - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } @@ -375,14 +375,14 @@ namespace MWMechanics { const ESM::Enchantment* ench = MWBase::Environment::get().getESMStore()->get().find(enchId); - for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 3adb399483..4be48296a9 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -262,13 +262,13 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co for (size_t i = 0; i < iter->mEffects.mList.size(); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; + const ESM::IndexedENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (first.mData.mEffectID != second.mEffectID || first.mData.mArea != second.mArea + || first.mData.mRange != second.mRange || first.mData.mSkill != second.mSkill + || first.mData.mAttribute != second.mAttribute || first.mData.mMagnMin != second.mMagnMin + || first.mData.mMagnMax != second.mMagnMax || first.mData.mDuration != second.mDuration) { mismatch = true; break; @@ -324,7 +324,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mModel = "m\\misc_potion_" + std::string(meshes[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string(meshes[index]) + "_01.dds"; - newRecord.mEffects.mList = mEffects; + newRecord.mEffects.populate(mEffects); const ESM::Potion* record = getRecord(newRecord); if (!record) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index a2f6d479f1..5bab25fbe5 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -221,7 +221,7 @@ namespace MWMechanics for (const auto& spellEffect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mData.mEffectID); static const int iAutoSpellAttSkillMin = MWBase::Environment::get() .getESMStore() ->get() @@ -230,7 +230,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { - ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill); + ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); auto found = actorSkills.find(skill); if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -238,7 +238,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) { - ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); + ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); auto found = actorAttributes.find(attribute); if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -253,22 +253,22 @@ namespace MWMechanics { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float minChance = std::numeric_limits::max(); - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { - minMagn = effect.mMagnMin; - maxMagn = effect.mMagnMax; + minMagn = effect.mData.mMagnMin; + maxMagn = effect.mData.mMagnMax; } int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - duration = effect.mDuration; + duration = effect.mData.mDuration; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) duration = std::max(1, duration); @@ -281,10 +281,10 @@ namespace MWMechanics float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + x += 0.05 * std::max(1, effect.mData.mArea) * magicEffect->mData.mBaseCost; x *= fEffectCostMult; - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; float s = 0.f; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c0f6111a79..57e61e74aa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1155,8 +1155,8 @@ namespace MWMechanics else if (groupname == "spellcast" && action == mAttackType + " release") { if (mCanCast) - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); - mCastingManualSpell = false; + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingScriptedSpell); + mCastingScriptedSpell = false; mCanCast = false; } else if (groupname == "containeropen" && action == "loot") @@ -1526,9 +1526,9 @@ namespace MWMechanics bool isMagicItem = false; // Play hand VFX and allow castSpell use (assuming an animation is going to be played) if - // spellcasting is successful. Manual spellcasting bypasses restrictions. + // spellcasting is successful. Scripted spellcasting bypasses restrictions. MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success; - if (!mCastingManualSpell) + if (!mCastingScriptedSpell) spellCastResult = world->startSpellCast(mPtr); mCanCast = spellCastResult == MWWorld::SpellCastState::Success; @@ -1558,9 +1558,9 @@ namespace MWMechanics else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed) { world->breakInvisibility(mPtr); - MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell); + MWMechanics::CastSpell cast(mPtr, {}, false, mCastingScriptedSpell); - const std::vector* effects{ nullptr }; + const std::vector* effects{ nullptr }; const MWWorld::ESMStore& store = world->getStore(); if (isMagicItem) { @@ -1579,7 +1579,7 @@ namespace MWMechanics if (mCanCast) { const ESM::MagicEffect* effect = store.get().find( - effects->back().mEffectID); // use last effect of list for color of VFX_Hands + effects->back().mData.mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); @@ -1593,7 +1593,7 @@ namespace MWMechanics "", false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation - const ESM::ENAMstruct& firstEffect = effects->front(); + const ESM::ENAMstruct& firstEffect = effects->front().mData; std::string startKey; std::string stopKey; @@ -1602,9 +1602,9 @@ namespace MWMechanics startKey = "start"; stopKey = "stop"; if (mCanCast) - world->castSpell( - mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; + world->castSpell(mPtr, + mCastingScriptedSpell); // No "release" text key to use, so cast immediately + mCastingScriptedSpell = false; mCanCast = false; } else @@ -2735,7 +2735,7 @@ namespace MWMechanics // Make sure we canceled the current attack or spellcasting, // because we disabled attack animations anyway. mCanCast = false; - mCastingManualSpell = false; + mCastingScriptedSpell = false; setAttackingOrSpell(false); if (mUpperBodyState != UpperBodyState::None) mUpperBodyState = UpperBodyState::WeaponEquipped; @@ -2887,7 +2887,7 @@ namespace MWMechanics bool CharacterController::isCastingSpell() const { - return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; + return mCastingScriptedSpell || mUpperBodyState == UpperBodyState::Casting; } bool CharacterController::isReadyToBlock() const @@ -2941,10 +2941,10 @@ namespace MWMechanics mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); } - void CharacterController::castSpell(const ESM::RefId& spellId, bool manualSpell) + void CharacterController::castSpell(const ESM::RefId& spellId, bool scriptedSpell) { setAttackingOrSpell(true); - mCastingManualSpell = manualSpell; + mCastingScriptedSpell = scriptedSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 430635fff5..8ead23f659 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -192,7 +192,7 @@ namespace MWMechanics bool mCanCast{ false }; - bool mCastingManualSpell{ false }; + bool mCastingScriptedSpell{ false }; bool mIsMovingBackward{ false }; osg::Vec2f mSmoothedSpeed; @@ -312,7 +312,7 @@ namespace MWMechanics bool isAttackingOrSpell() const; void setVisibility(float visibility) const; - void castSpell(const ESM::RefId& spellId, bool manualSpell = false); + void castSpell(const ESM::RefId& spellId, bool scriptedSpell = false); void setAIAttackType(std::string_view attackType); static std::string_view getRandomAttackType(); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 1de55b0c8d..7d0007f9e3 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -198,13 +198,13 @@ namespace MWMechanics float enchantmentCost = 0.f; float cost = 0.f; - for (const ESM::ENAMstruct& effect : mEffectList.mList) + for (const ESM::IndexedENAMstruct& effect : mEffectList.mList) { - float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; - int magMin = std::max(1, effect.mMagnMin); - int magMax = std::max(1, effect.mMagnMax); - int area = std::max(1, effect.mArea); - float duration = static_cast(effect.mDuration); + float baseCost = (store.get().find(effect.mData.mEffectID))->mData.mBaseCost; + int magMin = std::max(1, effect.mData.mMagnMin); + int magMax = std::max(1, effect.mData.mMagnMax); + int area = std::max(1, effect.mData.mArea); + float duration = static_cast(effect.mData.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) duration = fEnchantmentConstantDurationMult; @@ -212,7 +212,7 @@ namespace MWMechanics cost = std::max(1.f, cost); - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) cost *= 1.5f; enchantmentCost += precise ? cost : std::floor(cost); @@ -244,13 +244,7 @@ namespace MWMechanics for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; - const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; - - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (iter->mEffects.mList[i] != toFind.mEffects.mList[i]) { mismatch = true; break; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9319d030b8..47fba46c75 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -261,10 +261,10 @@ namespace MWMechanics mObjects.addObject(ptr); } - void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) + void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) { if (ptr.getClass().isActor()) - mActors.castSpell(ptr, spellId, manualSpell); + mActors.castSpell(ptr, spellId, scriptedSpell); } void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive) @@ -1978,11 +1978,7 @@ namespace MWMechanics // Transforming removes all temporary effects actor.getClass().getCreatureStats(actor).getActiveSpells().purge( - [](const auto& params) { - return params.getType() == ESM::ActiveSpells::Type_Consumable - || params.getType() == ESM::ActiveSpells::Type_Temporary; - }, - actor); + [](const auto& params) { return params.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, actor); mActors.updateActor(actor, 0.f); if (werewolf) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 516b778f1e..bf94589309 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -202,7 +202,7 @@ namespace MWMechanics /// Is \a ptr casting spell or using weapon now? bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const override; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) override; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 721131dcb0..2fd250f8c1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -29,11 +29,11 @@ namespace MWMechanics { CastSpell::CastSpell( - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell) + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool scriptedSpell) : mCaster(caster) , mTarget(target) , mFromProjectile(fromProjectile) - , mManualSpell(manualSpell) + , mScriptedSpell(scriptedSpell) { } @@ -41,21 +41,21 @@ namespace MWMechanics const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const { const auto world = MWBase::Environment::get().getWorld(); - std::map> toApply; + std::map> toApply; int index = -1; - for (const ESM::ENAMstruct& effectInfo : effects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects.mList) { ++index; - const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mData.mEffectID); - if (effectInfo.mRange != rangeType - || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) + if (effectInfo.mData.mRange != rangeType + || (effectInfo.mData.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor - if (mFromProjectile && effectInfo.mArea <= 0) + if (mFromProjectile && effectInfo.mData.mArea <= 0) continue; // Don't play explosion for projectiles with 0-area effects - if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() + if (!mFromProjectile && effectInfo.mData.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore) && (mCaster.isEmpty() || mCaster.getClass().isActor())) continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from @@ -70,16 +70,16 @@ namespace MWMechanics const std::string& texture = effect->mParticle; - if (effectInfo.mArea <= 0) + if (effectInfo.mData.mArea <= 0) { - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) world->spawnEffect( Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); continue; } else world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, - static_cast(effectInfo.mArea * 2)); + static_cast(effectInfo.mData.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -95,7 +95,7 @@ namespace MWMechanics std::vector objects; static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( - mHitPosition, static_cast(effectInfo.mArea * unitsPerFoot), objects); + mHitPosition, static_cast(effectInfo.mData.mArea * unitsPerFoot), objects); for (const MWWorld::Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing @@ -104,13 +104,6 @@ namespace MWMechanics continue; auto& list = toApply[affected]; - while (list.size() < static_cast(index)) - { - // Insert dummy effects to preserve indices - auto& dummy = list.emplace_back(effectInfo); - dummy.mRange = ESM::RT_Self; - assert(dummy.mRange != rangeType); - } list.push_back(effectInfo); } } @@ -151,45 +144,34 @@ namespace MWMechanics void CastSpell::inflict( const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const { + bool targetIsDeadActor = false; const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) { - // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); if (stats.isDead() && stats.isDeathAnimationFinished()) - return; + targetIsDeadActor = true; } // If none of the effects need to apply, we can early-out bool found = false; bool containsRecastable = false; - std::vector magicEffects; - magicEffects.reserve(effects.mList.size()); const auto& store = MWBase::Environment::get().getESMStore()->get(); - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (effect.mRange == range) + if (effect.mData.mRange == range) { found = true; - const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID); - // caster needs to be an actor for linked effects (e.g. Absorb) - if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked - && (mCaster.isEmpty() || !mCaster.getClass().isActor())) - { - magicEffects.push_back(nullptr); - continue; - } + const ESM::MagicEffect* magicEffect = store.find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable)) containsRecastable = true; - magicEffects.push_back(magicEffect); } - else - magicEffects.push_back(nullptr); } if (!found) return; - ActiveSpells::ActiveSpellParams params(*this, mCaster); + ActiveSpells::ActiveSpellParams params(mCaster, mId, mSourceName, mItem); + params.setFlag(mFlags); bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer()); const ActiveSpells* targetSpells = nullptr; @@ -204,31 +186,32 @@ namespace MWMechanics return; } - for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size(); - ++currentEffectIndex) + for (auto& enam : effects.mList) { - const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex]; - if (enam.mRange != range) + if (enam.mData.mRange != range) continue; - - const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex]; + const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID); if (!magicEffect) continue; + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (mCaster.isEmpty() || !mCaster.getClass().isActor())) + continue; ActiveSpells::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mTimeLeft = 0.f; - effect.mEffectIndex = static_cast(currentEffectIndex); + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; - if (mManualSpell) + if (mScriptedSpell) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - effect.mDuration = hasDuration ? static_cast(enam.mDuration) : 1.f; + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; if (!appliedOnce) @@ -240,8 +223,8 @@ namespace MWMechanics params.getEffects().emplace_back(effect); bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful - || enam.mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth) + || enam.mData.mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != mCaster && targetIsActor && !targetIsDeadActor && effectAffectsHealth) { // If player is attempting to cast a harmful spell on or is healing a living target, show the target's // HP bar. @@ -262,7 +245,10 @@ namespace MWMechanics if (!params.getEffects().empty()) { if (targetIsActor) - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + { + if (!targetIsDeadActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + } else { // Apply effects instantly. We can ignore effect deletion since the entire params object gets @@ -336,7 +322,7 @@ namespace MWMechanics ESM::RefId school = ESM::Skill::Alteration; if (!enchantment->mEffects.mList.empty()) { - short effectId = enchantment->mEffects.mList.front().mEffectID; + short effectId = enchantment->mEffects.mList.front().mData.mEffectID; const ESM::MagicEffect* magicEffect = store->get().find(effectId); school = magicEffect->mData.mSchool; } @@ -387,7 +373,8 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); inflict(mCaster, potion->mEffects, ESM::RT_Self); @@ -403,7 +390,7 @@ namespace MWMechanics bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell) + if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mScriptedSpell) { school = getSpellSchool(spell, mCaster); @@ -438,7 +425,7 @@ namespace MWMechanics stats.getSpells().usePower(spell); } - if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) + if (!mScriptedSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success); // A non-actor doesn't play its spell cast effects from a character controller, so play them here @@ -458,62 +445,27 @@ namespace MWMechanics bool CastSpell::cast(const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); mSourceName = ingredient->mName; - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; + auto effect = rollIngredientEffect(mCaster, ingredient, mCaster != getPlayer()); - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const auto magicEffect = store.get().find(effect.mEffectID); - const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); - - float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) - + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() - + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) - * creatureStats.getFatigueTerm(); - - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::roll0to99(prng); - if (roll > x) + if (effect) + inflict(mCaster, *effect, ESM::RT_Self); + else { // "X has no effect on you" - std::string message = store.get().find("sNotifyMessage50")->mValue.getString(); + std::string message = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage50") + ->mValue.getString(); message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } - float magnitude = 0; - float y = roll / std::min(x, 100.f); - y *= 0.25f * x; - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - effect.mDuration = 1; - else - effect.mDuration = static_cast(y); - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); - else - magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); - magnitude = std::max(1.f, magnitude); - } - else - magnitude = 1; - - effect.mMagnMax = static_cast(magnitude); - effect.mMagnMin = static_cast(magnitude); - - ESM::EffectList effects; - effects.mList.push_back(effect); - - inflict(mCaster, effects, ESM::RT_Self); - return true; } @@ -527,14 +479,14 @@ namespace MWMechanics playSpellCastingEffects(spell->mEffects.mList); } - void CastSpell::playSpellCastingEffects(const std::vector& effects) const + void CastSpell::playSpellCastingEffects(const std::vector& effects) const { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; - for (const ESM::ENAMstruct& effectData : effects) + for (const ESM::IndexedENAMstruct& effectData : effects) { - const auto effect = store.get().find(effectData.mEffectID); + const auto effect = store.get().find(effectData.mData.mEffectID); const ESM::Static* castStatic; @@ -587,7 +539,7 @@ namespace MWMechanics } if (animation && !mCaster.getClass().isActor()) - animation->addSpellCastGlow(effect); + animation->addSpellCastGlow(effect->getColor()); addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 8f10066e04..23d3b80713 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -26,7 +26,7 @@ namespace MWMechanics MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty - void playSpellCastingEffects(const std::vector& effects) const; + void playSpellCastingEffects(const std::vector& effects) const; void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const; @@ -41,13 +41,13 @@ namespace MWMechanics false }; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) - bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, - // etc.) + bool mScriptedSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, + // etc.) ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary }; + ESM::ActiveSpells::Flags mFlags{ ESM::ActiveSpells::Flag_Temporary }; CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false, - const bool manualSpell = false); + const bool scriptedSpell = false); bool cast(const ESM::Spell* spell); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 8c415949f5..96044ebc5b 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -289,7 +289,7 @@ namespace animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); int spellCost = 0; - if (const ESM::Spell* spell = esmStore.get().search(spellParams.getId())) + if (const ESM::Spell* spell = esmStore.get().search(spellParams.getSourceSpellId())) { spellCost = MWMechanics::calcSpellCost(*spell); } @@ -314,8 +314,7 @@ namespace auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); // Apply reflect and spell absorption - if (target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment - && spellParams.getType() != ESM::ActiveSpells::Type_Permanent) + if (target != caster && spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { bool canReflect = !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) && !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) @@ -358,9 +357,8 @@ namespace // Apply resistances if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { - const ESM::Spell* spell = nullptr; - if (spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - spell = MWBase::Environment::get().getESMStore()->get().search(spellParams.getId()); + const ESM::Spell* spell + = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary) ? spellParams.getSpell() : nullptr; float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); if (magnitudeMult == 0) @@ -429,10 +427,9 @@ namespace MWMechanics // Dispel removes entire spells at once target.getClass().getCreatureStats(target).getActiveSpells().purge( [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { - if (params.getType() == ESM::ActiveSpells::Type_Temporary) + if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { - const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(params.getId()); + const ESM::Spell* spell = params.getSpell(); if (spell && spell->mData.mType == ESM::Spell::ST_Spell) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); @@ -645,7 +642,7 @@ namespace MWMechanics else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) index = 2; // Damage "Dynamic" abilities reduce the base value - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, index, -effect.mMagnitude); else { @@ -666,7 +663,7 @@ namespace MWMechanics else if (!godmode) { // Damage Skill abilities reduce base skill :todd: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); @@ -725,7 +722,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); else adjustDynamicStat( @@ -737,7 +734,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -757,7 +754,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifySkill: if (!target.getClass().isNpc()) invalid = true; - else if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + else if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { // Abilities affect base stats, but not for drain auto& npcStats = target.getClass().getNpcStats(target); @@ -922,7 +919,7 @@ namespace MWMechanics { MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude @@ -947,7 +944,7 @@ namespace MWMechanics MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() <= magnitude) { @@ -985,7 +982,7 @@ namespace MWMechanics return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); - if (spellParams.getType() != ESM::ActiveSpells::Type_Ability + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues) && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) { MagicApplicationResult::Type result @@ -998,10 +995,9 @@ namespace MWMechanics oldMagnitude = effect.mMagnitude; else { - if (spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) - playEffects(target, *magicEffect, - spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua)) + playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)); if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) @@ -1016,8 +1012,7 @@ namespace MWMechanics if (effect.mDuration != 0) { float mult = dt; - if (spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) mult = std::min(effect.mTimeLeft, dt); effect.mMagnitude *= mult; } @@ -1195,7 +1190,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); else adjustDynamicStat( @@ -1206,7 +1201,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -1222,7 +1217,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifySkill: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 776381e6b2..d1407c4cbd 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -34,7 +34,7 @@ namespace if (effectFilter == -1) { const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(it->getId()); + = MWBase::Environment::get().getESMStore()->get().search(it->getSourceSpellId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } @@ -67,7 +67,7 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->getId() != spellId) + if (it->getSourceSpellId() != spellId) continue; const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; @@ -85,7 +85,7 @@ namespace int actorId = caster.getClass().getCreatureStats(caster).getActorId(); const auto& active = target.getClass().getCreatureStats(target).getActiveSpells(); return std::find_if(active.begin(), active.end(), [&](const auto& spell) { - return spell.getCasterActorId() == actorId && spell.getId() == id; + return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id; }) != active.end(); } @@ -110,13 +110,13 @@ namespace MWMechanics int getRangeTypes(const ESM::EffectList& effects) { int types = 0; - for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (it->mRange == ESM::RT_Self) + if (effect.mData.mRange == ESM::RT_Self) types |= RangeTypes::Self; - else if (it->mRange == ESM::RT_Touch) + else if (effect.mData.mRange == ESM::RT_Touch) types |= RangeTypes::Touch; - else if (it->mRange == ESM::RT_Target) + else if (effect.mData.mRange == ESM::RT_Target) types |= RangeTypes::Target; } return types; @@ -735,12 +735,12 @@ namespace MWMechanics static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectRating = rateEffect(effect, actor, enemy); + float effectRating = rateEffect(effect.mData, actor, enemy); if (useSpellMult) { - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectRating *= fAIRangeMagicSpellMult; else effectRating *= fAIMagicSpellMult; @@ -760,10 +760,10 @@ namespace MWMechanics float mult = fAIMagicSpellMult; - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { if (!MWBase::Environment::get().getWorld()->isSwimming(enemy)) mult = fAIRangeMagicSpellMult; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 12da7cdde8..26f0ebe4a2 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -174,7 +174,7 @@ namespace MWMechanics { for (const auto& effectIt : spell->mEffects.mList) { - if (effectIt.mEffectID == ESM::MagicEffect::Corprus) + if (effectIt.mData.mEffectID == ESM::MagicEffect::Corprus) { return true; } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 671939cb00..022aaec262 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -23,13 +24,13 @@ namespace MWMechanics { float cost = 0; - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method)); + float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect.mData, nullptr, method)); // This is applied to the whole spell cost for each effect when // creating spells, but is only applied on the effect itself in TES:CS. - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectCost *= 1.5; cost += effectCost; @@ -158,25 +159,83 @@ namespace MWMechanics return potion.mData.mValue; } + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index) + { + if (index >= 4) + throw std::range_error("Index out of range"); + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[index]; + effect.mSkill = ingredient->mData.mSkills[index]; + effect.mAttribute = ingredient->mData.mAttributes[index]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + if (effect.mEffectID < 0) + return std::nullopt; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const auto magicEffect = store.get().find(effect.mEffectID); + const MWMechanics::CreatureStats& creatureStats = caster.getClass().getCreatureStats(caster); + + float x = (caster.getClass().getSkill(caster, ESM::Skill::Alchemy) + + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); + if (roll > x) + { + return std::nullopt; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25f * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = 1; + else + effect.mDuration = static_cast(y); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); + else + magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = static_cast(magnitude); + effect.mMagnMin = static_cast(magnitude); + + ESM::EffectList effects; + effects.mList.push_back({ effect, index }); + return effects; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); float lowestSkill = 0; - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { - float x = static_cast(effect.mDuration); + float x = static_cast(effect.mData.mDuration); const auto magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; - x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); - x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; - if (effect.mRange == ESM::RT_Target) + x *= 0.5f * (effect.mData.mMagnMin + effect.mData.mMagnMax); + x += effect.mData.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get() .getESMStore() diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index fa9b0c64b9..fb9d14c8a5 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -3,10 +3,14 @@ #include +#include + namespace ESM { + struct EffectList; struct ENAMstruct; struct Enchantment; + struct Ingredient; struct MagicEffect; struct Potion; struct Spell; @@ -36,6 +40,8 @@ namespace MWMechanics int getEnchantmentCharge(const ESM::Enchantment& enchantment); int getPotionValue(const ESM::Potion& potion); + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index = 0); /** * @param spell spell to cast diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9cdbb19a98..101d4a3ea3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1512,7 +1512,7 @@ namespace MWRender return mObjectRoot.get(); } - void Animation::addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration) + void Animation::addSpellCastGlow(const osg::Vec4f& color, float glowDuration) { if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) { @@ -1521,12 +1521,11 @@ namespace MWRender if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) { - mGlowUpdater->setColor(effect->getColor()); + mGlowUpdater->setColor(color); mGlowUpdater->setDuration(glowDuration); } else - mGlowUpdater - = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, effect->getColor(), glowDuration); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a2226a3054..22b7167a9c 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -345,7 +345,7 @@ namespace MWRender // Add a spell casting glow to an object. From measuring video taken from the original engine, // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. - void addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration = 1.5); + void addSpellCastGlow(const osg::Vec4f& color, float glowDuration = 1.5); virtual void updatePtr(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 4bc59e1524..064e90f114 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -560,7 +560,7 @@ namespace MWScript runtime.pop(); if (ptr.getClass().isActor()) - ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffectsBySourceSpellId(ptr, spellid); } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 932c290aaa..a1eec196eb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -532,7 +532,7 @@ namespace MWWorld return result; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( - enchantment->mEffects.mList.front().mEffectID); + enchantment->mEffects.mList.front().mData.mEffectID); if (!magicEffect) return result; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7ecaaa217d..ff3c73311e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -171,31 +171,31 @@ namespace auto iter = spell.mEffects.mList.begin(); while (iter != spell.mEffects.mList.end()) { - const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + const ESM::MagicEffect* mgef = magicEffects.search(iter->mData.mEffectID); if (!mgef) { Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId - << ": dropping invalid effect (index " << iter->mEffectID << ")"; + << ": dropping invalid effect (index " << iter->mData.mEffectID << ")"; iter = spell.mEffects.mList.erase(iter); changed = true; continue; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mData.mAttribute != -1) { - iter->mAttribute = -1; + iter->mData.mAttribute = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected attribute argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mData.mSkill != -1) { - iter->mSkill = -1; + iter->mData.mSkill = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected skill argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } @@ -742,7 +742,16 @@ namespace MWWorld case ESM::REC_DYNA: reader.getSubNameIs("COUN"); - reader.getHT(mDynamicCount); + if (reader.getFormatVersion() <= ESM::MaxActiveSpellTypeVersion) + { + uint32_t dynamicCount32 = 0; + reader.getHT(dynamicCount32); + mDynamicCount = dynamicCount32; + } + else + { + reader.getHT(mDynamicCount); + } return true; default: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 16062c97db..c6271a4428 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -162,7 +162,7 @@ namespace MWWorld std::vector mStores; std::vector mDynamicStores; - unsigned int mDynamicCount; + uint64_t mDynamicCount; mutable std::unordered_map> mSpellListCache; @@ -209,6 +209,7 @@ namespace MWWorld void clearDynamic(); void rebuildIdsIndex(); + ESM::RefId generateId() { return ESM::RefId::generated(mDynamicCount++); } void movePlayerRecord(); @@ -229,7 +230,7 @@ namespace MWWorld template const T* insert(const T& x) { - const ESM::RefId id = ESM::RefId::generated(mDynamicCount++); + const ESM::RefId id = generateId(); Store& store = getWritable(); if (store.search(id) != nullptr) diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 4ff0e60c46..ce83b3e18f 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -46,8 +46,8 @@ namespace MWWorld for (auto& effect : spell->mEffects.mList) { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + if (effect.mData.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mData.mAttribute] = oldStats.mWorsenings; } creatureStats.mCorprusSpells[id] = stats; } @@ -58,30 +58,30 @@ namespace MWWorld if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = spell->mName; params.mCasterActorId = creatureStats.mActorId; if (spell->mData.mType == ESM::Spell::ST_Ability) - params.mType = ESM::ActiveSpells::Type_Ability; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags; else - params.mType = ESM::ActiveSpells::Type_Permanent; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Permanent_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - int effectIndex = 0; for (const auto& enam : spell->mEffects.mList) { - if (oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + if (oldParams.mPurgedEffects.find(enam.mIndex) == oldParams.mPurgedEffects.end()) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = effectIndex; - auto rand = oldParams.mEffectRands.find(effectIndex); + effect.mEffectIndex = enam.mIndex; + auto rand = oldParams.mEffectRands.find(enam.mIndex); if (rand != oldParams.mEffectRands.end()) { - float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + float magnitude + = (enam.mData.mMagnMax - enam.mData.mMagnMin) * rand->second + enam.mData.mMagnMin; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; @@ -92,13 +92,12 @@ namespace MWWorld else { effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mFlags = ESM::ActiveEffect::Flag_None; } params.mEffects.emplace_back(effect); } - effectIndex++; } creatureStats.mActiveSpells.mSpells.emplace_back(params); } @@ -132,30 +131,28 @@ namespace MWWorld if (!enchantment) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = std::move(name); params.mCasterActorId = creatureStats.mActorId; - params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - for (std::size_t effectIndex = 0; - effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + for (const auto& enam : enchantment->mEffects.mList) { - const auto& enam = enchantment->mEffects.mList[effectIndex]; - auto [random, multiplier] = oldMagnitudes[effectIndex]; - float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + auto [random, multiplier] = oldMagnitudes[enam.mIndex]; + float magnitude = (enam.mData.mMagnMax - enam.mData.mMagnMin) * random + enam.mData.mMagnMin; magnitude *= multiplier; if (magnitude <= 0) continue; ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; + effect.mEffectId = enam.mData.mEffectID; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = static_cast(effectIndex); + effect.mEffectIndex = enam.mIndex; // Prevent recalculation of resistances and don't reflect or absorb the effect effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; @@ -172,7 +169,7 @@ namespace MWWorld { auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), - [&](const auto& params) { return params.mId == spell.first; }); + [&](const auto& params) { return params.mSourceSpellId == spell.first; }); if (it != creatureStats.mActiveSpells.mSpells.end()) { it->mNextWorsening = spell.second.mNextWorsening; @@ -188,7 +185,7 @@ namespace MWWorld continue; for (auto& params : creatureStats.mActiveSpells.mSpells) { - if (params.mId == key.mSourceId) + if (params.mSourceSpellId == key.mSourceId) { bool found = false; for (auto& effect : params.mEffects) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6fc515981b..d6715c5ad2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -79,18 +79,17 @@ namespace int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; - for (std::vector::const_iterator iter(effects->mList.begin()); iter != effects->mList.end(); - ++iter) + for (const ESM::IndexedENAMstruct& effect : effects->mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(iter->mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); // Speed of multi-effect projectiles should be the average of the constituent effects, // based on observation of the original engine. speed += magicEffect->mData.mSpeed; count++; - if (iter->mRange != ESM::RT_Target) + if (effect.mData.mRange != ESM::RT_Target) continue; if (magicEffect->mBolt.empty()) @@ -106,7 +105,7 @@ namespace ->get() .find(magicEffect->mData.mSchool) ->mSchool->mBoltSound); - projectileEffects.mList.push_back(*iter); + projectileEffects.mList.push_back(effect); } if (count != 0) @@ -117,7 +116,7 @@ namespace { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().find( - effects->mList.begin()->mEffectID); + effects->mList.begin()->mData.mEffectID); texture = magicEffect->mParticle; } @@ -136,10 +135,10 @@ namespace { // Calculate combined light diffuse color from magical effects osg::Vec4 lightDiffuseColor; - for (const ESM::ENAMstruct& enam : effects.mList) + for (const ESM::IndexedENAMstruct& enam : effects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(enam.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(enam.mData.mEffectID); lightDiffuseColor += magicEffect->getColor(); } int numberOfEffects = effects.mList.size(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0468b36a2f..9ca8a0d12a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2935,14 +2935,14 @@ namespace MWWorld return result; } - void World::castSpell(const Ptr& actor, bool manualSpell) + void World::castSpell(const Ptr& actor, bool scriptedSpell) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; // For scripted spells we should not use hit contact - if (manualSpell) + if (scriptedSpell) { if (!casterIsPlayer) { @@ -3010,7 +3010,7 @@ namespace MWWorld const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); - MWMechanics::CastSpell cast(actor, target, false, manualSpell); + MWMechanics::CastSpell cast(actor, target, false, scriptedSpell); cast.mHitPosition = hitPosition; if (!selectedSpell.empty()) @@ -3698,22 +3698,22 @@ namespace MWWorld void World::preloadEffects(const ESM::EffectList* effectList) { - for (const ESM::ENAMstruct& effectInfo : effectList->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effectList->mList) { - const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mData.mEffectID); - if (MWMechanics::isSummoningEffect(effectInfo.mEffectID)) + if (MWMechanics::isSummoningEffect(effectInfo.mData.mEffectID)) { preload(mWorldScene.get(), mStore, ESM::RefId::stringRefId("VFX_Summon_Start")); - preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID)); + preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mData.mEffectID)); } preload(mWorldScene.get(), mStore, effect->mCasting); preload(mWorldScene.get(), mStore, effect->mHit); - if (effectInfo.mArea > 0) + if (effectInfo.mData.mArea > 0) preload(mWorldScene.get(), mStore, effect->mArea); - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) preload(mWorldScene.get(), mStore, effect->mBolt); } } diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 6d5fdf1c14..2629442563 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -530,29 +530,30 @@ namespace ESM TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange) { EffectList record; - record.mList.emplace_back(ENAMstruct{ - .mEffectID = 1, - .mSkill = 2, - .mAttribute = 3, - .mRange = 4, - .mArea = 5, - .mDuration = 6, - .mMagnMin = 7, - .mMagnMax = 8, - }); + record.mList.emplace_back(IndexedENAMstruct{ { + .mEffectID = 1, + .mSkill = 2, + .mAttribute = 3, + .mRange = 4, + .mArea = 5, + .mDuration = 6, + .mMagnMin = 7, + .mMagnMax = 8, + }, + 0 }); EffectList result; saveAndLoadRecord(record, GetParam(), result); EXPECT_EQ(result.mList.size(), record.mList.size()); - EXPECT_EQ(result.mList[0].mEffectID, record.mList[0].mEffectID); - EXPECT_EQ(result.mList[0].mSkill, record.mList[0].mSkill); - EXPECT_EQ(result.mList[0].mAttribute, record.mList[0].mAttribute); - EXPECT_EQ(result.mList[0].mRange, record.mList[0].mRange); - EXPECT_EQ(result.mList[0].mArea, record.mList[0].mArea); - EXPECT_EQ(result.mList[0].mDuration, record.mList[0].mDuration); - EXPECT_EQ(result.mList[0].mMagnMin, record.mList[0].mMagnMin); - EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); + EXPECT_EQ(result.mList[0].mData.mEffectID, record.mList[0].mData.mEffectID); + EXPECT_EQ(result.mList[0].mData.mSkill, record.mList[0].mData.mSkill); + EXPECT_EQ(result.mList[0].mData.mAttribute, record.mList[0].mData.mAttribute); + EXPECT_EQ(result.mList[0].mData.mRange, record.mList[0].mData.mRange); + EXPECT_EQ(result.mList[0].mData.mArea, record.mList[0].mData.mArea); + EXPECT_EQ(result.mList[0].mData.mDuration, record.mList[0].mData.mDuration); + EXPECT_EQ(result.mList[0].mData.mMagnMin, record.mList[0].mData.mMagnMin); + EXPECT_EQ(result.mList[0].mData.mMagnMax, record.mList[0].mData.mMagnMax); } TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange) diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 8ce47b7719..c3d86c3ebf 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -93,11 +93,12 @@ namespace ESM { for (const auto& params : spells) { - esm.writeHNRefId(tag, params.mId); + esm.writeHNRefId(tag, params.mSourceSpellId); + esm.writeHNRefId("SPID", params.mActiveSpellId); esm.writeHNT("CAST", params.mCasterActorId); esm.writeHNString("DISP", params.mDisplayName); - esm.writeHNT("TYPE", params.mType); + esm.writeHNT("FLAG", params.mFlags); if (params.mItem.isSet()) esm.writeFormId(params.mItem, true, "ITEM"); if (params.mWorsenings >= 0) @@ -130,14 +131,42 @@ namespace ESM while (esm.isNextSub(tag)) { ActiveSpells::ActiveSpellParams params; - params.mId = esm.getRefId(); + params.mSourceSpellId = esm.getRefId(); + if (format > MaxActiveSpellTypeVersion) + params.mActiveSpellId = esm.getHNRefId("SPID"); esm.getHNT(params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString("DISP"); if (format <= MaxClearModifiersFormatVersion) - params.mType = ActiveSpells::Type_Temporary; + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; else { - esm.getHNT(params.mType, "TYPE"); + if (format <= MaxActiveSpellTypeVersion) + { + Compatibility::ActiveSpells::EffectType type; + esm.getHNT(type, "TYPE"); + switch (type) + { + case Compatibility::ActiveSpells::Type_Ability: + params.mFlags = Compatibility::ActiveSpells::Type_Ability_Flags; + break; + case Compatibility::ActiveSpells::Type_Consumable: + params.mFlags = Compatibility::ActiveSpells::Type_Consumable_Flags; + break; + case Compatibility::ActiveSpells::Type_Enchantment: + params.mFlags = Compatibility::ActiveSpells::Type_Enchantment_Flags; + break; + case Compatibility::ActiveSpells::Type_Permanent: + params.mFlags = Compatibility::ActiveSpells::Type_Permanent_Flags; + break; + case Compatibility::ActiveSpells::Type_Temporary: + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; + break; + } + } + else + { + esm.getHNT(params.mFlags, "FLAG"); + } if (esm.peekNextSub("ITEM")) { if (format <= MaxActiveSpellSlotIndexFormatVersion) diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index 0e4e01eda3..daec9fc515 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -46,23 +46,28 @@ namespace ESM // format 0, saved games only struct ActiveSpells { - enum EffectType + enum Flags : uint32_t { - Type_Temporary, - Type_Ability, - Type_Enchantment, - Type_Permanent, - Type_Consumable + Flag_Temporary = 1 << 0, //!< Effect will end automatically once its duration ends. + Flag_Equipment = 1 << 1, //!< Effect will end automatically if item is unequipped. + Flag_SpellStore = 1 << 2, //!< Effect will end automatically if removed from the actor's spell store. + Flag_AffectsBaseValues = 1 << 3, //!< Effects will affect base values instead of current values. + Flag_Stackable + = 1 << 4, //!< Effect can stack. If this flag is not set, spells from the same caster and item cannot stack. + Flag_Lua + = 1 << 5, //!< Effect was added via Lua. Should not do any vfx/sound as this is handled by Lua scripts. }; struct ActiveSpellParams { - RefId mId; + RefId mActiveSpellId; + RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int32_t mCasterActorId; RefNum mItem; - EffectType mType; + Flags mFlags; + bool mStackable; int32_t mWorsenings; TimeStamp mNextWorsening; }; @@ -73,6 +78,29 @@ namespace ESM void load(ESMReader& esm); void save(ESMWriter& esm) const; }; + + namespace Compatibility + { + namespace ActiveSpells + { + enum EffectType + { + Type_Temporary, + Type_Ability, + Type_Enchantment, + Type_Permanent, + Type_Consumable, + }; + + using Flags = ESM::ActiveSpells::Flags; + constexpr Flags Type_Temporary_Flags = Flags::Flag_Temporary; + constexpr Flags Type_Consumable_Flags = static_cast(Flags::Flag_Temporary | Flags::Flag_Stackable); + constexpr Flags Type_Permanent_Flags = Flags::Flag_SpellStore; + constexpr Flags Type_Ability_Flags + = static_cast(Flags::Flag_SpellStore | Flags::Flag_AffectsBaseValues); + constexpr Flags Type_Enchantment_Flags = Flags::Flag_Equipment; + } + } } #endif diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 4f21f47fa2..a71eccfb84 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -22,19 +22,40 @@ namespace ESM } } + void EffectList::populate(const std::vector& effects) + { + mList.clear(); + for (size_t i = 0; i < effects.size(); i++) + mList.push_back({ effects[i], static_cast(i) }); + } + + void EffectList::updateIndexes() + { + for (size_t i = 0; i < mList.size(); i++) + mList[i].mIndex = i; + } + void EffectList::add(ESMReader& esm) { ENAMstruct s; esm.getSubComposite(s); - mList.push_back(s); + mList.push_back({ s, static_cast(mList.size()) }); } void EffectList::save(ESMWriter& esm) const { - for (const ENAMstruct& enam : mList) + for (const IndexedENAMstruct& enam : mList) { - esm.writeNamedComposite("ENAM", enam); + esm.writeNamedComposite("ENAM", enam.mData); } } + bool IndexedENAMstruct::operator!=(const IndexedENAMstruct& rhs) const + { + return mData.mEffectID != rhs.mData.mEffectID || mData.mArea != rhs.mData.mArea + || mData.mRange != rhs.mData.mRange || mData.mSkill != rhs.mData.mSkill + || mData.mAttribute != rhs.mData.mAttribute || mData.mMagnMin != rhs.mData.mMagnMin + || mData.mMagnMax != rhs.mData.mMagnMax || mData.mDuration != rhs.mData.mDuration; + } + } // end namespace diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index de13797496..8eb347d6c8 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -26,10 +26,21 @@ namespace ESM int32_t mArea, mDuration, mMagnMin, mMagnMax; }; + struct IndexedENAMstruct + { + bool operator!=(const IndexedENAMstruct& rhs) const; + bool operator==(const IndexedENAMstruct& rhs) const { return !(this->operator!=(rhs)); } + ENAMstruct mData; + uint32_t mIndex; + }; + /// EffectList, ENAM subrecord struct EffectList { - std::vector mList; + std::vector mList; + + void populate(const std::vector& effects); + void updateIndexes(); /// Load one effect, assumes subrecord name was already read void add(ESMReader& esm); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index d90742a512..36e43728e2 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,7 +26,8 @@ namespace ESM inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion MaxOldCountFormatVersion = 30; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; + inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 32; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..7963853e2b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -343,6 +343,11 @@ -- @field #string id Record id of the spell or item used to cast the spell -- @field #GameObject item The enchanted item used to cast the spell, or nil if the spell was not cast from an enchanted item. Note that if the spell was cast for a single-use enchantment such as a scroll, this will be nil. -- @field #GameObject caster The caster object, or nil if the spell has no defined caster +-- @field #boolean fromEquipment If set, this spell is tied to an equipped item and can only be ended by unequipping the item. +-- @field #boolean temporary If set, this spell effect is temporary and should end on its own. Either after a single application or after its duration has run out. +-- @field #boolean affectsBaseValues If set, this spell affects the base values of affected stats, rather than modifying current values. +-- @field #boolean stackable If set, this spell can be applied multiple times. If not set, the same spell can only be applied once from the same source (where source is determined by caster + item). In vanilla rules, consumables are stackable while spells and enchantments are not. +-- @field #number activeSpellId A number uniquely identifying this active spell within the affected actor's list of active spells. -- @field #list<#ActiveSpellEffect> effects The active effects (@{#ActiveSpellEffect}) of this spell. --- @@ -373,7 +378,7 @@ -- @type Enchantment -- @field #string id Enchantment id -- @field #number type @{#EnchantmentType} --- @field #number autocalcFlag If set, the casting cost should be computer rather than reading the cost field +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field -- @field #number cost -- @field #number charge Charge capacity. Should not be confused with current charge. -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the enchantment @@ -664,6 +669,8 @@ -- @field #number type @{#SpellType} -- @field #number cost -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the spell +-- @field #boolean alwaysSucceedFlag If set, the spell should ignore skill checks and always succeed. +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field --- -- @type MagicEffect @@ -673,16 +680,28 @@ -- @field #string school Skill ID that is this effect's school -- @field #number baseCost -- @field openmw.util#Color color --- @field #boolean harmful +-- @field #boolean harmful If set, the effect is considered harmful and should elicit a hostile reaction from affected NPCs. -- @field #boolean continuousVfx Whether the magic effect's vfx should loop or not +-- @field #boolean hasDuration If set, the magic effect has a duration. As an example, divine intervention has no duration while fire damage does. +-- @field #boolean hasMagnitude If set, the magic effect depends on a magnitude. As an example, cure common disease has no magnitude while chameleon does. +-- @field #boolean isAppliedOnce If set, the magic effect is applied fully on cast, rather than being continuously applied over the effect's duration. For example, chameleon is applied once, while fire damage is continuously applied for the duration. +-- @field #boolean casterLinked If set, it is implied the magic effect links back to the caster in some way and should end immediately or never be applied if the caster dies or is not an actor. +-- @field #boolean nonRecastable If set, this effect cannot be re-applied until it has ended. This is used by bound equipment spells. -- @field #string particle Identifier of the particle texture --- @field #string castingStatic Identifier of the vfx static used for casting +-- @field #string castStatic Identifier of the vfx static used for casting -- @field #string hitStatic Identifier of the vfx static used on hit -- @field #string areaStatic Identifier of the vfx static used for AOE spells +-- @field #string boltStatic Identifier of the projectile vfx static used for ranged spells +-- @field #string castSound Identifier of the sound used for casting +-- @field #string hitSound Identifier of the sound used on hit +-- @field #string areaSound Identifier of the sound used for AOE spells +-- @field #string boltSound Identifier of the projectile sound used for ranged spells + --- -- @type MagicEffectWithParams -- @field #MagicEffect effect @{#MagicEffect} +-- @field #string id ID of the associated @{#MagicEffect} -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #number range @@ -690,6 +709,7 @@ -- @field #number magnitudeMin -- @field #number magnitudeMax -- @field #number duration +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- -- @type ActiveEffect @@ -701,6 +721,7 @@ -- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitudeBase -- @field #number magnitudeModifier +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- @{#Sound}: Sounds and Speech -- @field [parent=#core] #Sound sound diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 60f3e79628..64dab547f1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -302,17 +302,59 @@ -- end --- --- Get whether a specific spell is active on the actor. +-- Get whether any instance of the specific spell is active on the actor. -- @function [parent=#ActorActiveSpells] isSpellActive -- @param self --- @param #any recordOrId record or string record ID of the active spell's source. valid records are @{openmw.core#Spell}, @{openmw.core#Enchantment}, #IngredientRecord, or #PotionRecord +-- @param #any recordOrId A record or string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. -- @return true if spell is active, false otherwise --- --- Remove the given spell and all its effects from the given actor's active spells. +-- If true, the actor has not used this power in the last 24h. Will return true for powers the actor does not have. +-- @function [parent=#ActorActiveSpells] canUsePower +-- @param self +-- @param #any spellOrId A @{openmw.core#Spell} or string record id. + +--- +-- Remove an active spell based on active spell ID (see @{openmw.core#ActiveSpell.activeSpellId}). Can only be used in global scripts or on self. Can only be used to remove spells with the temporary flag set (see @{openmw.core#ActiveSpell.temporary}). -- @function [parent=#ActorActiveSpells] remove -- @param self --- @param #any spellOrId @{openmw.core#Spell} or string spell id +-- @param #any id Active spell ID. + +--- +-- Adds a new spell to the list of active spells (only in global scripts or on self). +-- Note that this does not play any related VFX or sounds. +-- @function [parent=#ActorActiveSpells] add +-- @param self +-- @param #table options A table of parameters. Must contain the following required parameters: +-- +-- * `id` - A string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. +-- * `effects` - A list of indexes of the effects to be applied. These indexes must be in range of the record's list of @{openmw.core#MagicEffectWithParams}. Note that for Ingredients, normal ingredient consumption rules will be applied to effects. +-- +-- And may contain the following optional parameters: +-- +-- * `name` - The name to show in the list of active effects in the UI. Default: Name of the record identified by the id. +-- * `ignoreResistances` - If true, resistances will be ignored. Default: false +-- * `ignoreSpellAbsorption` - If true, spell absorption will not be applied. Default: false. +-- * `ignoreReflect` - If true, reflects will not be applied. Default: false. +-- * `caster` - A game object that identifies the caster. Default: nil +-- * `item` - A game object that identifies the specific enchanted item instance used to cast the spell. Default: nil +-- * `stackable` - If true, the spell will be able to stack. If false, existing instances of spells with the same id from the same source (where source is caster + item) +-- * `quiet` - If true, no messages will be printed if the spell is an Ingredient and it had no effect. Always true if the target is not the player. +-- @usage +-- -- Adds the effect of the chameleon spell to the character +-- Actor.activeSpells(self):add({id = 'chameleon', effects = { 0 }}) +-- @usage +-- -- Adds the effect of a standard potion of intelligence, without consuming any potions from the character's inventory. +-- -- Note that stackable = true to let the effect stack like a potion should. +-- Actor.activeSpells(self):add({id = 'p_fortify_intelligence_s', effects = { 0 }, stackable = true}) +-- @usage +-- -- Adds the negative effect of Greef twice over, and renames it to Good Greef. +-- Actor.activeSpells(self):add({id = 'potion_comberry_brandy_01', effects = { 1, 1 }, stackable = true, name = 'Good Greef'}) +-- @usage +-- -- Has the same effect as if the actor ate a chokeweed. With the same variable effect based on skill / random chance. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 0 }, stackable = true, name = 'Chokeweed'}) +-- -- Same as above, but uses a different index. Note that if multiple indexes are used, the randomicity is applied separately for each effect. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 1 }, stackable = true, name = 'Chokeweed'}) --- -- Return the spells (@{#ActorSpells}) of the given actor. From 7897ff7ac9fa3f1f1acc0c7dd91f2f2ba22fb6ff Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Mon, 25 Mar 2024 21:01:46 +0000 Subject: [PATCH 1237/2167] Fix weapon sheathing for non-nif meshes --- apps/openmw/mwrender/actoranimation.cpp | 11 ++++------- apps/openmw/mwrender/actorutil.cpp | 12 ++++++++++++ apps/openmw/mwrender/actorutil.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 3 +-- components/sceneutil/visitor.cpp | 25 +++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7da29a1cf0..6e18fb51a1 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -35,6 +35,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/ptr.hpp" +#include "actorutil.hpp" #include "vismask.hpp" namespace MWRender @@ -144,8 +145,7 @@ namespace MWRender if (mesh.empty()) return mesh; - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); @@ -222,8 +222,7 @@ namespace MWRender std::string_view boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. @@ -340,14 +339,12 @@ namespace MWRender showHolsteredWeapons = false; std::string mesh = weapon->getClass().getCorrectedModel(*weapon); - std::string scabbardName = mesh; - std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use the weapon mesh as fallback. - scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); + const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 8da921e532..7739d5a6f6 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -37,4 +37,16 @@ namespace MWRender || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); } + + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix) + { + size_t dotPos = filename.rfind('.'); + + // No extension found; return the original filename with suffix appended + if (dotPos == std::string::npos) + return filename + suffix; + + // Insert the suffix before the dot (extension) and return the new filename + return filename.substr(0, dotPos) + suffix + filename.substr(dotPos); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index 3107bf0183..6a5ab12dea 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -8,6 +8,7 @@ namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); bool isDefaultActorSkeleton(std::string_view model); + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix); } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b9ad471bf5..721806d992 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -539,8 +539,7 @@ namespace MWRender if (mesh.empty()) return std::string(); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index aadbf35af8..ffcf12b167 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -21,13 +21,36 @@ namespace SceneUtil mFoundNode = &group; return true; } + + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses + // whitespace-separated names) + std::string nodeName = group.getName(); + std::replace(nodeName.begin(), nodeName.end(), '_', ' '); + if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) + { + mFoundNode = &group; + return true; + } return false; } void FindByClassVisitor::apply(osg::Node& node) { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) + { mFoundNodes.push_back(&node); + } + else + { + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses + // whitespace-separated names) + std::string nodeName = node.className(); + std::replace(nodeName.begin(), nodeName.end(), '_', ' '); + if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) + mFoundNodes.push_back(&node); + } traverse(node); } @@ -53,6 +76,8 @@ namespace SceneUtil if (trans.libraryName() == std::string_view("osgAnimation")) { std::string nodeName = trans.getName(); + + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses // whitespace-separated names) std::replace(nodeName.begin(), nodeName.end(), '_', ' '); From e0b11c14c28b30b810047fd7efddfcdfd652d429 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 26 Mar 2024 14:44:02 +0000 Subject: [PATCH 1238/2167] Remove unused member mStackable --- components/esm3/activespells.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index daec9fc515..19e1f53be5 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -67,7 +67,6 @@ namespace ESM int32_t mCasterActorId; RefNum mItem; Flags mFlags; - bool mStackable; int32_t mWorsenings; TimeStamp mNextWorsening; }; From 59334f694dbcac004e58a271f92729371861dec3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 26 Mar 2024 23:11:54 +0000 Subject: [PATCH 1239/2167] Don't forget to add path to UserRole --- apps/launcher/datafilespage.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 87667bda37..8e0d729afa 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -712,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) return; ui.directoryListWidget->addItem(rootPath); + auto row = ui.directoryListWidget->count() - 1; + auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(rootPath))); mNewDataDirs.push_back(rootPath); refreshDataFilesView(); return; @@ -741,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) const auto* dir = select.dirListWidget->item(i); if (dir->checkState() == Qt::Checked) { - ui.directoryListWidget->insertItem(selectedRow++, dir->text()); + ui.directoryListWidget->insertItem(selectedRow, dir->text()); + auto* item = ui.directoryListWidget->item(selectedRow); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(dir->text()))); mNewDataDirs.push_back(dir->text()); + ++selectedRow; } } From 2e6878633145a7dc6b6897a1110b9fb4817ce52e Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 27 Mar 2024 07:32:53 +0000 Subject: [PATCH 1240/2167] Fix(CS): Actually allow unlocking doors ( #7899 ) --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 31 +++++++++++++++++++++++++-- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 2 ++ components/esm3/cellref.cpp | 12 ++++++----- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3daecddc..e82ca284d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed + Bug #7899: Editor: Doors can't be unlocked Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 53e0ba07cf..cb263eb8bc 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1182,6 +1182,26 @@ namespace CSMWorld bool isUserEditable() const override { return true; } }; + template + struct IsLockedColumn : public Column + { + IsLockedColumn(int flags) + : Column(Columns::ColumnId_IsLocked, ColumnBase::Display_Boolean, flags) + { + } + + QVariant get(const Record& record) const override { return record.get().mIsLocked; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mIsLocked = data.toBool(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct LockLevelColumn : public Column { @@ -1190,7 +1210,12 @@ namespace CSMWorld { } - QVariant get(const Record& record) const override { return record.get().mLockLevel; } + QVariant get(const Record& record) const override + { + if (record.get().mIsLocked) + return record.get().mLockLevel; + return QVariant(); + } void set(Record& record, const QVariant& data) override { @@ -1212,7 +1237,9 @@ namespace CSMWorld QVariant get(const Record& record) const override { - return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + if (record.get().mIsLocked) + return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + return QVariant(); } void set(Record& record, const QVariant& data) override diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index f487266dbb..0f42b12508 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -59,6 +59,7 @@ namespace CSMWorld { ColumnId_StackCount, "Count" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, + { ColumnId_IsLocked, "Locked" }, { ColumnId_LockLevel, "Lock Level" }, { ColumnId_Key, "Key" }, { ColumnId_Trap, "Trap" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index f5a8e446a5..a691eada9e 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -351,6 +351,8 @@ namespace CSMWorld ColumnId_SoundProbability = 317, + ColumnId_IsLocked = 318, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1f8ff54e89..1bff00701a 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -599,6 +599,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 1, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 2, true)); + mRefs.addColumn(new IsLockedColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new LockLevelColumn); mRefs.addColumn(new KeyColumn); mRefs.addColumn(new TrapColumn); diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index ecba6f7f5e..97ccfb730a 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -230,12 +230,14 @@ namespace ESM if (!inInventory) { - int lockLevel = mLockLevel; - if (lockLevel == 0 && mIsLocked) - lockLevel = ZeroLock; - if (lockLevel != 0) + if (mIsLocked) + { + int lockLevel = mLockLevel; + if (lockLevel == 0) + lockLevel = ZeroLock; esm.writeHNT("FLTV", lockLevel); - esm.writeHNOCRefId("KNAM", mKey); + esm.writeHNOCRefId("KNAM", mKey); + } esm.writeHNOCRefId("TNAM", mTrap); } From f2dc25e214ad49b215f1cec9326a6bc3fe2ce951 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 27 Mar 2024 12:44:35 +0400 Subject: [PATCH 1241/2167] Optimize bitmap fonts loading --- components/fontloader/fontloader.cpp | 120 +++++++++++++-------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 64aa32310b..c84392c421 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -448,6 +448,63 @@ namespace Gui source->addAttribute("value", bitmapFilename); MyGUI::xml::ElementPtr codes = root->createChild("Codes"); + // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game + // fonts + std::multimap additional; // fallback glyph index, unicode + additional.emplace(156, 0x00A2); // cent sign + additional.emplace(89, 0x00A5); // yen sign + additional.emplace(221, 0x00A6); // broken bar + additional.emplace(99, 0x00A9); // copyright sign + additional.emplace(97, 0x00AA); // prima ordinal indicator + additional.emplace(60, 0x00AB); // double left-pointing angle quotation mark + additional.emplace(45, 0x00AD); // soft hyphen + additional.emplace(114, 0x00AE); // registered trademark symbol + additional.emplace(45, 0x00AF); // macron + additional.emplace(241, 0x00B1); // plus-minus sign + additional.emplace(50, 0x00B2); // superscript two + additional.emplace(51, 0x00B3); // superscript three + additional.emplace(44, 0x00B8); // cedilla + additional.emplace(49, 0x00B9); // superscript one + additional.emplace(111, 0x00BA); // primo ordinal indicator + additional.emplace(62, 0x00BB); // double right-pointing angle quotation mark + additional.emplace(63, 0x00BF); // inverted question mark + additional.emplace(65, 0x00C6); // latin capital ae ligature + additional.emplace(79, 0x00D8); // latin capital o with stroke + additional.emplace(97, 0x00E6); // latin small ae ligature + additional.emplace(111, 0x00F8); // latin small o with stroke + additional.emplace(79, 0x0152); // latin capital oe ligature + additional.emplace(111, 0x0153); // latin small oe ligature + additional.emplace(83, 0x015A); // latin capital s with caron + additional.emplace(115, 0x015B); // latin small s with caron + additional.emplace(89, 0x0178); // latin capital y with diaresis + additional.emplace(90, 0x017D); // latin capital z with caron + additional.emplace(122, 0x017E); // latin small z with caron + additional.emplace(102, 0x0192); // latin small f with hook + additional.emplace(94, 0x02C6); // circumflex modifier + additional.emplace(126, 0x02DC); // small tilde + additional.emplace(69, 0x0401); // cyrillic capital io (no diaeresis latin e is available) + additional.emplace(137, 0x0451); // cyrillic small io + additional.emplace(45, 0x2012); // figure dash + additional.emplace(45, 0x2013); // en dash + additional.emplace(45, 0x2014); // em dash + additional.emplace(39, 0x2018); // left single quotation mark + additional.emplace(39, 0x2019); // right single quotation mark + additional.emplace(44, 0x201A); // single low quotation mark + additional.emplace(39, 0x201B); // single high quotation mark (reversed) + additional.emplace(34, 0x201C); // left double quotation mark + additional.emplace(34, 0x201D); // right double quotation mark + additional.emplace(44, 0x201E); // double low quotation mark + additional.emplace(34, 0x201F); // double high quotation mark (reversed) + additional.emplace(43, 0x2020); // dagger + additional.emplace(216, 0x2021); // double dagger (note: this glyph is not available) + additional.emplace(46, 0x2026); // ellipsis + additional.emplace(37, 0x2030); // per mille sign + additional.emplace(60, 0x2039); // single left-pointing angle quotation mark + additional.emplace(62, 0x203A); // single right-pointing angle quotation mark + additional.emplace(101, 0x20AC); // euro sign + additional.emplace(84, 0x2122); // trademark sign + additional.emplace(45, 0x2212); // minus sign + for (int i = 0; i < 256; i++) { float x1 = data[i].top_left.x * width; @@ -470,69 +527,10 @@ namespace Gui code->addAttribute( "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); - // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game - // fonts - std::multimap additional; // fallback glyph index, unicode - additional.insert(std::make_pair(156, 0x00A2)); // cent sign - additional.insert(std::make_pair(89, 0x00A5)); // yen sign - additional.insert(std::make_pair(221, 0x00A6)); // broken bar - additional.insert(std::make_pair(99, 0x00A9)); // copyright sign - additional.insert(std::make_pair(97, 0x00AA)); // prima ordinal indicator - additional.insert(std::make_pair(60, 0x00AB)); // double left-pointing angle quotation mark - additional.insert(std::make_pair(45, 0x00AD)); // soft hyphen - additional.insert(std::make_pair(114, 0x00AE)); // registered trademark symbol - additional.insert(std::make_pair(45, 0x00AF)); // macron - additional.insert(std::make_pair(241, 0x00B1)); // plus-minus sign - additional.insert(std::make_pair(50, 0x00B2)); // superscript two - additional.insert(std::make_pair(51, 0x00B3)); // superscript three - additional.insert(std::make_pair(44, 0x00B8)); // cedilla - additional.insert(std::make_pair(49, 0x00B9)); // superscript one - additional.insert(std::make_pair(111, 0x00BA)); // primo ordinal indicator - additional.insert(std::make_pair(62, 0x00BB)); // double right-pointing angle quotation mark - additional.insert(std::make_pair(63, 0x00BF)); // inverted question mark - additional.insert(std::make_pair(65, 0x00C6)); // latin capital ae ligature - additional.insert(std::make_pair(79, 0x00D8)); // latin capital o with stroke - additional.insert(std::make_pair(97, 0x00E6)); // latin small ae ligature - additional.insert(std::make_pair(111, 0x00F8)); // latin small o with stroke - additional.insert(std::make_pair(79, 0x0152)); // latin capital oe ligature - additional.insert(std::make_pair(111, 0x0153)); // latin small oe ligature - additional.insert(std::make_pair(83, 0x015A)); // latin capital s with caron - additional.insert(std::make_pair(115, 0x015B)); // latin small s with caron - additional.insert(std::make_pair(89, 0x0178)); // latin capital y with diaresis - additional.insert(std::make_pair(90, 0x017D)); // latin capital z with caron - additional.insert(std::make_pair(122, 0x017E)); // latin small z with caron - additional.insert(std::make_pair(102, 0x0192)); // latin small f with hook - additional.insert(std::make_pair(94, 0x02C6)); // circumflex modifier - additional.insert(std::make_pair(126, 0x02DC)); // small tilde - additional.insert(std::make_pair(69, 0x0401)); // cyrillic capital io (no diaeresis latin e is available) - additional.insert(std::make_pair(137, 0x0451)); // cyrillic small io - additional.insert(std::make_pair(45, 0x2012)); // figure dash - additional.insert(std::make_pair(45, 0x2013)); // en dash - additional.insert(std::make_pair(45, 0x2014)); // em dash - additional.insert(std::make_pair(39, 0x2018)); // left single quotation mark - additional.insert(std::make_pair(39, 0x2019)); // right single quotation mark - additional.insert(std::make_pair(44, 0x201A)); // single low quotation mark - additional.insert(std::make_pair(39, 0x201B)); // single high quotation mark (reversed) - additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark - additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark - additional.insert(std::make_pair(44, 0x201E)); // double low quotation mark - additional.insert(std::make_pair(34, 0x201F)); // double high quotation mark (reversed) - additional.insert(std::make_pair(43, 0x2020)); // dagger - additional.insert(std::make_pair(216, 0x2021)); // double dagger (note: this glyph is not available) - additional.insert(std::make_pair(46, 0x2026)); // ellipsis - additional.insert(std::make_pair(37, 0x2030)); // per mille sign - additional.insert(std::make_pair(60, 0x2039)); // single left-pointing angle quotation mark - additional.insert(std::make_pair(62, 0x203A)); // single right-pointing angle quotation mark - additional.insert(std::make_pair(101, 0x20AC)); // euro sign - additional.insert(std::make_pair(84, 0x2122)); // trademark sign - additional.insert(std::make_pair(45, 0x2212)); // minus sign - - for (const auto& [key, value] : additional) + for (auto [it, end] = additional.equal_range(i); it != end; ++it) { - if (key != i) - continue; code = codes->createChild("Code"); - code->addAttribute("index", value); + code->addAttribute("index", it->second); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); From e87c39eeb3db26836300b20ab6a5d2fc5dc83b05 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 10 Mar 2024 03:23:24 +0000 Subject: [PATCH 1242/2167] OpenCS: Editing and verifying of projectile speed for magic effects --- apps/opencs/model/tools/magiceffectcheck.cpp | 5 +++++ apps/opencs/model/world/columnimp.hpp | 20 ++++++++++++++++++++ apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 2 ++ 5 files changed, 30 insertions(+) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index e44119bb67..212b343e00 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -60,6 +60,11 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa ESM::MagicEffect effect = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect)); + if (effect.mData.mSpeed <= 0.0f) + { + messages.add(id, "Speed is less than or equal to zero", "", CSMDoc::Message::Severity_Error); + } + if (effect.mDescription.empty()) { messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index cb263eb8bc..b5205da27c 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -2079,6 +2079,26 @@ namespace CSMWorld bool isEditable() const override { return true; } }; + template + struct ProjectileSpeedColumn : public Column + { + ProjectileSpeedColumn() + : Column(Columns::ColumnId_ProjectileSpeed, ColumnBase::Display_Float) + { + } + + QVariant get(const Record& record) const override { return record.get().mData.mSpeed; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mData.mSpeed = data.toFloat(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct SchoolColumn : public Column { diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 0f42b12508..570e4134c1 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -378,6 +378,7 @@ namespace CSMWorld { ColumnId_Blocked, "Blocked" }, { ColumnId_LevelledCreatureId, "Levelled Creature" }, + { ColumnId_ProjectileSpeed, "Projectile Speed" }, // end marker { -1, 0 }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index a691eada9e..066f6395aa 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -353,6 +353,8 @@ namespace CSMWorld ColumnId_IsLocked = 318, + ColumnId_ProjectileSpeed = 319, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1bff00701a..bd699b12e0 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -502,6 +502,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new FixedRecordTypeColumn(UniversalId::Type_MagicEffect)); mMagicEffects.addColumn(new SchoolColumn); mMagicEffects.addColumn(new BaseCostColumn); + mMagicEffects.addColumn(new ProjectileSpeedColumn); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Icon)); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Particle)); mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_CastingObject)); @@ -512,6 +513,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_HitSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_AreaSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_BoltSound)); + mMagicEffects.addColumn( new FlagColumn(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); mMagicEffects.addColumn( From deb889403578a499d50a9150e5eca2cbd1d5d5d6 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 17 Mar 2024 20:06:23 +0000 Subject: [PATCH 1243/2167] ESM::MagicEffect::blank() set the default to 1 Signed-off-by: Sam Hellawell --- components/esm3/loadmgef.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 8d5b99b0c3..357dd94413 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -588,7 +588,7 @@ namespace ESM mData.mRed = 0; mData.mGreen = 0; mData.mBlue = 0; - mData.mSpeed = 0; + mData.mSpeed = 1; mIcon.clear(); mParticle.clear(); From a98a824f80ce53d26a7c11a7d7e8b1a13b2283ab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 13:58:36 +0000 Subject: [PATCH 1244/2167] Config paths to info log, not verbose --- apps/launcher/maindialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5424d4010b..5486251731 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -292,7 +292,7 @@ bool Launcher::MainDialog::setupLauncherSettings() if (!QFile::exists(path)) return true; - Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -351,7 +351,7 @@ bool Launcher::MainDialog::setupGameSettings() for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { - Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); if (!loadFile(path, &Config::GameSettings::readFile)) return false; } From e735bf67e198fcbfb74ae378b7a2e65d0e9270e9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 14:04:08 +0000 Subject: [PATCH 1245/2167] Brace-initialise SettingValue Clang didn't like it otherwise --- apps/launcher/datafilespage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 8e0d729afa..9c04be0e7e 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -714,7 +714,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) ui.directoryListWidget->addItem(rootPath); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); - item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(rootPath))); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath })); mNewDataDirs.push_back(rootPath); refreshDataFilesView(); return; @@ -746,7 +746,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) { ui.directoryListWidget->insertItem(selectedRow, dir->text()); auto* item = ui.directoryListWidget->item(selectedRow); - item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(dir->text()))); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ dir->text() })); mNewDataDirs.push_back(dir->text()); ++selectedRow; } From 47ef2d018fdcc607fb627764df04490aec191aaf Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 22:25:32 +0000 Subject: [PATCH 1246/2167] Always set userrole for archive list --- apps/launcher/datafilespage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9c04be0e7e..f671089bff 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -867,9 +867,8 @@ bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step) if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) return false; - const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); - - addArchive(item->text(), item->checkState(), newRow); + QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); + ui.archiveListWidget->insertItem(newRow, item); ui.archiveListWidget->setCurrentRow(newRow); return true; } @@ -880,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel row = ui.archiveListWidget->count(); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(selected); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ name })); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { auto item = ui.archiveListWidget->item(row); From 1360eeb839c67aaed6c787e2406e5d3fb29fbb6c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 25 Mar 2024 16:55:19 -0500 Subject: [PATCH 1247/2167] Fix #7901, make teleport fields non-interactive when mTeleport is false --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82ca284d5..f797a98dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed Bug #7899: Editor: Doors can't be unlocked + Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index cb263eb8bc..9e376a5ccf 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1137,7 +1137,8 @@ namespace CSMWorld struct TeleportColumn : public Column { TeleportColumn() - : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) { } @@ -1165,6 +1166,8 @@ namespace CSMWorld QVariant get(const Record& record) const override { + if (!record.get().mTeleport) + return QVariant(QVariant::UserType); return QString::fromUtf8(record.get().mDestCell.c_str()); } @@ -1320,6 +1323,10 @@ namespace CSMWorld QVariant get(const Record& record) const override { + int column = this->mColumnId; + if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXPos + && column <= Columns::ColumnId_DoorPositionZPos) + return QVariant(QVariant::UserType); const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } @@ -1354,6 +1361,10 @@ namespace CSMWorld QVariant get(const Record& record) const override { + int column = this->mColumnId; + if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXRot + && column <= Columns::ColumnId::ColumnId_DoorPositionZRot) + return QVariant(QVariant::UserType); const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } From 4e59246d2d2ca592a0247796ed8185e8fd7b5f4f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 26 Mar 2024 01:53:25 -0500 Subject: [PATCH 1248/2167] Fix(columnimp.hpp): Use QVariant() constructor instead of UserType to hide unused subs from view and make a member variable to tell if the column is used for a door or a regular position --- apps/opencs/model/world/columnimp.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 9e376a5ccf..b1c24a0c22 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1167,7 +1167,7 @@ namespace CSMWorld QVariant get(const Record& record) const override { if (!record.get().mTeleport) - return QVariant(QVariant::UserType); + return QVariant(); return QString::fromUtf8(record.get().mDestCell.c_str()); } @@ -1312,21 +1312,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; PosColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index, ColumnBase::Display_Float) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { - int column = this->mColumnId; - if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXPos - && column <= Columns::ColumnId_DoorPositionZPos) - return QVariant(QVariant::UserType); + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } @@ -1350,21 +1350,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; RotColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index, ColumnBase::Display_Double) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { - int column = this->mColumnId; - if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXRot - && column <= Columns::ColumnId::ColumnId_DoorPositionZRot) - return QVariant(QVariant::UserType); + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } From b8a17b16f7bdec3f47187175644e566903858649 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 26 Mar 2024 05:46:35 -0500 Subject: [PATCH 1249/2167] Cleanup(CS): Make TeleportColumn take flags as argument --- apps/opencs/model/world/columnimp.hpp | 5 ++--- apps/opencs/model/world/data.cpp | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index b1c24a0c22..d0cbe0fee6 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1136,9 +1136,8 @@ namespace CSMWorld template struct TeleportColumn : public Column { - TeleportColumn() - : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) + TeleportColumn(int flags) + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, flags) { } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1bff00701a..9ee6db5d4e 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -591,7 +591,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new ChargesColumn); mRefs.addColumn(new EnchantmentChargesColumn); mRefs.addColumn(new StackSizeColumn); - mRefs.addColumn(new TeleportColumn); + mRefs.addColumn(new TeleportColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new TeleportCellColumn); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 1, true)); From 8cbcb82dd4eca46d7cdd4b82305b0f7c5010b534 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Mar 2024 20:01:50 +0100 Subject: [PATCH 1250/2167] Prevent iterator invalidation when updating Lua UI and increase const correctness --- components/lua_ui/container.cpp | 10 +++++----- components/lua_ui/container.hpp | 6 +++--- components/lua_ui/element.cpp | 26 ++++++++++--------------- components/lua_ui/flex.cpp | 4 ++-- components/lua_ui/flex.hpp | 24 +++++++++++++++++------ components/lua_ui/text.cpp | 2 +- components/lua_ui/text.hpp | 2 +- components/lua_ui/textedit.cpp | 2 +- components/lua_ui/textedit.hpp | 2 +- components/lua_ui/widget.cpp | 25 +++++++++++++----------- components/lua_ui/widget.hpp | 34 ++++++++++++++++++++++++--------- 11 files changed, 81 insertions(+), 56 deletions(-) diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 52fea684d7..1999be8169 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -10,12 +10,12 @@ namespace LuaUi updateSizeToFit(); } - MyGUI::IntSize LuaContainer::childScalingSize() + MyGUI::IntSize LuaContainer::childScalingSize() const { return MyGUI::IntSize(); } - MyGUI::IntSize LuaContainer::templateScalingSize() + MyGUI::IntSize LuaContainer::templateScalingSize() const { return mInnerSize; } @@ -23,14 +23,14 @@ namespace LuaUi void LuaContainer::updateSizeToFit() { MyGUI::IntSize innerSize = MyGUI::IntSize(); - for (auto w : children()) + for (const auto w : children()) { MyGUI::IntCoord coord = w->calculateCoord(); innerSize.width = std::max(innerSize.width, coord.left + coord.width); innerSize.height = std::max(innerSize.height, coord.top + coord.height); } MyGUI::IntSize outerSize = innerSize; - for (auto w : templateChildren()) + for (const auto w : templateChildren()) { MyGUI::IntCoord coord = w->calculateCoord(); outerSize.width = std::max(outerSize.width, coord.left + coord.width); @@ -40,7 +40,7 @@ namespace LuaUi mOuterSize = outerSize; } - MyGUI::IntSize LuaContainer::calculateSize() + MyGUI::IntSize LuaContainer::calculateSize() const { return mOuterSize; } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 16f19d3c12..ef13dd0638 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -10,13 +10,13 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaContainer) public: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateCoord() override; protected: void updateChildren() override; - MyGUI::IntSize childScalingSize() override; - MyGUI::IntSize templateScalingSize() override; + MyGUI::IntSize childScalingSize() const override; + MyGUI::IntSize templateScalingSize() const override; private: void updateSizeToFit(); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ffd763b40b..ceaa746f15 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,25 +54,19 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->detachFromParent(); + ext->detachFromParent(false); } void detachElements(WidgetExtension* ext) { - for (auto* child : ext->children()) - { + auto predicate = [](WidgetExtension* child) { if (child->isRoot()) - child->detachFromParent(); - else - detachElements(child); - } - for (auto* child : ext->templateChildren()) - { - if (child->isRoot()) - child->detachFromParent(); - else - detachElements(child); - } + return true; + detachElements(child); + return false; + }; + ext->detachChildrenIf(predicate); + ext->detachTemplateChildrenIf(predicate); } void destroyRoot(WidgetExtension* ext) @@ -194,8 +188,8 @@ namespace LuaUi throw std::logic_error(std::string("Invalid widget type ") += type); std::string name = layout.get_or(LayoutKeys::name, std::string()); - MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); + MyGUI::Widget* widget + = MyGUI::Gui::getInstancePtr()->createWidgetT(type, {}, {}, MyGUI::Align::Default, {}, name); WidgetExtension* ext = dynamic_cast(widget); if (!ext) diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index c55b48ddb7..1a3293d406 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -79,7 +79,7 @@ namespace LuaUi WidgetExtension::updateChildren(); } - MyGUI::IntSize LuaFlex::childScalingSize() + MyGUI::IntSize LuaFlex::childScalingSize() const { // Call the base method to prevent relativeSize feedback loop MyGUI::IntSize size = WidgetExtension::calculateSize(); @@ -88,7 +88,7 @@ namespace LuaUi return size; } - MyGUI::IntSize LuaFlex::calculateSize() + MyGUI::IntSize LuaFlex::calculateSize() const { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 944daaec77..c91ffd00a2 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -11,10 +11,10 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaFlex) protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateProperties() override; void updateChildren() override; - MyGUI::IntSize childScalingSize() override; + MyGUI::IntSize childScalingSize() const override; void updateCoord() override; @@ -26,25 +26,37 @@ namespace LuaUi Alignment mArrange; template - T& primary(MyGUI::types::TPoint& point) + T& primary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.left : point.top; } template - T& secondary(MyGUI::types::TPoint& point) + T& secondary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.top : point.left; } template - T& primary(MyGUI::types::TSize& size) + T& primary(MyGUI::types::TSize& size) const { return mHorizontal ? size.width : size.height; } template - T& secondary(MyGUI::types::TSize& size) + T& secondary(MyGUI::types::TSize& size) const + { + return mHorizontal ? size.height : size.width; + } + + template + T primary(const MyGUI::types::TSize& size) const + { + return mHorizontal ? size.width : size.height; + } + + template + T secondary(const MyGUI::types::TSize& size) const { return mHorizontal ? size.height : size.width; } diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index e55f1750b9..35aa9402bf 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -46,7 +46,7 @@ namespace LuaUi updateCoord(); } - MyGUI::IntSize LuaText::calculateSize() + MyGUI::IntSize LuaText::calculateSize() const { if (mAutoSized) return getTextSize(); diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index fffe34a3ba..87c01a37e8 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -21,7 +21,7 @@ namespace LuaUi bool mAutoSized; protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; }; } diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index e12bd20c35..9bd241884a 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -63,7 +63,7 @@ namespace LuaUi mEditBox->attachToWidget(this); } - MyGUI::IntSize LuaTextEdit::calculateSize() + MyGUI::IntSize LuaTextEdit::calculateSize() const { MyGUI::IntSize normalSize = WidgetExtension::calculateSize(); if (mAutoSize) diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 8f23b51746..57e1209aff 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -20,7 +20,7 @@ namespace LuaUi void updateProperties() override; void updateCoord() override; void updateChildren() override; - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; private: void textChange(MyGUI::EditBox*); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index e61c36c452..3804e70096 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -101,7 +101,7 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { if (ext->mParent != this) - ext->detachFromParent(); + ext->detachFromParent(true); ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,13 +114,16 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } - void WidgetExtension::detachFromParent() + void WidgetExtension::detachFromParent(bool updateParent) { if (mParent) { - auto children = mParent->children(); - std::erase(children, this); - mParent->setChildren(children); + if (updateParent) + { + auto children = mParent->children(); + std::erase(children, this); + mParent->setChildren(children); + } mParent = nullptr; } widget()->detachFromWidget(); @@ -307,7 +310,7 @@ namespace LuaUi w->updateCoord(); } - MyGUI::IntSize WidgetExtension::parentSize() + MyGUI::IntSize WidgetExtension::parentSize() const { if (!mParent) return widget()->getParentSize(); // size of the layer @@ -317,7 +320,7 @@ namespace LuaUi return mParent->childScalingSize(); } - MyGUI::IntSize WidgetExtension::calculateSize() + MyGUI::IntSize WidgetExtension::calculateSize() const { if (mForceSize) return mForcedCoord.size(); @@ -330,7 +333,7 @@ namespace LuaUi return newSize; } - MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) + MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) const { if (mForcePosition) return mForcedCoord.point(); @@ -342,7 +345,7 @@ namespace LuaUi return newPosition; } - MyGUI::IntCoord WidgetExtension::calculateCoord() + MyGUI::IntCoord WidgetExtension::calculateCoord() const { MyGUI::IntCoord newCoord; newCoord = calculateSize(); @@ -350,12 +353,12 @@ namespace LuaUi return newCoord; } - MyGUI::IntSize WidgetExtension::childScalingSize() + MyGUI::IntSize WidgetExtension::childScalingSize() const { return mSlot->widget()->getSize(); } - MyGUI::IntSize WidgetExtension::templateScalingSize() + MyGUI::IntSize WidgetExtension::templateScalingSize() const { return widget()->getSize(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 05359705a1..59ac997688 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -31,11 +31,13 @@ namespace LuaUi virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } - WidgetExtension* slot() const { return mSlot; } bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } - void detachFromParent(); + void detachFromParent(bool updateParent); + + void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } + void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } void reset(); @@ -65,14 +67,14 @@ namespace LuaUi void setLayout(const sol::table& layout) { mLayout = layout; } template - T externalValue(std::string_view name, const T& defaultValue) + T externalValue(std::string_view name, const T& defaultValue) const { return parseExternal(mExternal, name, defaultValue); } - virtual MyGUI::IntSize calculateSize(); - virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); - MyGUI::IntCoord calculateCoord(); + virtual MyGUI::IntSize calculateSize() const; + virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size) const; + MyGUI::IntCoord calculateCoord() const; virtual bool isTextInput() { return false; } @@ -85,9 +87,9 @@ namespace LuaUi sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - MyGUI::IntSize parentSize(); - virtual MyGUI::IntSize childScalingSize(); - virtual MyGUI::IntSize templateScalingSize(); + MyGUI::IntSize parentSize() const; + virtual MyGUI::IntSize childScalingSize() const; + virtual MyGUI::IntSize templateScalingSize() const; template T propertyValue(std::string_view name, const T& defaultValue) @@ -176,6 +178,20 @@ namespace LuaUi void focusLoss(MyGUI::Widget*, MyGUI::Widget*); void updateVisible(); + + void detachChildrenIf(auto&& predicate, std::vector children) + { + for (auto it = children.begin(); it != children.end();) + { + if (predicate(*it)) + { + (*it)->detachFromParent(false); + it = children.erase(it); + } + else + ++it; + } + } }; class LuaWidget : public MyGUI::Widget, public WidgetExtension From 1d13f7db8f380635767bb935caf7e5c8fc1a6a50 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Mar 2024 20:17:05 +0100 Subject: [PATCH 1251/2167] Simplify detachFromParent --- components/lua_ui/element.cpp | 2 +- components/lua_ui/widget.cpp | 23 +++++++++++------------ components/lua_ui/widget.hpp | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ceaa746f15..f3f873a583 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,7 +54,7 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->detachFromParent(false); + ext->detachFromParent(); } void detachElements(WidgetExtension* ext) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 3804e70096..e7e1053ab7 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -101,7 +101,15 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { if (ext->mParent != this) - ext->detachFromParent(true); + { + if (ext->mParent) + { + auto children = ext->mParent->children(); + std::erase(children, this); + ext->mParent->setChildren(children); + } + ext->detachFromParent(); + } ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,18 +122,9 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } - void WidgetExtension::detachFromParent(bool updateParent) + void WidgetExtension::detachFromParent() { - if (mParent) - { - if (updateParent) - { - auto children = mParent->children(); - std::erase(children, this); - mParent->setChildren(children); - } - mParent = nullptr; - } + mParent = nullptr; widget()->detachFromWidget(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 59ac997688..24962f6820 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -34,7 +34,7 @@ namespace LuaUi bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } - void detachFromParent(bool updateParent); + void detachFromParent(); void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } @@ -185,7 +185,7 @@ namespace LuaUi { if (predicate(*it)) { - (*it)->detachFromParent(false); + (*it)->detachFromParent(); it = children.erase(it); } else From 76105cc2d1efe12d736f9f3dec409bd1a8e04d90 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 29 Mar 2024 09:34:52 +0300 Subject: [PATCH 1252/2167] Make sunlight scattering and wobbly shores optional --- apps/openmw/mwgui/settingswindow.cpp | 17 +++++++++++ apps/openmw/mwgui/settingswindow.hpp | 4 +++ apps/openmw/mwrender/water.cpp | 2 ++ components/settings/categories/water.hpp | 2 ++ .../reference/modding/settings/water.rst | 28 +++++++++++++++++ files/data/l10n/OMWEngine/en.yaml | 2 ++ files/data/l10n/OMWEngine/ru.yaml | 2 ++ .../data/mygui/openmw_settings_window.layout | 30 ++++++++++++++++++- files/settings-default.cfg | 6 ++++ files/shaders/compatibility/water.frag | 17 ++++++++--- 10 files changed, 105 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b569132141..396d0b18a3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -266,6 +266,9 @@ namespace MWGui getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); + getWidget(mWaterRefractionButton, "WaterRefractionButton"); + getWidget(mSunlightScatteringButton, "SunlightScatteringButton"); + getWidget(mWobblyShoresButton, "WobblyShoresButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); @@ -306,6 +309,8 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mWaterRefractionButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onRefractionButtonClicked); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition @@ -377,6 +382,10 @@ namespace MWGui const int waterRainRippleDetail = Settings::water().mRainRippleDetail; mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); + const bool waterRefraction = Settings::water().mRefraction; + mSunlightScatteringButton->setEnabled(waterRefraction); + mWobblyShoresButton->setEnabled(waterRefraction); + updateMaxLightsComboBox(mMaxLights); const Settings::WindowMode windowMode = Settings::video().mWindowMode; @@ -504,6 +513,14 @@ namespace MWGui } } + void SettingsWindow::onRefractionButtonClicked(MyGUI::Widget* _sender) + { + const bool refractionEnabled = Settings::water().mRefraction; + + mSunlightScatteringButton->setEnabled(refractionEnabled); + mWobblyShoresButton->setEnabled(refractionEnabled); + } + void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) { int size = 0; diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1f96f7de54..dc4e09f8ac 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -37,6 +37,9 @@ namespace MWGui MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; + MyGUI::Button* mWaterRefractionButton; + MyGUI::Button* mSunlightScatteringButton; + MyGUI::Button* mWobblyShoresButton; MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterRainRippleDetail; @@ -76,6 +79,7 @@ namespace MWGui void onResolutionCancel(); void highlightCurrentResolution(); + void onRefractionButtonClicked(MyGUI::Widget* _sender); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 2afaa06ad0..62266d6e2d 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -705,6 +705,8 @@ namespace MWRender defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; + defineMap["sunlightScattering"] = Settings::water().mSunlightScattering ? "1" : "0"; + defineMap["wobblyShores"] = Settings::water().mWobblyShores ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index 2e04114244..63adce4ee3 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -26,6 +26,8 @@ namespace Settings SettingValue mSmallFeatureCullingPixelSize{ mIndex, "Water", "small feature culling pixel size", makeMaxStrictSanitizerFloat(0) }; SettingValue mRefractionScale{ mIndex, "Water", "refraction scale", makeClampSanitizerFloat(0, 1) }; + SettingValue mSunlightScattering{ mIndex, "Water", "sunlight scattering" }; + SettingValue mWobblyShores{ mIndex, "Water", "wobbly shores" }; }; } diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index fe407071f2..b04b92de94 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -58,6 +58,34 @@ This setting has no effect if the shader setting is false. This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. +sunlight scattering +------------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting enables sunlight scattering. +This makes incident sunlight seemingly spread through water, simulating the optical property. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Sunlight Scattering' button in the Water tab of the Video panel of the Options menu. + +wobbly shores +------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting makes shores wobbly. +The water surface will smoothly fade into the shoreline and wobble based on water normal-mapping, which avoids harsh transitions. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Wobbly Shores' button in the Water tab of the Video panel of the Options menu. + reflection detail ----------------- diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 55ebbf3e94..d14aaa78fa 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -154,6 +154,7 @@ SensitivityHigh: "High" SensitivityLow: "Low" SettingsWindow: "Options" Subtitles: "Subtitles" +SunlightScattering: "Sunlight Scattering" TestingExteriorCells: "Testing Exterior Cells" TestingInteriorCells: "Testing Interior Cells" TextureFiltering: "Texture Filtering" @@ -178,3 +179,4 @@ WindowModeFullscreen: "Fullscreen" WindowModeHint: "Hint: Windowed Fullscreen mode\nalways uses the native display resolution." WindowModeWindowed: "Windowed" WindowModeWindowedFullscreen: "Windowed Fullscreen" +WobblyShores: "Wobbly Shores" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 07fc376675..1edecbf8b0 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -154,6 +154,7 @@ SensitivityHigh: "Высокая" SensitivityLow: "Низкая" SettingsWindow: "Настройки" Subtitles: "Субтитры" +SunlightScattering: "Рассеяние солнечного света" TestingExteriorCells: "Проверка наружных ячеек" TestingInteriorCells: "Проверка ячеек-помещений" TextureFiltering: "Фильтрация текстур" @@ -178,3 +179,4 @@ WindowModeFullscreen: "Полный экран" WindowModeHint: "Подсказка: режим Оконный без полей\nвсегда использует родное разрешение экрана." WindowModeWindowed: "Оконный" WindowModeWindowedFullscreen: "Оконный без полей" +WobblyShores: "Колеблющиеся берега" diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 9e2f707ef5..5a25a61936 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -457,7 +457,7 @@ - + @@ -467,6 +467,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 58f6c347f1..73331867a7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -680,6 +680,12 @@ small feature culling pixel size = 20.0 # By what factor water downscales objects. Only works with water shader and refractions on. refraction scale = 1.0 +# Make incident sunlight spread through water. +sunlight scattering = true + +# Fade and wobble water plane edges to avoid harsh shoreline transitions. +wobbly shores = true + [Windows] # Location and sizes of windows as a fraction of the OpenMW window or diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index c971f92b99..2debf2fac0 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -41,12 +41,13 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount +#if @sunlightScattering const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering +const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); // sunlight extinction +#endif -const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade - const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) @@ -57,7 +58,9 @@ const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +#if @wobblyShores const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to mask precision errors, the effect is almost impossible to see at a distance +#endif // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - @@ -213,7 +216,7 @@ void main(void) refraction = mix(refraction, waterColor, clamp(factor, 0.0, 1.0)); } - // sunlight scattering +#if @sunlightScattering // normal for sunlight scattering vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); @@ -222,9 +225,13 @@ void main(void) vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; + refraction = mix(refraction, scatterColour, lightScatter); +#endif + + gl_FragData[0].xyz = mix(refraction, reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; +#if @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; @@ -234,6 +241,8 @@ void main(void) shoreOffset *= fuzzFactor; shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); +#endif + #else gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); From 3d83585c46290e1959d1b7f88217f6fd3a584db4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Mar 2024 11:46:04 +0400 Subject: [PATCH 1253/2167] Make binding names layout-independent (bug 7908) --- CHANGELOG.md | 1 + apps/openmw/mwlua/inputbindings.cpp | 2 +- extern/oics/ICSInputControlSystem.cpp | 6 +----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..fa332b3074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,6 +165,7 @@ Bug #7898: Editor: Invalid reference scales are allowed Bug #7899: Editor: Doors can't be unlocked Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport + Bug #7908: Key bindings names in the settings menu are layout-specific Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index e9ed4fe485..09a5a0babb 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -221,7 +221,7 @@ namespace MWLua api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; - api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; + api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetScancodeName(code); }; api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "GameMenu", MWInput::A_GameMenu }, diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 9f03ec121e..bc860524ef 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -802,11 +802,7 @@ namespace ICS std::string InputControlSystem::scancodeToString(SDL_Scancode key) { - SDL_Keycode code = SDL_GetKeyFromScancode(key); - if (code == SDLK_UNKNOWN) - return std::string(SDL_GetScancodeName(key)); - else - return std::string(SDL_GetKeyName(code)); + return std::string(SDL_GetScancodeName(key)); } void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height) From 387e53b4684a595f64f787867a718c839b659a7d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Mar 2024 12:09:50 +0400 Subject: [PATCH 1254/2167] Add missing initialization --- extern/oics/ICSInputControlSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 9f03ec121e..063ff0f355 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -42,6 +42,7 @@ namespace ICS , mXmouseAxisBinded(false), mYmouseAxisBinded(false) , mClientWidth(1) , mClientHeight(1) + , mMouseAxisBindingInitialValues{0} { ICS_LOG(" - Creating InputControlSystem - "); From 9a24e77d3f6c65e617e939aff177314f968d485e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Mar 2024 19:01:11 +0100 Subject: [PATCH 1255/2167] Show F4 stats in pages --- apps/openmw/engine.cpp | 12 +- components/resource/stats.cpp | 639 +++++++++++++++++----------------- components/resource/stats.hpp | 45 +-- 3 files changed, 344 insertions(+), 352 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 49833040d6..63473fe67d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -965,17 +965,17 @@ void OMW::Engine::go() } // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); + osg::ref_ptr statsHandler = new Resource::Profiler(stats.is_open(), *mVFS); - initStatsHandler(*statshandler); + initStatsHandler(*statsHandler); - mViewer->addEventHandler(statshandler); + mViewer->addEventHandler(statsHandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); - mViewer->addEventHandler(resourceshandler); + osg::ref_ptr resourcesHandler = new Resource::StatsHandler(stats.is_open(), *mVFS); + mViewer->addEventHandler(resourcesHandler); if (stats.is_open()) - Resource::CollectStatistics(mViewer); + Resource::collectStatistics(*mViewer); // Start the game if (!mSaveGameFile.empty()) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 0542ffef28..5d88a55e57 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -2,7 +2,11 @@ #include #include +#include #include +#include +#include +#include #include @@ -18,143 +22,207 @@ namespace Resource { - - static bool collectStatRendering = false; - static bool collectStatCameraObjects = false; - static bool collectStatViewerObjects = false; - static bool collectStatResource = false; - static bool collectStatGPU = false; - static bool collectStatEvent = false; - static bool collectStatFrameRate = false; - static bool collectStatUpdate = false; - static bool collectStatEngine = false; - - static const VFS::Path::Normalized sFontName("Fonts/DejaVuLGCSansMono.ttf"); - - static void setupStatCollection() + namespace { - const char* envList = getenv("OPENMW_OSG_STATS_LIST"); - if (envList == nullptr) - return; + constexpr float statsWidth = 1280.0f; + constexpr float statsHeight = 1024.0f; + constexpr float characterSize = 17.0f; + constexpr float backgroundMargin = 5; + constexpr float backgroundSpacing = 3; + constexpr float maxStatsHeight = 420.0f; + constexpr std::size_t pageSize + = static_cast((maxStatsHeight - 2 * backgroundMargin) / characterSize); + constexpr int statsHandlerKey = osgGA::GUIEventAdapter::KEY_F4; + const VFS::Path::Normalized fontName("Fonts/DejaVuLGCSansMono.ttf"); - std::string_view kwList(envList); + bool collectStatRendering = false; + bool collectStatCameraObjects = false; + bool collectStatViewerObjects = false; + bool collectStatResource = false; + bool collectStatGPU = false; + bool collectStatEvent = false; + bool collectStatFrameRate = false; + bool collectStatUpdate = false; + bool collectStatEngine = false; - auto kwBegin = kwList.begin(); + const std::vector allStatNames = { + "FrameNumber", + "Compiling", + "WorkQueue", + "WorkThread", + "UnrefQueue", + "Texture", + "StateSet", + "Node", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "Groundcover Chunk", + "Object Chunk", + "Terrain Chunk", + "Terrain Texture", + "Land", + "Composite", + "Mechanics Actors", + "Mechanics Objects", + "Physics Actors", + "Physics Objects", + "Physics Projectiles", + "Physics HeightFields", + "Lua UsedMemory", + "NavMesh Jobs", + "NavMesh Waiting", + "NavMesh Pushed", + "NavMesh Processing", + "NavMesh DbJobs Write", + "NavMesh DbJobs Read", + "NavMesh DbCache Get", + "NavMesh DbCache Hit", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + "NavMesh Cache Get", + "NavMesh Cache Hit", + }; - while (kwBegin != kwList.end()) + void setupStatCollection() { - auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; - const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + std::string_view kwList(envList); - if (kw == "gpu") - collectStatGPU = true; - else if (kw == "event") - collectStatEvent = true; - else if (kw == "frame_rate") - collectStatFrameRate = true; - else if (kw == "update") - collectStatUpdate = true; - else if (kw == "engine") - collectStatEngine = true; - else if (kw == "rendering") - collectStatRendering = true; - else if (kw == "cameraobjects") - collectStatCameraObjects = true; - else if (kw == "viewerobjects") - collectStatViewerObjects = true; - else if (kw == "resource") - collectStatResource = true; - else if (kw == "times") + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) { - collectStatGPU = true; - collectStatEvent = true; - collectStatFrameRate = true; - collectStatUpdate = true; - collectStatEngine = true; - collectStatRendering = true; - } + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); - if (kwEnd == kwList.end()) - break; + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); - kwBegin = std::next(kwEnd); - } - } + if (kw == "gpu") + collectStatGPU = true; + else if (kw == "event") + collectStatEvent = true; + else if (kw == "frame_rate") + collectStatFrameRate = true; + else if (kw == "update") + collectStatUpdate = true; + else if (kw == "engine") + collectStatEngine = true; + else if (kw == "rendering") + collectStatRendering = true; + else if (kw == "cameraobjects") + collectStatCameraObjects = true; + else if (kw == "viewerobjects") + collectStatViewerObjects = true; + else if (kw == "resource") + collectStatResource = true; + else if (kw == "times") + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } - class SetFontVisitor : public osg::NodeVisitor - { - public: - SetFontVisitor(osgText::Font* font) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFont(font) - { - } + if (kwEnd == kwList.end()) + break; - void apply(osg::Drawable& node) override - { - if (osgText::Text* text = dynamic_cast(&node)) - { - text->setFont(mFont); + kwBegin = std::next(kwEnd); } } - private: - osgText::Font* mFont; - }; - - osg::ref_ptr getMonoFont(VFS::Manager* vfs) - { - if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs->exists(sFontName)) + osg::ref_ptr createBackgroundRectangle( + const osg::Vec3& pos, const float width, const float height, const osg::Vec4& color) { - Files::IStreamPtr streamPtr = vfs->get(sFontName); - return osgText::readRefFontStream(*streamPtr.get()); + osg::ref_ptr geometry = new osg::Geometry; + + geometry->setUseDisplayList(false); + + osg::ref_ptr stateSet = new osg::StateSet; + geometry->setStateSet(stateSet); + + osg::ref_ptr vertices = new osg::Vec3Array; + vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); + vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); + geometry->setVertexArray(vertices); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(color); + geometry->setColorArray(colors, osg::Array::BIND_OVERALL); + + osg::ref_ptr base + = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); + base->push_back(0); + base->push_back(1); + base->push_back(2); + base->push_back(3); + geometry->addPrimitiveSet(base); + + return geometry; } - return nullptr; + osg::ref_ptr getMonoFont(const VFS::Manager& vfs) + { + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs.exists(fontName)) + { + const Files::IStreamPtr streamPtr = vfs.get(fontName); + return osgText::readRefFontStream(*streamPtr); + } + + return nullptr; + } + + class SetFontVisitor : public osg::NodeVisitor + { + public: + SetFontVisitor(osgText::Font* font) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFont(font) + { + } + + void apply(osg::Drawable& node) override + { + if (osgText::Text* text = dynamic_cast(&node)) + { + text->setFont(mFont); + } + } + + private: + osgText::Font* mFont; + }; } - StatsHandler::StatsHandler(bool offlineCollect, VFS::Manager* vfs) - : _key(osgGA::GUIEventAdapter::KEY_F4) - , _initialized(false) - , _statsType(false) - , _offlineCollect(offlineCollect) - , _statsWidth(1280.0f) - , _statsHeight(1024.0f) - , _characterSize(18.0f) + Profiler::Profiler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mTextFont(getMonoFont(vfs)) { - _camera = new osg::Camera; - _camera->getOrCreateStateSet()->setGlobalDefaults(); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - _camera->setProjectionResizePolicy(osg::Camera::FIXED); - - _resourceStatsChildNum = 0; - - _textFont = getMonoFont(vfs); - } - - Profiler::Profiler(bool offlineCollect, VFS::Manager* vfs) - : _offlineCollect(offlineCollect) - , _initFonts(false) - { - _characterSize = 18; + _characterSize = characterSize; _font.clear(); - _textFont = getMonoFont(vfs); - setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); setupStatCollection(); } void Profiler::setUpFonts() { - if (_textFont != nullptr) + if (mTextFont != nullptr) { - SetFontVisitor visitor(_textFont); + SetFontVisitor visitor(mTextFont); _switch->accept(visitor); } - _initFonts = true; + mInitFonts = true; } bool Profiler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) @@ -162,24 +230,44 @@ namespace Resource osgViewer::ViewerBase* viewer = nullptr; bool handled = StatsHandler::handle(ea, aa); - if (_initialized && !_initFonts) + if (_initialized && !mInitFonts) setUpFonts(); auto* view = dynamic_cast(&aa); if (view) viewer = view->getViewerBase(); - if (viewer) + if (viewer != nullptr) { // Add/remove openmw stats to the osd as necessary viewer->getViewerStats()->collectStats("engine", _statsType >= StatsHandler::StatsType::VIEWER_STATS); - if (_offlineCollect) - CollectStatistics(viewer); + if (mOfflineCollect) + collectStatistics(*viewer); } return handled; } + StatsHandler::StatsHandler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mSwitch(new osg::Switch) + , mCamera(new osg::Camera) + , mTextFont(getMonoFont(vfs)) + { + osg::ref_ptr stateset = mSwitch->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); +#ifdef OSG_GL1_AVAILABLE + stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); +#endif + + mCamera->getOrCreateStateSet()->setGlobalDefaults(); + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); + mCamera->setProjectionResizePolicy(osg::Camera::FIXED); + mCamera->addChild(mSwitch); + } + bool StatsHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { if (ea.getHandled()) @@ -189,18 +277,21 @@ namespace Resource { case (osgGA::GUIEventAdapter::KEYDOWN): { - if (ea.getKey() == _key) + if (ea.getKey() == statsHandlerKey) { - osgViewer::View* myview = dynamic_cast(&aa); - if (!myview) + osgViewer::View* const view = dynamic_cast(&aa); + if (view == nullptr) return false; - osgViewer::ViewerBase* viewer = myview->getViewerBase(); + osgViewer::ViewerBase* const viewer = view->getViewerBase(); - toggle(viewer); + if (viewer == nullptr) + return false; - if (_offlineCollect) - CollectStatistics(viewer); + toggle(*viewer); + + if (mOfflineCollect) + collectStatistics(*viewer); aa.requestRedraw(); return true; @@ -223,66 +314,69 @@ namespace Resource if (width <= 0 || height <= 0) return; - _camera->setViewport(0, 0, width, height); - if (fabs(height * _statsWidth) <= fabs(width * _statsHeight)) + mCamera->setViewport(0, 0, width, height); + if (std::abs(height * statsWidth) <= std::abs(width * statsHeight)) { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(_statsWidth - width * _statsHeight / height, _statsWidth, 0.0, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(statsWidth - width * statsHeight / height, statsWidth, 0.0, statsHeight)); } else { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(0.0, _statsWidth, _statsHeight - height * _statsWidth / width, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(0.0, statsWidth, statsHeight - height * statsWidth / width, statsHeight)); } } - void StatsHandler::toggle(osgViewer::ViewerBase* viewer) + void StatsHandler::toggle(osgViewer::ViewerBase& viewer) { - if (!_initialized) + if (!mInitialized) { setUpHUDCamera(viewer); setUpScene(viewer); + mInitialized = true; } - _statsType = !_statsType; - - if (!_statsType) + if (mPage == mSwitch->getNumChildren()) { - _camera->setNodeMask(0); - _switch->setAllChildrenOff(); + mPage = 0; - viewer->getViewerStats()->collectStats("resource", false); + mCamera->setNodeMask(0); + mSwitch->setAllChildrenOff(); + + viewer.getViewerStats()->collectStats("resource", false); } else { - _camera->setNodeMask(0xffffffff); - _switch->setSingleChildOn(_resourceStatsChildNum); + mCamera->setNodeMask(0xffffffff); + mSwitch->setSingleChildOn(mPage); - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); + + ++mPage; } } - void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) + void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase& viewer) { // Try GraphicsWindow first so we're likely to get the main viewer window - osg::GraphicsContext* context = dynamic_cast(_camera->getGraphicsContext()); + osg::GraphicsContext* context = dynamic_cast(mCamera->getGraphicsContext()); if (!context) { osgViewer::Viewer::Windows windows; - viewer->getWindows(windows); + viewer.getWindows(windows); if (!windows.empty()) context = windows.front(); else { // No GraphicsWindows were found, so let's try to find a GraphicsContext - context = _camera->getGraphicsContext(); + context = mCamera->getGraphicsContext(); if (!context) { osgViewer::Viewer::Contexts contexts; - viewer->getContexts(contexts); + viewer.getContexts(contexts); if (contexts.empty()) return; @@ -292,241 +386,151 @@ namespace Resource } } - _camera->setGraphicsContext(context); + mCamera->setGraphicsContext(context); - _camera->setRenderOrder(osg::Camera::POST_RENDER, 11); + mCamera->setRenderOrder(osg::Camera::POST_RENDER, 11); - _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - _camera->setViewMatrix(osg::Matrix::identity()); + mCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + mCamera->setViewMatrix(osg::Matrix::identity()); setWindowSize(context->getTraits()->width, context->getTraits()->height); // only clear the depth buffer - _camera->setClearMask(0); - _camera->setAllowEventFocus(false); + mCamera->setClearMask(0); + mCamera->setAllowEventFocus(false); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - - _initialized = true; + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); } - osg::Geometry* createBackgroundRectangle( - const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) + namespace { - osg::StateSet* ss = new osg::StateSet; - - osg::Geometry* geometry = new osg::Geometry; - - geometry->setUseDisplayList(false); - geometry->setStateSet(ss); - - osg::Vec3Array* vertices = new osg::Vec3Array; - geometry->setVertexArray(vertices); - - vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); - vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); - - osg::Vec4Array* colors = new osg::Vec4Array; - colors->push_back(color); - geometry->setColorArray(colors, osg::Array::BIND_OVERALL); - - osg::DrawElementsUShort* base = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); - base->push_back(0); - base->push_back(1); - base->push_back(2); - base->push_back(3); - - geometry->addPrimitiveSet(base); - - return geometry; - } - - class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback - { - public: - ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) - : mStats(stats) - , mStatNames(statNames) + class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback { - } - - void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override - { - if (!mStats) - return; - - osgText::Text* text = (osgText::Text*)(drawable); - - std::ostringstream viewStr; - viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(14); - // Used fixed formatting, as scientific will switch to "...e+.." notation for - // large numbers of vertices/drawables/etc. - viewStr.setf(std::ios::fixed); - viewStr.precision(0); - - unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; - - for (const auto& statName : mStatNames.get()) + public: + explicit ResourceStatsTextDrawCallback(osg::Stats* stats, std::span statNames) + : mStats(stats) + , mStatNames(statNames) { - if (statName.empty()) - viewStr << std::endl; - else - { - double value = 0.0; - if (mStats->getAttribute(frameNumber, statName, value)) - viewStr << std::setw(8) << value << std::endl; - else - viewStr << std::setw(8) << "." << std::endl; - } } - text->setText(viewStr.str()); + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override + { + if (mStats == nullptr) + return; - text->drawImplementation(renderInfo); - } + osgText::Text* text = (osgText::Text*)(drawable); - osg::ref_ptr mStats; - std::reference_wrapper> mStatNames; - }; + std::ostringstream viewStr; + viewStr.setf(std::ios::left, std::ios::adjustfield); + viewStr.width(14); + // Used fixed formatting, as scientific will switch to "...e+.." notation for + // large numbers of vertices/drawables/etc. + viewStr.setf(std::ios::fixed); + viewStr.precision(0); - void StatsHandler::setUpScene(osgViewer::ViewerBase* viewer) + const unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; + + for (const std::string& statName : mStatNames) + { + if (statName.empty()) + viewStr << std::endl; + else + { + double value = 0.0; + if (mStats->getAttribute(frameNumber, statName, value)) + viewStr << std::setw(8) << value << std::endl; + else + viewStr << std::setw(8) << "." << std::endl; + } + } + + text->setText(viewStr.str()); + + text->drawImplementation(renderInfo); + } + + private: + osg::ref_ptr mStats; + std::span mStatNames; + }; + } + + void StatsHandler::setUpScene(osgViewer::ViewerBase& viewer) { - _switch = new osg::Switch; + const osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); + const osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); + const osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - _camera->addChild(_switch); + const auto longest = std::max_element(allStatNames.begin(), allStatNames.end(), + [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); + const std::size_t longestSize = longest->size(); + const float statNamesWidth = longestSize * characterSize * 0.6 + 2 * backgroundMargin; + const float statTextWidth = 7 * characterSize + 2 * backgroundMargin; + const float statHeight = pageSize * characterSize + 2 * backgroundMargin; + const float width = statNamesWidth + backgroundSpacing + statTextWidth; - osg::StateSet* stateset = _switch->getOrCreateStateSet(); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); -#ifdef OSG_GL1_AVAILABLE - stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); -#endif - - osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); - osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); - osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - float backgroundMargin = 5; - float backgroundSpacing = 3; - - // resource stats + for (std::size_t offset = 0; offset < allStatNames.size(); offset += pageSize) { - osg::Group* group = new osg::Group; + osg::ref_ptr group = new osg::Group; + group->setCullingActive(false); - _resourceStatsChildNum = _switch->getNumChildren(); - _switch->addChild(group, false); - static const std::vector statNames({ - "FrameNumber", - "", - "Compiling", - "WorkQueue", - "WorkThread", - "UnrefQueue", - "", - "Texture", - "StateSet", - "Node", - "Shape", - "Shape Instance", - "Image", - "Nif", - "Keyframe", - "", - "Groundcover Chunk", - "Object Chunk", - "Terrain Chunk", - "Terrain Texture", - "Land", - "Composite", - "", - "NavMesh Jobs", - "NavMesh Waiting", - "NavMesh Pushed", - "NavMesh Processing", - "NavMesh DbJobs Write", - "NavMesh DbJobs Read", - "NavMesh DbCache Get", - "NavMesh DbCache Hit", - "NavMesh CacheSize", - "NavMesh UsedTiles", - "NavMesh CachedTiles", - "NavMesh Cache Get", - "NavMesh Cache Hit", - "", - "Mechanics Actors", - "Mechanics Objects", - "", - "Physics Actors", - "Physics Objects", - "Physics Projectiles", - "Physics HeightFields", - "", - "Lua UsedMemory", - }); - - static const auto longest = std::max_element(statNames.begin(), statNames.end(), - [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); - const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; - const float statTextWidth = 7 * _characterSize + 2 * backgroundMargin; - const float statHeight = statNames.size() * _characterSize + 2 * backgroundMargin; - osg::Vec3 pos(_statsWidth - statNamesWidth - backgroundSpacing - statTextWidth, statHeight, 0.0f); + const std::size_t count = std::min(allStatNames.size() - offset, pageSize); + std::span currentStatNames(allStatNames.data() + offset, count); + osg::Vec3 pos(statsWidth - width, statHeight - characterSize, 0.0f); group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statNamesWidth, statHeight, backgroundColor)); osg::ref_ptr staticText = new osgText::Text; group->addChild(staticText.get()); staticText->setColor(staticTextColor); - staticText->setCharacterSize(_characterSize); + staticText->setCharacterSize(characterSize); staticText->setPosition(pos); std::ostringstream viewStr; viewStr.clear(); viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(longest->size()); - for (const auto& statName : statNames) - { + viewStr.width(longestSize); + for (const std::string& statName : currentStatNames) viewStr << statName << std::endl; - } staticText->setText(viewStr.str()); pos.x() += statNamesWidth + backgroundSpacing; group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statTextWidth, statHeight, backgroundColor)); osg::ref_ptr statsText = new osgText::Text; group->addChild(statsText.get()); statsText->setColor(dynamicTextColor); - statsText->setCharacterSize(_characterSize); + statsText->setCharacterSize(characterSize); statsText->setPosition(pos); statsText->setText(""); - statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); + statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer.getViewerStats(), currentStatNames)); - if (_textFont) + if (mTextFont != nullptr) { - staticText->setFont(_textFont); - statsText->setFont(_textFont); + staticText->setFont(mTextFont); + statsText->setFont(mTextFont); } + + mSwitch->addChild(group, false); } } void StatsHandler::getUsage(osg::ApplicationUsage& usage) const { - usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); + usage.addKeyboardMouseBinding(statsHandlerKey, "On screen resource usage stats."); } - void CollectStatistics(osgViewer::ViewerBase* viewer) + void collectStatistics(osgViewer::ViewerBase& viewer) { osgViewer::Viewer::Cameras cameras; - viewer->getCameras(cameras); + viewer.getCameras(cameras); for (auto* camera : cameras) { if (collectStatGPU) @@ -537,17 +541,16 @@ namespace Resource camera->getStats()->collectStats("scene", true); } if (collectStatEvent) - viewer->getViewerStats()->collectStats("event", true); + viewer.getViewerStats()->collectStats("event", true); if (collectStatFrameRate) - viewer->getViewerStats()->collectStats("frame_rate", true); + viewer.getViewerStats()->collectStats("frame_rate", true); if (collectStatUpdate) - viewer->getViewerStats()->collectStats("update", true); + viewer.getViewerStats()->collectStats("update", true); if (collectStatResource) - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); if (collectStatViewerObjects) - viewer->getViewerStats()->collectStats("scene", true); + viewer.getViewerStats()->collectStats("scene", true); if (collectStatEngine) - viewer->getViewerStats()->collectStats("engine", true); + viewer.getViewerStats()->collectStats("engine", true); } - } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index fc2899d386..7381dac417 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -28,57 +28,46 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(bool offlineCollect, VFS::Manager* vfs); + explicit Profiler(bool offlineCollect, const VFS::Manager& vfs); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; private: void setUpFonts(); - bool _offlineCollect; - bool _initFonts; - osg::ref_ptr _textFont; + bool mInitFonts = false; + bool mOfflineCollect; + osg::ref_ptr mTextFont; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(bool offlineCollect, VFS::Manager* vfs); - - void setKey(int key) { _key = key; } - int getKey() const { return _key; } + explicit StatsHandler(bool offlineCollect, const VFS::Manager& vfs); bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; - void setWindowSize(int w, int h); - - void toggle(osgViewer::ViewerBase* viewer); - - void setUpHUDCamera(osgViewer::ViewerBase* viewer); - void setUpScene(osgViewer::ViewerBase* viewer); - /** Get the keyboard and mouse usage of this manipulator.*/ void getUsage(osg::ApplicationUsage& usage) const override; private: - osg::ref_ptr _switch; - int _key; - osg::ref_ptr _camera; - bool _initialized; - bool _statsType; - bool _offlineCollect; + unsigned mPage = 0; + bool mInitialized = false; + bool mOfflineCollect; + osg::ref_ptr mSwitch; + osg::ref_ptr mCamera; + osg::ref_ptr mTextFont; - float _statsWidth; - float _statsHeight; + void setWindowSize(int w, int h); - float _characterSize; + void toggle(osgViewer::ViewerBase& viewer); - int _resourceStatsChildNum; + void setUpHUDCamera(osgViewer::ViewerBase& viewer); - osg::ref_ptr _textFont; + void setUpScene(osgViewer::ViewerBase& viewer); }; - void CollectStatistics(osgViewer::ViewerBase* viewer); - + void collectStatistics(osgViewer::ViewerBase& viewer); } #endif From ae41ebfc836c34d61ae6fe72d389e7ec57077d4e Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 22:38:45 +0100 Subject: [PATCH 1256/2167] Report CellPreloader stats --- apps/openmw/mwworld/cellpreloader.cpp | 16 +++++++++++++++- apps/openmw/mwworld/cellpreloader.hpp | 15 +++++++++++++++ apps/openmw/mwworld/scene.cpp | 5 +++++ apps/openmw/mwworld/scene.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 1 + components/resource/stats.cpp | 5 +++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 364f3e169e..7157e67d82 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -276,6 +278,7 @@ namespace MWWorld { oldestCell->second.mWorkItem->abort(); mPreloadCells.erase(oldestCell); + ++mEvicted; } else return; @@ -285,7 +288,8 @@ namespace MWWorld mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); - mPreloadCells[&cell] = PreloadEntry(timestamp, item); + mPreloadCells.emplace(&cell, PreloadEntry(timestamp, item)); + ++mAdded; } void CellPreloader::notifyLoaded(CellStore* cell) @@ -300,6 +304,7 @@ namespace MWWorld } mPreloadCells.erase(found); + ++mLoaded; } } @@ -329,6 +334,7 @@ namespace MWWorld it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); + ++mExpired; } else ++it; @@ -467,4 +473,12 @@ namespace MWWorld mPreloadCells.clear(); } + void CellPreloader::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "CellPreloader Count", mPreloadCells.size()); + stats.setAttribute(frameNumber, "CellPreloader Added", mAdded); + stats.setAttribute(frameNumber, "CellPreloader Evicted", mEvicted); + stats.setAttribute(frameNumber, "CellPreloader Loaded", mLoaded); + stats.setAttribute(frameNumber, "CellPreloader Expired", mExpired); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index ddf13cab83..ce5d5e7a0f 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -2,11 +2,20 @@ #define OPENMW_MWWORLD_CELLPRELOADER_H #include + #include + #include #include #include +#include + +namespace osg +{ + class Stats; +} + namespace Resource { class ResourceSystem; @@ -76,6 +85,8 @@ namespace MWWorld bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const; void setTerrain(Terrain::World* terrain); + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: void clearAllTasks(); @@ -118,6 +129,10 @@ namespace MWWorld std::vector mLoadedTerrainPositions; double mLoadedTerrainTimestamp; + std::size_t mEvicted = 0; + std::size_t mAdded = 0; + std::size_t mExpired = 0; + std::size_t mLoaded = 0; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a5787e301e..64a258cff8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1285,4 +1285,9 @@ namespace MWWorld } } } + + void Scene::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mPreloader->reportStats(frameNumber, stats); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index fdca9bb87f..6c915d4f92 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -19,6 +19,7 @@ namespace osg { class Vec3f; + class Stats; } namespace ESM @@ -203,6 +204,8 @@ namespace MWWorld void testExteriorCells(); void testInteriorCells(); + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4fc7a21339..ec58f779d0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3786,6 +3786,7 @@ namespace MWWorld { DetourNavigator::reportStats(mNavigator->getStats(), frameNumber, stats); mPhysics->reportStats(frameNumber, stats); + mWorldScene->reportStats(frameNumber, stats); } void World::updateSkyDate() diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 5d88a55e57..e361be36dd 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -72,6 +72,11 @@ namespace Resource "Physics Projectiles", "Physics HeightFields", "Lua UsedMemory", + "CellPreloader Count", + "CellPreloader Added", + "CellPreloader Evicted", + "CellPreloader Loaded", + "CellPreloader Expired", "NavMesh Jobs", "NavMesh Waiting", "NavMesh Pushed", From 215404e126d20f1aa067be1a212a95387cd64601 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 00:23:49 +0100 Subject: [PATCH 1257/2167] Report more stats from caches --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/landmanager.cpp | 4 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- .../resource/testobjectcache.cpp | 32 +++- components/CMakeLists.txt | 2 +- components/resource/bulletshapemanager.cpp | 4 +- components/resource/cachestats.cpp | 40 +++++ components/resource/cachestats.hpp | 28 ++++ components/resource/imagemanager.cpp | 2 +- components/resource/keyframemanager.cpp | 3 +- components/resource/multiobjectcache.cpp | 14 +- components/resource/multiobjectcache.hpp | 7 +- components/resource/niffilemanager.cpp | 3 +- components/resource/objectcache.hpp | 78 +++++----- components/resource/scenemanager.cpp | 2 +- components/resource/stats.cpp | 146 ++++++++++++------ components/resource/stats.hpp | 1 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/texturemanager.cpp | 3 +- 19 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 components/resource/cachestats.cpp create mode 100644 components/resource/cachestats.hpp diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 4f99ee7560..7a29f1bb07 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -463,6 +463,6 @@ namespace MWRender void Groundcover::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + Resource::reportStats("Groundcover Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index f8a3ebd962..d17933b2b7 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -1,7 +1,5 @@ #include "landmanager.hpp" -#include - #include #include #include @@ -53,7 +51,7 @@ namespace MWRender void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); + Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index f1bccc13c9..f426672784 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -1013,7 +1013,7 @@ namespace MWRender void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); + Resource::reportStats("Object Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 1c8cff6af0..e2f5799edb 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -114,9 +114,11 @@ namespace Resource cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + ASSERT_EQ(cache->getStats().mExpired, 0); cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + ASSERT_EQ(cache->getStats().mExpired, 1); } TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) @@ -249,7 +251,7 @@ namespace Resource EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); } - TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrAddedItems) { osg::ref_ptr> cache(new GenericObjectCache); @@ -258,7 +260,33 @@ namespace Resource cache->addEntryToObjectCache(13, value1); cache->addEntryToObjectCache(42, value2); - EXPECT_EQ(cache->getCacheSize(), 2); + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mSize, 2); + } + + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrGetsAndHits) + { + osg::ref_ptr> cache(new GenericObjectCache); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 0); + EXPECT_EQ(stats.mHit, 0); + } + + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(13, value); + cache->getRefFromObjectCache(13); + cache->getRefFromObjectCache(42); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 2); + EXPECT_EQ(stats.mHit, 1); + } } TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ef01e19460..7ac01ef169 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -125,7 +125,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation foreachbulletobject errormarker + resourcemanager stats animation foreachbulletobject errormarker cachestats ) add_component_dir (shader diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b37e81111d..93c53d8cb0 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -213,8 +213,8 @@ namespace Resource void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); - stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); + Resource::reportStats("Shape", frameNumber, mCache->getStats(), *stats); + Resource::reportStats("Shape Instance", frameNumber, mInstanceCache->getStats(), *stats); } } diff --git a/components/resource/cachestats.cpp b/components/resource/cachestats.cpp new file mode 100644 index 0000000000..9cc0cea517 --- /dev/null +++ b/components/resource/cachestats.cpp @@ -0,0 +1,40 @@ +#include "cachestats.hpp" + +#include + +namespace Resource +{ + namespace + { + std::string makeAttribute(std::string_view prefix, std::string_view suffix) + { + std::string result; + result.reserve(prefix.size() + 1 + suffix.size()); + result += prefix; + result += ' '; + result += suffix; + return result; + } + } + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out) + { + constexpr std::string_view suffixes[] = { + "Count", + "Get", + "Hit", + "Expired", + }; + + for (std::string_view suffix : suffixes) + out.push_back(makeAttribute(prefix, suffix)); + } + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst) + { + dst.setAttribute(frameNumber, makeAttribute(prefix, "Count"), static_cast(src.mSize)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Get"), static_cast(src.mGet)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Hit"), static_cast(src.mHit)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Expired"), static_cast(src.mExpired)); + } +} diff --git a/components/resource/cachestats.hpp b/components/resource/cachestats.hpp new file mode 100644 index 0000000000..c25f801dba --- /dev/null +++ b/components/resource/cachestats.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_CACHESATS +#define OPENMW_COMPONENTS_RESOURCE_CACHESATS + +#include +#include +#include + +namespace osg +{ + class Stats; +} + +namespace Resource +{ + struct CacheStats + { + std::size_t mSize = 0; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; + }; + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out); + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst); +} + +#endif diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 09c7048059..a7d2ef61a1 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -202,7 +202,7 @@ namespace Resource void ImageManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); + Resource::reportStats("Image", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 68b7adbe9a..0cbbe40d60 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -250,7 +249,7 @@ namespace Resource void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize()); + Resource::reportStats("Keyframe", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index f94dabc77c..71500b0ceb 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -25,6 +25,7 @@ namespace Resource { objectsToRemove.push_back(oitr->second); _objectCache.erase(oitr++); + ++mExpired; } else { @@ -57,13 +58,15 @@ namespace Resource osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string& fileName) { std::lock_guard lock(_objectCacheMutex); + ++mGet; ObjectCacheMap::iterator found = _objectCache.find(fileName); if (found == _objectCache.end()) return osg::ref_ptr(); else { - osg::ref_ptr object = found->second; + osg::ref_ptr object = std::move(found->second); _objectCache.erase(found); + ++mHit; return object; } } @@ -79,10 +82,15 @@ namespace Resource } } - unsigned int MultiObjectCache::getCacheSize() const + CacheStats MultiObjectCache::getStats() const { std::lock_guard lock(_objectCacheMutex); - return _objectCache.size(); + return CacheStats{ + .mSize = _objectCache.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; } } diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index e1629f3197..654a88b524 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -8,6 +8,8 @@ #include #include +#include "cachestats.hpp" + namespace osg { class Object; @@ -37,13 +39,16 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); - unsigned int getCacheSize() const; + CacheStats getStats() const; protected: typedef std::multimap> ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index c66c7de849..481126f304 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -59,7 +58,7 @@ namespace Resource void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize()); + Resource::reportStats("Nif", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index dffa0e9fdb..e619b7102c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -20,6 +20,8 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE +#include "cachestats.hpp" + #include #include #include @@ -29,26 +31,28 @@ #include #include #include +#include namespace osg { class Object; class State; class NodeVisitor; + class Stats; } namespace Resource { + struct GenericObjectCacheItem + { + osg::ref_ptr mValue; + double mLastUsage; + }; template class GenericObjectCache : public osg::Referenced { public: - GenericObjectCache() - : osg::Referenced(true) - { - } - // Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced // from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other // places so nullptr or not references elsewhere items are not always removed. @@ -64,6 +68,7 @@ namespace Resource item.mLastUsage = referenceTime; if (item.mLastUsage > expiryTime) return false; + ++mExpired; if (item.mValue != nullptr) objectsToRemove.push_back(std::move(item.mValue)); return true; @@ -105,34 +110,29 @@ namespace Resource osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) - return itr->second.mValue; - else - return nullptr; + if (Item* const item = find(key)) + return item->mValue; + return nullptr; } std::optional> getRefFromObjectCacheOrNone(const auto& key) { const std::lock_guard lock(mMutex); - const auto it = mItems.find(key); - if (it == mItems.end()) - return std::nullopt; - return it->second.mValue; + if (Item* const item = find(key)) + return item->mValue; + return std::nullopt; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) + if (Item* const item = find(key)) { - itr->second.mLastUsage = timeStamp; + item->mLastUsage = timeStamp; return true; } - else - return false; + return false; } /** call releaseGLObjects on all objects attached to the object cache.*/ @@ -162,13 +162,6 @@ namespace Resource f(k, v.mValue.get()); } - /** Get the number of objects in the cache. */ - unsigned int getCacheSize() const - { - std::lock_guard lock(mMutex); - return mItems.size(); - } - template std::optional>> lowerBound(K&& key) { @@ -179,21 +172,36 @@ namespace Resource return std::pair(it->first, it->second.mValue); } - protected: - struct Item + CacheStats getStats() const { - osg::ref_ptr mValue; - double mLastUsage; - }; + const std::lock_guard lock(mMutex); + return CacheStats{ + .mSize = mItems.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; + } + + protected: + using Item = GenericObjectCacheItem; std::map> mItems; mutable std::mutex mMutex; - }; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; - class ObjectCache : public GenericObjectCache - { + Item* find(const auto& key) + { + ++mGet; + const auto it = mItems.find(key); + if (it == mItems.end()) + return nullptr; + ++mHit; + return &it->second; + } }; - } #endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 45c84f093f..e4d0b4363d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1125,7 +1125,7 @@ namespace Resource stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets()); } - stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); + Resource::reportStats("Node", frameNumber, mCache->getStats(), *stats); } osg::ref_ptr SceneManager::createShaderVisitor(const std::string& shaderPrefix) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index e361be36dd..65b009deff 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -20,6 +20,8 @@ #include +#include "cachestats.hpp" + namespace Resource { namespace @@ -45,52 +47,95 @@ namespace Resource bool collectStatUpdate = false; bool collectStatEngine = false; - const std::vector allStatNames = { - "FrameNumber", - "Compiling", - "WorkQueue", - "WorkThread", - "UnrefQueue", - "Texture", - "StateSet", - "Node", - "Shape", - "Shape Instance", - "Image", - "Nif", - "Keyframe", - "Groundcover Chunk", - "Object Chunk", - "Terrain Chunk", - "Terrain Texture", - "Land", - "Composite", - "Mechanics Actors", - "Mechanics Objects", - "Physics Actors", - "Physics Objects", - "Physics Projectiles", - "Physics HeightFields", - "Lua UsedMemory", - "CellPreloader Count", - "CellPreloader Added", - "CellPreloader Evicted", - "CellPreloader Loaded", - "CellPreloader Expired", - "NavMesh Jobs", - "NavMesh Waiting", - "NavMesh Pushed", - "NavMesh Processing", - "NavMesh DbJobs Write", - "NavMesh DbJobs Read", - "NavMesh DbCache Get", - "NavMesh DbCache Hit", - "NavMesh CacheSize", - "NavMesh UsedTiles", - "NavMesh CachedTiles", - "NavMesh Cache Get", - "NavMesh Cache Hit", - }; + std::vector generateAllStatNames() + { + constexpr std::string_view firstPage[] = { + "FrameNumber", + "", + "Compiling", + "WorkQueue", + "WorkThread", + "UnrefQueue", + "", + "Texture", + "StateSet", + "Composite", + "", + "Mechanics Actors", + "Mechanics Objects", + "", + "Physics Actors", + "Physics Objects", + "Physics Projectiles", + "Physics HeightFields", + "", + "Lua UsedMemory", + "", + "", + "", + "", + }; + + constexpr std::string_view caches[] = { + "Node", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "Groundcover Chunk", + "Object Chunk", + "Terrain Chunk", + "Terrain Texture", + "Land", + }; + + constexpr std::string_view cellPreloader[] = { + "CellPreloader Count", + "CellPreloader Added", + "CellPreloader Evicted", + "CellPreloader Loaded", + "CellPreloader Expired", + }; + + constexpr std::string_view navMesh[] = { + "NavMesh Jobs", + "NavMesh Waiting", + "NavMesh Pushed", + "NavMesh Processing", + "NavMesh DbJobs Write", + "NavMesh DbJobs Read", + "NavMesh DbCache Get", + "NavMesh DbCache Hit", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + "NavMesh Cache Get", + "NavMesh Cache Hit", + }; + + std::vector statNames; + + for (std::string_view name : firstPage) + statNames.emplace_back(name); + + for (std::size_t i = 0; i < std::size(caches); ++i) + { + Resource::addCacheStatsAttibutes(caches[i], statNames); + if ((i + 1) % 5 != 0) + statNames.emplace_back(); + } + + for (std::string_view name : cellPreloader) + statNames.emplace_back(name); + + statNames.emplace_back(); + + for (std::string_view name : navMesh) + statNames.emplace_back(name); + + return statNames; + } void setupStatCollection() { @@ -258,6 +303,7 @@ namespace Resource , mSwitch(new osg::Switch) , mCamera(new osg::Camera) , mTextFont(getMonoFont(vfs)) + , mStatNames(generateAllStatNames()) { osg::ref_ptr stateset = mSwitch->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); @@ -465,7 +511,7 @@ namespace Resource const osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); const osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - const auto longest = std::max_element(allStatNames.begin(), allStatNames.end(), + const auto longest = std::max_element(mStatNames.begin(), mStatNames.end(), [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); const std::size_t longestSize = longest->size(); const float statNamesWidth = longestSize * characterSize * 0.6 + 2 * backgroundMargin; @@ -473,14 +519,14 @@ namespace Resource const float statHeight = pageSize * characterSize + 2 * backgroundMargin; const float width = statNamesWidth + backgroundSpacing + statTextWidth; - for (std::size_t offset = 0; offset < allStatNames.size(); offset += pageSize) + for (std::size_t offset = 0; offset < mStatNames.size(); offset += pageSize) { osg::ref_ptr group = new osg::Group; group->setCullingActive(false); - const std::size_t count = std::min(allStatNames.size() - offset, pageSize); - std::span currentStatNames(allStatNames.data() + offset, count); + const std::size_t count = std::min(mStatNames.size() - offset, pageSize); + std::span currentStatNames(mStatNames.data() + offset, count); osg::Vec3 pos(statsWidth - width, statHeight - characterSize, 0.0f); group->addChild( diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 7381dac417..0ea421e83d 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -57,6 +57,7 @@ namespace Resource osg::ref_ptr mSwitch; osg::ref_ptr mCamera; osg::ref_ptr mTextFont; + std::vector mStatNames; void setWindowSize(int w, int h); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 8df5dc3a77..7ccd89ac21 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -63,7 +63,7 @@ namespace Terrain void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize()); + Resource::reportStats("Terrain Chunk", frameNumber, mCache->getStats(), *stats); } void ChunkManager::clearCache() diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index 360d87bf48..6b388faf69 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -1,6 +1,5 @@ #include "texturemanager.hpp" -#include #include #include @@ -56,7 +55,7 @@ namespace Terrain void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize()); + Resource::reportStats("Terrain Texture", frameNumber, mCache->getStats(), *stats); } } From f70bf42a9ec206b13cfca82d208e4b3b0ac24b92 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Mar 2024 21:50:45 +0100 Subject: [PATCH 1258/2167] Remove superfluous Trading class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/tradewindow.cpp | 72 +++++++++++++++++++++++- apps/openmw/mwgui/tradewindow.hpp | 3 - apps/openmw/mwmechanics/trading.cpp | 87 ----------------------------- apps/openmw/mwmechanics/trading.hpp | 20 ------- 5 files changed, 72 insertions(+), 112 deletions(-) delete mode 100644 apps/openmw/mwmechanics/trading.cpp delete mode 100644 apps/openmw/mwmechanics/trading.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f92e8a0bc1..c9bfa87648 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -103,7 +103,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + character actors objects aistate weaponpriority spellpriority weapontype spellutil spelleffects ) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c62d360412..ba752303d2 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -42,6 +43,75 @@ namespace return static_cast(price * count); } + bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) + { + // accept if merchant offer is better than player offer + if (playerOffer <= merchantOffer) + { + return true; + } + + // reject if npc is a creature + if (merchant.getType() != ESM::NPC::sRecordId) + { + return false; + } + + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + // Is the player buying? + bool buying = (merchantOffer < 0); + int a = std::abs(merchantOffer); + int b = std::abs(playerOffer); + int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); + + int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); + + const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); + const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); + + float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); + float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); + float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); + float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); + float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); + + float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); + float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); + float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); + float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d + + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::rollDice(100, prng) + 1; + + // reject if roll fails + // (or if player tries to buy things and get money) + if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) + { + return false; + } + + // apply skill gain on successful barter + float skillGain = 0.f; + int finalPrice = std::abs(playerOffer); + int initialMerchantOffer = std::abs(merchantOffer); + + if (!buying && (finalPrice > initialMerchantOffer)) + { + skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + } + else if (buying && (finalPrice < initialMerchantOffer)) + { + skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + } + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); + + return true; + } } namespace MWGui @@ -328,7 +398,7 @@ namespace MWGui } } - bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); + bool offerAccepted = haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); // apply disposition change if merchant is NPC if (mPtr.getClass().isNpc()) diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 7d5fd399df..33c39cb269 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -1,8 +1,6 @@ #ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H -#include "../mwmechanics/trading.hpp" - #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -53,7 +51,6 @@ namespace MWGui ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; - MWMechanics::Trading mTrading; static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp deleted file mode 100644 index b7e361c0b9..0000000000 --- a/apps/openmw/mwmechanics/trading.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "trading.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - Trading::Trading() {} - - bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) - { - // accept if merchant offer is better than player offer - if (playerOffer <= merchantOffer) - { - return true; - } - - // reject if npc is a creature - if (merchant.getType() != ESM::NPC::sRecordId) - { - return false; - } - - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - // Is the player buying? - bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); - int b = std::abs(playerOffer); - int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); - - int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); - - const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); - const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); - - float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); - float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); - float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); - float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); - float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); - - float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); - float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); - float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); - float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d - + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); - - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::rollDice(100, prng) + 1; - - // reject if roll fails - // (or if player tries to buy things and get money) - if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) - { - return false; - } - - // apply skill gain on successful barter - float skillGain = 0.f; - int finalPrice = std::abs(playerOffer); - int initialMerchantOffer = std::abs(merchantOffer); - - if (!buying && (finalPrice > initialMerchantOffer)) - { - skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); - } - else if (buying && (finalPrice < initialMerchantOffer)) - { - skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); - } - player.getClass().skillUsageSucceeded( - player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); - - return true; - } -} diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp deleted file mode 100644 index e30b82f5e8..0000000000 --- a/apps/openmw/mwmechanics/trading.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OPENMW_MECHANICS_TRADING_H -#define OPENMW_MECHANICS_TRADING_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class Trading - { - public: - Trading(); - - bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer); - }; -} - -#endif From d08e47bc40b436e3c5114a1eb912cdf581fdcec9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Mar 2024 22:34:53 +0100 Subject: [PATCH 1259/2167] Expose AI stats to Lua --- CHANGELOG.md | 1 + apps/openmw/mwlua/stats.cpp | 73 +++++++++++++++++++++++++++++++++- files/lua_api/openmw/types.lua | 34 ++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..4211f47538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -217,6 +217,7 @@ Feature #7792: Support Timescale Clouds Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context + Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ad0f585207..209a852697 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -31,7 +31,7 @@ namespace using Index = const SelfObject::CachedStat::Index&; template - auto addIndexedAccessor(Index index) + auto addIndexedAccessor(auto index) { return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); }; } @@ -425,6 +425,62 @@ namespace MWLua stats.setSkill(id, stat); } }; + + class AIStat + { + ObjectVariant mObject; + MWMechanics::AiSetting mIndex; + + AIStat(ObjectVariant object, MWMechanics::AiSetting index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &AIStat::setValue, static_cast(mIndex), prop, + [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).*getter)(); + }); + } + + int getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::Stat::getBase)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::Stat::getModifier)); + return std::max(0, base + modifier); + } + + static std::optional create(ObjectVariant object, MWMechanics::AiSetting index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return AIStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] = value; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto index = static_cast(std::get(i)); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAiSetting(index); + int intValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(intValue); + else if (prop == "modifier") + stat.setModifier(intValue); + stats.setAiSetting(index, stat); + } + }; } namespace sol @@ -465,6 +521,10 @@ namespace sol struct is_automagical : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -529,6 +589,17 @@ namespace MWLua stats["attributes"] = LuaUtil::makeReadOnly(attributes); for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) attributes[ESM::RefId(attribute.mId).serializeText()] = addIndexedAccessor(attribute.mId); + + auto aiStatT = context.mLua->sol().new_usertype("AIStat"); + addProp(context, aiStatT, "base", &MWMechanics::Stat::getBase); + addProp(context, aiStatT, "modifier", &MWMechanics::Stat::getModifier); + aiStatT["modified"] = sol::readonly_property([=](const AIStat& stat) { return stat.getModified(context); }); + sol::table ai(context.mLua->sol(), sol::create); + stats["ai"] = LuaUtil::makeReadOnly(ai); + ai["alarm"] = addIndexedAccessor(MWMechanics::AiSetting::Alarm); + ai["fight"] = addIndexedAccessor(MWMechanics::AiSetting::Fight); + ai["flee"] = addIndexedAccessor(MWMechanics::AiSetting::Flee); + ai["hello"] = addIndexedAccessor(MWMechanics::AiSetting::Hello); } void addNpcStatsBindings(sol::table& npc, const Context& context) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index e935fcbba3..149d9bd9fa 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -445,6 +445,12 @@ -- @field #number modifier The skill's modifier. -- @field #number progress [0-1] The NPC's skill progress. +--- +-- @type AIStat +-- @field #number base The stat's base value. +-- @field #number modifier The stat's modifier. +-- @field #number modified The actor's current ai value (read-only.) + --- -- @type DynamicStats @@ -466,6 +472,33 @@ -- @param openmw.core#GameObject actor -- @return #DynamicStat +--- +-- @type AIStats + +--- +-- Alarm (returns @{#AIStat}) +-- @function [parent=#AIStats] alarm +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Fight (returns @{#AIStat}) +-- @function [parent=#AIStats] fight +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Flee (returns @{#AIStat}) +-- @function [parent=#AIStats] flee +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Hello (returns @{#AIStat}) +-- @function [parent=#AIStats] hello +-- @param openmw.core#GameObject actor +-- @return #AIStat + --- -- @type AttributeStats @@ -686,6 +719,7 @@ -- @type ActorStats -- @field #DynamicStats dynamic -- @field #AttributeStats attributes +-- @field #AIStats ai --- -- Level (returns @{#LevelStat}) From cb357997c9c07be7e696a62c046133b2ec6d2394 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Mar 2024 14:36:45 +0100 Subject: [PATCH 1260/2167] Copy DIAL type to INFO when saving --- CHANGELOG.md | 1 + apps/opencs/model/doc/savingstages.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..a8f7ee4d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7861: OpenMW-CS: Incorrect DIAL's type in INFO records Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 82135e0042..77effc3a5c 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -135,7 +135,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - ESM::Dialogue dialogue = topic.get(); + const ESM::Dialogue& dialogue = topic.get(); writer.startRecord(dialogue.sRecordId); dialogue.save(writer, true); writer.endRecord(dialogue.sRecordId); @@ -187,6 +187,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages { ESM::DialInfo info = record.get(); info.mId = record.get().mOriginalId; + info.mData.mType = topic.get().mType; if (iter == infos.begin()) info.mPrev = ESM::RefId(); From 4607722ce90310eccb206113ceacc4526f20a2d4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Mar 2024 11:37:02 +0200 Subject: [PATCH 1261/2167] Increment API version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5263d849e8..2bffdba34e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 58) +set(OPENMW_LUA_API_REVISION 59) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") From 7a291e502518d180b34076fbd1e0b894d47d989a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 27 Mar 2024 17:50:55 -0500 Subject: [PATCH 1262/2167] FIX(CS): Re-add gold value column for objects --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/refidcollection.cpp | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 570e4134c1..bdbb8f697c 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -57,6 +57,7 @@ namespace CSMWorld { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, { ColumnId_StackCount, "Count" }, + { ColumnId_GoldValue, "Value" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_IsLocked, "Locked" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 066f6395aa..3c4bff07f6 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -355,6 +355,8 @@ namespace CSMWorld ColumnId_ProjectileSpeed = 319, + ColumnId_GoldValue = 320, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 694f67e445..c3af3d4673 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection() inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_GoldValue, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns(inventoryColumns); From bb3c22e4a584ee550f394e2e1c13aa505ec71eab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 1 Apr 2024 00:15:58 +0100 Subject: [PATCH 1263/2167] Add and register SettingValue stream operators --- components/config/gamesettings.cpp | 21 +++++++++++++++++++++ components/config/gamesettings.hpp | 3 +++ 2 files changed, 24 insertions(+) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 21110562d5..a7da8fa150 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -24,6 +24,11 @@ namespace Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg) : mCfgMgr(cfg) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // this needs calling once so Qt can see its stream operators, which it needs when dragging and dropping + // it's automatic with Qt 6 + qRegisterMetaTypeStreamOperators("Config::SettingValue"); +#endif } void Config::GameSettings::validatePaths() @@ -591,3 +596,19 @@ void Config::GameSettings::clear() mDataDirs.clear(); mDataLocal.clear(); } + +QDataStream& Config::operator<<(QDataStream& out, const SettingValue& settingValue) +{ + out << settingValue.value; + out << settingValue.originalRepresentation; + out << settingValue.context; + return out; +} + +QDataStream& Config::operator>>(QDataStream& in, SettingValue& settingValue) +{ + in >> settingValue.value; + in >> settingValue.originalRepresentation; + in >> settingValue.context; + return in; +} diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index d23f225eb0..7627d5153a 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -132,6 +132,9 @@ namespace Config static bool isOrderedLine(const QString& line); }; + + QDataStream& operator<<(QDataStream& out, const SettingValue& settingValue); + QDataStream& operator>>(QDataStream& in, SettingValue& settingValue); } Q_DECLARE_METATYPE(Config::SettingValue) From 36ca3b8bd923e826eff476a27729763dd71d6632 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Mon, 1 Apr 2024 20:55:38 +0000 Subject: [PATCH 1264/2167] French localisation update --- files/data/l10n/Interface/fr.yaml | 2 +- files/data/l10n/OMWControls/fr.yaml | 72 ++++++++++++++++++++++++++++- files/data/l10n/OMWEngine/fr.yaml | 67 ++++++++++++++------------- files/data/l10n/OMWShaders/fr.yaml | 1 + 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/files/data/l10n/Interface/fr.yaml b/files/data/l10n/Interface/fr.yaml index bac4346364..0a078a1676 100644 --- a/files/data/l10n/Interface/fr.yaml +++ b/files/data/l10n/Interface/fr.yaml @@ -22,4 +22,4 @@ None: "Aucun" OK: "Valider" Cancel: "Annuler" Close: "Fermer" -#Copy: "Copy" +Copy: "Copier" diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index dab9ddb8fc..95fa88a6ac 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -9,11 +9,79 @@ alwaysRunDescription: | Inactif : Le personnage se déplace par défaut en marchant.\n\n La touche Maj. inverse temporairement ce paramètre.\n\n La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée. + toggleSneak: "Mode discrétion maintenu" toggleSneakDescription: | Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mouvements à la manette adoucis" +smoothControllerMovementDescription: | + Active le lissage des mouvements du stick analogique. Ceci permet une transition entre marche et course moins abrupte. + +TogglePOV_name: "Changer de vue" +TogglePOV_description: "Change entre la vue à la première et la troisième personne. Maintenir la touche pour activer le mode attente." + +Zoom3rdPerson_name: "Zoom Avant/Arrière" +Zoom3rdPerson_description: "Approche / éloigne la caméra lorsque la caméra est à la troisième personne." + +MoveForward_name: "En avant" +MoveForward_description: "Annulé par En arrière." + +MoveBackward_name: "En arrière" +MoveBackward_description: "Annulé par En avant." + +MoveLeft_name: "À gauche" +MoveLeft_description: "Annulé par Droite." + +MoveRight_name: "À droite" +MoveRight_description: "Annulé par Gauche." + +Use_name: "Utiliser" +Use_description: "Attaque avec une arme ou lance une magie, dépend de la préparation du joueur." + +Run_name: "Courir" +Run_description: "Maintenir pour courir/marcher, en fonction du paramètre Toujours Courir." + +AlwaysRun_name: "Toujours courir" +AlwaysRun_description: "Active le paramètre Toujours courir." + +Jump_name: "Sauter" +Jump_description: "Saute lorsque le joueur touche le sol." + +AutoMove_name: "Course automatique" +AutoMove_description: "Active un mouvement continu vers l'avant." + +Sneak_name: "Discrétion" +Sneak_description: "Maintenez la touche si le paramètre Mode discrétion maintenu est désactivé." + +ToggleSneak_name: "Maintenir la discrétion" +ToggleSneak_description: "Entre en mode discrétion si le paramètre Mode discrétion maintenu est activé." + +ToggleWeapon_name: "Préparer arme" +ToggleWeapon_description: "Entre ou sort de la position armée." + +ToggleSpell_name: "Préparer sort" +ToggleSpell_description: "Entre ou sort de la position lanceur de sort." + +Inventory_name: "Inventaire" +Inventory_description: "Ouvre l'inventaire." + +Journal_name: "Journal" +Journal_description: "Ouvre le journal." + +QuickKeysMenu_name: "Menu Touches rapides" +QuickKeysMenu_description: "Ouvre le menu des touches rapides." + +SmoothMoveForward_name: "En avant, doux" +SmoothMoveForward_description: "Déplace le joueur en avant, avec une transition douce entre la marche et la course." + +SmoothMoveBackward_name: "En arrière, doux" +SmoothMoveBackward_description: "Déplace le joueur en arrière, avec une transition douce entre la marche et la course." + +SmoothMoveLeft_name: "À gauche, doux" +SmoothMoveLeft_description: "Déplace le joueur à gauche, avec une transition douce entre la marche et la course.." + +SmoothMoveRight_name: "À droite, doux" +SmoothMoveRight_description: "Déplace le joueur à droite, avec une transition douce entre la marche et la course." diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 990ecfce9d..814314ff5a 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -2,11 +2,11 @@ ConsoleWindow: "Console" - # Debug window DebugWindow: "Fenêtre de débogage" LogViewer: "Journal" +LuaProfiler: "Profileur Lua" PhysicsProfiler: "Profileur des performances de la physique" @@ -22,22 +22,19 @@ LoadingInProgress: "Chargement de la sauvegarde" LoadingRequiresNewVersionError: |- Ce fichier de sauvegarde provient d'une version plus récente d'OpenMW, il n'est par consequent pas supporté. Mettez à jour votre version d'OpenMW afin de pouvoir charger cette sauvegarde. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. +LoadingRequiresOldVersionError: |- + Ce fichier de sauvegarde provient d'une version trop ancienne d'OpenMW, il n'est par consequent pas supporté. + Ouvrez et enregistez cette sauvegarde avec {version} pour la mettre à jour. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." QuitGameConfirmation: "Quitter la partie ?" SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Échec de la sauvegarde de la capture d'écran" +ScreenshotMade: "Capture d'écran %s sauvegardée" # Save game menu -SelectCharacter: "Sélection du personnage..." -TimePlayed: "Temps de jeu" - DeleteGame: "Supprimer la partie" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" @@ -46,19 +43,21 @@ MissingContentFilesConfirmation: |- Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie. Voulez-vous continuer ? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nUn fichier manquant trouvé : } + few{\n\n{files} fichiers manquant trouvés :\n} + other{\n\n{files} fichiers manquant trouvés :\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nCliquez sur Copier pour placer ce nom dans le presse-papier.} + few{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + other{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + } OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?" +SelectCharacter: "Sélection du personnage..." +TimePlayed: "Temps de jeu" # Settings menu @@ -100,31 +99,31 @@ InvertXAxis: "Inverser l'axe X" InvertYAxis: "Inverser l'axe Y" Language: "Localisation" LanguageNote: "Note: Ce paramètre n'affecte pas les textes des fichiers ESM." -LightingMethodLegacy: "Traditionnelle" LightingMethod: "Méthode d'affichage des lumières" -LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" +LightingMethodLegacy: "Traditionnelle" LightingMethodShaders: "Shaders" +LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" LightingResetToDefaults: "Voulez-vous réinitialiser les paramètres d'affichage des lumières à leur valeur par défaut ? Ces changements requièrent un redémarrage de l'application." +Lights: "Sources lumineuses" LightsBoundingSphereMultiplier: "Multiplicateur de sphère englobante" LightsBoundingSphereMultiplierTooltip: "valeur par défaut: 1.65\nMultiplicateur pour le rayon de la sphère incluant les sources lumineuses.\nUn multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.\n\nCe paramètre ne modifie ni l'intensité ni la luminance des lumières." LightsFadeStartMultiplier: "Seuil de perte d'éclat lumineux" LightsFadeStartMultiplierTooltip: "valeur par défaut: 0.85\nFraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.\n\nSélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Définit la gestion des sources lumineuses :\n\n + \"Traditionnelle\" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.\n\n + \"Shaders (mode de compatibilité)\" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.\n\n + \"Shaders\" offre tous les bénéfices apportés par \"Shaders (mode de compatibilité)\", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance." LightsMaximumDistance: "Distance maximale des sources lumineuses" LightsMaximumDistanceTooltip: "valeur par défaut: 8192\nDistance maximale d'affichage des sources lumineuses (en unité de distance).\n\nMettez cette valeur à 0 pour une distance d'affichage infinie." LightsMinimumInteriorBrightness: "Luminosité intérieure minimale" LightsMinimumInteriorBrightnessTooltip: "valeur par défaut: 0.08\nLuminosité ambiante minimum en intérieur.\n\nAugmentez cette valeur si les intérieurs vous semblent trop sombres." -Lights: "Sources lumineuses" MaxLights: "Maximum de sources lumineuses" MaxLightsTooltip: "valeur par défaut: 8\nNombre maximum de sources lumineuses par objet.\n\nUne valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle." MenuHelpDelay: "Délai d'affichage du menu d'aide" MenuTransparency: "Transparence des menus" MouseAndKeyboard: "Souris/Clavier" -PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessing: "Post-traitement" +PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessingTooltip: "Modifiable dans le HUD de post-traitement, accessible à partir les paramètres de contrôle." Preferences: "Préférences" PrimaryLanguage: "Localisation principale" @@ -147,19 +146,20 @@ ReflectionShaderDetailWorld: "Monde" Refraction: "Réfraction" ResetControls: "Réinitialiser les contrôles" Screenshot: "Capture d'écran" -ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." Scripts: "Scripts" +ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." SecondaryLanguage: "Localisation secondaire" SecondaryLanguageTooltip: "Localisation utilisée si le texte est absent de la localisation principale." SensitivityHigh: "Haute" SensitivityLow: "Faible" SettingsWindow: "Options" Subtitles: "Sous-titres" +SunlightScattering: "Diffusion des rayons solaires" TestingExteriorCells: "Vérification des espaces (cells) extérieurs" TestingInteriorCells: "Vérification des espaces (cells) intérieurs" +TextureFiltering: "Filtre appliqué aux textures" TextureFilteringBilinear: "Bilinéaire" TextureFilteringDisabled: "Aucun" -TextureFiltering: "Filtre appliqué aux textures" TextureFilteringOther: "Autre" TextureFilteringTrilinear: "Trilinéaire" ToggleHUD: "Afficher/masquer le HUD" @@ -169,11 +169,14 @@ TransparencyNone: "Basse" Video: "Graphisme" ViewDistance: "Distance d'affichage" VSync: "VSync" +VSyncAdaptive: "Adaptif" Water: "Eau" WaterShader: "Shader pour l'eau" WaterShaderTextureQuality: "Qualité des textures" WindowBorder: "Bordure de fenêtre" -WindowModeFullscreen: "Plein écran" WindowMode: "Mode d'affichage" +WindowModeFullscreen: "Plein écran" +WindowModeHint: "Info : Le mode \"Fenêtré plein écran\" utilise toujours la résolution native de l'écran." WindowModeWindowed: "Fenêtré" WindowModeWindowedFullscreen: "Fenêtré plein écran" +WobblyShores: "Rivages vacillants" diff --git a/files/data/l10n/OMWShaders/fr.yaml b/files/data/l10n/OMWShaders/fr.yaml index 3b4e47370e..45513589eb 100644 --- a/files/data/l10n/OMWShaders/fr.yaml +++ b/files/data/l10n/OMWShaders/fr.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Rapport couleur/profondeur" DisplayDepthFactorDescription: "Détermine la corrélation entre la valeur de profondeur d'un pixel et sa couleur de sortie. Une valeur élevée mène à une image plus lumineuse." DisplayDepthName: "Visualise le Z-buffer (tampon de profondeur)." DisplayNormalsName: "Visualise le G-buffer de normales (normals pass)" +NormalsInWorldSpace: "Affiche la normale de la surface de chaque objet" ContrastLevelDescription: "Niveau de contraste" ContrastLevelName: "Contraste" GammaLevelDescription: "Correction Gamma" From b3188b593cc61af3e68ff7932b6f713c67244fe9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 15:20:24 +0400 Subject: [PATCH 1265/2167] Disable MyGUI windows snapping --- files/data/mygui/openmw_windows.skin.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/files/data/mygui/openmw_windows.skin.xml b/files/data/mygui/openmw_windows.skin.xml index 14f6930b3c..9491862b34 100644 --- a/files/data/mygui/openmw_windows.skin.xml +++ b/files/data/mygui/openmw_windows.skin.xml @@ -457,7 +457,6 @@ - @@ -592,7 +591,6 @@ - @@ -729,7 +727,6 @@ - From 939760007e37b06a9d05445ee7ca400b59446941 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 15:20:53 +0400 Subject: [PATCH 1266/2167] Resize console window on resolution change, not reset it --- apps/openmw/mwgui/console.cpp | 5 ----- apps/openmw/mwgui/console.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b430e08142..a188f3c86b 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -771,11 +771,6 @@ namespace MWGui return output.append(matches.front()); } - void Console::onResChange(int width, int height) - { - setCoord(10, 10, width - 10, height / 2); - } - void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) { if (mPtr == currentPtr) diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 79d18847a4..2b6ecfc8ad 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -47,8 +47,6 @@ namespace MWGui void onOpen() override; - void onResChange(int width, int height) override; - // Print a message to the console, in specified color. void print(const std::string& msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); From 1b544b93d25149a375791949cc7f54fcacd654a7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 16:07:29 +0400 Subject: [PATCH 1267/2167] Do not allow to move resizable windows outside of game window --- apps/openmw/mwgui/inventorywindow.cpp | 2 ++ apps/openmw/mwgui/windowbase.cpp | 29 ++++++++++++++++++++++++++ apps/openmw/mwgui/windowbase.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 12 ++++++++--- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0fbd15dda2..4805f7f3cb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -417,6 +417,8 @@ namespace MWGui void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { + WindowBase::clampWindowCoordinates(_sender); + adjustPanes(); const WindowSettingValues settings = getModeSettings(mGuiMode); diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index a680e38cf8..eff5c98588 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -7,6 +7,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include #include #include "draganddrop.hpp" @@ -77,6 +78,34 @@ void WindowBase::center() mMainWidget->setCoord(coord); } +void WindowBase::clampWindowCoordinates(MyGUI::Window* window) +{ + auto minSize = window->getMinSize(); + minSize.height = static_cast(minSize.height * Settings::gui().mScalingFactor); + minSize.width = static_cast(minSize.width * Settings::gui().mScalingFactor); + + // Window's minimum size is larger than the screen size, can not clamp coordinates + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (minSize.width > viewSize.width || minSize.height > viewSize.height) + return; + + int left = std::max(0, window->getPosition().left); + int top = std::max(0, window->getPosition().top); + int width = std::clamp(window->getSize().width, 0, viewSize.width); + int height = std::clamp(window->getSize().height, 0, viewSize.height); + if (left + width > viewSize.width) + left = viewSize.width - width; + + if (top + height > viewSize.height) + top = viewSize.height - height; + + if (window->getSize().width != width || window->getSize().height != height) + window->setSize(width, height); + + if (window->getPosition().left != left || window->getPosition().top != top) + window->setPosition(left, top); +} + WindowModal::WindowModal(const std::string& parLayout) : WindowBase(parLayout) { diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 54fb269305..466060c6ad 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -52,6 +52,8 @@ namespace MWGui virtual std::string_view getWindowIdForLua() const { return ""; } void setDisabledByLua(bool disabled) { mDisabledByLua = disabled; } + static void clampWindowCoordinates(MyGUI::Window* window); + protected: virtual void onTitleDoubleClicked(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 0105d7c1ba..61a8b3361c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1200,6 +1200,8 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; window->setPosition(MyGUI::IntPoint(static_cast(rect.mX * x), static_cast(rect.mY * y))); window->setSize(MyGUI::IntSize(static_cast(rect.mW * x), static_cast(rect.mH * y))); + + WindowBase::clampWindowCoordinates(window); } for (const auto& window : mWindows) @@ -1714,13 +1716,15 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; - layout->mMainWidget->setPosition( + MyGUI::Window* window = layout->mMainWidget->castType(); + window->setPosition( MyGUI::IntPoint(static_cast(rect.mX * viewSize.width), static_cast(rect.mY * viewSize.height))); - layout->mMainWidget->setSize( + window->setSize( MyGUI::IntSize(static_cast(rect.mW * viewSize.width), static_cast(rect.mH * viewSize.height))); - MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + WindowBase::clampWindowCoordinates(window); + mTrackedWindows.emplace(window, settings); } @@ -1750,6 +1754,8 @@ namespace MWGui if (it == mTrackedWindows.end()) return; + WindowBase::clampWindowCoordinates(window); + const WindowSettingValues& settings = it->second; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); From 02e000ab5b1c817a15e5b52665838c8bd7cb2733 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 2 Apr 2024 11:56:30 +0400 Subject: [PATCH 1268/2167] Add changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ecf8bb8..587e5e7a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -221,6 +221,8 @@ Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) + Feature #7875: Disable MyGUI windows snapping + Feature #7914: Do not allow to move GUI windows out of screen Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION From 3b930e44716004045b57c291866502442b8df46c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 3 Apr 2024 07:12:53 +0000 Subject: [PATCH 1269/2167] Restore !613 --- apps/opencs/model/filter/parser.cpp | 5 ++- apps/opencs/model/filter/textnode.hpp | 2 ++ apps/opencs/model/prefs/state.cpp | 1 + apps/opencs/model/prefs/values.hpp | 1 + apps/opencs/model/world/idtableproxymodel.cpp | 35 ++++++++++++++++--- apps/opencs/model/world/idtableproxymodel.hpp | 9 +++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index aadad5f8f5..6248b03bb4 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -452,7 +452,10 @@ std::shared_ptr CSMFilter::Parser::parseText() return std::shared_ptr(); } - return std::make_shared(columnId, text); + auto node = std::make_shared(columnId, text); + if (!node->isValid()) + error(); + return node; } std::shared_ptr CSMFilter::Parser::parseValue() diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp index 14efa0a3a0..d629cbe336 100644 --- a/apps/opencs/model/filter/textnode.hpp +++ b/apps/opencs/model/filter/textnode.hpp @@ -34,6 +34,8 @@ namespace CSMFilter ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. + + bool isValid() { return mRegExp.isValid(); } }; } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c11996a6ea..ba3a544f36 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -90,6 +90,7 @@ void CSMPrefs::State::declare() .setTooltip( "When editing a record, open the view in a new window," " rather than docked in the main view."); + declareInt(mValues->mIdTables.mFilterDelay, "Delay before applying a filter (in miliseconds)"); declareCategory("ID Dialogues"); declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 9899a239a9..d3f78e5d01 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -138,6 +138,7 @@ namespace CSMPrefs EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; + Settings::SettingValue mFilterDelay{ mIndex, sName, "filter-delay", 500 }; }; struct IdDialoguesCategory : Settings::WithIndex diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index d4e342f5de..c03b4ea30a 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -63,9 +63,18 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIn CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent) : QSortFilterProxyModel(parent) + , mFilterTimer{ new QTimer(this) } , mSourceModel(nullptr) { setSortCaseSensitivity(Qt::CaseInsensitive); + + mFilterTimer->setSingleShot(true); + int intervalSetting = CSMPrefs::State::get()["ID Tables"]["filter-delay"].toInt(); + mFilterTimer->setInterval(intervalSetting); + + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, + [this](const CSMPrefs::Setting* setting) { this->settingChanged(setting); }); + connect(mFilterTimer.get(), &QTimer::timeout, this, [this]() { this->timerTimeout(); }); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex(const std::string& id, int column) const @@ -87,10 +96,8 @@ void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel* model) void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr& filter) { - beginResetModel(); - mFilter = filter; - updateColumnMap(); - endResetModel(); + mAwaitingFilter = filter; + mFilterTimer->start(); } bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const @@ -131,6 +138,26 @@ void CSMWorld::IdTableProxyModel::refreshFilter() } } +void CSMWorld::IdTableProxyModel::timerTimeout() +{ + if (mAwaitingFilter) + { + beginResetModel(); + mFilter = mAwaitingFilter; + updateColumnMap(); + endResetModel(); + mAwaitingFilter.reset(); + } +} + +void CSMWorld::IdTableProxyModel::settingChanged(const CSMPrefs::Setting* setting) +{ + if (*setting == "ID Tables/filter-delay") + { + mFilterTimer->setInterval(setting->toInt()); + } +} + void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end) { refreshFilter(); diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 639cf47287..b7d38f5a2d 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -10,6 +10,9 @@ #include #include #include +#include + +#include "../prefs/state.hpp" #include "columns.hpp" @@ -29,6 +32,8 @@ namespace CSMWorld Q_OBJECT std::shared_ptr mFilter; + std::unique_ptr mFilterTimer; + std::shared_ptr mAwaitingFilter; std::map mColumnMap; // column ID, column index in this model (or -1) // Cache of enum values for enum columns (e.g. Modified, Record Type). @@ -68,6 +73,10 @@ namespace CSMWorld virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void timerTimeout(); + + void settingChanged(const CSMPrefs::Setting* setting); + signals: void rowAdded(const std::string& id); From 5e99545b9c5fd111218c60af6271e0f5e75df1fb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 3 Apr 2024 21:11:49 +0300 Subject: [PATCH 1270/2167] Don't discard stagger/KO animation movement --- apps/openmw/mwmechanics/character.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c847bae033..f7c381d33e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2698,6 +2698,9 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (mHitState != CharState_None) + return true; + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) return false; From dfdd7aa684a02594f2013ad9460e1c0f8fc9b2d1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 3 Apr 2024 21:16:47 +0300 Subject: [PATCH 1271/2167] Always queue movement even when there's none --- apps/openmw/mwmechanics/character.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f7c381d33e..646cee8a23 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2479,9 +2479,7 @@ namespace MWMechanics movement.x() *= scale; movement.y() *= scale; - // Update movement - if (movement != osg::Vec3f()) - world->queueMovement(mPtr, movement); + world->queueMovement(mPtr, movement); } mSkipAnim = false; From 2288a691d28ee6df2cd2d386a345a65c18002f9b Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:10:51 +0100 Subject: [PATCH 1272/2167] Replace osgAnimation bone underscore naming at load time, map bone instances, reset root bone transform each frame --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 24 ++++++-- apps/openmw/mwrender/animation.hpp | 1 + components/misc/strings/algorithm.hpp | 7 +++ components/resource/keyframemanager.cpp | 27 +++++---- components/resource/scenemanager.cpp | 79 ++++++++++++++++++++++++- components/sceneutil/extradata.cpp | 7 ++- components/sceneutil/osgacontroller.cpp | 53 +++++++++++++++++ components/sceneutil/osgacontroller.hpp | 3 + components/sceneutil/visitor.cpp | 49 ++++----------- components/sceneutil/visitor.hpp | 20 +++++++ 11 files changed, 211 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ecf8bb8..6c223026b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0baf68ed5d..1807b0050f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -530,6 +530,7 @@ namespace MWRender , mHasMagicEffects(false) , mAlpha(1.f) , mPlayScriptedOnly(false) + , mRequiresBoneMap(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -964,8 +965,17 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - SceneUtil::NodeMapVisitor visitor(mNodeMap); - mObjectRoot->accept(visitor); + // If the base of this animation is an osgAnimation, we should map the bones not matrix transforms + if (mRequiresBoneMap) + { + SceneUtil::NodeMapVisitorBoneOnly visitor(mNodeMap); + mObjectRoot->accept(visitor); + } + else + { + SceneUtil::NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + } mNodeMapCreated = true; } return mNodeMap; @@ -1447,10 +1457,9 @@ namespace MWRender } } + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1466,8 +1475,6 @@ namespace MWRender } else { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1479,6 +1486,10 @@ namespace MWRender mInsert->addChild(mObjectRoot); } + // osgAnimation formats with skeletons should have their nodemap be bone instances + // FIXME: better way to detect osgAnimation here instead of relying on extension? + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + if (previousStateset) mObjectRoot->setStateSet(previousStateset); @@ -1791,6 +1802,7 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); + return controller; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 22b7167a9c..4cff658011 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -246,6 +246,7 @@ namespace MWRender osg::ref_ptr mLightListCallback; bool mPlayScriptedOnly; + bool mRequiresBoneMap; const NodeMap& getNodeMap() const; diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 28bc696cd3..2bf7125c8b 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,6 +15,13 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; + inline std::string underscoresToSpaces(const std::string_view& oldName) + { + std::string newName(oldName); + std::replace(newName.begin(), newName.end(), '_', ' '); + return newName; + } + inline bool ciLess(std::string_view x, std::string_view y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), CiCharLess()); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0cbbe40d60..67a434e47b 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -37,11 +37,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_l_clavicle", "left_clavicle", "bip01_l_upperarm", "left_upper_arm", - "bip01_l_forearm", "bip01_l_hand", "left_hand", "left_wrist", "shield_bone", "bip01_l_pinky1", - "bip01_l_pinky2", "bip01_l_pinky3", "bip01_l_ring1", "bip01_l_ring2", "bip01_l_ring3", "bip01_l_middle1", - "bip01_l_middle2", "bip01_l_middle3", "bip01_l_pointer1", "bip01_l_pointer2", "bip01_l_pointer3", - "bip01_l_thumb1", "bip01_l_thumb2", "bip01_l_thumb3", "left_forearm" }; + static const std::array boneNames = { "bip01 l clavicle", "left clavicle", "bip01 l upperarm", "left upper arm", + "bip01 l forearm", "bip01 l hand", "left hand", "left wrist", "shield bone", "bip01 l pinky1", + "bip01 l pinky2", "bip01 l pinky3", "bip01 l ring1", "bip01 l ring2", "bip01 l ring3", "bip01 l middle1", + "bip01 l middle2", "bip01 l middle3", "bip01 l pointer1", "bip01 l pointer2", "bip01 l pointer3", + "bip01 l thumb1", "bip01 l thumb2", "bip01 l thumb3", "left forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -51,11 +51,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_r_clavicle", "right_clavicle", "bip01_r_upperarm", - "right_upper_arm", "bip01_r_forearm", "bip01_r_hand", "right_hand", "right_wrist", "bip01_r_thumb1", - "bip01_r_thumb2", "bip01_r_thumb3", "weapon_bone", "bip01_r_pinky1", "bip01_r_pinky2", "bip01_r_pinky3", - "bip01_r_ring1", "bip01_r_ring2", "bip01_r_ring3", "bip01_r_middle1", "bip01_r_middle2", "bip01_r_middle3", - "bip01_r_pointer1", "bip01_r_pointer2", "bip01_r_pointer3", "right_forearm" }; + static const std::array boneNames = { "bip01 r clavicle", "right clavicle", "bip01 r upperarm", + "right upper arm", "bip01 r forearm", "bip01 r hand", "right hand", "right wrist", "bip01 r thumb1", + "bip01 r thumb2", "bip01 r thumb3", "weapon bone", "bip01 r pinky1", "bip01 r pinky2", "bip01 r pinky3", + "bip01 r ring1", "bip01 r ring2", "bip01 r ring3", "bip01 r middle1", "bip01 r middle2", "bip01 r middle3", + "bip01 r pointer1", "bip01 r pointer2", "bip01 r pointer3", "right forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -66,7 +66,7 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name) { static const std::array boneNames - = { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" }; + = { "bip01 spine1", "bip01 spine2", "bip01 neck", "bip01 head", "head", "neck", "chest", "groin" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -88,9 +88,7 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") - { animation->setName(std::string("idle")); - } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -99,6 +97,9 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { + // Repalce channel target name to match the renamed bones/transforms + channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); + if (name == "Bip01 R Clavicle") { if (!belongsToRightUpperExtremity(channel->getTargetName())) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e4d0b4363d..b8272c8b26 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include #include @@ -353,6 +356,66 @@ namespace Resource std::vector> mRigGeometryHolders; }; + void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig) + { + osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap(); + if (!vertexInfluenceMap) + return; + + std::vector> renameList; + + // Collecting updates + for (const auto& influence : *vertexInfluenceMap) + { + const std::string& oldBoneName = influence.first; + std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); + if (newBoneName != oldBoneName) + renameList.emplace_back(oldBoneName, newBoneName); + } + + // Applying updates (cant update map while iterating it!) + for (const auto& rename : renameList) + { + const std::string& oldName = rename.first; + const std::string& newName = rename.second; + + // Check if new name already exists to avoid overwriting + if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) + (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); + + vertexInfluenceMap->erase(oldName); + } + } + + class RenameBonesVisitor : public osg::NodeVisitor + { + public: + RenameBonesVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::MatrixTransform& node) override + { + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + + // osgAnimation update callback name must match bone name/channel targets + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + osgAnimation::AnimationUpdateCallback* animCb + = dynamic_cast*>(cb); + + if (animCb) + animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); + + cb = cb->getNestedCallback(); + } + + traverse(node); + } + }; + SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) @@ -556,6 +619,7 @@ namespace Resource VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); + const bool isColladaFile = ext == "dae"; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -571,7 +635,7 @@ namespace Resource // findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't // implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); - if (ext == "dae") + if (isColladaFile) options->setOptionString("daeUseSequencedTextureUnits"); const std::array fileHash = Files::getHash(normalizedFilename.value(), model); @@ -599,9 +663,13 @@ namespace Resource node->accept(rigFinder); for (osg::Node* foundRigNode : rigFinder.mFoundNodes) { - if (foundRigNode->libraryName() == std::string("osgAnimation")) + if (foundRigNode->libraryName() == std::string_view("osgAnimation")) { osgAnimation::RigGeometry* foundRigGeometry = static_cast(foundRigNode); + + if (isColladaFile) + Resource::updateVertexInfluenceMap(*foundRigGeometry); + osg::ref_ptr newRig = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL); @@ -616,13 +684,18 @@ namespace Resource } } - if (ext == "dae") + if (isColladaFile) { Resource::ColladaDescriptionVisitor colladaDescriptionVisitor; node->accept(colladaDescriptionVisitor); if (colladaDescriptionVisitor.mSkeleton) { + // Collada bones may have underscores in place of spaces due to a collada limitation + // we should rename the bones and update callbacks here at load time + Resource::RenameBonesVisitor renameBoneVisitor; + node->accept(renameBoneVisitor); + if (osg::Group* group = dynamic_cast(node)) { group->removeChildren(0, group->getNumChildren()); diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index bd82e9abba..8d024d5824 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -44,11 +45,15 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (!mSceneMgr->getSoftParticles()) return; std::string source; - constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6ca..40b6640973 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -24,6 +26,10 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { + // If osgAnimation had underscores, we should update the umt name also + // otherwise the animation channel and updates wont be applied + umt->setName(Misc::StringUtils::underscoresToSpaces(umt->getName())); + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); for (const auto& channel : channels) { @@ -128,6 +134,47 @@ namespace SceneUtil return osg::Vec3f(); } + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + { + std::string animationName; + float newTime = time; + + // Find the correct animation based on time + for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) + { + if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) + { + newTime = time - emulatedAnimation.mStartTime; + animationName = emulatedAnimation.mName; + } + } + + // Find the bone's transform track in animation + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() != animationName) + continue; + + const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); + + for (const auto& channel : channels) + { + if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform") + continue; + + if (osgAnimation::MatrixLinearSampler* templateSampler + = dynamic_cast(channel->getSampler())) + { + osg::Matrixf matrix; + templateSampler->getValueAt(newTime, matrix); + return matrix; + } + } + } + + return osg::Matrixf::identity(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) @@ -162,6 +209,12 @@ namespace SceneUtil update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); } } + + // Reset the transform of this node to whats in the animation + // we force this here because downstream some code relies on the bone having a non-modified transform + // as this is how the NIF controller behaves. RotationController is a good example of this. + // Without this here, it causes osgAnimation skeletons to spin wildly + static_cast(node)->setMatrix(getTransformForNode(time, node->getName())); } traverse(node, nv); diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 8739d68b99..000580631c 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -59,6 +59,9 @@ namespace SceneUtil /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; + /// @brief Handles finding bone position in the animation + osg::Matrixf getTransformForNode(float time, const std::string& name) const; + /// @brief Calls animation track update() void update(float time, const std::string& animationName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index ffcf12b167..8d1bf46322 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -13,7 +15,6 @@ namespace SceneUtil { - bool FindByNameVisitor::checkGroup(osg::Group& group) { if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) @@ -22,35 +23,13 @@ namespace SceneUtil return true; } - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = group.getName(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - { - mFoundNode = &group; - return true; - } return false; } void FindByClassVisitor::apply(osg::Node& node) { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) - { mFoundNodes.push_back(&node); - } - else - { - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = node.className(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - mFoundNodes.push_back(&node); - } traverse(node); } @@ -69,23 +48,19 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Geometry&) {} + void NodeMapVisitorBoneOnly::apply(osg::MatrixTransform& trans) + { + // Choose first found bone in file + if (dynamic_cast(&trans) != nullptr) + mMap.emplace(trans.getName(), &trans); + + traverse(trans); + } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) { // Choose first found node in file - - if (trans.libraryName() == std::string_view("osgAnimation")) - { - std::string nodeName = trans.getName(); - - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - mMap.emplace(nodeName, &trans); - } - else - mMap.emplace(trans.getName(), &trans); - + mMap.emplace(trans.getName(), &trans); traverse(trans); } diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 3e3df4d4b3..a5af88d7de 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -70,6 +70,26 @@ namespace SceneUtil NodeMap& mMap; }; + /// Maps names to bone nodes + class NodeMapVisitorBoneOnly : public osg::NodeVisitor + { + public: + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + + NodeMapVisitorBoneOnly(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + { + } + + void apply(osg::MatrixTransform& trans) override; + + private: + NodeMap& mMap; + }; + /// @brief Base class for visitors that remove nodes from a scene graph. /// Subclasses need to fill the mToRemove vector. /// To use, node->accept(removeVisitor); removeVisitor.remove(); From ceabeab0fd106cbcaf9b33c9c36712e3a620d7c8 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:11:15 +0100 Subject: [PATCH 1273/2167] Fix RotateController not updating skeleton --- apps/openmw/mwrender/rotatecontroller.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index d7f8bb902c..73c4cddff5 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -1,6 +1,7 @@ #include "rotatecontroller.hpp" #include +#include namespace MWRender { @@ -40,9 +41,19 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); - node->setMatrix(matrix); + // If we are linked to a bone we must call setMatrixInSkeletonSpace + osgAnimation::Bone* b = dynamic_cast(node); + if (b) + { + osgAnimation::Bone* parent = b->getBoneParent(); + if (parent) + b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); + else + b->setMatrixInSkeletonSpace(matrix); + } + traverse(node, nv); } From fbe84f4668c1fc3c51fe1e2139b71e99a67ead00 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 31 Mar 2024 23:08:47 +0300 Subject: [PATCH 1274/2167] Remove underwater fog radialization remnants The fog is now view-independent so this is not something to concern ourselves as with anymore --- files/shaders/compatibility/water.frag | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 2debf2fac0..268b9c99a2 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -152,23 +152,12 @@ void main(void) float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); - float radialise = 1.0; - -#if @radialFog - float radialDepth = distance(position.xyz, cameraPos); - // TODO: Figure out how to properly radialise refraction depth and thus underwater fog - // while avoiding oddities when the water plane is close to the clipping plane - // radialise = radialDepth / linearDepth; -#else - float radialDepth = 0.0; -#endif - vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION - float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far) * radialise; - float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far) * radialise; + float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); + float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif @@ -196,7 +185,7 @@ void main(void) if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); - depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); // fade to realWaterDepth at a distance to compensate for physically inaccurate depth calculation @@ -248,6 +237,12 @@ void main(void) gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); #endif +#if @radialFog + float radialDepth = distance(position.xyz, cameraPos); +#else + float radialDepth = 0.0; +#endif + gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); #if !@disableNormals From 0be7d7fa4c35197c5fad842aa49276d7e74000cc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Apr 2024 01:35:50 +0300 Subject: [PATCH 1275/2167] Reduce the amount of redundant code in the water shader --- files/shaders/compatibility/water.frag | 74 ++++++++++++-------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 268b9c99a2..be2647a8df 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -10,8 +10,6 @@ #include "lib/core/fragment.h.glsl" -#define REFRACTION @refraction_enabled - // Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- @@ -20,18 +18,11 @@ const float VISIBILITY = 2500.0; const float VISIBILITY_DEPTH = VISIBILITY * 1.5; const float DEPTH_FADE = 0.15; -const float BIG_WAVES_X = 0.1; // strength of big waves -const float BIG_WAVES_Y = 0.1; - -const float MID_WAVES_X = 0.1; // strength of middle sized waves -const float MID_WAVES_Y = 0.1; -const float MID_WAVES_RAIN_X = 0.2; -const float MID_WAVES_RAIN_Y = 0.2; - -const float SMALL_WAVES_X = 0.1; // strength of small waves -const float SMALL_WAVES_Y = 0.1; -const float SMALL_WAVES_RAIN_X = 0.3; -const float SMALL_WAVES_RAIN_Y = 0.3; +const vec2 BIG_WAVES = vec2(0.1, 0.1); // strength of big waves +const vec2 MID_WAVES = vec2(0.1, 0.1); // strength of middle sized waves +const vec2 MID_WAVES_RAIN = vec2(0.2, 0.2); +const vec2 SMALL_WAVES = vec2(0.1, 0.1); // strength of small waves +const vec2 SMALL_WAVES_RAIN = vec2(0.3, 0.3); const float WAVE_CHOPPYNESS = 0.05; // wave choppyness const float WAVE_SCALE = 75.0; // overall wave scale @@ -133,9 +124,9 @@ void main(void) float distortionLevel = 2.0; rippleAdd += distortionLevel * vec3(texture2D(rippleMap, rippleMapUV).ba * blendFar * blendClose, 0.0); - vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y); - vec2 midWaves = mix(vec2(MID_WAVES_X,MID_WAVES_Y),vec2(MID_WAVES_RAIN_X,MID_WAVES_RAIN_Y),rainIntensity); - vec2 smallWaves = mix(vec2(SMALL_WAVES_X,SMALL_WAVES_Y),vec2(SMALL_WAVES_RAIN_X,SMALL_WAVES_RAIN_Y),rainIntensity); + vec2 bigWaves = BIG_WAVES; + vec2 midWaves = mix(MID_WAVES, MID_WAVES_RAIN, rainIntensity); + vec2 smallWaves = mix(SMALL_WAVES, SMALL_WAVES_RAIN, rainIntensity); float bump = mix(BUMP,BUMP_RAIN,rainIntensity); vec3 normal = (normal0 * bigWaves.x + normal1 * bigWaves.y + normal2 * midWaves.x + @@ -153,34 +144,32 @@ void main(void) float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; -#if REFRACTION +#if @refraction_enabled float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); - screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); + screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH, 0.0, 1.0); #endif // reflection vec3 reflection = sampleReflectionMap(screenCoords + screenCoordsOffset).rgb; - // specular - float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0),SPEC_HARDNESS) * shadow; - vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); // alpha component is sun visibility; we want to start fading lighting effects when visibility is low sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); + // specular + float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; + // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5; + float waterTransparency = clamp(fresnel * 6.0 + specular, 0.0, 1.0); -#if REFRACTION - // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); - +#if @refraction_enabled // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); @@ -211,30 +200,35 @@ void main(void) normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); lNormal = normalize(vec3(-lNormal.x * bump, -lNormal.y * bump, lNormal.z)); float sunHeight = lVec.z; - vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); - vec3 lR = reflect(lVec, lNormal); - float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + vec3 scatterColour = mix(SCATTER_COLOUR * vec3(1.0, 0.4, 0.0), SCATTER_COLOUR, max(1.0 - exp(-sunHeight * SUN_EXT), 0.0)); + float scatterLambert = max(dot(lVec, lNormal) * 0.7 + 0.3, 0.0); + float scatterReflectAngle = max(dot(reflect(lVec, lNormal), vVec) * 2.0 - 1.2, 0.0); + float lightScatter = scatterLambert * scatterReflectAngle * SCATTER_AMOUNT * sunFade * sunSpec.a * max(1.0 - exp(-sunHeight), 0.0); refraction = mix(refraction, scatterColour, lightScatter); #endif - gl_FragData[0].xyz = mix(refraction, reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = 1.0; + gl_FragData[0].rgb = mix(refraction, reflection, fresnel); + gl_FragData[0].a = 1.0; + // no alpha here, so make sure raindrop ripple specularity gets properly subdued + rainSpecular *= waterTransparency; +#else + gl_FragData[0].rgb = mix(waterColor, reflection, (1.0 + fresnel) * 0.5); + gl_FragData[0].a = waterTransparency; +#endif -#if @wobblyShores + gl_FragData[0].rgb += specular * sunSpec.rgb + rainSpecular; + +#if @refraction_enabled && @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; - float verticalWaterDepth = realWaterDepth * mix(abs(vVec.z), 1.0, 0.2); // an estimate + float viewFactor = mix(abs(vVec.z), 1.0, 0.2); + float verticalWaterDepth = realWaterDepth * viewFactor; // an estimate float shoreOffset = verticalWaterDepth - (normal2.r + mix(0.0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8.0; - float fuzzFactor = min(1.0, 1000.0/surfaceDepth) * mix(abs(vVec.z), 1.0, 0.2); + float fuzzFactor = min(1.0, 1000.0 / surfaceDepth) * viewFactor; shoreOffset *= fuzzFactor; shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); - gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); -#endif - -#else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); + gl_FragData[0].rgb = mix(rawRefraction, gl_FragData[0].rgb, shoreOffset); #endif #if @radialFog From 612177be09030804c64d3c01a593df40646cf41a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Apr 2024 00:16:52 +0300 Subject: [PATCH 1276/2167] Improve some cryptic naming in the water shader --- files/shaders/compatibility/water.frag | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index be2647a8df..a18f22a797 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -133,15 +133,15 @@ void main(void) normal3 * midWaves.y + normal4 * smallWaves.x + normal5 * smallWaves.y + rippleAdd); normal = normalize(vec3(-normal.x * bump, -normal.y * bump, normal.z)); - vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); + vec3 sunWorldDir = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 vVec = normalize(position.xyz - cameraPos.xyz); + vec3 viewDir = normalize(position.xyz - cameraPos.xyz); float sunFade = length(gl_LightModel.ambient.xyz); // fresnel float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air - float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); + float fresnel = clamp(fresnel_dielectric(viewDir, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if @refraction_enabled @@ -162,7 +162,7 @@ void main(void) sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // specular - float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; + float specular = pow(max(dot(reflect(viewDir, normal), sunWorldDir), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); @@ -195,14 +195,13 @@ void main(void) } #if @sunlightScattering - // normal for sunlight scattering - vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + - normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); - lNormal = normalize(vec3(-lNormal.x * bump, -lNormal.y * bump, lNormal.z)); - float sunHeight = lVec.z; + vec3 scatterNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + + normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); + scatterNormal = normalize(vec3(-scatterNormal.xy * bump, scatterNormal.z)); + float sunHeight = sunWorldDir.z; vec3 scatterColour = mix(SCATTER_COLOUR * vec3(1.0, 0.4, 0.0), SCATTER_COLOUR, max(1.0 - exp(-sunHeight * SUN_EXT), 0.0)); - float scatterLambert = max(dot(lVec, lNormal) * 0.7 + 0.3, 0.0); - float scatterReflectAngle = max(dot(reflect(lVec, lNormal), vVec) * 2.0 - 1.2, 0.0); + float scatterLambert = max(dot(sunWorldDir, scatterNormal) * 0.7 + 0.3, 0.0); + float scatterReflectAngle = max(dot(reflect(sunWorldDir, scatterNormal), viewDir) * 2.0 - 1.2, 0.0); float lightScatter = scatterLambert * scatterReflectAngle * SCATTER_AMOUNT * sunFade * sunSpec.a * max(1.0 - exp(-sunHeight), 0.0); refraction = mix(refraction, scatterColour, lightScatter); #endif @@ -222,7 +221,7 @@ void main(void) // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; - float viewFactor = mix(abs(vVec.z), 1.0, 0.2); + float viewFactor = mix(abs(viewDir.z), 1.0, 0.2); float verticalWaterDepth = realWaterDepth * viewFactor; // an estimate float shoreOffset = verticalWaterDepth - (normal2.r + mix(0.0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8.0; float fuzzFactor = min(1.0, 1000.0 / surfaceDepth) * viewFactor; From f2e0129436248676af20f82a61f7cea94f7a7d29 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 4 Apr 2024 21:12:47 +0300 Subject: [PATCH 1277/2167] Convert water/ripple defines to camelCase --- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/ripples.cpp | 2 +- apps/openmw/mwrender/water.cpp | 8 ++++---- files/shaders/compatibility/ripples_blobber.frag | 2 +- files/shaders/compatibility/ripples_simulate.frag | 4 ++-- files/shaders/compatibility/water.frag | 6 +++--- files/shaders/compatibility/water.vert | 2 +- files/shaders/lib/core/fragment.glsl | 2 +- files/shaders/lib/core/fragment.h.glsl | 2 +- files/shaders/lib/core/fragment_multiview.glsl | 2 +- files/shaders/lib/water/rain_ripples.glsl | 10 ++++------ 12 files changed, 21 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0a04954091..470ce04131 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -161,7 +161,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; - defines["refraction_enabled"] = "0"; + defines["waterRefraction"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index acc8976219..dc71e455b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -441,7 +441,7 @@ namespace MWRender globalDefines["radialFog"] = (exponentialFog || Settings::fog().mRadialFog) ? "1" : "0"; globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; - globalDefines["refraction_enabled"] = "0"; + globalDefines["waterRefraction"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 28427c3671..d0069d8d92 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -100,7 +100,7 @@ namespace MWRender { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(sRTTSize) + ".0" } }; + Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 62266d6e2d..a28ca0b7b7 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -700,11 +700,11 @@ namespace MWRender { // use a define map to conditionally compile the shader std::map defineMap; - defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); + defineMap["waterRefraction"] = std::string(mRefraction ? "1" : "0"); const int rippleDetail = Settings::water().mRainRippleDetail; - defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); - defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); - defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; + defineMap["rainRippleDetail"] = std::to_string(rippleDetail); + defineMap["rippleMapWorldScale"] = std::to_string(RipplesSurface::sWorldScaleFactor); + defineMap["rippleMapSize"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; defineMap["sunlightScattering"] = Settings::water().mSunlightScattering ? "1" : "0"; defineMap["wobblyShores"] = Settings::water().mWobblyShores ? "1" : "0"; diff --git a/files/shaders/compatibility/ripples_blobber.frag b/files/shaders/compatibility/ripples_blobber.frag index ea874af83e..d9cadcda98 100644 --- a/files/shaders/compatibility/ripples_blobber.frag +++ b/files/shaders/compatibility/ripples_blobber.frag @@ -13,7 +13,7 @@ uniform vec2 offset; void main() { - vec2 uv = (gl_FragCoord.xy + offset) / @ripple_map_size; + vec2 uv = (gl_FragCoord.xy + offset) / @rippleMapSize; vec4 color = texture2D(imageIn, uv); float wavesizeMultiplier = getTemporalWaveSizeMultiplier(osg_SimulationTime); diff --git a/files/shaders/compatibility/ripples_simulate.frag b/files/shaders/compatibility/ripples_simulate.frag index f36cab5b40..fb416df2c6 100644 --- a/files/shaders/compatibility/ripples_simulate.frag +++ b/files/shaders/compatibility/ripples_simulate.frag @@ -6,9 +6,9 @@ uniform sampler2D imageIn; void main() { - vec2 uv = gl_FragCoord.xy / @ripple_map_size; + vec2 uv = gl_FragCoord.xy / @rippleMapSize; - float pixelSize = 1.0 / @ripple_map_size; + float pixelSize = 1.0 / @rippleMapSize; float oneOffset = pixelSize; float oneAndHalfOffset = 1.5 * pixelSize; diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index a18f22a797..d1324e01bd 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -144,7 +144,7 @@ void main(void) float fresnel = clamp(fresnel_dielectric(viewDir, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; -#if @refraction_enabled +#if @waterRefraction float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum @@ -169,7 +169,7 @@ void main(void) vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5; float waterTransparency = clamp(fresnel * 6.0 + specular, 0.0, 1.0); -#if @refraction_enabled +#if @waterRefraction // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); @@ -217,7 +217,7 @@ void main(void) gl_FragData[0].rgb += specular * sunSpec.rgb + rainSpecular; -#if @refraction_enabled && @wobblyShores +#if @waterRefraction && @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; diff --git a/files/shaders/compatibility/water.vert b/files/shaders/compatibility/water.vert index 93796a7b9c..a67f412a07 100644 --- a/files/shaders/compatibility/water.vert +++ b/files/shaders/compatibility/water.vert @@ -21,7 +21,7 @@ void main(void) position = gl_Vertex; worldPos = position.xyz + nodePosition.xyz; - rippleMapUV = (worldPos.xy - playerPos.xy + (@ripple_map_size * @ripple_map_world_scale / 2.0)) / @ripple_map_size / @ripple_map_world_scale; + rippleMapUV = (worldPos.xy - playerPos.xy + (@rippleMapSize * @rippleMapWorldScale / 2.0)) / @rippleMapSize / @rippleMapWorldScale; vec4 viewPos = modelToView(gl_Vertex); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/lib/core/fragment.glsl b/files/shaders/lib/core/fragment.glsl index 68fbe5e39d..9b983148cb 100644 --- a/files/shaders/lib/core/fragment.glsl +++ b/files/shaders/lib/core/fragment.glsl @@ -9,7 +9,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture2D(reflectionMap, uv); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2D refractionMap; uniform sampler2D refractionDepthMap; diff --git a/files/shaders/lib/core/fragment.h.glsl b/files/shaders/lib/core/fragment.h.glsl index 1bc2a1f79a..b8c3f9a32b 100644 --- a/files/shaders/lib/core/fragment.h.glsl +++ b/files/shaders/lib/core/fragment.h.glsl @@ -6,7 +6,7 @@ vec4 sampleReflectionMap(vec2 uv); -#if @refraction_enabled +#if @waterRefraction vec4 sampleRefractionMap(vec2 uv); float sampleRefractionDepthMap(vec2 uv); #endif diff --git a/files/shaders/lib/core/fragment_multiview.glsl b/files/shaders/lib/core/fragment_multiview.glsl index cc804d6c80..2880087104 100644 --- a/files/shaders/lib/core/fragment_multiview.glsl +++ b/files/shaders/lib/core/fragment_multiview.glsl @@ -12,7 +12,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture(reflectionMap, vec3((uv), gl_ViewID_OVR)); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2DArray refractionMap; uniform sampler2DArray refractionDepthMap; diff --git a/files/shaders/lib/water/rain_ripples.glsl b/files/shaders/lib/water/rain_ripples.glsl index 4e5f85017b..6ec3f101fe 100644 --- a/files/shaders/lib/water/rain_ripples.glsl +++ b/files/shaders/lib/water/rain_ripples.glsl @@ -1,8 +1,6 @@ #ifndef LIB_WATER_RIPPLES #define LIB_WATER_RIPPLES -#define RAIN_RIPPLE_DETAIL @rain_ripple_detail - const float RAIN_RIPPLE_GAPS = 10.0; const float RAIN_RIPPLE_RADIUS = 0.2; @@ -51,7 +49,7 @@ vec4 circle(vec2 coords, vec2 corner, float adjusted_time) float d = length(toCenter); float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 // normal mapped ripples if(ringfollower < -1.0 || ringfollower > 1.0) return vec4(0.0); @@ -88,7 +86,7 @@ vec4 rain(vec2 uv, float time) vec2 f_part = fract(uv); vec2 i_part = floor(uv); float adjusted_time = time * 1.2 + randPhase(i_part); -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 vec4 a = circle(f_part, i_part, adjusted_time); vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET); vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0); @@ -115,11 +113,11 @@ vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake return rain(uv, time) + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time) - #if RAIN_RIPPLE_DETAIL == 2 +#if @rainRippleDetail == 2 + rain(uv * 0.75 + vec2( 3.7,18.9),time) + rain(uv * 0.9 + vec2( 5.7,30.1),time) + rain(uv * 1.0 + vec2(10.5 ,5.7),time) - #endif +#endif ; } From 76199e1fb2c5327c7ff9fae605d1b8303c697c4c Mon Sep 17 00:00:00 2001 From: Benjamin Y Date: Thu, 4 Apr 2024 19:45:45 +0000 Subject: [PATCH 1278/2167] Update settingspage.ui with description for PPL --- apps/launcher/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 86039f77ee..41fb4d8b7a 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1106,7 +1106,7 @@ - <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> Force per-pixel lighting From a9346d3c8a9c8183cda86ec6992eac7ca913e487 Mon Sep 17 00:00:00 2001 From: Benjamin Y Date: Thu, 4 Apr 2024 16:02:38 -0400 Subject: [PATCH 1279/2167] updated localization files --- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index af7bd573a1..da9c50627c 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index a11e5cbad8..b80e3801d8 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index f735883064..7cc80a287d 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1463,7 +1463,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Минимальный уровень освещения в помещениях - <html><head/><body><p>If true, force per-pixel lighting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> From a51d560174d90ac55049fd8ab687533dfe24fbfc Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 01:59:40 +0100 Subject: [PATCH 1280/2167] Move bone rename logic to ColladaDescriptionVisitor, undo formatting/refactoring --- apps/openmw/mwrender/animation.cpp | 5 ++++- apps/openmw/mwrender/rotatecontroller.cpp | 1 + components/misc/strings/algorithm.hpp | 2 +- components/resource/keyframemanager.cpp | 4 +++- components/resource/scenemanager.cpp | 26 ++++++++++------------- components/sceneutil/extradata.cpp | 6 +----- components/sceneutil/osgacontroller.cpp | 2 +- components/sceneutil/osgacontroller.hpp | 2 +- 8 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1807b0050f..729c69f58b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1457,9 +1457,10 @@ namespace MWRender } } - osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1475,6 +1476,8 @@ namespace MWRender } else { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 73c4cddff5..47b271c40d 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -41,6 +41,7 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); + node->setMatrix(matrix); // If we are linked to a bone we must call setMatrixInSkeletonSpace diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 2bf7125c8b..18f72104bd 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,7 +15,7 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; - inline std::string underscoresToSpaces(const std::string_view& oldName) + inline std::string underscoresToSpaces(const std::string_view oldName) { std::string newName(oldName); std::replace(newName.begin(), newName.end(), '_', ' '); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 67a434e47b..84e7a7e311 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -88,7 +88,9 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") + { animation->setName(std::string("idle")); + } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -97,7 +99,7 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { - // Repalce channel target name to match the renamed bones/transforms + // Replace channel target name to match the renamed bones/transforms channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); if (name == "Bip01 R Clavicle") diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b8272c8b26..3b7deeaab3 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -271,6 +271,11 @@ namespace Resource void apply(osg::Node& node) override { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) @@ -362,27 +367,18 @@ namespace Resource if (!vertexInfluenceMap) return; - std::vector> renameList; - - // Collecting updates - for (const auto& influence : *vertexInfluenceMap) + std::vector renameList; + for (const auto& [boneName, unused] : *vertexInfluenceMap) { - const std::string& oldBoneName = influence.first; - std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); - if (newBoneName != oldBoneName) - renameList.emplace_back(oldBoneName, newBoneName); + if (boneName.find('_') != std::string::npos) + renameList.push_back(boneName); } - // Applying updates (cant update map while iterating it!) - for (const auto& rename : renameList) + for (const std::string& oldName : renameList) { - const std::string& oldName = rename.first; - const std::string& newName = rename.second; - - // Check if new name already exists to avoid overwriting + const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName); if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); - vertexInfluenceMap->erase(oldName); } } diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 8d024d5824..5e91830bba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -45,15 +45,11 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { - // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces - // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - if (!mSceneMgr->getSoftParticles()) return; std::string source; + constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 40b6640973..5a3ae60293 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -134,7 +134,7 @@ namespace SceneUtil return osg::Vec3f(); } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; float newTime = time; diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 000580631c..bb9a621760 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -60,7 +60,7 @@ namespace SceneUtil osg::Vec3f getTranslation(float time) const override; /// @brief Handles finding bone position in the animation - osg::Matrixf getTransformForNode(float time, const std::string& name) const; + osg::Matrixf getTransformForNode(float time, const std::string_view name) const; /// @brief Calls animation track update() void update(float time, const std::string& animationName); From 8fecbb55ffca2d4edbd091b41ecb2aa3bdacfa3c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 5 Apr 2024 10:12:31 +0400 Subject: [PATCH 1281/2167] Do not store 'location' tags in translation files --- CI/check_qt_translations.sh | 6 +- CMakeLists.txt | 6 +- files/lang/components_ru.ts | 2 +- files/lang/launcher_ru.ts | 6 +- files/lang/wizard_de.ts | 182 ----------------------------------- files/lang/wizard_fr.ts | 182 ----------------------------------- files/lang/wizard_ru.ts | 186 +----------------------------------- 7 files changed, 12 insertions(+), 558 deletions(-) diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh index f3a82ed2e6..1fc2e19002 100755 --- a/CI/check_qt_translations.sh +++ b/CI/check_qt_translations.sh @@ -4,8 +4,8 @@ set -o pipefail LUPDATE="${LUPDATE:-lupdate}" -${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts -${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts -${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts +${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} -locations none components/contentselector components/process -ts files/lang/components_*.ts ! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bffdba34e..93da5feec4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1106,17 +1106,17 @@ if (USE_QT) file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) add_custom_target(translations - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher VERBATIM COMMAND_EXPAND_LISTS) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index cca6591afe..3fe4db1e6f 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -65,7 +65,7 @@ Arguments: Параметры: - + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index ec7aeccc57..52499b7b3c 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -109,7 +109,7 @@ &New Content List - Новый список плагинов + &Новый список плагинов Clone Content List @@ -527,7 +527,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>Could not create directory %0</b><br><br>%1<br> - <br><b>Не удалось создать директорию %0</b><br><br> + <br><b>Не удалось создать директорию %0</b><br><br>%1<br> @@ -1046,7 +1046,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Size of characters in game texts. - Размер символов в текстах + Размер символов в текстах. Font size diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 7bf54e90b1..5749cf2d5d 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,59 +482,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - Error writing OpenMW configuration file @@ -650,207 +525,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 7f42087dbf..9b5acbc9e9 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,59 +482,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - Error writing OpenMW configuration file @@ -650,207 +525,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 3113774cd3..0a2e6e5561 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage WizardPage - Select Components Выбор компонентов - Which components should be installed? Какие компоненты должны быть установлены? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> - Selected components: Выбранные компоненты: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage WizardPage - Completing the OpenMW Wizard Завершение работы Мастера установки OpenMW - Placeholder Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage WizardPage - Select Existing Installation Выбрать установленную копию игры - Select an existing installation for OpenMW to use or modify. Выбрать установленную копию игры для использования или изменения через OpenMW. - Detected installations: Обнаруженные установленные копии: - Browse... Выбрать... @@ -78,42 +65,34 @@ ImportPage - WizardPage WizardPage - Import Settings Импортировать настройки - Import settings from the Morrowind installation. Импортировать настройки из установленной копии Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini Импортировать растровые шрифты из Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -125,17 +104,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage WizardPage - Installing Установка - Please wait while Morrowind is installed on your computer. Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. @@ -143,32 +119,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage WizardPage - Select Installation Destination Выберите путь для установки - Where should Morrowind be installed? Куда нужно установить Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Morrowind будет установлен в следующее место. + Morrowind будет установлен в следующее место. - Browse... Выбрать... @@ -176,17 +146,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage WizardPage - Welcome to the OpenMW Wizard Добро пожаловать в Мастер установки - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. @@ -194,27 +161,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage WizardPage - Select Morrowind Language Выберите язык вашей копии Morrowind - What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -222,62 +184,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage WizardPage - Select Installation Method Выберите способ установки - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD CD/DVD-диск - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Установить игру с диска + Установить игру с диска. - Existing Installation Установленная копия игры - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Выбрать установленную копию игры. - Don't have a copy? Нет копии игры? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Купить игру @@ -285,42 +235,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> - B&rowse... &Выбрать... - Select configuration file Выберите файл с настройками - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> - Most recent Morrowind not detected Актуальная версия Morrowind не найдена - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? @@ -328,57 +270,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install &Установить - &Skip &Пропустить - Morrowind (installed) Morrowind (установлен) - Morrowind Morrowind - Tribunal (installed) Tribunal (установлен) - Tribunal Tribunal - Bloodmoon (installed) Bloodmoon (установлен) - Bloodmoon Bloodmoon - About to install Tribunal after Bloodmoon Попытка установить Tribunal после Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> - Re-install &Bloodmoon Переустановить &Bloodmoon @@ -386,17 +317,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> @@ -404,32 +332,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected Установленные копии игры не найдены - Error detecting Morrowind configuration Попытка найти настройки Morrowind завершилась ошибкой - Morrowind configuration file (*.ini) Файл настроек Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) Выберите Morrowind.esm (расположен в Data Files) - Morrowind master file (Morrowind.esm) Мастер-файл Morrowind (Morrowind.esm) - Error detecting Morrowind files Не удалось обнаружить файлы Morrowind @@ -437,78 +359,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> <p>Попытка установить компонент %1.</p> - Attempting to install component %1. Попытка установить компонент %1. - %1 Installation Установка %1 - Select %1 installation media Выберите установочный дистрибутив %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> - <p>Detected old version of component Morrowind.</p> lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> - Detected old version of component Morrowind. Обнаружена устаревшая версия компонента Morrowind. - Morrowind Installation Установка Morrowind - Installation finished Установка завершена - Installation completed successfully! Установка успешно завершена! - Installation failed! Установка не удалась! - <p><br/><span style="color:red;"><b>Error: %1</b></p> <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> - An error occurred Произошла ошибка @@ -516,37 +422,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination Не удалось создать директорию назначения - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> - Insufficient permissions Не хватает прав доступа - Destination not empty Выбранная директория не пустая - Select where to install Morrowind Выберите, куда установить Morrowind @@ -554,37 +453,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English Английский - French Французский - German Немецкий - Italian Итальянский - Polish Польский - Russian Русский - Spanish Испанский @@ -592,59 +484,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard Мастер OpenMW - - Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - - Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - Quit Wizard Завершить работу Мастера - Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW @@ -652,207 +527,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! Не удалось открыть файл с настройками Morrowind! - - Opening %1 failed: %2. Попытка открыть %1 не удалась: %2. - Failed to write Morrowind configuration file! Не удалось записать данные в файл с настройками Morrowind! - Writing to %1 failed: %2. Запись в %1 завершилась с ошибкой: %2. - Installing: %1 Установка: %1 - - Installing: %1 directory Установка: директория %1 - Installation finished! Установка завершена! - - Component parameter is invalid! Некорректный параметр для компонента! - - An invalid component parameter was supplied. Задан некорректный параметр для компонента. - Failed to find a valid archive containing %1.bsa! Retrying. Не удалось найти архив, содержащий %1.bsa! Повторная попытка. - Installing %1 Установка %1 - Installation media path not set! путь к установочному дистрибутиву не задан! - The source path for %1 was not set. Исходный пусть для %1 не задан. - - Cannot create temporary directory! Не удалось создать временную директорию! - - Failed to create %1. Не удалось создать %1. - Cannot move into temporary directory! Не удалось переместить во временную директорию! - Failed to move into %1. Не удалось переместить в %1. - Moving installation files Перемещение файлов установки - - - - Could not install directory! Не удалось установить директорию! - - - - Installing %1 to %2 failed. Не удалось установить %1 в %2. - Could not install translation file! Не удалось установить файл с переводом! - Failed to install *%1 files. Не удалось установить файлы *%1. - Could not install Morrowind data file! Не удалось установить файл с данными Morrowind! - - Failed to install %1. Не удалось установить %1. - Could not install Morrowind configuration file! Не удалось установить файл с настройками Morrowind! - Installing: Sound directory Установка: директория Sound - Could not find Tribunal data file! Не удалось найти файл с данными Tribunal! - - - Failed to find %1. Не удалось найти %1. - Could not find Tribunal patch file! Не удалось найти файл с патчем для Tribunal! - Could not find Bloodmoon data file! Не удалось найти файл с данными Bloodmoon! - Updating Morrowind configuration file Обновление файла с настройками Morrowind - %1 installation finished! Установка %1 завершена! - Extracting: %1 Извлечение: %1 - - - Failed to open InstallShield Cabinet File. Не удалось открыть файл InstallShield Cabinet. - - - Opening %1 failed. Не удалось открыть %1. - Failed to extract %1. Не удалось извлечь %1. - Complete path: %1 Полный путь: %1 From 36cccef6067d674384f5039f95efababf73664ba Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 23:43:59 +0100 Subject: [PATCH 1282/2167] Fix formatting --- apps/openmw/mwrender/animation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 729c69f58b..82081658a6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1805,7 +1805,6 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); - return controller; } From b0008d22e6a049f13e6fe9889ac326792670632e Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 6 Apr 2024 23:07:11 +0200 Subject: [PATCH 1283/2167] Attempt to fix openmw issue #7906 l10n name is taken not from the ActionInfo/TriggerInfo's 'key' param, but from the 'l10n' param when making the inputBinding setting renderer --- files/data/scripts/omw/input/settings.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 5243a86844..83a862b2d2 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -102,7 +102,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) local info = inputTypes[arg.type][arg.key] if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end - local l10n = core.l10n(info.key) + local l10n = core.l10n(info.l10n) local name = { template = I.MWUI.templates.textNormal, From 5856bc8a0eb5158f97702026aa9f7cb6cfc3ecec Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:13:51 -0500 Subject: [PATCH 1284/2167] Add setCrimeLevel --- apps/openmw/mwlua/types/player.cpp | 4 ++++ files/lua_api/openmw/types.lua | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 130d3ded21..144d98c678 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -186,6 +186,10 @@ namespace MWLua const MWWorld::Class& cls = o.ptr().getClass(); return cls.getNpcStats(o.ptr()).getBounty(); }; + player["setCrimeLevel"] = [](const Object& o, int amount) { + const MWWorld::Class& cls = o.ptr().getClass(); + cls.getNpcStats(o.ptr()).setBounty(amount); + }; player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 149d9bd9fa..bf1dc6cb4b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1123,6 +1123,12 @@ -- @param openmw.core#GameObject player -- @return #number +--- +-- Sets the bounty or crime level of the player +-- @function [parent=#Player] setCrimeLevel +-- @param openmw.core#GameObject player +-- @param #number crimeLevel The requested crime level + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished From 4ca13a94049b9c59ff832779c849c87df05e11f8 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:17:51 -0500 Subject: [PATCH 1285/2167] Verify the player --- apps/openmw/mwlua/types/player.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 144d98c678..e517bdf77d 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -187,6 +187,9 @@ namespace MWLua return cls.getNpcStats(o.ptr()).getBounty(); }; player["setCrimeLevel"] = [](const Object& o, int amount) { + verifyPlayer(o); + if (!dynamic_cast(&o)) + throw std::runtime_error("Only global scripts can change crime level"); const MWWorld::Class& cls = o.ptr().getClass(); cls.getNpcStats(o.ptr()).setBounty(amount); }; From b859fc10e2745da0fa574f2aa42bab97ea0c2bd7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:18:14 -0500 Subject: [PATCH 1286/2167] Add doc note --- files/lua_api/openmw/types.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index bf1dc6cb4b..90344cbae1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1124,7 +1124,7 @@ -- @return #number --- --- Sets the bounty or crime level of the player +-- Sets the bounty or crime level of the player, may only be used in global scripts -- @function [parent=#Player] setCrimeLevel -- @param openmw.core#GameObject player -- @param #number crimeLevel The requested crime level From d68d20eb1ae4dfa0303a4b745d69b7bb04bbb05e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Apr 2024 10:42:29 +0200 Subject: [PATCH 1287/2167] Disallow Lua activation of inventory objects --- files/data/scripts/omw/activationhandlers.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index edadc30f79..a70696824f 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -33,6 +33,9 @@ local function onActivate(obj, actor) if world.isWorldPaused() then return end + if obj.parentContainer then + return + end local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do From e549490bb1724d65ef67bf9717a11dbc75cdc0c6 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 08:28:43 -0500 Subject: [PATCH 1288/2167] record crime ID --- apps/openmw/mwlua/types/player.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index e517bdf77d..b2befe89de 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -192,6 +192,8 @@ namespace MWLua throw std::runtime_error("Only global scripts can change crime level"); const MWWorld::Class& cls = o.ptr().getClass(); cls.getNpcStats(o.ptr()).setBounty(amount); + if (amount == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); }; player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; From a29be8909d33e0e83f510cf71ab76f36a7ec6236 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 10:35:13 -0500 Subject: [PATCH 1289/2167] Add bindings to select the next menu element --- apps/openmw/mwlua/inputbindings.cpp | 6 ++++++ files/data/scripts/omw/input/gamepadcontrols.lua | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index dbe79ca377..cc1e4b99f8 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -214,6 +214,12 @@ namespace MWLua input->setGamepadGuiCursorEnabled(v); MWBase::Environment::get().getWindowManager()->setCursorActive(v); }; + api["_selectNextMenuElement"] = []() { + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Period, 0, false); + }; + api["_selectPrevMenuElement"] = []() { + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Slash, 0, false); + }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getAxisValue"] = [input](int axis) { diff --git a/files/data/scripts/omw/input/gamepadcontrols.lua b/files/data/scripts/omw/input/gamepadcontrols.lua index 0af17efa39..9c8810b98f 100644 --- a/files/data/scripts/omw/input/gamepadcontrols.lua +++ b/files/data/scripts/omw/input/gamepadcontrols.lua @@ -25,5 +25,17 @@ return { setGamepadCursorActive = function(state) input._setGamepadCursorActive(state) end, + + --- Sends an event to the interface to select the next menu element + -- @function [parent=#GamepadControls] selectNextMenuElement + selectNextMenuElement = function(state) + input._selectNextMenuElement() + end, + + --- Sends an event to the interface to select the next menu element + -- @function [parent=#GamepadControls] selectPrevMenuElement + selectPrevMenuElement = function(state) + input._selectPrevMenuElement() + end, } } From da4e6b38a8b74be0528d0cddbbd97c43715486f2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 10:37:15 -0500 Subject: [PATCH 1290/2167] Clang format --- apps/openmw/mwlua/inputbindings.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index cc1e4b99f8..1b9ed3bf11 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -214,12 +214,10 @@ namespace MWLua input->setGamepadGuiCursorEnabled(v); MWBase::Environment::get().getWindowManager()->setCursorActive(v); }; - api["_selectNextMenuElement"] = []() { - MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Period, 0, false); - }; - api["_selectPrevMenuElement"] = []() { - MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Slash, 0, false); - }; + api["_selectNextMenuElement"] + = []() { MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Period, 0, false); }; + api["_selectPrevMenuElement"] + = []() { MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Slash, 0, false); }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getAxisValue"] = [input](int axis) { From 15c6e90bdb6764ef89302a91ff4a5109edc8400b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 10:48:09 -0500 Subject: [PATCH 1291/2167] Allow shoulder buttons to work when text input is active --- apps/openmw/mwinput/controllermanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 8e6496ddf1..0c0a3de57c 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -277,11 +277,11 @@ namespace MWInput key = MyGUI::KeyCode::Apostrophe; break; case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: - key = MyGUI::KeyCode::Period; - break; + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Period, 0, false); + return true; case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: - key = MyGUI::KeyCode::Slash; - break; + MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Slash, 0, false); + return true; case SDL_CONTROLLER_BUTTON_LEFTSTICK: mGamepadGuiCursorEnabled = !mGamepadGuiCursorEnabled; MWBase::Environment::get().getWindowManager()->setCursorActive(mGamepadGuiCursorEnabled); From d23c10622d143043dbb1415b55b8798dfe05ac48 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 7 Apr 2024 21:02:54 +0100 Subject: [PATCH 1292/2167] Use dynamic cast to check for bone --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3b7deeaab3..bf2674a98f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -273,7 +273,7 @@ namespace Resource { // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + if (dynamic_cast(&node)) node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); if (osg::StateSet* stateset = node.getStateSet()) From a51b6c7392a7f216923e9254400c7b0821b6adb8 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 15:23:27 -0500 Subject: [PATCH 1293/2167] Remove menu select --- files/data/scripts/omw/input/gamepadcontrols.lua | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/files/data/scripts/omw/input/gamepadcontrols.lua b/files/data/scripts/omw/input/gamepadcontrols.lua index 9c8810b98f..0af17efa39 100644 --- a/files/data/scripts/omw/input/gamepadcontrols.lua +++ b/files/data/scripts/omw/input/gamepadcontrols.lua @@ -25,17 +25,5 @@ return { setGamepadCursorActive = function(state) input._setGamepadCursorActive(state) end, - - --- Sends an event to the interface to select the next menu element - -- @function [parent=#GamepadControls] selectNextMenuElement - selectNextMenuElement = function(state) - input._selectNextMenuElement() - end, - - --- Sends an event to the interface to select the next menu element - -- @function [parent=#GamepadControls] selectPrevMenuElement - selectPrevMenuElement = function(state) - input._selectPrevMenuElement() - end, } } From f8224b29d4f050c699fb3e7663e9e0ef15673e4d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 21:59:54 +0100 Subject: [PATCH 1294/2167] Resolve merge conflicts These were caused by https://gitlab.com/OpenMW/openmw/-/merge_requests/4005 I've had to cherry-pick parts of that MR into this commit, otherwise the CI would yell at me when it noticed the location tags had gone. --- CI/check_qt_translations.sh | 6 +- CMakeLists.txt | 6 +- files/lang/components_ru.ts | 2 +- files/lang/launcher_ru.ts | 6 +- files/lang/wizard_de.ts | 186 +---------------------------------- files/lang/wizard_fr.ts | 190 +----------------------------------- files/lang/wizard_ru.ts | 186 +---------------------------------- 7 files changed, 18 insertions(+), 564 deletions(-) diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh index f3a82ed2e6..1fc2e19002 100755 --- a/CI/check_qt_translations.sh +++ b/CI/check_qt_translations.sh @@ -4,8 +4,8 @@ set -o pipefail LUPDATE="${LUPDATE:-lupdate}" -${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts -${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts -${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts +${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} -locations none components/contentselector components/process -ts files/lang/components_*.ts ! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..8a0a59bf4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1089,17 +1089,17 @@ if (USE_QT) file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) add_custom_target(translations - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher VERBATIM COMMAND_EXPAND_LISTS) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index b16168effb..d70ebe320a 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -73,7 +73,7 @@ Arguments: Параметры: - + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 843222a423..5c044d38d4 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -109,7 +109,7 @@ &New Content List - Новый список плагинов + &Новый список плагинов Clone Content List @@ -547,7 +547,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>Could not create directory %0</b><br><br>%1<br> - <br><b>Не удалось создать директорию %0</b><br><br> + <br><b>Не удалось создать директорию %0</b><br><br>%1<br> @@ -1066,7 +1066,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Size of characters in game texts. - Размер символов в текстах + Размер символов в текстах. Font size diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 4fecd1de72..d793175dcb 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,267 +482,193 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + Error writing OpenMW configuration file - - - Error writing OpenMW configuration file + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index c2950c0a42..ba3c8aaa19 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,267 +482,193 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - + Error writing OpenMW configuration file + + + Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - - Error writing OpenMW configuration file - - Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 2461204681..0a2e6e5561 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage WizardPage - Select Components Выбор компонентов - Which components should be installed? Какие компоненты должны быть установлены? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> - Selected components: Выбранные компоненты: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage WizardPage - Completing the OpenMW Wizard Завершение работы Мастера установки OpenMW - Placeholder Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage WizardPage - Select Existing Installation Выбрать установленную копию игры - Select an existing installation for OpenMW to use or modify. Выбрать установленную копию игры для использования или изменения через OpenMW. - Detected installations: Обнаруженные установленные копии: - Browse... Выбрать... @@ -78,42 +65,34 @@ ImportPage - WizardPage WizardPage - Import Settings Импортировать настройки - Import settings from the Morrowind installation. Импортировать настройки из установленной копии Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini Импортировать растровые шрифты из Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -125,17 +104,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage WizardPage - Installing Установка - Please wait while Morrowind is installed on your computer. Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. @@ -143,32 +119,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage WizardPage - Select Installation Destination Выберите путь для установки - Where should Morrowind be installed? Куда нужно установить Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Morrowind будет установлен в следующее место. + Morrowind будет установлен в следующее место. - Browse... Выбрать... @@ -176,17 +146,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage WizardPage - Welcome to the OpenMW Wizard Добро пожаловать в Мастер установки - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. @@ -194,27 +161,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage WizardPage - Select Morrowind Language Выберите язык вашей копии Morrowind - What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -222,62 +184,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage WizardPage - Select Installation Method Выберите способ установки - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD CD/DVD-диск - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Установить игру с диска + Установить игру с диска. - Existing Installation Установленная копия игры - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Выбрать установленную копию игры. - Don't have a copy? Нет копии игры? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Купить игру @@ -285,42 +235,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> - B&rowse... &Выбрать... - Select configuration file Выберите файл с настройками - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> - Most recent Morrowind not detected Актуальная версия Morrowind не найдена - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? @@ -328,57 +270,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install &Установить - &Skip &Пропустить - Morrowind (installed) Morrowind (установлен) - Morrowind Morrowind - Tribunal (installed) Tribunal (установлен) - Tribunal Tribunal - Bloodmoon (installed) Bloodmoon (установлен) - Bloodmoon Bloodmoon - About to install Tribunal after Bloodmoon Попытка установить Tribunal после Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> - Re-install &Bloodmoon Переустановить &Bloodmoon @@ -386,17 +317,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> @@ -404,32 +332,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected Установленные копии игры не найдены - Error detecting Morrowind configuration Попытка найти настройки Morrowind завершилась ошибкой - Morrowind configuration file (*.ini) Файл настроек Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) Выберите Morrowind.esm (расположен в Data Files) - Morrowind master file (Morrowind.esm) Мастер-файл Morrowind (Morrowind.esm) - Error detecting Morrowind files Не удалось обнаружить файлы Morrowind @@ -437,78 +359,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> <p>Попытка установить компонент %1.</p> - Attempting to install component %1. Попытка установить компонент %1. - %1 Installation Установка %1 - Select %1 installation media Выберите установочный дистрибутив %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> - <p>Detected old version of component Morrowind.</p> lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> - Detected old version of component Morrowind. Обнаружена устаревшая версия компонента Morrowind. - Morrowind Installation Установка Morrowind - Installation finished Установка завершена - Installation completed successfully! Установка успешно завершена! - Installation failed! Установка не удалась! - <p><br/><span style="color:red;"><b>Error: %1</b></p> <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> - An error occurred Произошла ошибка @@ -516,37 +422,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination Не удалось создать директорию назначения - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> - Insufficient permissions Не хватает прав доступа - Destination not empty Выбранная директория не пустая - Select where to install Morrowind Выберите, куда установить Morrowind @@ -554,37 +453,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English Английский - French Французский - German Немецкий - Italian Итальянский - Polish Польский - Russian Русский - Spanish Испанский @@ -592,59 +484,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard Мастер OpenMW - - Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - - Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - Quit Wizard Завершить работу Мастера - Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW @@ -652,207 +527,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! Не удалось открыть файл с настройками Morrowind! - - Opening %1 failed: %2. Попытка открыть %1 не удалась: %2. - Failed to write Morrowind configuration file! Не удалось записать данные в файл с настройками Morrowind! - Writing to %1 failed: %2. Запись в %1 завершилась с ошибкой: %2. - Installing: %1 Установка: %1 - - Installing: %1 directory Установка: директория %1 - Installation finished! Установка завершена! - - Component parameter is invalid! Некорректный параметр для компонента! - - An invalid component parameter was supplied. Задан некорректный параметр для компонента. - Failed to find a valid archive containing %1.bsa! Retrying. Не удалось найти архив, содержащий %1.bsa! Повторная попытка. - Installing %1 Установка %1 - Installation media path not set! путь к установочному дистрибутиву не задан! - The source path for %1 was not set. Исходный пусть для %1 не задан. - - Cannot create temporary directory! Не удалось создать временную директорию! - - Failed to create %1. Не удалось создать %1. - Cannot move into temporary directory! Не удалось переместить во временную директорию! - Failed to move into %1. Не удалось переместить в %1. - Moving installation files Перемещение файлов установки - - - - Could not install directory! Не удалось установить директорию! - - - - Installing %1 to %2 failed. Не удалось установить %1 в %2. - Could not install translation file! Не удалось установить файл с переводом! - Failed to install *%1 files. Не удалось установить файлы *%1. - Could not install Morrowind data file! Не удалось установить файл с данными Morrowind! - - Failed to install %1. Не удалось установить %1. - Could not install Morrowind configuration file! Не удалось установить файл с настройками Morrowind! - Installing: Sound directory Установка: директория Sound - Could not find Tribunal data file! Не удалось найти файл с данными Tribunal! - - - Failed to find %1. Не удалось найти %1. - Could not find Tribunal patch file! Не удалось найти файл с патчем для Tribunal! - Could not find Bloodmoon data file! Не удалось найти файл с данными Bloodmoon! - Updating Morrowind configuration file Обновление файла с настройками Morrowind - %1 installation finished! Установка %1 завершена! - Extracting: %1 Извлечение: %1 - - - Failed to open InstallShield Cabinet File. Не удалось открыть файл InstallShield Cabinet. - - - Opening %1 failed. Не удалось открыть %1. - Failed to extract %1. Не удалось извлечь %1. - Complete path: %1 Полный путь: %1 From 48f1f085378e84d802d928de53a405dff0cc0bcd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 22:12:39 +0100 Subject: [PATCH 1295/2167] Hide things that depend on present-but-inactive game files https://gitlab.com/OpenMW/openmw/-/merge_requests/3925#note_1843962919 --- components/contentselector/model/contentmodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 66fde2063f..8e7b77d308 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -110,7 +110,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index return Qt::NoItemFlags; if (file->builtIn() || file->fromAnotherConfigFile()) - return Qt::NoItemFlags; + return Qt::ItemIsEnabled; // game files can always be checked if (file == mGameFile) @@ -228,7 +228,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int { if (file == mGameFile) return ContentType_GameFile; - else + else if (flags(index)) return ContentType_Addon; break; From 810e2c44a6d5cf1a131f15626b6365fc9c135486 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 22:34:17 +0100 Subject: [PATCH 1296/2167] Merge conflict resolution part II --- files/lang/wizard_de.ts | 4 ++-- files/lang/wizard_fr.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index d793175dcb..5749cf2d5d 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -514,11 +514,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Error writing OpenMW configuration file + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + Error writing OpenMW configuration file diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index ba3c8aaa19..9b5acbc9e9 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -505,10 +505,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Quit Wizard - - Error writing OpenMW configuration file - - Are you sure you want to exit the Wizard? @@ -521,6 +517,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + Error writing OpenMW configuration file + + Wizard::UnshieldWorker From edb9da5a67ec46544d6aefb04e8521d331b9702f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 21 Feb 2024 10:28:01 +0400 Subject: [PATCH 1297/2167] Capitalize in-game setting --- files/data/l10n/OMWEngine/de.yaml | 2 +- files/data/l10n/OMWEngine/en.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index aab58fb30c..142ab44994 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -161,7 +161,7 @@ WindowModeWindowedFullscreen: "Fenster Vollbild" #GammaCorrection: "Gamma Correction" #GammaDark: "Dark" #GammaLight: "Light" -#GmstOverridesL10n: "Strings from ESM files have priority" +#GmstOverridesL10n: "Strings From ESM Files Have Priority" #InvertYAxis: "Invert Y Axis" #MenuHelpDelay: "Menu Help Delay" #MenuTransparency: "Menu Transparency" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index d14aaa78fa..37f9486083 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -94,7 +94,7 @@ FrameRateHint: "Hint: press F3 to show\nthe current frame rate." GammaCorrection: "Gamma Correction" GammaDark: "Dark" GammaLight: "Light" -GmstOverridesL10n: "Strings from ESM files have priority" +GmstOverridesL10n: "Strings From ESM Files Have Priority" InvertXAxis: "Invert X Axis" InvertYAxis: "Invert Y Axis" Language: "Language" From e8c3c8115a4954e826faa38656c07c583cbbaded Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 21 Feb 2024 10:17:02 +0400 Subject: [PATCH 1298/2167] Capitalize captions in Launcher and Wizard --- apps/launcher/ui/datafilespage.ui | 10 +- apps/launcher/ui/graphicspage.ui | 10 +- apps/launcher/ui/importpage.ui | 6 +- apps/launcher/ui/settingspage.ui | 224 ++++----- apps/wizard/ui/importpage.ui | 6 +- files/lang/launcher_de.ts | 802 +++++++++++++++--------------- files/lang/launcher_fr.ts | 802 +++++++++++++++--------------- files/lang/launcher_ru.ts | 226 ++++----- files/lang/wizard_de.ts | 6 +- files/lang/wizard_fr.ts | 6 +- files/lang/wizard_ru.ts | 8 +- 11 files changed, 1053 insertions(+), 1053 deletions(-) diff --git a/apps/launcher/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui index 2b54307838..3c9967ef15 100644 --- a/apps/launcher/ui/datafilespage.ui +++ b/apps/launcher/ui/datafilespage.ui @@ -36,7 +36,7 @@ - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -160,7 +160,7 @@ - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -251,7 +251,7 @@ - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -305,7 +305,7 @@ - Remove unused tiles + Remove Unused Tiles true @@ -317,7 +317,7 @@ - Max size + Max Size diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index fa92c7b789..b3e2b15e39 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -26,7 +26,7 @@ - Window mode + Window Mode @@ -111,14 +111,14 @@ - Framerate limit + Framerate Limit - Window border + Window Border @@ -207,14 +207,14 @@ - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization diff --git a/apps/launcher/ui/importpage.ui b/apps/launcher/ui/importpage.ui index 4626d29e8a..0b5d014afa 100644 --- a/apps/launcher/ui/importpage.ui +++ b/apps/launcher/ui/importpage.ui @@ -54,7 +54,7 @@ - File to import settings from: + File to Import Settings From: @@ -73,7 +73,7 @@ - Import add-on and plugin selection (creates a new Content List) + Import Add-on and Plugin Selection true @@ -88,7 +88,7 @@ so OpenMW provides another set of fonts to avoid these issues. These fonts use T to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import bitmap fonts setup + Import Bitmap Fonts true diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 665c9e9712..14aa0118cb 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -39,7 +39,7 @@ <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - Always allow actors to follow over water + Always Allow Actors to Follow over Water @@ -49,7 +49,7 @@ <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes @@ -59,7 +59,7 @@ <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix @@ -69,7 +69,7 @@ <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior @@ -79,7 +79,7 @@ <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions @@ -89,7 +89,7 @@ <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance @@ -99,7 +99,7 @@ <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Day night switch nodes + Day Night Switch Nodes @@ -109,7 +109,7 @@ <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - Followers defend immediately + Followers Defend Immediately @@ -119,7 +119,7 @@ <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding @@ -129,7 +129,7 @@ <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance @@ -139,7 +139,7 @@ <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - Graphic herbalism + Graphic Herbalism @@ -149,7 +149,7 @@ <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Swim upward correction + Swim Upward Correction @@ -159,7 +159,7 @@ <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Enchanted weapons are magical + Enchanted Weapons Are Magical @@ -169,7 +169,7 @@ <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix @@ -179,7 +179,7 @@ <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value @@ -189,7 +189,7 @@ <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Can loot during death animation + Can Loot During Death Animation @@ -199,7 +199,7 @@ <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat @@ -209,7 +209,7 @@ <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior @@ -219,7 +219,7 @@ <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor @@ -230,7 +230,7 @@ - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat @@ -246,12 +246,12 @@ - Affect werewolves + Affect Werewolves - Do not affect werewolves + Do Not Affect Werewolves @@ -262,7 +262,7 @@ <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads + Background Physics Threads @@ -272,7 +272,7 @@ - Actor collision shape type + Actor Collision Shape Type @@ -282,16 +282,16 @@ Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Axis-aligned bounding box + Axis-Aligned Bounding Box - Axis-aligned bounding box + Axis-Aligned Bounding Box - Rotating box + Rotating Box @@ -343,7 +343,7 @@ <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Smooth movement + Smooth Movement @@ -353,7 +353,7 @@ <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - Use additional animation sources + Use Additional Animation Sources @@ -376,7 +376,7 @@ <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Turn to movement direction + Turn to Movement Direction @@ -389,7 +389,7 @@ <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - Weapon sheathing + Weapon Sheathing @@ -402,7 +402,7 @@ <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Shield sheathing + Shield Sheathing @@ -412,7 +412,7 @@ <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation @@ -422,7 +422,7 @@ <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - Use magic item animation + Use Magic Item Animation @@ -447,7 +447,7 @@ If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps @@ -457,7 +457,7 @@ <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Soft particles + Soft Particles @@ -471,7 +471,7 @@ (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps @@ -481,7 +481,7 @@ <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps @@ -504,7 +504,7 @@ <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps @@ -514,7 +514,7 @@ <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test @@ -524,7 +524,7 @@ <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Use anti-aliased alpha testing + Use Anti-Aliased Alpha Testing @@ -537,7 +537,7 @@ </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting @@ -547,7 +547,7 @@ <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Weather particle occlusion + Weather Particle Occlusion @@ -570,7 +570,7 @@ <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - Exponential fog + Exponential Fog @@ -613,7 +613,7 @@ This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Radial fog + Radial Fog @@ -626,7 +626,7 @@ <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - Sky blending start + Sky Blending Start @@ -636,7 +636,7 @@ <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Sky blending + Sky Blending @@ -657,7 +657,7 @@ <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - Object paging min size + Object Paging Min Size @@ -680,7 +680,7 @@ - Viewing distance + Viewing Distance @@ -719,7 +719,7 @@ <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - Distant land + Distant Land @@ -729,7 +729,7 @@ <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - Active grid object paging + Active Grid Object Paging @@ -744,16 +744,22 @@ - - + + false - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + 3 - - Auto exposure speed + + 0.010000000000000 + + + 10.000000000000000 + + + 0.001000000000000 @@ -779,26 +785,20 @@ <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Transparent postpass + Transparent Postpass - - + + false - - 3 + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - 0.010000000000000 - - - 10.000000000000000 - - - 0.001000000000000 + + Auto Exposure Speed @@ -808,7 +808,7 @@ <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Enable post processing + Enable Post Processing @@ -827,17 +827,17 @@ - bounds + Bounds - primitives + Primitives - none + None @@ -848,7 +848,7 @@ <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method @@ -883,7 +883,7 @@ <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Enable actor shadows + Enable Actor Shadows @@ -917,7 +917,7 @@ <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - Fade start multiplier + Fade Start Multiplier @@ -927,7 +927,7 @@ <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Enable player shadows + Enable Player Shadows @@ -937,7 +937,7 @@ <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - Shadow map resolution + Shadow Map Resolution @@ -947,7 +947,7 @@ <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: @@ -957,7 +957,7 @@ <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Enable object shadows + Enable Object Shadows @@ -1002,7 +1002,7 @@ <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows @@ -1012,7 +1012,7 @@ <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows @@ -1033,7 +1033,7 @@ <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - Maximum light distance + Maximum Light Distance @@ -1056,7 +1056,7 @@ <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Max lights + Max Lights @@ -1066,7 +1066,7 @@ <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - Fade start multiplier + Fade Start Multiplier @@ -1079,7 +1079,7 @@ <p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - Lighting method + Lighting Method @@ -1108,7 +1108,7 @@ <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Bounding sphere multiplier + Bounding Sphere Multiplier @@ -1118,7 +1118,7 @@ <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - Minimum interior brightness + Minimum Interior Brightness @@ -1213,7 +1213,7 @@ Select your preferred audio device. - Audio device + Audio Device @@ -1299,7 +1299,7 @@ Select your preferred HRTF profile. - HRTF profile + HRTF Profile @@ -1335,7 +1335,7 @@ In third-person view, use the camera as the sound listener instead of the player character. - Use the camera as the sound listener + Use the Camera as the Sound Listener @@ -1383,7 +1383,7 @@ - Tooltip and crosshair + Tooltip and Crosshair @@ -1429,7 +1429,7 @@ <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - GUI scaling factor + GUI Scaling Factor @@ -1439,7 +1439,7 @@ <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Show effect duration + Show Effect Duration @@ -1449,7 +1449,7 @@ <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color @@ -1459,7 +1459,7 @@ Size of characters in game texts. - Font size + Font Size @@ -1469,7 +1469,7 @@ <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Can zoom on maps + Can Zoom on Maps @@ -1479,7 +1479,7 @@ <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show projectile damage + Show Projectile Damage @@ -1489,7 +1489,7 @@ <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show melee info + Show Melee Info @@ -1499,14 +1499,14 @@ <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Stretch menu background + Stretch Menu Background - Show owned objects + Show Owned Objects @@ -1516,7 +1516,7 @@ <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - Show enchant chance + Show Enchant Chance @@ -1554,7 +1554,7 @@ <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves @@ -1563,7 +1563,7 @@ - Maximum quicksaves + Maximum Quicksaves @@ -1590,7 +1590,7 @@ - Screenshot format + Screenshot Format @@ -1618,7 +1618,7 @@ - Notify on saved screenshot + Notify on Saved Screenshot @@ -1668,14 +1668,14 @@ <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Grab cursor + Grab Cursor - Skip menu and generate default character + Skip Menu and Generate Default Character @@ -1700,14 +1700,14 @@ - Start default character at + Start Default Character at - default cell + Default Cell @@ -1716,7 +1716,7 @@ - Run script after startup: + Run Script After Startup: diff --git a/apps/wizard/ui/importpage.ui b/apps/wizard/ui/importpage.ui index acc0d268ec..669920c5f3 100644 --- a/apps/wizard/ui/importpage.ui +++ b/apps/wizard/ui/importpage.ui @@ -33,7 +33,7 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini true @@ -43,7 +43,7 @@ - Import add-on and plugin selection + Import Add-on and Plugin Selection true @@ -53,7 +53,7 @@ - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 925733f9d3..e3101cf6df 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -7,18 +7,10 @@ Content Files - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Data Directories - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Scan directories for likely data directories and append them at the end of the list. @@ -67,10 +59,6 @@ Move selected archive one position up - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Move selected archive one position down @@ -95,14 +83,6 @@ Cancel - - Remove unused tiles - - - - Max size - - MiB @@ -159,6 +139,26 @@ Ctrl+R + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Remove Unused Tiles + + + + Max Size + + GraphicsPage @@ -226,28 +226,28 @@ Screen - - Window mode - - - - Framerate limit - - - - Window border - - Resolution - Anti-aliasing + Window Mode - Vertical synchronization + Framerate Limit + + + + Window Border + + + + Anti-Aliasing + + + + Vertical Synchronization @@ -269,18 +269,10 @@ Morrowind Settings Importer - - File to import settings from: - - Browse... - - Import add-on and plugin selection (creates a new Content List) - - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar @@ -288,11 +280,19 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Import bitmap fonts setup + Run &Settings Importer - Run &Settings Importer + File to Import Settings From: + + + + Import Bitmap Fonts + + + + Import Add-on and Plugin Selection @@ -606,18 +606,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - Permanent barter disposition changes - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - Followers defend immediately - - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> @@ -630,102 +622,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - - Classic Calm spells behavior - - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - - Soulgem values rebalance - - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - - Swim upward correction - - - - Enchanted weapons are magical - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - Classic reflected Absorb spells behavior - - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - - Use navigation mesh for pathfinding - - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - - NPCs avoid collisions - - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - - Racial variation in speed fix - - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - - Can loot during death animation - - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - - Unarmed creature attacks damage armor - - Off - - Affect werewolves - - - - Do not affect werewolves - - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - - Axis-aligned bounding box - - - - Rotating box - - Cylinder @@ -742,58 +678,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - Use magic item animation - - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - - Smooth movement - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - Use additional animation sources - - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - - Turn to movement direction - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - Weapon sheathing - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - Shield sheathing - - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - - Player movement ignores animation - - Shaders @@ -804,18 +712,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - - Auto use object normal maps - - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - - Auto use terrain normal maps - - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately (see 'specular map pattern', e.g. for a base texture foo.dds, @@ -824,18 +724,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov (.osg file, not supported in .nif files). Affects objects.</p></body></html> - - Auto use object specular maps - - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - - Auto use terrain specular maps - - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. @@ -843,18 +735,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov </p></body></html> - - Bump/reflect map local lighting - - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - - Use anti-aliased alpha testing - - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> @@ -863,10 +747,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - - Adjust coverage for alpha test - - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> @@ -880,74 +760,38 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - - Radial fog - - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - - Exponential fog - - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - - Sky blending - - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - - Sky blending start - - Terrain - - Viewing distance - - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - - Object paging min size - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - Distant land - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - - Active grid object paging - - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - - Day night switch nodes - - Post Processing @@ -956,26 +800,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - - Enable post processing - - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - - Transparent postpass - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - Auto exposure speed - - Audio @@ -1016,42 +848,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - - GUI scaling factor - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - Show effect duration - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - Change dialogue topic color - - Size of characters in game texts. - - Font size - - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - - Can zoom on maps - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -1060,46 +872,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - Show projectile damage - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - Show melee info - - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - - Stretch menu background - - - - Show owned objects - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - Show enchant chance - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - Merchant equipping fix - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> @@ -1116,10 +904,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - Add "Time Played" to saves - - JPG @@ -1132,10 +916,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov TGA - - Notify on saved screenshot - - Testing @@ -1148,26 +928,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - Grab cursor - - - - Skip menu and generate default character - - - - Start default character at - - - - default cell - - - - Run script after startup: - - Browse… @@ -1180,54 +940,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Gameplay - - Always allow actors to follow over water - - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - - Only magical ammo bypass resistance - - - - Graphic herbalism - - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - - Trainers choose offered skills by base value - - - - Steal from knocked out actors in combat - - - - Factor strength into hand-to-hand combat - - - - Background physics threads - - - - Actor collision shape type - - - - Soft particles - - - - Weather particle occlusion - - cells @@ -1236,26 +956,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Shadows - - bounds - - - - primitives - - - - none - - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - - Shadow planes computation method - - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> @@ -1268,10 +972,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - - Enable actor shadows - - 512 @@ -1292,74 +992,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - - Fade start multiplier - - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - - Enable player shadows - - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - - Shadow map resolution - - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - - Shadow distance limit: - - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - - Enable object shadows - - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - - Enable indoor shadows - - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - - Enable terrain shadows - - Lighting - - Lighting method - - - - Audio device - - - - HRTF profile - - Tooltip @@ -1368,22 +1028,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Crosshair - - Tooltip and crosshair - - - - Maximum quicksaves - - Screenshots - - Screenshot format - - <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1432,23 +1080,375 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use the camera as the sound listener + Permanent Barter Disposition Changes - Maximum light distance + Classic Calm Spells Behavior - Max lights + NPCs Avoid Collisions - Bounding sphere multiplier + Soulgem Values Rebalance - Minimum interior brightness + Day Night Switch Nodes + + + + Followers Defend Immediately + + + + Only Magical Ammo Bypass Resistance + + + + Graphic Herbalism + + + + Swim Upward Correction + + + + Enchanted Weapons Are Magical + + + + Merchant Equipping Fix + + + + Can Loot During Death Animation + + + + Classic Reflected Absorb Spells Behavior + + + + Unarmed Creature Attacks Damage Armor + + + + Affect Werewolves + + + + Do Not Affect Werewolves + + + + Background Physics Threads + + + + Actor Collision Shape Type + + + + Axis-Aligned Bounding Box + + + + Rotating Box + + + + Smooth Movement + + + + Use Additional Animation Sources + + + + Weapon Sheathing + + + + Shield Sheathing + + + + Player Movement Ignores Animation + + + + Use Magic Item Animation + + + + Auto Use Object Normal Maps + + + + Soft Particles + + + + Auto Use Object Specular Maps + + + + Auto Use Terrain Normal Maps + + + + Auto Use Terrain Specular Maps + + + + Use Anti-Aliased Alpha Testing + + + + Bump/Reflect Map Local Lighting + + + + Weather Particle Occlusion + + + + Exponential Fog + + + + Radial Fog + + + + Sky Blending Start + + + + Sky Blending + + + + Object Paging Min Size + + + + Viewing Distance + + + + Distant Land + + + + Active Grid Object Paging + + + + Transparent Postpass + + + + Auto Exposure Speed + + + + Enable Post Processing + + + + Shadow Planes Computation Method + + + + Enable Actor Shadows + + + + Fade Start Multiplier + + + + Enable Player Shadows + + + + Shadow Map Resolution + + + + Shadow Distance Limit: + + + + Enable Object Shadows + + + + Enable Indoor Shadows + + + + Enable Terrain Shadows + + + + Maximum Light Distance + + + + Max Lights + + + + Lighting Method + + + + Bounding Sphere Multiplier + + + + Minimum Interior Brightness + + + + Audio Device + + + + HRTF Profile + + + + Tooltip and Crosshair + + + + GUI Scaling Factor + + + + Show Effect Duration + + + + Change Dialogue Topic Color + + + + Font Size + + + + Show Projectile Damage + + + + Show Melee Info + + + + Stretch Menu Background + + + + Show Owned Objects + + + + Show Enchant Chance + + + + Maximum Quicksaves + + + + Screenshot Format + + + + Grab Cursor + + + + Default Cell + + + + Bounds + + + + Primitives + + + + None + + + + Always Allow Actors to Follow over Water + + + + Racial Variation in Speed Fix + + + + Use Navigation Mesh for Pathfinding + + + + Trainers Choose Offered Skills by Base Value + + + + Steal from Knocked out Actors in Combat + + + + Factor Strength into Hand-to-Hand Combat + + + + Turn to Movement Direction + + + + Adjust Coverage for Alpha Test + + + + Use the Camera as the Sound Listener + + + + Can Zoom on Maps + + + + Add "Time Played" to Saves + + + + Notify on Saved Screenshot + + + + Skip Menu and Generate Default Character + + + + Start Default Character at + + + + Run Script After Startup: diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3471fc6c5c..89f5cb1c9b 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -7,18 +7,10 @@ Content Files - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Data Directories - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Scan directories for likely data directories and append them at the end of the list. @@ -67,10 +59,6 @@ Move selected archive one position up - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Move selected archive one position down @@ -95,14 +83,6 @@ Cancel - - Remove unused tiles - - - - Max size - - MiB @@ -159,6 +139,26 @@ Ctrl+R + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Remove Unused Tiles + + + + Max Size + + GraphicsPage @@ -226,28 +226,28 @@ Screen - - Window mode - - - - Framerate limit - - - - Window border - - Resolution - Anti-aliasing + Window Mode - Vertical synchronization + Framerate Limit + + + + Window Border + + + + Anti-Aliasing + + + + Vertical Synchronization @@ -269,18 +269,10 @@ Morrowind Settings Importer - - File to import settings from: - - Browse... - - Import add-on and plugin selection (creates a new Content List) - - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar @@ -288,11 +280,19 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Import bitmap fonts setup + Run &Settings Importer - Run &Settings Importer + File to Import Settings From: + + + + Import Bitmap Fonts + + + + Import Add-on and Plugin Selection @@ -606,18 +606,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - - Permanent barter disposition changes - - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - - Followers defend immediately - - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> @@ -630,102 +622,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - - Classic Calm spells behavior - - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - - Soulgem values rebalance - - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - - Swim upward correction - - - - Enchanted weapons are magical - - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - - Classic reflected Absorb spells behavior - - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - - Use navigation mesh for pathfinding - - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - - NPCs avoid collisions - - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - - Racial variation in speed fix - - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - - Can loot during death animation - - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - - Unarmed creature attacks damage armor - - Off - - Affect werewolves - - - - Do not affect werewolves - - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - - Axis-aligned bounding box - - - - Rotating box - - Cylinder @@ -742,58 +678,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - Use magic item animation - - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - - Smooth movement - - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - - Use additional animation sources - - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - - Turn to movement direction - - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - - Weapon sheathing - - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - - Shield sheathing - - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - - Player movement ignores animation - - Shaders @@ -804,18 +712,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - - Auto use object normal maps - - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - - Auto use terrain normal maps - - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately (see 'specular map pattern', e.g. for a base texture foo.dds, @@ -824,18 +724,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov (.osg file, not supported in .nif files). Affects objects.</p></body></html> - - Auto use object specular maps - - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - - Auto use terrain specular maps - - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. @@ -843,18 +735,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov </p></body></html> - - Bump/reflect map local lighting - - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - - Use anti-aliased alpha testing - - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> @@ -863,10 +747,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - - Adjust coverage for alpha test - - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> @@ -880,74 +760,38 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - - Radial fog - - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - - Exponential fog - - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - - Sky blending - - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - - Sky blending start - - Terrain - - Viewing distance - - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - - Object paging min size - - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - Distant land - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - - Active grid object paging - - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - - Day night switch nodes - - Post Processing @@ -956,26 +800,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - - Enable post processing - - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - - Transparent postpass - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - Auto exposure speed - - Audio @@ -1016,42 +848,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - - GUI scaling factor - - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - - Show effect duration - - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - - Change dialogue topic color - - Size of characters in game texts. - - Font size - - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - - Can zoom on maps - - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> @@ -1060,46 +872,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - Show projectile damage - - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - - Show melee info - - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - - Stretch menu background - - - - Show owned objects - - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - - Show enchant chance - - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - - Merchant equipping fix - - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> @@ -1116,10 +904,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - Add "Time Played" to saves - - JPG @@ -1132,10 +916,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov TGA - - Notify on saved screenshot - - Testing @@ -1148,26 +928,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - - Grab cursor - - - - Skip menu and generate default character - - - - Start default character at - - - - default cell - - - - Run script after startup: - - Browse… @@ -1180,54 +940,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Gameplay - - Always allow actors to follow over water - - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - - Only magical ammo bypass resistance - - - - Graphic herbalism - - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - - Trainers choose offered skills by base value - - - - Steal from knocked out actors in combat - - - - Factor strength into hand-to-hand combat - - - - Background physics threads - - - - Actor collision shape type - - - - Soft particles - - - - Weather particle occlusion - - cells @@ -1236,26 +956,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Shadows - - bounds - - - - primitives - - - - none - - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - - Shadow planes computation method - - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> @@ -1268,10 +972,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - - Enable actor shadows - - 512 @@ -1292,74 +992,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - - Fade start multiplier - - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - - Enable player shadows - - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - - Shadow map resolution - - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - - Shadow distance limit: - - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - - Enable object shadows - - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - - Enable indoor shadows - - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - - Enable terrain shadows - - Lighting - - Lighting method - - - - Audio device - - - - HRTF profile - - Tooltip @@ -1368,22 +1028,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Crosshair - - Tooltip and crosshair - - - - Maximum quicksaves - - Screenshots - - Screenshot format - - <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> @@ -1432,23 +1080,375 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use the camera as the sound listener + Permanent Barter Disposition Changes - Maximum light distance + Classic Calm Spells Behavior - Max lights + NPCs Avoid Collisions - Bounding sphere multiplier + Soulgem Values Rebalance - Minimum interior brightness + Day Night Switch Nodes + + + + Followers Defend Immediately + + + + Only Magical Ammo Bypass Resistance + + + + Graphic Herbalism + + + + Swim Upward Correction + + + + Enchanted Weapons Are Magical + + + + Merchant Equipping Fix + + + + Can Loot During Death Animation + + + + Classic Reflected Absorb Spells Behavior + + + + Unarmed Creature Attacks Damage Armor + + + + Affect Werewolves + + + + Do Not Affect Werewolves + + + + Background Physics Threads + + + + Actor Collision Shape Type + + + + Axis-Aligned Bounding Box + + + + Rotating Box + + + + Smooth Movement + + + + Use Additional Animation Sources + + + + Weapon Sheathing + + + + Shield Sheathing + + + + Player Movement Ignores Animation + + + + Use Magic Item Animation + + + + Auto Use Object Normal Maps + + + + Soft Particles + + + + Auto Use Object Specular Maps + + + + Auto Use Terrain Normal Maps + + + + Auto Use Terrain Specular Maps + + + + Use Anti-Aliased Alpha Testing + + + + Bump/Reflect Map Local Lighting + + + + Weather Particle Occlusion + + + + Exponential Fog + + + + Radial Fog + + + + Sky Blending Start + + + + Sky Blending + + + + Object Paging Min Size + + + + Viewing Distance + + + + Distant Land + + + + Active Grid Object Paging + + + + Transparent Postpass + + + + Auto Exposure Speed + + + + Enable Post Processing + + + + Shadow Planes Computation Method + + + + Enable Actor Shadows + + + + Fade Start Multiplier + + + + Enable Player Shadows + + + + Shadow Map Resolution + + + + Shadow Distance Limit: + + + + Enable Object Shadows + + + + Enable Indoor Shadows + + + + Enable Terrain Shadows + + + + Maximum Light Distance + + + + Max Lights + + + + Lighting Method + + + + Bounding Sphere Multiplier + + + + Minimum Interior Brightness + + + + Audio Device + + + + HRTF Profile + + + + Tooltip and Crosshair + + + + GUI Scaling Factor + + + + Show Effect Duration + + + + Change Dialogue Topic Color + + + + Font Size + + + + Show Projectile Damage + + + + Show Melee Info + + + + Stretch Menu Background + + + + Show Owned Objects + + + + Show Enchant Chance + + + + Maximum Quicksaves + + + + Screenshot Format + + + + Grab Cursor + + + + Default Cell + + + + Bounds + + + + Primitives + + + + None + + + + Always Allow Actors to Follow over Water + + + + Racial Variation in Speed Fix + + + + Use Navigation Mesh for Pathfinding + + + + Trainers Choose Offered Skills by Base Value + + + + Steal from Knocked out Actors in Combat + + + + Factor Strength into Hand-to-Hand Combat + + + + Turn to Movement Direction + + + + Adjust Coverage for Alpha Test + + + + Use the Camera as the Sound Listener + + + + Can Zoom on Maps + + + + Add "Time Played" to Saves + + + + Notify on Saved Screenshot + + + + Skip Menu and Generate Default Character + + + + Start Default Character at + + + + Run Script After Startup: diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 52499b7b3c..a02ca403df 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -84,11 +84,11 @@ Отменить - Remove unused tiles + Remove Unused Tiles Удалить неиспользуемые тайлы - Max size + Max Size Максимальный размер @@ -148,16 +148,16 @@ Ctrl+R - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> @@ -227,15 +227,15 @@ Экран - Window mode + Window Mode Режим окна - Framerate limit + Framerate Limit Максимум кадров в секунду - Window border + Window Border Рамка окна @@ -243,11 +243,11 @@ Разрешение экрана - Anti-aliasing + Anti-Aliasing Сглаживание - Vertical synchronization + Vertical Synchronization Вертикальная синхронизация @@ -270,7 +270,7 @@ Импорт настроек из Morrowind - File to import settings from: + File to Import Settings From: Импортировать настройки из файла: @@ -278,8 +278,8 @@ Выбрать... - Import add-on and plugin selection (creates a new Content List) - Импортировать подключенные плагины (создает новый список плагинов) + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, @@ -290,7 +290,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Включите эту опцию, если вы все равно хотите использовать оригинальные шрифты вместо шрифтов OpenMW, или если вы используете сторонние растровые шрифты. - Import bitmap fonts setup + Import Bitmap Fonts Импортировать растровые шрифты @@ -609,7 +609,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает изменение отношения торговцев к персонажу игрока из-за торговли постоянным.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes Постоянная смена отношения торговцев @@ -617,7 +617,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Немедленно вводить спутников персонажа игрока в бой с противниками, начавшими бой с ними или персонажем игрока. Если настройка отключена, они не вступят в бой до первой атаки от противников или игрока.</p></body></html> - Followers defend immediately + Followers Defend Immediately Спутники защищаются сразу @@ -633,7 +633,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает вступление в бой персонажей, подвергнутых эффектам Усмирения, каждый кадр - как в Morrowind без MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior Классическое поведение Усмирения @@ -641,7 +641,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает стоимость заполненных камней душ зависимой только от силы души.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance Ребаланс стоимости камней душ @@ -649,11 +649,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает так, чтобы персонаж плыл немного вверх относительно камеры в режиме от третьего лица. Предназначено для того, чтобы было проще плыть по поверхности воды без погружения в нее.</p></body></html> - Swim upward correction + Swim Upward Correction Коррекция при плавании вверх - Enchanted weapons are magical + Enchanted Weapons Are Magical Зачарованное оружие - магическое @@ -665,7 +665,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эффекты отраженных заклинаний Поглощения не отзеркаливаются - как в Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior Классическое поведение Поглощения @@ -673,7 +673,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, NPC пытаются избегать столкновения с другими персонажами.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions Персонажи избегают столкновения @@ -681,7 +681,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Не учитывать множитель веса расы при вычислении скорости перемещения.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix Фикс влияния веса рас на Скорость @@ -689,7 +689,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, игрок может обыскивать тела персонажей (например, призванных существ) во время анимации их смерти, если они не находятся в бою. Если при этом игрок убрал тело, то нам приходится сразу увеличивать счетчик количества погибших и запускать скрипт удаленного персонажа.</p><p>Когда эта настройка отключена, игроку всегда придется ждать завершения анимации смерти. В основном предотвращает эксплойт с призванными существами (сбор дорогого оружия с дремор и золотых святых). Конфликтует с модами на манекены, которые используют команду SkipAnim для предотвращения завершения анимации смерти.</p></body></html> - Can loot during death animation + Can Loot During Death Animation Обыск тел во время анимации смерти @@ -701,7 +701,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет атакам существ без оружия повреждать броню цели, по аналогии с атаками оружием.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor Атаки существ повреждают броню @@ -709,11 +709,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Отключено - Affect werewolves + Affect Werewolves Включая оборотней - Do not affect werewolves + Do Not Affect Werewolves Не включая оборотней @@ -725,11 +725,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Форма объектов столкновений, используемых для физики и поиска путей с помощью навигационной сетки. Цилиндры дают лучшую совместимость между поиском путей и возможностью двигаться по ним. Изменение значения этой настройки влияет на создание навигационной сетки, поэтому сетка, сгенерированная с одним значением, не может быть использована для другого. - Axis-aligned bounding box + Axis-Aligned Bounding Box Параллелепипед - Rotating box + Rotating Box Вращающийся параллелепипед @@ -749,7 +749,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использовать анимации сотворения заклинаний для магических предметов, по аналогии с заклинаниями.</p></body></html> - Use magic item animation + Use Magic Item Animation Анимации магических предметов @@ -757,7 +757,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает перемещение персонажей более плавным. Рекомендуется использовать совместно с настройкой "Поворот в направлении движения".</p></body></html> - Smooth movement + Smooth Movement Плавное перемещение @@ -765,7 +765,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Загружать KF-файлы и файлы скелетов из директории Animations</p></body></html> - Use additional animation sources + Use Additional Animation Sources Использовать источники анимаций @@ -773,7 +773,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Влияет на перемещение по диагонали и в сторону. Включение этой настройки делает перемещение более реалистичным.</p><p>При ее выключении тело персонажа всегда целиком направлено в направлении движения. Так как в игре нет анимаций для движения по диагонали, из-за этого получается скольжение.</p><p>Когда настройка включена, только ноги персонажа поворачиваются в направлении движения. Верхняя часть тела поворачивается частично. Голова всегда смотрит в одном направлении с камерой. В бою этот режим работает только для движения по диагонали, вне боя он также влияет на движение влево и вправо. Также во время плавания тело персонажа поворачивается в направлении движения.</p></body></html> - Turn to movement direction + Turn to Movement Direction Поворот в направлении движения @@ -781,7 +781,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать зачехленное оружие (с колчанами и ножнами). Требует ресурсы из модов.</p></body></html> - Weapon sheathing + Weapon Sheathing Зачехление оружия @@ -789,7 +789,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать щиты на спине. Требует ресурсы из модов.</p></body></html> - Shield sheathing + Shield Sheathing Зачехление щитов @@ -805,7 +805,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Когда настройка отключена, карты используются, только если они явно заданы в модели (.nif или .osg). Влияет на объекты.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps Карты нормалей объектов @@ -813,7 +813,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>См. 'Карты нормалей объектов'. Влияет на ландшафт.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps Карты нормалей ландшафта @@ -829,7 +829,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov (.osg-моделях, они не поддерживаются в .nif-моделях). Влияет на объекты.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps Спекулярные карты объектов @@ -837,7 +837,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если существует файл с маской, заданной с помощью настройки 'terrain specular map pattern' то он будет использоваться в качестве спекулярной карты. Он должен представлять собой текстуру, содержащую цвет слоя в RGB-канале и мощность спекулярного освещения в альфа-канале.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps Спекулярные карты ландшафта @@ -851,7 +851,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting Локальное освещение карт отражений @@ -859,7 +859,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> - Use anti-aliased alpha testing + Use Anti-Aliased Alpha Testing Сглаживание альфа-тестирования @@ -871,7 +871,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эмуляция сохранения покрытия в MIP-картах, чтобы модели с альфа-тестированием не усыхали по мере удаления от камеры. Однако если MIP-карты на уровне самих текстур уже созданы с сохранением покрытия, покрытие будет расти по мере удаления от камеры, так что читайте инструкции к вашим модам.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test Покрытие альфа-тестирования @@ -889,7 +889,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov С этой настройкой плотность тумана для объекта зависит от расстояния от камеры до объекта (т.н. евклидово расстояние), благодаря чему туман выглядит не так искусственно, особенно при большом поле зрения.</p></body></html> - Radial fog + Radial Fog Радиальный туман @@ -897,7 +897,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использование экспоненциальной формулы для тумана. По умолчанию используется линейный туман.</p></body></html> - Exponential fog + Exponential Fog Экспоненциальный туман @@ -905,7 +905,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Снизить видимость плоскости отсечения с помощью смешения объектов с небом.</p></body></html> - Sky blending + Sky Blending Смешивать с небом @@ -913,7 +913,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Множитель расстояния обзора, определяющий начало смешения.</p></body></html> - Sky blending start + Sky Blending Start Минимальное расстояние смешения @@ -921,7 +921,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Ландшафт - Viewing distance + Viewing Distance Расстояние обзора @@ -929,7 +929,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько большим должен быть объект, чтобы отображаться на удаленном ландшафте. Для этого размер объекта делится на его расстояние до камеры, и результат сравнивается со значением этой настройки. Чем меньше значение, тем больше объектов будет отображаться в сцене.</p></body></html> - Object paging min size + Object Paging Min Size Минимальный размер склеиваемых объектов @@ -937,7 +937,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, на удаленном ландшафте будут отображаться объекты, склеенные вместе с помощью специального алгоритма. Когда выключено, будет отображаться только сам ландшафт.</p></body></html> - Distant land + Distant Land Удаленный ландшафт @@ -945,7 +945,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Склеивать объекты в активных ячейках.</p></body></html> - Active grid object paging + Active Grid Object Paging Склеивание в активных ячейках @@ -953,7 +953,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, модели с поддержкой переключения в зависимости от времени суток будут ее использовать.</p></body></html> - Day night switch nodes + Day Night Switch Nodes Переключение узлов дня и ночи @@ -965,7 +965,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, то будет включена постобработка графики.</p></body></html> - Enable post processing + Enable Post Processing Включить постобработку @@ -973,7 +973,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отрисовывать прозрачные объекты заново - со строгой границей прозрачности.</p></body></html> - Transparent postpass + Transparent Postpass Дополнительный проход для прозрачных объектов @@ -981,7 +981,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько быстро может меняться эффект аккомодации глаза от кадра к кадру. Более низкие значения означают более медленные преобразования.</p></body></html> - Auto exposure speed + Auto Exposure Speed Скорость автоматической экспозиции @@ -1025,7 +1025,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> - GUI scaling factor + GUI Scaling Factor Масштаб интерфейса @@ -1033,7 +1033,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> - Show effect duration + Show Effect Duration Показывать длительность эффектов @@ -1041,7 +1041,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color Смена цвета тем для диалогов @@ -1049,7 +1049,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Размер символов в текстах. - Font size + Font Size Размер шрифтов @@ -1057,7 +1057,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> - Can zoom on maps + Can Zoom on Maps Включить масштабирование карты @@ -1069,7 +1069,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> - Show projectile damage + Show Projectile Damage Показывать урон снарядов @@ -1077,7 +1077,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> - Show melee info + Show Melee Info Показывать информацию об оружии @@ -1085,11 +1085,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> - Stretch menu background + Stretch Menu Background Растягивать фон меню - Show owned objects + Show Owned Objects Выделять объекты с владельцами @@ -1097,7 +1097,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> - Show enchant chance + Show Enchant Chance Показывать шанс успеха зачарования @@ -1105,7 +1105,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает экипировку торговцами предметов, которые им проданы.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix Фикс экипировки предметов торговцами @@ -1125,7 +1125,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves Выводить "Время в игре" в сохранениях @@ -1141,7 +1141,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov TGA - Notify on saved screenshot + Notify on Saved Screenshot Уведомление при сохранении снимка @@ -1157,23 +1157,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> - Grab cursor + Grab Cursor Захватывать курсор - Skip menu and generate default character + Skip Menu and Generate Default Character Пропустить меню и создать персонажа по умолчанию - Start default character at + Start Default Character at Запустить с персонажем по умолчанию в локации - default cell + Default Cell по умолчанию - Run script after startup: + Run Script After Startup: Запустить скрипт после запуска: @@ -1185,7 +1185,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Когда настройка включена, в фоне создаются навигационные меши для геометрии игрового мира. Когда она отключена, то используются только путевые точки из игровых файлов. На однопоточных системах включенный навигатор может значительно снизить скорость смены локаций. На многопоточных системах влияние на производительность незначительное. Также на многопоточных системах задержки могут зависеть от других настроек или производительности системы. Передвижение по открытому миру, а также вход в локации и выход из них могут привести к обновлению кэша. Персонажи могут не иметь возможности найти путь вокруг них, пока обновление не будет завершено. Можете попробовать отключить навигатор, если вы предпочитаете старомодный ИИ, который не знает, как до вас добраться, пока вы стоите за камнем и колдуете огненные стрелы.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding Использовать навигационную сетку @@ -1193,7 +1193,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>В режиме вида от третьего лица камера перемещается согласно анимациям движения персонажа игрока. Включение этой настройки отключает это поведение, убирая зависимость движения персонажа игрока от состояния его анимаций. Такое поведение было в OpenMW версии 0.48 и ранее.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation Движение игрока обходит анимации @@ -1201,7 +1201,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Игровой процесс - Always allow actors to follow over water + Always Allow Actors to Follow over Water Позволить всем следовать по воде @@ -1209,11 +1209,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если настройка включена, требуются магические метательные снаряды, чтобы обойти сопротивление обычному оружию или уязвимость к нему. Если отключена, то требуются магические снаряды или магическое оружие дальнего боя.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance Только снаряды обходят сопротивление - Graphic herbalism + Graphic Herbalism Графический гербализм @@ -1221,31 +1221,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешает зачарованному оружию без флага Магическое обходить сопротивление обычному оружию, как в Morrowind.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value Учителя выбирают базовые навыки - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat Кража у персонажей без сознания в бою - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat Учет Силы в рукопашном бою - Background physics threads + Background Physics Threads Количество фоновых потоков для физики - Actor collision shape type + Actor Collision Shape Type Форма объекта столкновений для персонажей - Soft particles + Soft Particles Мягкие частицы - Weather particle occlusion + Weather Particle Occlusion Окклюзия погодных частиц @@ -1257,23 +1257,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Тени - bounds - по границам объектов + Bounds + По границам объектов - primitives - по примитивам + Primitives + По примитивам - none - отключено + None + Отключено <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> <html><head/><body><p>Определяет используемый тип вычисления границ сцены. Вычисление по границам объектов (по умолчанию) дает хороший баланс между производительностью и качеством теней, вычисление по примитивам улучшает качество теней, а еще можно полностью отключить вычисление.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method Метод определения границ для теней @@ -1289,7 +1289,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от NPC и существ, кроме персонажа игрока. Может незначительно снизить производительность.</p></body></html> - Enable actor shadows + Enable Actor Shadows Включить тени от персонажей @@ -1313,7 +1313,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель от расстояния выше, при котором тени начнут плавно угасать.</p></body></html> - Fade start multiplier + Fade Start Multiplier Множитель начала угасания теней @@ -1321,7 +1321,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от персонажа игрока. Может очень незначительно снизить производительность.</p></body></html> - Enable player shadows + Enable Player Shadows Включить тени от персонажа игрока @@ -1329,7 +1329,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешение отдельных теневых карт. Увеличение значения улучшает качество теней, но может незначительно снизить производительность.</p></body></html> - Shadow map resolution + Shadow Map Resolution Разрешение теневой карты @@ -1337,7 +1337,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Расстояние от камеры, при достижении которого тени полностью исчезают.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: Максимальная дистанция теней: @@ -1345,7 +1345,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от объектов. Может значительно снизить производительность.</p></body></html> - Enable object shadows + Enable Object Shadows Включить тени от объектов @@ -1353,7 +1353,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Из-за ограничений в файлах Morrowind, только персонажи могут отбрасывать тени в интерьерах, что может раздражать.</p><p>Не имеет эффекта, если тени от персонажей отключены.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows Включить тени в интерьерах @@ -1361,7 +1361,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от ландшафта (включая удаленный ландшафт). Может значительно снизить производительность и качество теней.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows Включить тени от ландшафта @@ -1369,15 +1369,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Освещение - Lighting method + Lighting Method Способ освещения - Audio device + Audio Device Звуковое устройство - HRTF profile + HRTF Profile Профиль HRTF @@ -1389,11 +1389,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Прицел - Tooltip and crosshair + Tooltip and Crosshair Всплывающая подсказка и прицел - Maximum quicksaves + Maximum Quicksaves Количество быстрых сохранений @@ -1401,7 +1401,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Снимки экрана - Screenshot format + Screenshot Format Формат снимков экрана @@ -1409,7 +1409,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> - Maximum light distance + Maximum Light Distance Дальность отображения источников света @@ -1417,7 +1417,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> - Max lights + Max Lights Макс. кол-во источников света @@ -1447,7 +1447,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> - Bounding sphere multiplier + Bounding Sphere Multiplier Множитель размера ограничивающей сферы @@ -1459,11 +1459,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. - Use the camera as the sound listener + Use the Camera as the Sound Listener Использовать камеру как слушателя - Minimum interior brightness + Minimum Interior Brightness Минимальный уровень освещения в помещениях diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 5749cf2d5d..33c7a5fb53 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 9b5acbc9e9..ab0b01af73 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 0a2e6e5561..5784b11eac 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -81,15 +81,15 @@ <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini + Import Settings From Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection - Импортировать список подключенных плагинов + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Импортировать растровые шрифты из Morrowind.ini From c4857260460b7924f27ed9523c7cb1d94e5d7a9d Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 8 Apr 2024 09:11:40 +0000 Subject: [PATCH 1299/2167] Update Swedish translations --- files/data/l10n/OMWEngine/sv.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 15a9afa495..791efb660d 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -110,9 +110,9 @@ LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljuse LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." LightsLightingMethodTooltip: "Välj intern hantering av ljuskällor.\n\n -# \"Legacy\" använder alltid max 8 ljuskällor per objekt och ger ljussättning lik ett gammaldags spel.\n\n -# \"Shader (compatibility)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n -# \"Shaders\" har alla fördelar som \"Shaders (compatibility)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." + \"Gammaldags\" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.\n\n + \"Shader (kompatibilitet)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n + \"Shader\" har alla fördelar som \"Shader (kompatibilitet)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" @@ -154,6 +154,7 @@ SensitivityHigh: "Hög" SensitivityLow: "Låg" SettingsWindow: "Inställningar" Subtitles: "Undertexter" +SunlightScattering: "Solljusspridning" TestingExteriorCells: "Testar exteriöra celler" TestingInteriorCells: "Testar interiöra celler" TextureFiltering: "Texturfiltrering" @@ -166,8 +167,9 @@ TogglePostProcessorHUD: "Visa/dölj Postprocess-HUD" TransparencyFull: "Full" TransparencyNone: "Ingen" Video: "Bild" -VSync: "VSynk" ViewDistance: "Siktavstånd" +VSync: "VSynk" +VSyncAdaptive: "Adaptiv" Water: "Vatten" WaterShader: "Vattenshader" WaterShaderTextureQuality: "Texturkvalitet" @@ -177,3 +179,4 @@ WindowModeFullscreen: "Fullskärm" WindowModeHint: "Notera: Fullskärm i fönsterläge\nanvänder alltid skärmens nativa upplösning." WindowModeWindowed: "Fönster" WindowModeWindowedFullscreen: "Fullskärm i fönsterläge" +WobblyShores: "Vaggande stränder" From 0d547c54385c6eb7750e449aef6dc3fa812a863b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 8 Apr 2024 13:37:36 +0100 Subject: [PATCH 1300/2167] Resolve merge conflicts from https://gitlab.com/OpenMW/openmw/-/merge_requests/3893 --- apps/wizard/ui/importpage.ui | 6 +++--- files/lang/wizard_de.ts | 6 +++--- files/lang/wizard_fr.ts | 6 +++--- files/lang/wizard_ru.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/wizard/ui/importpage.ui b/apps/wizard/ui/importpage.ui index acc0d268ec..669920c5f3 100644 --- a/apps/wizard/ui/importpage.ui +++ b/apps/wizard/ui/importpage.ui @@ -33,7 +33,7 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini true @@ -43,7 +43,7 @@ - Import add-on and plugin selection + Import Add-on and Plugin Selection true @@ -53,7 +53,7 @@ - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 5749cf2d5d..33c7a5fb53 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 9b5acbc9e9..ab0b01af73 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 0a2e6e5561..e26735443e 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -81,15 +81,15 @@ <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini + Import Settings From Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Импортировать растровые шрифты из Morrowind.ini From a179e9c001c36ac8f7dbe4915c7ee6e02d80e466 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 8 Apr 2024 13:43:42 +0100 Subject: [PATCH 1301/2167] The rest of the merge conflict I didn't notice it as GitLab didn't highlight the diff properly. --- files/lang/wizard_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index e26735443e..5784b11eac 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -86,7 +86,7 @@ Import Add-on and Plugin Selection - Импортировать список подключенных плагинов + Импортировать список подключенных аддонов и плагинов Import Bitmap Fonts Setup From Morrowind.ini From 1ed82662ee3de7f4cef3568bbb6cecec8ef29e73 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Apr 2024 17:54:13 +0200 Subject: [PATCH 1302/2167] Don't show nameless higher ranks --- CHANGELOG.md | 1 + apps/openmw/mwgui/statswindow.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 587e5e7a52..03b5769c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -223,6 +223,7 @@ Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) Feature #7875: Disable MyGUI windows snapping Feature #7914: Do not allow to move GUI windows out of screen + Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 6e7d2c2ba2..69f0b4b449 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -582,9 +582,8 @@ namespace MWGui const std::set& expelled = PCstats.getExpelled(); bool firstFaction = true; - for (auto& factionPair : mFactions) + for (const auto& [factionId, factionRank] : mFactions) { - const ESM::RefId& factionId = factionPair.first; const ESM::Faction* faction = store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; @@ -611,10 +610,10 @@ namespace MWGui text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - const int rank = std::clamp(factionPair.second, 0, 9); - text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; - - if (rank < 9) + const auto rank = static_cast(std::max(0, factionRank)); + if (rank < faction->mRanks.size()) + text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; + if (rank + 1 < faction->mRanks.size() && !faction->mRanks[rank + 1].empty()) { // player doesn't have max rank yet text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank + 1]; From 4018b1ae598e585cd168c0bd9f6f69222e3a5c49 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 9 Apr 2024 21:24:53 -0500 Subject: [PATCH 1303/2167] Remove hidden functions --- apps/openmw/mwlua/inputbindings.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 1b9ed3bf11..dbe79ca377 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -214,10 +214,6 @@ namespace MWLua input->setGamepadGuiCursorEnabled(v); MWBase::Environment::get().getWindowManager()->setCursorActive(v); }; - api["_selectNextMenuElement"] - = []() { MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Period, 0, false); }; - api["_selectPrevMenuElement"] - = []() { MWBase::Environment::get().getWindowManager()->injectKeyPress(MyGUI::KeyCode::Slash, 0, false); }; api["getMouseMoveX"] = [input]() { return input->getMouseMoveX(); }; api["getMouseMoveY"] = [input]() { return input->getMouseMoveY(); }; api["getAxisValue"] = [input](int axis) { From 2f6acbd7da1c5ee026209214a9c1fda76f348a52 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Apr 2024 21:16:22 +0200 Subject: [PATCH 1304/2167] Deregister the player before loading a new one --- apps/openmw/mwworld/player.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b498bb488b..7849ba1458 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -325,6 +325,7 @@ namespace MWWorld player.mObject.mEnabled = true; } + MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer); mPlayer.load(player.mObject); for (size_t i = 0; i < mSaveAttributes.size(); ++i) From 8751203849708d92605faac2d96dd354a0b62e9e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 10 Apr 2024 22:23:41 +0300 Subject: [PATCH 1305/2167] Don't run target-specific spell infliction code when there's no target (#7926) --- apps/openmw/mwmechanics/spellcasting.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2fd250f8c1..a7e27c9ddd 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -188,6 +188,9 @@ namespace MWMechanics for (auto& enam : effects.mList) { + if (target.isEmpty()) + break; + if (enam.mData.mRange != range) continue; const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID); From 8c2c66d59e033c6ae8467ad04f6f791d9721d38d Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 02:16:06 +0100 Subject: [PATCH 1306/2167] .nif check, matrix mult feedback, auto usage, reuse NodeMap typedef --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/rotatecontroller.cpp | 6 +++--- components/resource/scenemanager.cpp | 4 +--- components/sceneutil/visitor.hpp | 12 ++++-------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 82081658a6..31b5d36c79 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1491,7 +1491,7 @@ namespace MWRender // osgAnimation formats with skeletons should have their nodemap be bone instances // FIXME: better way to detect osgAnimation here instead of relying on extension? - mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, ".nif"); if (previousStateset) mObjectRoot->setStateSet(previousStateset); diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 47b271c40d..61a2a2628a 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -50,9 +50,9 @@ namespace MWRender { osgAnimation::Bone* parent = b->getBoneParent(); if (parent) - b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); - else - b->setMatrixInSkeletonSpace(matrix); + matrix *= parent->getMatrixInSkeletonSpace(); + + b->setMatrixInSkeletonSpace(matrix); } traverse(node, nv); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index bf2674a98f..4bdd620819 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -399,9 +399,7 @@ namespace Resource osg::Callback* cb = node.getUpdateCallback(); while (cb) { - osgAnimation::AnimationUpdateCallback* animCb - = dynamic_cast*>(cb); - + auto animCb = dynamic_cast*>(cb); if (animCb) animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index a5af88d7de..a9a943423c 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -50,14 +50,14 @@ namespace SceneUtil std::vector mFoundNodes; }; + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) @@ -74,10 +74,6 @@ namespace SceneUtil class NodeMapVisitorBoneOnly : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitorBoneOnly(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) From c3420ed306ddc2a9c369dc4e0903e5e566f49e5f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 03:01:00 +0100 Subject: [PATCH 1307/2167] Fix build --- apps/opencs/view/render/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index a9cc34b00d..09d896e7e7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -62,7 +62,7 @@ namespace CSVRender osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; - SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + SceneUtil::NodeMap mNodeMap; }; } From fb4edda45dde6cf3ee57e6bf4b28718bb1e91251 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Apr 2024 19:45:29 +0200 Subject: [PATCH 1308/2167] Validate INFO filters when loading the record --- apps/openmw/mwdialogue/filter.cpp | 19 +++---- apps/openmw/mwdialogue/selectwrapper.cpp | 12 +++- apps/openmw/mwdialogue/selectwrapper.hpp | 4 ++ components/esm3/loadinfo.cpp | 71 +++++++++++++++++++++++- 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 8b67ea28b3..2693811357 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -30,7 +30,7 @@ namespace { bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) { - const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName()); + const ESM::RefId selectId = select.getId(); if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) return actor.getCellRef().getRefId() != selectId; if (actor.getClass().isNpc()) @@ -356,19 +356,18 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons { case SelectWrapper::Function_Journal: - return MWBase::Environment::get().getJournal()->getJournalIndex(ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId()); case SelectWrapper::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - return store.count(ESM::RefId::stringRefId(select.getName())); + return store.count(select.getId()); } case SelectWrapper::Function_Dead: - return MWBase::Environment::get().getMechanicsManager()->countDeaths( - ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId()); case SelectWrapper::Function_Choice: @@ -546,24 +545,24 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con case SelectWrapper::Function_NotId: - return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName())); + return mActor.getCellRef().getRefId() != select.getId(); case SelectWrapper::Function_NotFaction: - return !(mActor.getClass().getPrimaryFaction(mActor) == ESM::RefId::stringRefId(select.getName())); + return mActor.getClass().getPrimaryFaction(mActor) != select.getId(); case SelectWrapper::Function_NotClass: - return !(mActor.get()->mBase->mClass == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mClass != select.getId(); case SelectWrapper::Function_NotRace: - return !(mActor.get()->mBase->mRace == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mRace != select.getId(); case SelectWrapper::Function_NotCell: { std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); - return !Misc::StringUtils::ciStartsWith(actorCell, select.getName()); + return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName()); } case SelectWrapper::Function_SameGender: diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 94f7f73097..0cee8bb009 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -454,5 +454,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase(std::string_view(mSelect.mSelectRule).substr(5)); + return Misc::StringUtils::lowerCase(getCellName()); +} + +std::string_view MWDialogue::SelectWrapper::getCellName() const +{ + return std::string_view(mSelect.mSelectRule).substr(5); +} + +ESM::RefId MWDialogue::SelectWrapper::getId() const +{ + return ESM::RefId::stringRefId(getCellName()); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index 0d376d957c..f736b504d8 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -95,6 +95,10 @@ namespace MWDialogue std::string getName() const; ///< Return case-smashed name. + + std::string_view getCellName() const; + + ESM::RefId getId() const; }; } diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 714d59fef4..b091edd3f6 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,7 +3,65 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include +#include + +namespace +{ + enum class SelectRuleStatus + { + Valid, + Invalid, + Ignorable + }; + + SelectRuleStatus isValidSelectRule(std::string_view rule) + { + if (rule.size() < 5) + return SelectRuleStatus::Invalid; + if (rule[4] < '0' || rule[4] > '5') // Comparison operators + return SelectRuleStatus::Invalid; + if (rule[1] == '1') // Function + { + int function = Misc::StringUtils::toNumeric(rule.substr(2, 2), -1); + if (function >= 0 && function <= 73) + return SelectRuleStatus::Valid; + return SelectRuleStatus::Invalid; + } + if (rule.size() == 5) // Missing ID + return SelectRuleStatus::Invalid; + if (rule[3] != 'X') + return SelectRuleStatus::Ignorable; + constexpr auto ignorable + = [](bool valid) { return valid ? SelectRuleStatus::Valid : SelectRuleStatus::Ignorable; }; + switch (rule[1]) + { + case '2': + case '3': + case 'C': + return ignorable(rule[2] == 's' || rule[2] == 'l' || rule[2] == 'f'); + case '4': + return ignorable(rule[2] == 'J'); + case '5': + return ignorable(rule[2] == 'I'); + case '6': + return ignorable(rule[2] == 'D'); + case '7': + return ignorable(rule[2] == 'X'); + case '8': + return ignorable(rule[2] == 'F'); + case '9': + return ignorable(rule[2] == 'C'); + case 'A': + return ignorable(rule[2] == 'R'); + case 'B': + return ignorable(rule[2] == 'L'); + default: + return SelectRuleStatus::Invalid; + } + } +} namespace ESM { @@ -69,7 +127,18 @@ namespace ESM SelectStruct ss; ss.mSelectRule = esm.getHString(); ss.mValue.read(esm, Variant::Format_Info); - mSelects.push_back(ss); + auto valid = isValidSelectRule(ss.mSelectRule); + if (ss.mValue.getType() != VT_Int && ss.mValue.getType() != VT_Float) + valid = SelectRuleStatus::Invalid; + if (valid == SelectRuleStatus::Invalid) + Log(Debug::Warning) << "Skipping invalid SCVR for INFO " << mId; + else + { + mSelects.push_back(ss); + if (valid == SelectRuleStatus::Ignorable) + Log(Debug::Info) + << "Found malformed SCVR for INFO " << mId << " at index " << ss.mSelectRule[0]; + } break; } case fourCC("BNAM"): From cb92d34ddda7c8b10613ae9a09d3817d1659d250 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Apr 2024 17:04:55 +0200 Subject: [PATCH 1309/2167] Reorder RefData members to decrease its size --- apps/openmw/mwworld/refdata.cpp | 30 +++++++++++++++--------------- apps/openmw/mwworld/refdata.hpp | 14 +++++--------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f7ba76da21..dc49ff0a4e 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -58,12 +58,12 @@ namespace MWWorld RefData::RefData() : mBaseNode(nullptr) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { for (int i = 0; i < 3; ++i) { @@ -74,50 +74,50 @@ namespace MWWorld RefData::RefData(const ESM::CellRef& cellRef) : mBaseNode(nullptr) + , mPosition(cellRef.mPos) + , mCustomData(nullptr) + , mFlags(0) // Loading from ESM/ESP files -> assume unchanged , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mPosition(cellRef.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData(const ESM4::Reference& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM4::ActorCharacter& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr) - , mDeletedByContentFile(deletedByContentFile) - , mEnabled(objectState.mEnabled != 0) - , mPhysicsPostponed(false) , mPosition(objectState.mPosition) , mAnimationState(objectState.mAnimationState) , mCustomData(nullptr) - , mChanged(true) , mFlags(objectState.mFlags) // Loading from a savegame -> assume changed + , mDeletedByContentFile(deletedByContentFile) + , mEnabled(objectState.mEnabled != 0) + , mPhysicsPostponed(false) + , mChanged(true) { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 1b57971f11..e0b62c94b6 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -47,6 +47,10 @@ namespace MWWorld MWScript::Locals mLocals; std::shared_ptr mLuaScripts; + ESM::Position mPosition; + ESM::AnimationState mAnimationState; + std::unique_ptr mCustomData; + unsigned int mFlags; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. @@ -58,20 +62,12 @@ namespace MWWorld bool mPhysicsPostponed : 1; private: - ESM::Position mPosition; - - ESM::AnimationState mAnimationState; - - std::unique_ptr mCustomData; + bool mChanged : 1; void copy(const RefData& refData); void cleanup(); - bool mChanged; - - unsigned int mFlags; - public: RefData(); From a4625ea784d25e7408f480a4ceda4ae21e0bc54e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Apr 2024 22:29:33 +0200 Subject: [PATCH 1310/2167] Deduplicate dialogue filter parsing --- apps/esmtool/labels.cpp | 11 +- apps/esmtool/record.cpp | 128 ++- apps/opencs/model/tools/topicinfocheck.cpp | 79 +- apps/opencs/model/tools/topicinfocheck.hpp | 2 +- apps/opencs/model/world/infoselectwrapper.cpp | 782 ++++++------------ apps/opencs/model/world/infoselectwrapper.hpp | 171 +--- .../model/world/nestedcoladapterimp.cpp | 57 +- .../view/world/idcompletiondelegate.cpp | 22 +- apps/openmw/mwdialogue/filter.cpp | 159 ++-- apps/openmw/mwdialogue/selectwrapper.cpp | 505 ++++------- apps/openmw/mwdialogue/selectwrapper.hpp | 62 +- components/CMakeLists.txt | 2 +- components/esm3/dialoguecondition.cpp | 204 +++++ components/esm3/dialoguecondition.hpp | 134 +++ components/esm3/loadinfo.cpp | 83 +- components/esm3/loadinfo.hpp | 15 +- 16 files changed, 1002 insertions(+), 1414 deletions(-) create mode 100644 components/esm3/dialoguecondition.cpp create mode 100644 components/esm3/dialoguecondition.hpp diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 6def8b4a42..83b4a486e1 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,5 +1,6 @@ #include "labels.hpp" +#include #include #include #include @@ -572,13 +573,14 @@ std::string_view enchantTypeLabel(int idx) std::string_view ruleFunction(int idx) { - if (idx >= 0 && idx <= 72) + if (idx >= ESM::DialogueCondition::Function_FacReactionLowest + && idx <= ESM::DialogueCondition::Function_PcWerewolfKills) { static constexpr std::string_view ruleFunctions[] = { - "Reaction Low", - "Reaction High", + "Lowest Faction Reaction", + "Highest Faction Reaction", "Rank Requirement", - "NPC? Reputation", + "NPC Reputation", "Health Percent", "Player Reputation", "NPC Level", @@ -648,6 +650,7 @@ std::string_view ruleFunction(int idx) "Flee", "Should Attack", "Werewolf", + "Werewolf Kills", }; return ruleFunctions[idx]; } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 4526b891e2..cd38dadf3f 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -57,112 +57,82 @@ namespace std::cout << " Cell Name: " << p.mCellName << std::endl; } - std::string ruleString(const ESM::DialInfo::SelectStruct& ss) + std::string ruleString(const ESM::DialogueCondition& ss) { - std::string rule = ss.mSelectRule; + std::string_view type_str = "INVALID"; + std::string_view func_str; - if (rule.length() < 5) - return "INVALID"; - - char type = rule[1]; - char indicator = rule[2]; - - std::string type_str = "INVALID"; - std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - int func = Misc::StringUtils::toNumeric(rule.substr(2, 2), 0); - - switch (type) + switch (ss.mFunction) { - case '1': - type_str = "Function"; - func_str = std::string(ruleFunction(func)); + case ESM::DialogueCondition::Function_Global: + type_str = "Global"; + func_str = ss.mVariable; break; - case '2': - if (indicator == 's') - type_str = "Global short"; - else if (indicator == 'l') - type_str = "Global long"; - else if (indicator == 'f') - type_str = "Global float"; + case ESM::DialogueCondition::Function_Local: + type_str = "Local"; + func_str = ss.mVariable; break; - case '3': - if (indicator == 's') - type_str = "Local short"; - else if (indicator == 'l') - type_str = "Local long"; - else if (indicator == 'f') - type_str = "Local float"; + case ESM::DialogueCondition::Function_Journal: + type_str = "Journal"; + func_str = ss.mVariable; break; - case '4': - if (indicator == 'J') - type_str = "Journal"; + case ESM::DialogueCondition::Function_Item: + type_str = "Item count"; + func_str = ss.mVariable; break; - case '5': - if (indicator == 'I') - type_str = "Item type"; + case ESM::DialogueCondition::Function_Dead: + type_str = "Dead"; + func_str = ss.mVariable; break; - case '6': - if (indicator == 'D') - type_str = "NPC Dead"; + case ESM::DialogueCondition::Function_NotId: + type_str = "Not ID"; + func_str = ss.mVariable; break; - case '7': - if (indicator == 'X') - type_str = "Not ID"; + case ESM::DialogueCondition::Function_NotFaction: + type_str = "Not Faction"; + func_str = ss.mVariable; break; - case '8': - if (indicator == 'F') - type_str = "Not Faction"; + case ESM::DialogueCondition::Function_NotClass: + type_str = "Not Class"; + func_str = ss.mVariable; break; - case '9': - if (indicator == 'C') - type_str = "Not Class"; + case ESM::DialogueCondition::Function_NotRace: + type_str = "Not Race"; + func_str = ss.mVariable; break; - case 'A': - if (indicator == 'R') - type_str = "Not Race"; + case ESM::DialogueCondition::Function_NotCell: + type_str = "Not Cell"; + func_str = ss.mVariable; break; - case 'B': - if (indicator == 'L') - type_str = "Not Cell"; - break; - case 'C': - if (indicator == 's') - type_str = "Not Local"; + case ESM::DialogueCondition::Function_NotLocal: + type_str = "Not Local"; + func_str = ss.mVariable; break; default: + type_str = "Function"; + func_str = ruleFunction(ss.mFunction); break; } - // Append the variable name to the function string if any. - if (type != '1') - func_str = rule.substr(5); - - // In the previous switch, we assumed that the second char was X - // for all types not qual to one. If this wasn't true, go back to - // the error message. - if (type != '1' && rule[3] != 'X') - func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - - char oper = rule[4]; - std::string oper_str = "??"; - switch (oper) + std::string_view oper_str = "??"; + switch (ss.mComparison) { - case '0': + case ESM::DialogueCondition::Comp_Eq: oper_str = "=="; break; - case '1': + case ESM::DialogueCondition::Comp_Ne: oper_str = "!="; break; - case '2': + case ESM::DialogueCondition::Comp_Gt: oper_str = "> "; break; - case '3': + case ESM::DialogueCondition::Comp_Ge: oper_str = ">="; break; - case '4': + case ESM::DialogueCondition::Comp_Ls: oper_str = "< "; break; - case '5': + case ESM::DialogueCondition::Comp_Le: oper_str = "<="; break; default: @@ -170,7 +140,7 @@ namespace } std::ostringstream stream; - stream << ss.mValue; + std::visit([&](auto value) { stream << value; }, ss.mValue); std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); @@ -842,7 +812,7 @@ namespace EsmTool << std::endl; std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; - for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) + for (const auto& rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; if (!mData.mResultScript.empty()) diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index ba99a33a28..fab90d951a 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message // Check info conditions - for (std::vector::const_iterator it = topicInfo.mSelects.begin(); - it != topicInfo.mSelects.end(); ++it) + for (const auto& select : topicInfo.mSelects) { - verifySelectStruct((*it), id, messages); + verifySelectStruct(select, id, messages); } } @@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem( } bool CSMTools::TopicInfoCheckStage::verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::ConstInfoSelectWrapper infoCondition(select); - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + if (select.mFunction == ESM::DialogueCondition::Function_None) { messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); return false; } - else if (!infoCondition.variantTypeIsValid()) - { - std::ostringstream stream; - stream << "Value of condition '" << infoCondition.toString() << "' has invalid "; - - switch (select.mValue.getType()) - { - case ESM::VT_None: - stream << "None"; - break; - case ESM::VT_Short: - stream << "Short"; - break; - case ESM::VT_Int: - stream << "Int"; - break; - case ESM::VT_Long: - stream << "Long"; - break; - case ESM::VT_Float: - stream << "Float"; - break; - case ESM::VT_String: - stream << "String"; - break; - default: - stream << "unknown"; - break; - } - stream << " type"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); - return false; - } else if (infoCondition.conditionIsAlwaysTrue()) { messages.add( @@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct( } // Id checks - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) + if (select.mFunction == ESM::DialogueCondition::Function_Global + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Journal + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item - && !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Item + && !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Dead + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotId + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotClass + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotRace + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell - && !verifyCell(infoCondition.getVariableName(), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotCell + && !verifyCell(select.mVariable, id, messages)) { return false; } diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index 1bb2fc61dc..3069fbc0ff 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -84,7 +84,7 @@ namespace CSMTools const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); template diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index fb1adf16d4..d1af341c04 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -6,16 +6,9 @@ #include -const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; - -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; -const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; -const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; - const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { - "Rank Low", - "Rank High", + "Faction Reaction Low", + "Faction Reaction High", "Rank Requirement", "Reputation", "Health Percent", @@ -121,17 +114,17 @@ const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { // static functions -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Function name) { - if (name < Function_None) + if (name < ESM::DialogueCondition::Function_None) return FunctionEnumStrings[name]; else return "(Invalid Data: Function)"; } -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Comparison type) { - if (type < Relation_None) + if (type < ESM::DialogueCondition::Comp_None) return RelationEnumStrings[type]; else return "(Invalid Data: Relation)"; @@ -147,20 +140,21 @@ std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType typ // ConstInfoSelectWrapper -CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialogueCondition& select) : mConstSelect(select) { - readRule(); + updateHasVariable(); + updateComparisonType(); } -CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +ESM::DialogueCondition::Function CSMWorld::ConstInfoSelectWrapper::getFunctionName() const { - return mFunctionName; + return mConstSelect.mFunction; } -CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +ESM::DialogueCondition::Comparison CSMWorld::ConstInfoSelectWrapper::getRelationType() const { - return mRelationType; + return mConstSelect.mComparison; } CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const @@ -175,24 +169,21 @@ bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const { - return mVariableName; + return mConstSelect.mVariable; } bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); @@ -203,19 +194,16 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); @@ -224,170 +212,36 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const return false; } -bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const -{ - return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); -} - -const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const -{ - return mConstSelect.mValue; -} - std::string CSMWorld::ConstInfoSelectWrapper::toString() const { std::ostringstream stream; - stream << convertToString(mFunctionName) << " "; + stream << convertToString(getFunctionName()) << " "; if (mHasVariable) - stream << mVariableName << " "; + stream << getVariableName() << " "; - stream << convertToString(mRelationType) << " "; + stream << convertToString(getRelationType()) << " "; - switch (mConstSelect.mValue.getType()) - { - case ESM::VT_Int: - stream << mConstSelect.mValue.getInteger(); - break; - - case ESM::VT_Float: - stream << mConstSelect.mValue.getFloat(); - break; - - default: - stream << "(Invalid value type)"; - break; - } + std::visit([&](auto value) { stream << value; }, mConstSelect.mValue); return stream.str(); } -void CSMWorld::ConstInfoSelectWrapper::readRule() -{ - if (mConstSelect.mSelectRule.size() < RuleMinSize) - throw std::runtime_error("InfoSelectWrapper: rule is to small"); - - readFunctionName(); - readRelationType(); - readVariableName(); - updateHasVariable(); - updateComparisonType(); -} - -void CSMWorld::ConstInfoSelectWrapper::readFunctionName() -{ - char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; - std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); - int convertedIndex = -1; - - // Read in function index, form ## from 00 .. 73, skip leading zero - if (functionIndex[0] == '0') - functionIndex = functionIndex[1]; - - std::stringstream stream; - stream << functionIndex; - stream >> convertedIndex; - - switch (functionPrefix) - { - case '1': - if (convertedIndex >= 0 && convertedIndex <= 73) - mFunctionName = static_cast(convertedIndex); - else - mFunctionName = Function_None; - break; - - case '2': - mFunctionName = Function_Global; - break; - case '3': - mFunctionName = Function_Local; - break; - case '4': - mFunctionName = Function_Journal; - break; - case '5': - mFunctionName = Function_Item; - break; - case '6': - mFunctionName = Function_Dead; - break; - case '7': - mFunctionName = Function_NotId; - break; - case '8': - mFunctionName = Function_NotFaction; - break; - case '9': - mFunctionName = Function_NotClass; - break; - case 'A': - mFunctionName = Function_NotRace; - break; - case 'B': - mFunctionName = Function_NotCell; - break; - case 'C': - mFunctionName = Function_NotLocal; - break; - default: - mFunctionName = Function_None; - break; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readRelationType() -{ - char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; - - switch (relationIndex) - { - case '0': - mRelationType = Relation_Equal; - break; - case '1': - mRelationType = Relation_NotEqual; - break; - case '2': - mRelationType = Relation_Greater; - break; - case '3': - mRelationType = Relation_GreaterOrEqual; - break; - case '4': - mRelationType = Relation_Less; - break; - case '5': - mRelationType = Relation_LessOrEqual; - break; - default: - mRelationType = Relation_None; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readVariableName() -{ - if (mConstSelect.mSelectRule.size() >= VarNameOffset) - mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); - else - mVariableName.clear(); -} - void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() { - switch (mFunctionName) + switch (getFunctionName()) { - case Function_Global: - case Function_Local: - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_NotLocal: mHasVariable = true; break; @@ -399,103 +253,103 @@ void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() { - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: mComparisonType = Comparison_Boolean; break; // Integer - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_RankLow: - case Function_RankHigh: - case Function_RankRequirement: - case Function_Reputation: - case Function_PcReputation: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcGender: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_FactionRankDifference: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Weather: - case Function_Level: - case Function_CreatureTarget: - case Function_FriendHit: - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_CreatureTarget: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcWerewolfKills: mComparisonType = Comparison_Integer; break; // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: - case Function_Health_Percent: - case Function_PcHealthPercent: - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: mComparisonType = Comparison_Numeric; break; @@ -511,15 +365,15 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con const int IntMin = std::numeric_limits::min(); const std::pair InvalidRange(IntMax, IntMin); - int value = mConstSelect.mValue.getInteger(); + int value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: if (value == IntMax) { return InvalidRange; @@ -530,10 +384,10 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con } break; - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, IntMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: if (value == IntMin) { return InvalidRange; @@ -543,7 +397,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con return std::pair(IntMin, value - 1); } - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(IntMin, value); default: @@ -557,24 +411,24 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange const float FloatMin = -std::numeric_limits::infinity(); const float Epsilon = std::numeric_limits::epsilon(); - float value = mConstSelect.mValue.getFloat(); + float value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: return std::pair(value + Epsilon, FloatMax); - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, FloatMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: return std::pair(FloatMin, value - Epsilon); - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(FloatMin, value); default: @@ -587,120 +441,120 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const const int IntMax = std::numeric_limits::max(); const int IntMin = std::numeric_limits::min(); - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return std::pair(0, 1); // Integer - case Function_RankLow: - case Function_RankHigh: - case Function_Reputation: - case Function_PcReputation: - case Function_Journal: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Journal: return std::pair(IntMin, IntMax); - case Function_Item: - case Function_Dead: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Level: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcWerewolfKills: return std::pair(0, IntMax); - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: return std::pair(0, 100); - case Function_Weather: + case ESM::DialogueCondition::Function_Weather: return std::pair(0, 9); - case Function_FriendHit: + case ESM::DialogueCondition::Function_FriendHit: return std::pair(0, 4); - case Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: return std::pair(0, 3); - case Function_CreatureTarget: + case ESM::DialogueCondition::Function_CreatureTarget: return std::pair(0, 2); - case Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return std::pair(0, 1); - case Function_FactionRankDifference: + case ESM::DialogueCondition::Function_FactionRankDifference: return std::pair(-9, 9); // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(IntMin, IntMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, IntMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -713,21 +567,21 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() c const float FloatMax = std::numeric_limits::infinity(); const float FloatMin = -std::numeric_limits::infinity(); - switch (mFunctionName) + switch (getFunctionName()) { // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(FloatMin, FloatMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, FloatMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -768,19 +622,19 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return false; - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: // If value is not within range, it will always be true return !rangeContains(conditionRange.first, validRange); - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If the valid range is completely within the condition range, it will always be true return rangeFullyContains(conditionRange, validRange); @@ -795,18 +649,18 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return !rangeContains(conditionRange.first, validRange); - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: return false; - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If ranges do not overlap, it will never be true return !rangesOverlap(conditionRange, validRange); @@ -817,153 +671,47 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( return false; } +QVariant CSMWorld::ConstInfoSelectWrapper::getValue() const +{ + return std::visit([](auto value) { return QVariant(value); }, mConstSelect.mValue); +} + // InfoSelectWrapper -CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialogueCondition& select) : CSMWorld::ConstInfoSelectWrapper(select) , mSelect(select) { } -void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +void CSMWorld::InfoSelectWrapper::setFunctionName(ESM::DialogueCondition::Function name) { - mFunctionName = name; + mSelect.mFunction = name; updateHasVariable(); updateComparisonType(); + if (getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric + && std::holds_alternative(mSelect.mValue)) + { + mSelect.mValue = std::visit([](auto value) { return static_cast(value); }, mSelect.mValue); + } } -void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +void CSMWorld::InfoSelectWrapper::setRelationType(ESM::DialogueCondition::Comparison type) { - mRelationType = type; + mSelect.mComparison = type; } void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) { - mVariableName = name; + mSelect.mVariable = name; } -void CSMWorld::InfoSelectWrapper::setDefaults() +void CSMWorld::InfoSelectWrapper::setValue(int value) { - if (!variantTypeIsValid()) - mSelect.mValue.setType(ESM::VT_Int); - - switch (mComparisonType) - { - case Comparison_Boolean: - setRelationType(Relation_Equal); - mSelect.mValue.setInteger(1); - break; - - case Comparison_Integer: - case Comparison_Numeric: - setRelationType(Relation_Greater); - mSelect.mValue.setInteger(0); - break; - - default: - // Do nothing - break; - } - - update(); + mSelect.mValue = value; } -void CSMWorld::InfoSelectWrapper::update() +void CSMWorld::InfoSelectWrapper::setValue(float value) { - std::ostringstream stream; - - // Leading 0 - stream << '0'; - - // Write Function - - bool writeIndex = false; - size_t functionIndex = static_cast(mFunctionName); - - switch (mFunctionName) - { - case Function_None: - stream << '0'; - break; - case Function_Global: - stream << '2'; - break; - case Function_Local: - stream << '3'; - break; - case Function_Journal: - stream << '4'; - break; - case Function_Item: - stream << '5'; - break; - case Function_Dead: - stream << '6'; - break; - case Function_NotId: - stream << '7'; - break; - case Function_NotFaction: - stream << '8'; - break; - case Function_NotClass: - stream << '9'; - break; - case Function_NotRace: - stream << 'A'; - break; - case Function_NotCell: - stream << 'B'; - break; - case Function_NotLocal: - stream << 'C'; - break; - default: - stream << '1'; - writeIndex = true; - break; - } - - if (writeIndex && functionIndex < 10) // leading 0 - stream << '0' << functionIndex; - else if (writeIndex) - stream << functionIndex; - else - stream << "00"; - - // Write Relation - switch (mRelationType) - { - case Relation_Equal: - stream << '0'; - break; - case Relation_NotEqual: - stream << '1'; - break; - case Relation_Greater: - stream << '2'; - break; - case Relation_GreaterOrEqual: - stream << '3'; - break; - case Relation_Less: - stream << '4'; - break; - case Relation_LessOrEqual: - stream << '5'; - break; - default: - stream << '0'; - break; - } - - if (mHasVariable) - stream << mVariableName; - - mSelect.mSelectRule = stream.str(); -} - -ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() -{ - return mSelect.mValue; + mSelect.mValue = value; } diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index 4ecdc676dc..d8d108444f 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -7,133 +7,13 @@ #include -namespace ESM -{ - class Variant; -} +#include namespace CSMWorld { - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // - - // Wrapper for DialInfo::SelectStruct class ConstInfoSelectWrapper { public: - // Order matters - enum FunctionName - { - Function_RankLow = 0, - Function_RankHigh, - Function_RankRequirement, - Function_Reputation, - Function_Health_Percent, - Function_PcReputation, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcMagicka, - Function_PcFatigue, - Function_PcStrength, - Function_PcBlock, - Function_PcArmorer, - Function_PcMediumArmor, - Function_PcHeavyArmor, - Function_PcBluntWeapon, - Function_PcLongBlade, - Function_PcAxe, - Function_PcSpear, - Function_PcAthletics, - Function_PcEnchant, - Function_PcDestruction, - Function_PcAlteration, - Function_PcIllusion, - Function_PcConjuration, - Function_PcMysticism, - Function_PcRestoration, - Function_PcAlchemy, - Function_PcUnarmored, - Function_PcSecurity, - Function_PcSneak, - Function_PcAcrobatics, - Function_PcLightArmor, - Function_PcShortBlade, - Function_PcMarksman, - Function_PcMerchantile, - Function_PcSpeechcraft, - Function_PcHandToHand, - Function_PcGender, - Function_PcExpelled, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_SameSex, - Function_SameRace, - Function_SameFaction, - Function_FactionRankDifference, - Function_Detected, - Function_Alarmed, - Function_Choice, - Function_PcIntelligence, - Function_PcWillpower, - Function_PcAgility, - Function_PcSpeed, - Function_PcEndurance, - Function_PcPersonality, - Function_PcLuck, - Function_PcCorpus, - Function_Weather, - Function_PcVampire, - Function_Level, - Function_Attacked, - Function_TalkedToPc, - Function_PcHealth, - Function_CreatureTarget, - Function_FriendHit, - Function_Fight, - Function_Hello, - Function_Alarm, - Function_Flee, - Function_ShouldAttack, - Function_Werewolf, - Function_PcWerewolfKills = 73, - - Function_Global, - Function_Local, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - - Function_None - }; - - enum RelationType - { - Relation_Equal, - Relation_NotEqual, - Relation_Greater, - Relation_GreaterOrEqual, - Relation_Less, - Relation_LessOrEqual, - - Relation_None - }; - enum ComparisonType { Comparison_Boolean, @@ -143,25 +23,18 @@ namespace CSMWorld Comparison_None }; - static const size_t RuleMinSize; - - static const size_t FunctionPrefixOffset; - static const size_t FunctionIndexOffset; - static const size_t RelationIndexOffset; - static const size_t VarNameOffset; - static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; static const char* ComparisonEnumStrings[]; - static std::string convertToString(FunctionName name); - static std::string convertToString(RelationType type); + static std::string convertToString(ESM::DialogueCondition::Function name); + static std::string convertToString(ESM::DialogueCondition::Comparison type); static std::string convertToString(ComparisonType type); - ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + ConstInfoSelectWrapper(const ESM::DialogueCondition& select); - FunctionName getFunctionName() const; - RelationType getRelationType() const; + ESM::DialogueCondition::Function getFunctionName() const; + ESM::DialogueCondition::Comparison getRelationType() const; ComparisonType getComparisonType() const; bool hasVariable() const; @@ -169,17 +42,12 @@ namespace CSMWorld bool conditionIsAlwaysTrue() const; bool conditionIsNeverTrue() const; - bool variantTypeIsValid() const; - const ESM::Variant& getVariant() const; + QVariant getValue() const; std::string toString() const; protected: - void readRule(); - void readFunctionName(); - void readRelationType(); - void readVariableName(); void updateHasVariable(); void updateComparisonType(); @@ -207,38 +75,29 @@ namespace CSMWorld template bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; - FunctionName mFunctionName; - RelationType mRelationType; ComparisonType mComparisonType; bool mHasVariable; - std::string mVariableName; private: - const ESM::DialInfo::SelectStruct& mConstSelect; + const ESM::DialogueCondition& mConstSelect; }; - // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + // Wrapper for DialogueCondition that can modify the wrapped select struct class InfoSelectWrapper : public ConstInfoSelectWrapper { public: - InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + InfoSelectWrapper(ESM::DialogueCondition& select); // Wrapped SelectStruct will not be modified until update() is called - void setFunctionName(FunctionName name); - void setRelationType(RelationType type); + void setFunctionName(ESM::DialogueCondition::Function name); + void setRelationType(ESM::DialogueCondition::Comparison type); void setVariableName(const std::string& name); - - // Modified wrapped SelectStruct - void update(); - - // This sets properties based on the function name to its defaults and updates the wrapped object - void setDefaults(); - - ESM::Variant& getVariant(); + void setValue(int value); + void setValue(float value); private: - ESM::DialInfo::SelectStruct& mSelect; + ESM::DialogueCondition& mSelect; void writeRule(); }; diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 0d90ea0d68..86d38f9cd2 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -538,13 +538,11 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; // default row - ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "01000"; - condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); + ESM::DialogueCondition condStruct; + condStruct.mIndex = conditions.size(); conditions.insert(conditions.begin() + position, condStruct); @@ -555,7 +553,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (rowToRemove < 0 || rowToRemove >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -569,8 +567,8 @@ namespace CSMWorld { Info info = record.get(); - info.mSelects = static_cast>&>(nestedTable) - .mNestedTable; + info.mSelects + = static_cast>&>(nestedTable).mNestedTable; record.setModified(info); } @@ -578,14 +576,14 @@ namespace CSMWorld NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mSelects); + return new NestedTableWrapper>(record.get().mSelects); } QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -611,19 +609,7 @@ namespace CSMWorld } case 3: { - switch (infoSelectWrapper.getVariant().getType()) - { - case ESM::VT_Int: - { - return infoSelectWrapper.getVariant().getInteger(); - } - case ESM::VT_Float: - { - return infoSelectWrapper.getVariant().getFloat(); - } - default: - return QVariant(); - } + return infoSelectWrapper.getValue(); } default: throw std::runtime_error("Info condition subcolumn index out of range"); @@ -635,7 +621,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -647,27 +633,17 @@ namespace CSMWorld { case 0: // Function { - infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - - if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric - && infoSelectWrapper.getVariant().getType() != ESM::VT_Int) - { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - } - - infoSelectWrapper.update(); + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); break; } case 1: // Variable { infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); - infoSelectWrapper.update(); break; } case 2: // Relation { - infoSelectWrapper.setRelationType(static_cast(value.toInt())); - infoSelectWrapper.update(); + infoSelectWrapper.setRelationType(static_cast(value.toInt())); break; } case 3: // Value @@ -679,13 +655,11 @@ namespace CSMWorld // QVariant seems to have issues converting 0 if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } else if (value.toFloat(&conversionResult) && conversionResult) { - infoSelectWrapper.getVariant().setType(ESM::VT_Float); - infoSelectWrapper.getVariant().setFloat(value.toFloat()); + infoSelectWrapper.setValue(value.toFloat()); } break; } @@ -694,8 +668,7 @@ namespace CSMWorld { if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } break; } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 3e26ed9250..e39be392f4 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt switch (conditionFunction) { - case CSMWorld::ConstInfoSelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal); } - case CSMWorld::ConstInfoSelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Dead: - case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell); } - case CSMWorld::ConstInfoSelectWrapper::Function_Local: - case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: { return new CSVWidget::DropLineEdit(display, parent); } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 2693811357..acf87ccc61 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -31,15 +31,15 @@ namespace bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) { const ESM::RefId selectId = select.getId(); - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) + if (select.getFunction() == ESM::DialogueCondition::Function_NotId) return actor.getCellRef().getRefId() != selectId; if (actor.getClass().isNpc()) { - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction) + if (select.getFunction() == ESM::DialogueCondition::Function_NotFaction) return actor.getClass().getPrimaryFaction(actor) != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotClass) return actor.get()->mBase->mClass != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotRace) return actor.get()->mBase->mRace != selectId; } return true; @@ -47,7 +47,7 @@ namespace bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor) { - for (const ESM::DialInfo::SelectStruct& select : info.mSelects) + for (const auto& select : info.mSelects) { MWDialogue::SelectWrapper wrapper = select; if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean) @@ -62,7 +62,7 @@ namespace } else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric) { - if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local) + if (wrapper.getFunction() == ESM::DialogueCondition::Function_Local) { const ESM::RefId& scriptName = actor.getClass().getScript(actor); if (scriptName.empty()) @@ -207,9 +207,8 @@ bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const { - for (std::vector::const_iterator iter(info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - if (!testSelectStruct(*iter)) + for (const auto& select : info.mSelects) + if (!testSelectStruct(select)) return false; return true; @@ -270,11 +269,11 @@ bool MWDialogue::Filter::testSelectStruct(const SelectWrapper& select) const // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; - if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) + if (select.getFunction() == ESM::DialogueCondition::Function_Choice && mChoice == -1) // If not currently in a choice, we reject all conditions that test against choices. return false; - if (select.getFunction() == SelectWrapper::Function_Weather + if (select.getFunction() == ESM::DialogueCondition::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) // Reject weather conditions in interior cells @@ -305,29 +304,31 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co { switch (select.getFunction()) { - case SelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: // internally all globals are float :( return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName())); - case SelectWrapper::Function_Local: + case ESM::DialogueCondition::Function_Local: { return testFunctionLocal(select); } - case SelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_NotLocal: { return !testFunctionLocal(select); } - case SelectWrapper::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); return select.selectCompare( static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } - case SelectWrapper::Function_PcDynamicStat: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -336,7 +337,7 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co return select.selectCompare(value); } - case SelectWrapper::Function_HealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: { return select.selectCompare( static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); @@ -354,26 +355,29 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons switch (select.getFunction()) { - case SelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId()); - case SelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); return store.count(select.getId()); } - case SelectWrapper::Function_Dead: + case ESM::DialogueCondition::Function_Dead: return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId()); - case SelectWrapper::Function_Choice: + case ESM::DialogueCondition::Function_Choice: return mChoice; - case SelectWrapper::Function_AiSetting: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: { int argument = select.getArgument(); if (argument < 0 || argument > 3) @@ -386,32 +390,65 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons .getAiSetting(static_cast(argument)) .getModified(false); } - case SelectWrapper::Function_PcAttribute: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: { ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified(); } - case SelectWrapper::Function_PcSkill: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: { ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument()); return static_cast(player.getClass().getNpcStats(player).getSkill(skill).getModified()); } - case SelectWrapper::Function_FriendlyHit: + case ESM::DialogueCondition::Function_FriendHit: { int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); return hits > 4 ? 4 : hits; } - case SelectWrapper::Function_PcLevel: + case ESM::DialogueCondition::Function_PcLevel: return player.getClass().getCreatureStats(player).getLevel(); - case SelectWrapper::Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return player.get()->mBase->isMale() ? 0 : 1; - case SelectWrapper::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcClothingModifier: { const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -428,11 +465,11 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_PcCrimeLevel: return player.getClass().getNpcStats(player).getBounty(); - case SelectWrapper::Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) @@ -454,23 +491,23 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return result; } - case SelectWrapper::Function_Level: + case ESM::DialogueCondition::Function_Level: return mActor.getClass().getCreatureStats(mActor).getLevel(); - case SelectWrapper::Function_PCReputation: + case ESM::DialogueCondition::Function_PcReputation: return player.getClass().getNpcStats(player).getReputation(); - case SelectWrapper::Function_Weather: + case ESM::DialogueCondition::Function_Weather: return MWBase::Environment::get().getWorld()->getCurrentWeather(); - case SelectWrapper::Function_Reputation: + case ESM::DialogueCondition::Function_Reputation: return mActor.getClass().getNpcStats(mActor).getReputation(); - case SelectWrapper::Function_FactionRankDiff: + case ESM::DialogueCondition::Function_FactionRankDifference: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -482,14 +519,14 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return rank - npcRank; } - case SelectWrapper::Function_WerewolfKills: + case ESM::DialogueCondition::Function_PcWerewolfKills: return player.getClass().getNpcStats(player).getWerewolfKills(); - case SelectWrapper::Function_RankLow: - case SelectWrapper::Function_RankHigh: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: { - bool low = select.getFunction() == SelectWrapper::Function_RankLow; + bool low = select.getFunction() == ESM::DialogueCondition::Function_FacReactionLowest; const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor); @@ -512,7 +549,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_CreatureTargetted: + case ESM::DialogueCondition::Function_CreatureTarget: { MWWorld::Ptr target; @@ -539,53 +576,49 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con switch (select.getFunction()) { - case SelectWrapper::Function_False: - - return false; - - case SelectWrapper::Function_NotId: + case ESM::DialogueCondition::Function_NotId: return mActor.getCellRef().getRefId() != select.getId(); - case SelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: return mActor.getClass().getPrimaryFaction(mActor) != select.getId(); - case SelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: return mActor.get()->mBase->mClass != select.getId(); - case SelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: return mActor.get()->mBase->mRace != select.getId(); - case SelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName()); } - case SelectWrapper::Function_SameGender: + case ESM::DialogueCondition::Function_SameSex: return (player.get()->mBase->mFlags & ESM::NPC::Female) == (mActor.get()->mBase->mFlags & ESM::NPC::Female); - case SelectWrapper::Function_SameRace: + case ESM::DialogueCondition::Function_SameRace: return mActor.get()->mBase->mRace == player.get()->mBase->mRace; - case SelectWrapper::Function_SameFaction: + case ESM::DialogueCondition::Function_SameFaction: return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); - case SelectWrapper::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcCommonDisease: return player.getClass().getCreatureStats(player).hasCommonDisease(); - case SelectWrapper::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: return player.getClass().getCreatureStats(player).hasBlightDisease(); - case SelectWrapper::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcCorpus: return player.getClass() .getCreatureStats(player) @@ -594,7 +627,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() != 0; - case SelectWrapper::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcExpelled: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -604,7 +637,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con return player.getClass().getNpcStats(player).getExpelled(faction); } - case SelectWrapper::Function_PcVampire: + case ESM::DialogueCondition::Function_PcVampire: return player.getClass() .getCreatureStats(player) @@ -613,27 +646,27 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() > 0; - case SelectWrapper::Function_TalkedToPc: + case ESM::DialogueCondition::Function_TalkedToPc: return mTalkedToPlayer; - case SelectWrapper::Function_Alarmed: + case ESM::DialogueCondition::Function_Alarmed: return mActor.getClass().getCreatureStats(mActor).isAlarmed(); - case SelectWrapper::Function_Detected: + case ESM::DialogueCondition::Function_Detected: return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); - case SelectWrapper::Function_Attacked: + case ESM::DialogueCondition::Function_Attacked: return mActor.getClass().getCreatureStats(mActor).getAttacked(); - case SelectWrapper::Function_ShouldAttack: + case ESM::DialogueCondition::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); - case SelectWrapper::Function_Werewolf: + case ESM::DialogueCondition::Function_Werewolf: return mActor.getClass().getNpcStats(mActor).isWerewolf(); diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 0cee8bb009..cc07ec8709 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -10,21 +10,21 @@ namespace { template - bool selectCompareImp(char comp, T1 value1, T2 value2) + bool selectCompareImp(ESM::DialogueCondition::Comparison comp, T1 value1, T2 value2) { switch (comp) { - case '0': + case ESM::DialogueCondition::Comp_Eq: return value1 == value2; - case '1': + case ESM::DialogueCondition::Comp_Ne: return value1 != value2; - case '2': + case ESM::DialogueCondition::Comp_Gt: return value1 > value2; - case '3': + case ESM::DialogueCondition::Comp_Ge: return value1 >= value2; - case '4': + case ESM::DialogueCondition::Comp_Ls: return value1 < value2; - case '5': + case ESM::DialogueCondition::Comp_Le: return value1 <= value2; } @@ -32,409 +32,242 @@ namespace } template - bool selectCompareImp(const ESM::DialInfo::SelectStruct& select, T value1) + bool selectCompareImp(const ESM::DialogueCondition& select, T value1) { - if (select.mValue.getType() == ESM::VT_Int) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getInteger()); - } - else if (select.mValue.getType() == ESM::VT_Float) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getFloat()); - } - else - throw std::runtime_error("unsupported variable type in dialogue info select"); + return std::visit( + [&](auto value) { return selectCompareImp(select.mComparison, value1, value); }, select.mValue); } } -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const -{ - const int index = Misc::StringUtils::toNumeric(mSelect.mSelectRule.substr(2, 2), 0); - - switch (index) - { - case 0: - return Function_RankLow; - case 1: - return Function_RankHigh; - case 2: - return Function_RankRequirement; - case 3: - return Function_Reputation; - case 4: - return Function_HealthPercent; - case 5: - return Function_PCReputation; - case 6: - return Function_PcLevel; - case 7: - return Function_PcHealthPercent; - case 8: - case 9: - return Function_PcDynamicStat; - case 10: - return Function_PcAttribute; - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - return Function_PcSkill; - case 38: - return Function_PcGender; - case 39: - return Function_PcExpelled; - case 40: - return Function_PcCommonDisease; - case 41: - return Function_PcBlightDisease; - case 42: - return Function_PcClothingModifier; - case 43: - return Function_PcCrimeLevel; - case 44: - return Function_SameGender; - case 45: - return Function_SameRace; - case 46: - return Function_SameFaction; - case 47: - return Function_FactionRankDiff; - case 48: - return Function_Detected; - case 49: - return Function_Alarmed; - case 50: - return Function_Choice; - case 51: - case 52: - case 53: - case 54: - case 55: - case 56: - case 57: - return Function_PcAttribute; - case 58: - return Function_PcCorprus; - case 59: - return Function_Weather; - case 60: - return Function_PcVampire; - case 61: - return Function_Level; - case 62: - return Function_Attacked; - case 63: - return Function_TalkedToPc; - case 64: - return Function_PcDynamicStat; - case 65: - return Function_CreatureTargetted; - case 66: - return Function_FriendlyHit; - case 67: - case 68: - case 69: - case 70: - return Function_AiSetting; - case 71: - return Function_ShouldAttack; - case 72: - return Function_Werewolf; - case 73: - return Function_WerewolfKills; - } - - return Function_False; -} - -MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select) +MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialogueCondition& select) : mSelect(select) { } -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const +ESM::DialogueCondition::Function MWDialogue::SelectWrapper::getFunction() const { - char type = mSelect.mSelectRule[1]; - - switch (type) - { - case '1': - return decodeFunction(); - case '2': - return Function_Global; - case '3': - return Function_Local; - case '4': - return Function_Journal; - case '5': - return Function_Item; - case '6': - return Function_Dead; - case '7': - return Function_NotId; - case '8': - return Function_NotFaction; - case '9': - return Function_NotClass; - case 'A': - return Function_NotRace; - case 'B': - return Function_NotCell; - case 'C': - return Function_NotLocal; - } - - return Function_None; + return mSelect.mFunction; } int MWDialogue::SelectWrapper::getArgument() const { - if (mSelect.mSelectRule[1] != '1') - return 0; - - int index = 0; - - std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index; - - switch (index) + switch (mSelect.mFunction) { // AI settings - case 67: + case ESM::DialogueCondition::Function_Fight: return 1; - case 68: + case ESM::DialogueCondition::Function_Hello: return 0; - case 69: + case ESM::DialogueCondition::Function_Alarm: return 3; - case 70: + case ESM::DialogueCondition::Function_Flee: return 2; // attributes - case 10: + case ESM::DialogueCondition::Function_PcStrength: return 0; - case 51: + case ESM::DialogueCondition::Function_PcIntelligence: return 1; - case 52: + case ESM::DialogueCondition::Function_PcWillpower: return 2; - case 53: + case ESM::DialogueCondition::Function_PcAgility: return 3; - case 54: + case ESM::DialogueCondition::Function_PcSpeed: return 4; - case 55: + case ESM::DialogueCondition::Function_PcEndurance: return 5; - case 56: + case ESM::DialogueCondition::Function_PcPersonality: return 6; - case 57: + case ESM::DialogueCondition::Function_PcLuck: return 7; // skills - case 11: + case ESM::DialogueCondition::Function_PcBlock: return 0; - case 12: + case ESM::DialogueCondition::Function_PcArmorer: return 1; - case 13: + case ESM::DialogueCondition::Function_PcMediumArmor: return 2; - case 14: + case ESM::DialogueCondition::Function_PcHeavyArmor: return 3; - case 15: + case ESM::DialogueCondition::Function_PcBluntWeapon: return 4; - case 16: + case ESM::DialogueCondition::Function_PcLongBlade: return 5; - case 17: + case ESM::DialogueCondition::Function_PcAxe: return 6; - case 18: + case ESM::DialogueCondition::Function_PcSpear: return 7; - case 19: + case ESM::DialogueCondition::Function_PcAthletics: return 8; - case 20: + case ESM::DialogueCondition::Function_PcEnchant: return 9; - case 21: + case ESM::DialogueCondition::Function_PcDestruction: return 10; - case 22: + case ESM::DialogueCondition::Function_PcAlteration: return 11; - case 23: + case ESM::DialogueCondition::Function_PcIllusion: return 12; - case 24: + case ESM::DialogueCondition::Function_PcConjuration: return 13; - case 25: + case ESM::DialogueCondition::Function_PcMysticism: return 14; - case 26: + case ESM::DialogueCondition::Function_PcRestoration: return 15; - case 27: + case ESM::DialogueCondition::Function_PcAlchemy: return 16; - case 28: + case ESM::DialogueCondition::Function_PcUnarmored: return 17; - case 29: + case ESM::DialogueCondition::Function_PcSecurity: return 18; - case 30: + case ESM::DialogueCondition::Function_PcSneak: return 19; - case 31: + case ESM::DialogueCondition::Function_PcAcrobatics: return 20; - case 32: + case ESM::DialogueCondition::Function_PcLightArmor: return 21; - case 33: + case ESM::DialogueCondition::Function_PcShortBlade: return 22; - case 34: + case ESM::DialogueCondition::Function_PcMarksman: return 23; - case 35: + case ESM::DialogueCondition::Function_PcMerchantile: return 24; - case 36: + case ESM::DialogueCondition::Function_PcSpeechcraft: return 25; - case 37: + case ESM::DialogueCondition::Function_PcHandToHand: return 26; // dynamic stats - case 8: + case ESM::DialogueCondition::Function_PcMagicka: return 1; - case 9: + case ESM::DialogueCondition::Function_PcFatigue: return 2; - case 64: + case ESM::DialogueCondition::Function_PcHealth: + return 0; + default: return 0; } - - return 0; } MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const { - static const Function integerFunctions[] = { - Function_Journal, - Function_Item, - Function_Dead, - Function_Choice, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_FriendlyHit, - Function_PcLevel, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_FactionRankDiff, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - Function_CreatureTargetted, - // end marker - Function_None, - }; - - static const Function numericFunctions[] = { - Function_Global, - Function_Local, - Function_NotLocal, - Function_PcDynamicStat, - Function_PcHealthPercent, - Function_HealthPercent, - // end marker - Function_None, - }; - - static const Function booleanFunctions[] = { - Function_False, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_PcExpelled, - Function_PcVampire, - Function_TalkedToPc, - Function_Alarmed, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_Werewolf, - // end marker - Function_None, - }; - - static const Function invertedBooleanFunctions[] = { - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; integerFunctions[i] != Function_None; ++i) - if (integerFunctions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_CreatureTarget: return Type_Integer; - - for (int i = 0; numericFunctions[i] != Function_None; ++i) - if (numericFunctions[i] == function) + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: + case ESM::DialogueCondition::Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: return Type_Numeric; - - for (int i = 0; booleanFunctions[i] != Function_None; ++i) - if (booleanFunctions[i] == function) + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return Type_Boolean; - - for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i) - if (invertedBooleanFunctions[i] == function) + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: return Type_Inverted; - - return Type_None; + default: + return Type_None; + }; } bool MWDialogue::SelectWrapper::isNpcOnly() const { - static const Function functions[] = { - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_RankRequirement, - Function_Reputation, - Function_FactionRankDiff, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; functions[i] != Function_None; ++i) - if (functions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Werewolf: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: return true; - - return false; + default: + return false; + } } bool MWDialogue::SelectWrapper::selectCompare(int value) const @@ -454,15 +287,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase(getCellName()); + return Misc::StringUtils::lowerCase(mSelect.mVariable); } std::string_view MWDialogue::SelectWrapper::getCellName() const { - return std::string_view(mSelect.mSelectRule).substr(5); + return mSelect.mVariable; } ESM::RefId MWDialogue::SelectWrapper::getId() const { - return ESM::RefId::stringRefId(getCellName()); + return ESM::RefId::stringRefId(mSelect.mVariable); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index f736b504d8..d831b6cea0 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -7,62 +7,9 @@ namespace MWDialogue { class SelectWrapper { - const ESM::DialInfo::SelectStruct& mSelect; + const ESM::DialogueCondition& mSelect; public: - enum Function - { - Function_None, - Function_False, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - Function_Local, - Function_Global, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_Choice, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_FriendlyHit, - Function_TalkedToPc, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcDynamicStat, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_HealthPercent, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_Alarmed, - Function_FactionRankDiff, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_CreatureTargetted, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh - }; - enum Type { Type_None, @@ -72,13 +19,10 @@ namespace MWDialogue Type_Inverted }; - private: - Function decodeFunction() const; - public: - SelectWrapper(const ESM::DialInfo::SelectStruct& select); + SelectWrapper(const ESM::DialogueCondition& select); - Function getFunction() const; + ESM::DialogueCondition::Function getFunction() const; int getArgument() const; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7ac01ef169..68411be2fc 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -181,7 +181,7 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp formatversion landrecorddata selectiongroup + infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition ) add_component_dir (esmterrain diff --git a/components/esm3/dialoguecondition.cpp b/components/esm3/dialoguecondition.cpp new file mode 100644 index 0000000000..ba8f9586ce --- /dev/null +++ b/components/esm3/dialoguecondition.cpp @@ -0,0 +1,204 @@ +#include "dialoguecondition.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "variant.hpp" + +#include +#include +#include + +namespace ESM +{ + std::optional DialogueCondition::load(ESMReader& esm, ESM::RefId context) + { + std::string rule = esm.getHString(); + ESM::Variant variant; + variant.read(esm, Variant::Format_Info); + if (rule.size() < 5) + { + Log(Debug::Warning) << "Found invalid SCVR rule of size " << rule.size() << " in INFO " << context; + return {}; + } + if (rule[4] < '0' || rule[4] > '5') + { + Log(Debug::Warning) << "Found invalid SCVR comparison operator " << static_cast(rule[4]) << " in INFO " + << context; + return {}; + } + DialogueCondition condition; + if (rule[0] >= '0' && rule[0] <= '9') + condition.mIndex = rule[0] - '0'; + else + { + Log(Debug::Info) << "Found invalid SCVR index " << static_cast(rule[0]) << " in INFO " << context; + condition.mIndex = 0; + } + if (rule[1] == '1') + { + int function = Misc::StringUtils::toNumeric(std::string_view{ rule }.substr(2, 2), -1); + if (function >= Function_FacReactionLowest && function <= Function_PcWerewolfKills) + condition.mFunction = static_cast(function); + else + { + Log(Debug::Warning) << "Encountered invalid SCVR function index " << function << " in INFO " << context; + return {}; + } + } + else if (rule[1] > '1' && rule[1] <= '9' || rule[1] >= 'A' && rule[1] <= 'C') + { + if (rule.size() == 5) + { + Log(Debug::Warning) << "Missing variable for SCVR of type " << rule[1] << " in INFO " << context; + return {}; + } + bool malformed = rule[3] != 'X'; + if (rule[1] == '2') + { + condition.mFunction = Function_Global; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '3') + { + condition.mFunction = Function_Local; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '4') + { + condition.mFunction = Function_Journal; + malformed |= rule[2] != 'J'; + } + else if (rule[1] == '5') + { + condition.mFunction = Function_Item; + malformed |= rule[2] != 'I'; + } + else if (rule[1] == '6') + { + condition.mFunction = Function_Dead; + malformed |= rule[2] != 'D'; + } + else if (rule[1] == '7') + { + condition.mFunction = Function_NotId; + malformed |= rule[2] != 'X'; + } + else if (rule[1] == '8') + { + condition.mFunction = Function_NotFaction; + malformed |= rule[2] != 'F'; + } + else if (rule[1] == '9') + { + condition.mFunction = Function_NotClass; + malformed |= rule[2] != 'C'; + } + else if (rule[1] == 'A') + { + condition.mFunction = Function_NotRace; + malformed |= rule[2] != 'R'; + } + else if (rule[1] == 'B') + { + condition.mFunction = Function_NotCell; + malformed |= rule[2] != 'L'; + } + else if (rule[1] == 'C') + { + condition.mFunction = Function_NotLocal; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + if (malformed) + Log(Debug::Info) << "Found malformed SCVR rule in INFO " << context; + } + else + { + Log(Debug::Warning) << "Found invalid SCVR function " << static_cast(rule[1]) << " in INFO " + << context; + return {}; + } + condition.mComparison = static_cast(rule[4]); + condition.mVariable = rule.substr(5); + if (variant.getType() == VT_Int) + condition.mValue = variant.getInteger(); + else if (variant.getType() == VT_Float) + condition.mValue = variant.getFloat(); + else + { + Log(Debug::Warning) << "Found invalid SCVR variant " << variant.getType() << " in INFO " << context; + return {}; + } + return condition; + } + + void DialogueCondition::save(ESMWriter& esm) const + { + auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue); + std::string rule; + rule.reserve(5 + mVariable.size()); + rule += static_cast(mIndex + '0'); + const auto appendVariableType = [&]() { + if (variant.getType() == VT_Float) + rule += "fX"; + else + { + int32_t value = variant.getInteger(); + if (static_cast(value) == value) + rule += "sX"; + else + rule += "lX"; + } + }; + if (mFunction == Function_Global) + { + rule += '2'; + appendVariableType(); + } + else if (mFunction == Function_Local) + { + rule += '3'; + appendVariableType(); + } + else if (mFunction == Function_Journal) + rule += "4JX"; + else if (mFunction == Function_Item) + rule += "5IX"; + else if (mFunction == Function_Dead) + rule += "6DX"; + else if (mFunction == Function_NotId) + rule += "7XX"; + else if (mFunction == Function_NotFaction) + rule += "8FX"; + else if (mFunction == Function_NotClass) + rule += "9CX"; + else if (mFunction == Function_NotRace) + rule += "ARX"; + else if (mFunction == Function_NotCell) + rule += "BLX"; + else if (mFunction == Function_NotLocal) + { + rule += 'C'; + appendVariableType(); + } + else + { + rule += "100"; + char* start = rule.data() + rule.size(); + char* end = start; + if (mFunction < Function_PcStrength) + start--; + else + start -= 2; + auto result = std::to_chars(start, end, mFunction); + if (result.ec != std::errc()) + { + Log(Debug::Error) << "Failed to save SCVR rule"; + return; + } + } + rule += static_cast(mComparison); + rule += mVariable; + esm.writeHNString("SCVR", rule); + variant.write(esm, Variant::Format_Info); + } +} diff --git a/components/esm3/dialoguecondition.hpp b/components/esm3/dialoguecondition.hpp new file mode 100644 index 0000000000..15ad4944f5 --- /dev/null +++ b/components/esm3/dialoguecondition.hpp @@ -0,0 +1,134 @@ +#ifndef OPENMW_ESM3_DIALOGUECONDITION_H +#define OPENMW_ESM3_DIALOGUECONDITION_H + +#include +#include +#include +#include + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct DialogueCondition + { + enum Function : std::int8_t + { + Function_FacReactionLowest = 0, + Function_FacReactionHighest, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills = 73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None, // Editor only + }; + + enum Comparison : char + { + Comp_Eq = '0', + Comp_Ne = '1', + Comp_Gt = '2', + Comp_Ge = '3', + Comp_Ls = '4', + Comp_Le = '5', + + Comp_None = ' ', // Editor only + }; + + std::string mVariable; + std::variant mValue = 0; + std::uint8_t mIndex = 0; + Function mFunction = Function_None; + Comparison mComparison = Comp_None; + + static std::optional load(ESMReader& esm, ESM::RefId context); + + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index b091edd3f6..8b1147ed45 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,65 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include #include -#include - -namespace -{ - enum class SelectRuleStatus - { - Valid, - Invalid, - Ignorable - }; - - SelectRuleStatus isValidSelectRule(std::string_view rule) - { - if (rule.size() < 5) - return SelectRuleStatus::Invalid; - if (rule[4] < '0' || rule[4] > '5') // Comparison operators - return SelectRuleStatus::Invalid; - if (rule[1] == '1') // Function - { - int function = Misc::StringUtils::toNumeric(rule.substr(2, 2), -1); - if (function >= 0 && function <= 73) - return SelectRuleStatus::Valid; - return SelectRuleStatus::Invalid; - } - if (rule.size() == 5) // Missing ID - return SelectRuleStatus::Invalid; - if (rule[3] != 'X') - return SelectRuleStatus::Ignorable; - constexpr auto ignorable - = [](bool valid) { return valid ? SelectRuleStatus::Valid : SelectRuleStatus::Ignorable; }; - switch (rule[1]) - { - case '2': - case '3': - case 'C': - return ignorable(rule[2] == 's' || rule[2] == 'l' || rule[2] == 'f'); - case '4': - return ignorable(rule[2] == 'J'); - case '5': - return ignorable(rule[2] == 'I'); - case '6': - return ignorable(rule[2] == 'D'); - case '7': - return ignorable(rule[2] == 'X'); - case '8': - return ignorable(rule[2] == 'F'); - case '9': - return ignorable(rule[2] == 'C'); - case 'A': - return ignorable(rule[2] == 'R'); - case 'B': - return ignorable(rule[2] == 'L'); - default: - return SelectRuleStatus::Invalid; - } - } -} namespace ESM { @@ -124,21 +66,9 @@ namespace ESM break; case fourCC("SCVR"): { - SelectStruct ss; - ss.mSelectRule = esm.getHString(); - ss.mValue.read(esm, Variant::Format_Info); - auto valid = isValidSelectRule(ss.mSelectRule); - if (ss.mValue.getType() != VT_Int && ss.mValue.getType() != VT_Float) - valid = SelectRuleStatus::Invalid; - if (valid == SelectRuleStatus::Invalid) - Log(Debug::Warning) << "Skipping invalid SCVR for INFO " << mId; - else - { - mSelects.push_back(ss); - if (valid == SelectRuleStatus::Ignorable) - Log(Debug::Info) - << "Found malformed SCVR for INFO " << mId << " at index " << ss.mSelectRule[0]; - } + auto filter = DialogueCondition::load(esm, mId); + if (filter) + mSelects.emplace_back(std::move(*filter)); break; } case fourCC("BNAM"): @@ -189,11 +119,8 @@ namespace ESM esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) - { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write(esm, Variant::Format_Info); - } + for (const auto& rule : mSelects) + rule.save(esm); esm.writeHNOString("BNAM", mResultScript); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index c2756e8d9c..a3fb4abffa 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -4,8 +4,10 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include + +#include "dialoguecondition.hpp" #include "variant.hpp" namespace ESM @@ -47,13 +49,6 @@ namespace ESM }; // 12 bytes DATAstruct mData; - // The rules for whether or not we will select this dialog item. - struct SelectStruct - { - std::string mSelectRule; // This has a complicated format - Variant mValue; - }; - // Journal quest indices (introduced with the quest system in Tribunal) enum QuestStatus { @@ -65,7 +60,7 @@ namespace ESM // Rules for when to include this item in the final list of options // visible to the player. - std::vector mSelects; + std::vector mSelects; // Id of this, previous and next INFO items RefId mId, mPrev, mNext; From b34dac1a270b0bc88f9bc063508a3be0e857b463 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Fri, 12 Apr 2024 15:01:59 +0000 Subject: [PATCH 1311/2167] Launcher and Wizard french localisation --- files/lang/components_fr.ts | 38 +- files/lang/launcher_fr.ts | 713 ++++++++++++++++++------------------ files/lang/wizard_fr.ts | 308 ++++++++-------- 3 files changed, 532 insertions(+), 527 deletions(-) diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts index 309424cda4..706dc1c988 100644 --- a/files/lang/components_fr.ts +++ b/files/lang/components_fr.ts @@ -5,89 +5,91 @@ ContentSelector Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. - + Sélectionnez la langue utilisée dans les fichiers ESM/ESP afin qu'OpenMW détecte leur encodage. ContentSelectorModel::ContentModel Unable to find dependent file: %1 - + Impossible de trouver le fichier de dépendance : %1 Dependent file needs to be active: %1 - + Le fichier de dépendance doit être activé : %1 This file needs to load after %1 - + Ce fichier doit être chargé après %1 ContentSelectorModel::EsmFile <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> - + <b>Auteur :</b> %1<br/><b>Version du format :</b> %2<br/><b>Modifié le :</b> %3<br/><b>Emplacement :</b><br/>%4<br/><br/><b>Description :</b><br/>%5<br/><br/><b>Dépendences : </b>%6<br/> <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> - + <br/><b>Ce fichier de contenu ne peut être désactivé, car il fait partie intégrante d'OpenMW.</b><br/> <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> - + <br/><b>Ce fichier de contenu ne peut être désactivé, car il est activé par un fichier de configuration non contrôlé par l'utilisateur.</b><br/> ContentSelectorView::ContentSelector &Check Selected - + &Activer la sélection &Uncheck Selected - + &Désactiver la sélection &Copy Path(s) to Clipboard - + &Copier l'emplacement dans le presse papier <No game file> - + <Pas de fichier de jeu> Process::ProcessInvoker Error starting executable - + Erreur au démarrage de l'application Error running executable - + Erreur lors de l'exécution de l'application Arguments: - + +Arguments : + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de trouver %1</b></p><p>L'application est manquante.</p><p>Assurez-vous qu'OpenMW est installé correctement puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>L'application n'est pas exécutable.</p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>Une erreur est apparue au lancement de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Le programme %1 a envoyé une erreur.</b></p><p>Une erreur est apparue durant l'exécution de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 7f2418c164..f349e196af 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -5,736 +5,736 @@ DataFilesPage Content Files - + Fichiers de données Data Directories - + Dossiers de données Scan directories for likely data directories and append them at the end of the list. - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci à la fin de la liste. Append - + Ajouter Scan directories for likely data directories and insert them above the selected position - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci au dessus de la position actuellement sélectionnée. Insert Above - + Insérer au dessus Move selected directory one position up - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le haut. Move Up - + Monter Move selected directory one position down - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le bas. Move Down - + Descendre Remove selected directory - + Supprime le(s) dossier(s) sélectionné(s) Remove - + Supprimer Archive Files - + Fichiers archive Move selected archive one position up - + Déplace les archives sélectionnées d'une position vers le haut Move selected archive one position down - + Déplace les archives sélectionnées d'une position vers le bas Navigation Mesh Cache - + Mise en cache des mesh de navigation Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. - + Génère un cache de mesh de navigation pour tout le contenu du jeu. Celui-ci sera utilisé par le moteur de jeu afin d'accélérer la transition entre les différentes zones de jeu. Update - + Mettre à jour Cancel navigation mesh generation. Already processed data will be saved. - + Annule la génération de mesh de navigation. Les données déjà sauvegardées seront sauvegardées. Cancel - + Annuler MiB - + Mio Content List - + Liste de contenu Select a content list - + Sélectionne une Liste de contenu. New Content List - + Nouvelle Liste de contenu &New Content List - + &Nouvelle Liste de contenu Clone Content List - + Cloner une Liste de contenu Delete Content List - + Supprimer une Liste de contenu Ctrl+N - + Ctrl+N Ctrl+G - + Ctrl+G Ctrl+D - + Ctrl+D Check Selection - + Activer la sélection Uncheck Selection - + Désactiver la sélection Refresh Data Files - + Rafraîchir la liste des fichiers Ctrl+R - + Ctrl+R <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les fichiers de contenu qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les dossiers de données qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les archives qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignées</span>.</p></body></html> Remove Unused Tiles - + Ignorer les tuiles inutilisées Max Size - + Taille maximale GraphicsPage 0 - + 0 2 - + 2 4 - + 4 8 - + 8 16 - + 16 Custom: - + Personnalisé : Standard: - + Standard : Fullscreen - + Plein écran Windowed Fullscreen - + Fenêtré plein écran Windowed - + Fenêtré Disabled - + Désactivé Enabled - + Activé Adaptive - + Adaptatif FPS - + FPS × - + × Screen - + Écran Resolution - + Résolution Window Mode - + Mode d'affichage Framerate Limit - + Limite de framerate Window Border - + Bordure de fenêtre Anti-Aliasing - + Antialiasing Vertical Synchronization - + Synchronisation verticale ImportPage Form - + Form Morrowind Installation Wizard - + Assistant d'installation de Morrowind Run &Installation Wizard - + Lancer &l'Assistant d'installation Morrowind Settings Importer - + Import des paramètres de Morrowind Browse... - + Parcourir... Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et ressemblent aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. Run &Settings Importer - + Lancer &l'import des paramètres File to Import Settings From: - + Ficher depuis lequel les paramètres seront importés : Import Bitmap Fonts - + Importer les paramètres des polices en bitmap Import Add-on and Plugin Selection - + Importer les extensions et la sélection des modules complémentaires (crée une nouvelle Liste de contenu) Launcher::DataFilesPage English - + Anglais French - + Français German - + Allemand Italian - + Italien Polish - + Polonais Russian - + Russe Spanish - + Espagnol New Content List - + Créer une Liste de contenu Content List name: - + Nom de la Liste de contenu : Clone Content List - + Cloner la Liste de contenu Select Directory - + Sélectionnez un dossier Delete Content List - + Supprimer la Liste de contenu Are you sure you want to delete <b>%1</b>? - + Êtes-vous sûre de vouloir supprimer <b>%1</b> ? Delete - + Supprimer Contains content file(s) - + Contient les fichiers de contenu Will be added to the current profile - + Sera ajouté au profil actuel &Check Selected - + &Active la sélection &Uncheck Selected - + &Désctive la sélection Resolved as %1 - + Localisation : %1 This is the data-local directory and cannot be disabled - + Ceci est le dossier data-local, il ne peut être désactivé. This directory is part of OpenMW and cannot be disabled - + Ce dossier ne peut être désactivé, car il fait partie intégrante d'OpenMW. This directory is enabled in an openmw.cfg other than the user one - + Ce dossier est activé dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. This archive is enabled in an openmw.cfg other than the user one - + Cette archive est activée dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. Launcher::GraphicsPage Error receiving number of screens - + Erreur lors de l'obtention du nombre d'écrans <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> - + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> Screen - + Écran Error receiving resolutions - + Erreur lors de l'obtention des résolutions <br><b>SDL_GetNumDisplayModes failed:</b><br><br> - + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> <br><b>SDL_GetDisplayMode failed:</b><br><br> - + <br><b>SDL_GetDisplayMode failed:</b><br><br> Launcher::ImportPage Error writing OpenMW configuration file - + Erreur lors de l'écriture du fichier de configuration Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) Importer finished - + Importation terminée Failed to import settings from INI file. - + Échec lors de l'importation du fichier INI. <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir ou de créer %1 en écriture </b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez.</p></body></html> Launcher::MainDialog Close - + Fermer Launch OpenMW - + Lancer OpenMW Help - + Aide Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration d'OpenMW First run - + Premier lancement Run &Installation Wizard - + Lancer l'Assistant d'installation Skip - + Passer OpenMW %1 release - + OpenMW %1 release OpenMW development (%1) - + OpenMW development (%1) Compiled on %1 %2 - + Compilé le %1 %2 Error detecting Morrowind installation - + Erreur lors de la détection de l'installation de Morrowind Run &Installation Wizard... - + Lancer l'Assistant d'installation Error reading OpenMW configuration files - + Erreur lors de la lecture du fichier de configuration d'OpenMW Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW Error writing user settings file - + Erreur lors de l'écriture du fichier de paramètres utilisateur Error writing Launcher configuration file - + Erreur lors de l'écriture dans le fichier de paramètres du Lanceur No game file selected - + Aucun fichier de jeu sélectionné <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> - + <html><head/><body><p><b>Bienvenue sur OpenMW !</b></p><p>Il est recommandé de lancer l'Assistant d'installation .</p><p>L'assistant vous permettra de sélectionner une installation de Morrowind existante, ou d'installer Morrowind pour son utilisation dans OpenMW.</p></body></html> <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. - + <br><b>Impossible de trouver l'emmplacement de Data Files</b><br><br>Le dossier contenant les fichiers de données n'a pas été trouvé. <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> - + <br>Ce problème peut survenir lors d'une installation incomplète d'OpenMW.<br>Réinstaller OpenMW pourrait résoudre le problème.<br> <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir ou de créer %0 en écriture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> - + <br><b>Vous n'avez pas sélectionné de fichier de jeu.</b><br><br>OpenMW ne peut démarrer sans qu'un fichier de jeu soit sélectionné.<br> Error creating OpenMW configuration directory: code %0 - + Erreur lors de la création du dossier de configuration d'OpenMW : code %0 <br><b>Could not create directory %0</b><br><br>%1<br> - + <br><b>Impossible de créer le dossier %0</b><br><br>%1<br> Launcher::SettingsPage Text file (*.txt) - + Fichier texte (*.txt) MainWindow OpenMW Launcher - + Lanceur d'OpenMW OpenMW version - + Version d'OpenMW toolBar - + toolBar Data Files - + Données de jeu Allows to setup data files and directories - + Configurez et sélectionnez les fichiers, dossiers et archives de jeu Settings - + Options Allows to tweak engine settings - + Modifiez les options du moteur de jeu Import - + Importer Allows to import data from original engine - + Importez des données depuis le moteur de jeu original Display - + Affichage Allows to change display settings - + Modifiez les paramètres d'affichage QObject Select configuration file - + Sélectionnez un fichier de configuration Select script file - + Sélectionnez un fichier de script SelectSubdirs Select directories you wish to add - + Sélectionnez le dossier que vous désirez ajouter SettingsPage <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - + <html><head/><body><p>Rends le changement de disposition des marchands due au Marchandage permanent.</p></body></html> <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - + <html><head/><body><p>Permets aux compagnons et aux escortes de démarrer le combat avec des ennemis entrés en combat avec eux ou avec le joueur. Si l'option est désactivée, ils attendent que les ennemis les attaquent en premier.</p></body></html> <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - + <html><head/><body><p>Cette option retire le plafond sur l'effet magique Atténuation de Fatigue, comme c'est le cas avec Absorption de Fatigue. Cette option vous permet d'assommer des personnages en utilisant cet effet magique.</p></body></html> Uncapped Damage Fatigue - + Domages de fatique déplafonnés <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - + <html><head/><body><p>Si cette option est activée, le jeu interrompt le combat avec les PNJ affectés par l'effet magique Apaisement à chaque frame. Ce comportement similaire à celui du moteur d'origine, sans MCP.</p></body></html> <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - + <html><head/><body><p>Si l'option est cochée, la valeur des gemmes sprirituelles dépend uniquement de la puissance de l'âme contenue.</p></body></html> <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - + <html><head/><body><p>Cette option fait que le joueur nage légèrement au-dessus de la ligne de visée. S'applique uniquement dans le mode Vue à la troisième personne. Ceci facilite la nage, hors plongée.</p></body></html> <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - + <html><head/><body><p>Cette option rend possible le vol d'objets à un PNJ tombé à terre durant un combat.</p></body></html> <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Lorsque cette option est activée, un mesh de navigation est construit en arrière-plan à partir de la géométrie de l'environnement. Celui-ci est alors utilisé pour la recherche d'itinéraire.</p><p>Lorsqu'elle est désactivée, seule la grille des chemins est utilisée pour tracer les itinéraires.</p><p>Cette option peut affecter de façon significative les systèmes à simple cœur, lorsque le joueur passe d'un environnement intérieur à l'environnement extérieur. Ce paramètre peut aussi affecter, dans une moindre mesure, les systèmes multicœurs. La lenteur due à la génération de mesh de navigation des systèmes multicœurs peut dépendre des autres options ainsi que de la puissance du système. Se déplacer au sein de l'environnement extérieur, entrer et sortir d'un lieu génère un mesh de navigation. Les PNJ et les créatures peuvent ne pas trouver les mesh de navigation générés autour d'eux. Désactivez cette option si vous désirez avoir une intelligence artificielle à l'ancienne où les ennemis ne savent pas quoi faire lorsque le joueur se tient derrière un rocher et lance des boules de feu.</p></body></html> <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - + <html><head/><body><p>Si l'option est activée, les PNJ tentent des manœuvres d'évitement afin d'éviter d'entrer en collision les uns avec les autres.</p></body></html> <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - + <html><head/><body><p>N'utilise pas le poids de la race du PNJ pour calculer sa vitesse de mouvement.</p></body></html> <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - + <html><head/><body><p>Lorsque cette option est activée, le joueur est autorisé à piller créatures et PNJ (p. ex. les créatures invoquées) durant leur animation de mort, si elles ne sont pas en combat. Dans ce cas, le jeu incrémente le conteur de mort et lance son script instantanément.</p><p>Lorsque cette option est désactivée, le joueur doit attendre la fin de l'animation de mort. Dans ce cas, l'utilisation de l'exploit des créatures invoquées (piller des créatures invoquées telles que des Drémoras ou des Saintes Dorées afin d'obtenir des armes de grandes valeurs) est rendu beaucoup plus ardu. Cette option entre en confit avec les Mods de mannequin. En effet, ceux-ci utilisent SkipAnim afin d'éviter la fin de l'animation de mort.</p></body></html> <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - + <html><head/><body><p>L'option donne aux créatures non armées la capacité d'endommager les pièces d'armure, comme le font les PNJ et les créatures armées.</p></body></html> Off - + Inactif <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - + <html><head/><body><p>Détermine le nombre de thread qui seront générés en arrière-plan pour mettre à jour la physique du jeu. La valeur 0 signifie que ces mises à jour seront calculées par le thread principal.</p><p>Une valeur supérieure à 1 requière que la librairie Bullet soit compilée avec le support multithread.</p></body></html> Cylinder - + Cylindrique Visuals - + Visuels Animations - + Animations <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - + <html><head/><body><p>Anime l'utilisation d'objet magique, de façon similaire à l'utilisation des sorts.</p></body></html> <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - + <html><head/><body><p>Cette option rend les mouvements des PNJ et du joueur plus souple. Recommandé si l'option "Se tourner en direction du mouvement" est activée.</p></body></html> <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - + <html><head/><body><p>Charge les fichiers d'animation groupée KF et les fichiers de structure de squelettes depuis le dossier Animation.</p></body></html> <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - + <html><head/><body><p>Affecte les mouvements latéraux et diagonaux. Activer cette option rend les mouvements plus réalistes.</p><p>Si elle est désactivée, le corps entier du personnage joueur est pointé dans la direction de visée. Les mouvements diagonaux n'ont pas d'animation spécifique et glissent sur le sol.</p><p>Si elle est activée, le bas du corps du personnage joueur est orienté suivant la direction du mouvement, le haut du corps est orienté partiellement, et la tête est alignée avec la direction de visée. En mode combat, cet effet n'est appliqué qu'aux mouvements diagonaux. Hors mode combat, cet effet est aussi appliqué aux mouvements purement latéraux. Cette option affecte aussi la nage, le moteur de jeu oriente alors tout le corps dans la direction du mouvement.</p></body></html> <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - + <html><head/><body><p>Cette option permet d'afficher les armes rengainées dans leur étui (p. ex. carquois et fourreau). Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - + <html><head/><body><p>Cette option permet d'afficher les boucliers rengainés. Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - + <html><head/><body><p>En vue à la troisième personne, la caméra se balance avec mouvement d'animation du joueur. Activer cette option désactive ce balancement de sorte que le personnage joueur se déplace indépendamment de son animation. Ce comportement était activé par défaut dans les versions d'OpenMW 0.48 et antérieures.</p></body></html> Shaders - + Shaders <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les normal maps (textures d'orientation de la surface) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de normal maps devrait être nommée foo_n.dds (voir 'normal map pattern' dans la documentation pour plus d'informations).</p><p>Si l'option est désactivée, les normal maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .nif ou .osg). Cette option affecte les objets.</p></body></html> <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - + <html><head/><body><p>Voir "Utilisation auto des normal maps pour les objets". Affecte le terrain.</p></body></html> <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately @@ -742,734 +742,737 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov the specular map texture would have to be named foo_spec.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.osg file, not supported in .nif files). Affects objects.</p></body></html> - + <html><body><p>Si cette option est activée, les specular maps (textures indiquant si un objet est brillant ou mat) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de specular maps devrait être nommée foo_spec.dds (voir 'specular map pattern' dans la documentation pour plus d'informations, en anglais).</p><p>Si le paramètre est désactivé, les specular maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .osg, non supporté par le format nif). Cette option affecte les objets.</p></body></html> <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - + <html><head/><body><p>Si l'option est activée et qu'un fichier dont le nom respecte la structure des specular maps (voir Utilisation auto des specular maps pour les objets), le moteur de jeu utilise cette texture comme "diffuse specular map" : Cette texture doit contenir une couche (habituelle) de couleur RVB et un facteur de spécularité (≃brillance) dans le canal alpha.</p></body></html> <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. Affected objects will use shaders. </p></body></html> - + <html><head/><body><p>En temps normal, les réflexions dues au placage d'environnement (environment map) ne sont pas affectées par l'éclairage de la scène. En conséquence, les objets affectés par le placage d'environnement (et de relief) ont tendance à briller dans le noir. Le "Morrowind Code Patch" a, pour remédier à ça, inclus une option qui applique le placage d'environnement (et de relief) avant d'appliquer l'éclairage. Cette option est son équivalent. Les objets affectés utilisent toujours les shaders.</p></body></html> <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - + <html><body><p>Cette option permet à l'antialiasing à multiéchantillonnage (MSAA) de fonctionner avec le test alpha des mesh. Ceci produit un meilleur rendu des bords, sans pixélisation. Cette option peut affecter négativement les performances.</p></body></html> <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - + <html><body><p>Cette option active la génération douce des effets de particules. Cette technique adoucit l'intersection entre chaque particule et les éléments de géométrie opaque à l'aide d'un fondu progressif entre les deux.</p></body></html> <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - + <html><body><p>Lorsque cette option est activée, le moteur réalise une simulation de couverture préservée des mipmaps (coverage-preserving mipmaps). Ceci empêche les mesh testés en transparence (alpha test) de diminuer en taille lorsqu'ils s'éloignent. En contrepartie, cette option tend à agrandir les mesh ayant déjà une couverture préservée des mipmaps. Référez-vous à l'installation du contenu additionnel (mod) pour savoir quelle option est la mieux adaptée à celui-ci.</p></body></html> <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - + <html><body><p>Expérimental ! Cette option empêche la pluie et la neige de tomber au travers des promontoires et des toits.</p></body></html> Fog - + Brouillard <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - + <html><body><p>Si cette option est désactivée, le brouillard s'épaissit linéairement avec la distance jusqu'à atteindre le plan de coupure perpendiculaire à la caméra (clipping plane) à la distance de coupure. Ceci cause des distorsions sur les bords de l'écran.</p><p>Si elle est activée, le brouillard utilise la distance de chaque objet avec la caméra (appelée distance euclidienne) pour calculer la densité de brouillard. Cette option rend le brouillard moins artificiel, particulièrement pour les larges champs de vision.</p></body></html> <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - + <html><body><p>Si l'option est activée, le moteur de jeu utilise une décroissance exponentielle de la visibilité due au brouillard.</p><p>Si elle est désactivée, il utilise une décroissance linéaire de la visibilité due au brouillard.</p></body></html> <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - + <html><head/><body><p>Si l'option est activée, le moteur de jeu réduit la visibilité du plan de coupure du brouillard en effectuant un fondu avec le ciel.</p></body></html> <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - + <html><head/><body><p>Fraction de la distance d'affichage maximale à partir de laquelle le fondu avec le ciel commence.</p></body></html> Terrain - + Terrain <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - + <html><body><p>Cette option contrôle la taille apparente minimale que les objets doivent avoir pour être visibles à l'écran. Le rapport entre la taille de l'objet et sa distance à la caméra est comparé à cette valeur seuil. Plus cette valeur est faible, plus le nombre d'objets visibles à l'écran sera élevé.</p></body></html> <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - + <html><body><p>Si l'option est activée, le moteur utilise la pagination et différents niveau de détails (LOD) afin d'afficher le terrain entier.</p><p>Si elle est désactivée, seul le terrain de jeu actif est affiché.</p></body></html> <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - + <html><body><p>Si l'option est activée, utilise la pagination des objets pour rendre l'espace de jeu actif.</p></body></html> <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - + <html><body><p>Si cette option est activée, le moteur prend en charge les "day night switch nodes" des modèles 3D. Ceci permet de sélectionner entre jour et nuit l'apparence de ces modèles.</p></body></html> Post Processing - + Post-processing <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - + <html><body><p>Si cette option est activée, le post-traitement est lui aussi activé (les shaders de post-traitement sont configurables en jeu).</p></body></html> <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - + <html><head/><body><p>Si l'option est activée, le moteur réalise une seconde passe de rendu des objets transparents, en forçant leur découpe par le canal alpha de leurs textures.</p></body></html> <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - + <html><head/><body><p>Cette option contrôle la vitesse selon laquelle l'adaptation visuelle change d'une image à la suivante. Une valeur faible correspond à une adaptation lente.</p></body></html> Audio - + Audio Select your preferred audio device. - + Sélectionnez votre périphérique audio principal. Default - + Par défaut This setting controls HRTF, which simulates 3D sound on stereo systems. - + Cette option contrôle l'HRTF, qui émule la spatialisation 3D du son pour un système stéréo. HRTF - + HRTF Automatic - + Automatique On - + Actif Select your preferred HRTF profile. - + Sélectionnez le profil HRTF qui vous convient le mieux. Interface - + Interface <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - + <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - + <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> Size of characters in game texts. - + <html><body><p>Taille des caractères des textes du jeu.</p></body></html> <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - + <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - + <html><body><p>Si cette option est activée, les contenants compatibles avec "Graphic Herbalism" l'utilisent, au lieu d'ouvrir le menu de son contenu.</p></body></html> <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - + <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - + <html><body><p>Empêche les marchands d'équiper les objets qui leur sont vendus.</p></body></html> <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - + <html><body><p>Lorsque l'option est activée, les entraîneurs choisissent les compétences à entraîner en utilisant uniquement les points de compétences de base. Ceci permet d'améliorer les compétences de marchandages de l'entraîneur, sans que le marchandage devienne une compétence proposée.</p></body></html> Miscellaneous - + Divers Saves - + Sauvegardes <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - + <html><body><p>Cette option affiche le temps de jeu de chaque sauvegarde dans leur menu de sélection.</p></body></html> JPG - + JPG PNG - + PNG TGA - + TGA Testing - + Testeurs These settings are intended for testing mods and will cause issues if used for normal gameplay. - + Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - + <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> Browse… - + Parcourir... Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - + Les volumes de collision sont utilisés à la fois par la simulation de la physique du jeu et par la génération de mesh de navigation utilisé pour les choix d'itinéraires. Les collisions cylindriques donnent la meilleure correspondance entre l'itinéraire choisi et la capacité à les emprunter. Changer la valeur de cette option affecte la génération de mesh de navigation, dès lors les mesh de navigation précédemment mis en cache ne seront plus compatibles. Gameplay - + Jouabilité <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - + <html><body><p>Si l'option est activée, une munition magique est requise pour contourner la résistance ou la faiblesse normale d'une arme.</p><p>Si l'option est désactivée, une arme à distance magique ou une munition magique est requise.</p></body></html> <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - + <html><body><p>Lorsque cette option est activée, elle permet aussi aux armes enchantées, non indiquées comme étant magique, de contourner la résistance de l'arme, comme c'est le cas dans le moteur original.</p></body></html> cells - + cellules Shadows - + Ombres <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - + <html><body><p>Type de méthode utilisée pour calculer de "compute scene bound" (limite de scène). Le choix "bordures" (par défaut) donne un bon équilibre entre qualité des ombres et performances, "primitives" pour un meilleur rendu ou "aucun" pour éviter ce calcul.</p></body></html> <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - + <html><body><p>64 unités de jeu correspondent à 1 yard, c'est-à-dire environ 0.9 m.</p></body></html> unit(s) - + unité(s) <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des PNJ et des créatures. Elle peut avoir un impact faible sur les performances.</p></body></html> 512 - + 512 1024 - + 1024 2048 - + 2048 4096 - + 4096 <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - + <html><body><p>Fraction de la limite ci-dessus à partir de laquelle les ombres commencent à disparaître.</p></body></html> <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - + <html><body><p>Cette option affiche l'ombre du personnage joueur. Elle pourrait avoir un impact très faible sur les performances.</p></body></html> <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - + <html><body><p>La résolution de chaque carte d'ombre. Augmenter cette valeur, de façon importante, augmente la qualité des ombres, mais peut impliquer un impact mineur sur les performances.</p></body></html> <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - + <html><body><p>Distance à la caméra à partir de laquelle les ombres disparaissent entièrement.</p></body></html> <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des objets. Elle peut avoir un impact significative sur les performances.</p></body></html> <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - + <html><body><p>À cause de certaines limitations avec les données de Morrowind, seuls les personnages et les créatures peuvent projeter une ombre en intérieur, ceci peut déconcerter certains joueurs.</p><p>Cette option n'a aucun effet si les ombres des PNJ/créature et du personnage joueur n'ont pas été activées.</p></body></html> <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des terrains. Elle peut avoir un impact faible sur les performances et la qualité des ombres.</p></body></html> Lighting - + Éclairage Tooltip - + Infobulles Crosshair - + Réticule de visée Screenshots - + Captures d'écran <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - + <html><body><p>Cette option donne aux PNJ/créatures la capacité de nager en surface lorsqu'ils suivent un autre acteur, indépendamment de leur capacité à nager. Elle fonctionne uniquement lorsque l'utilisation des mesh de navigation pour le choix d'itinéraire est activée.</p></body></html> <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - + <html><body><p>L'effet réfléchi des sorts d'absorption n'est pas appliqué, comme dans le moteur original.</p></body></html> <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - + <html><body><p>Distance maximale d'affichage des sources lumineuses (en unité de distance).</p><p>Mettez cette valeur à 0 pour une distance d'affichage infinie.</p></body></html> <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - + <html><body><p>Nombre maximum de sources lumineuses par objet.</p><p>Une valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle.</p></body></html> <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - + <html><body><p>Fraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.</p><p>Sélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte.</p></body></html> <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> <p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> <p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - + <html><body><p>Définit la gestion des sources lumineuses :</p> +<p>"Traditionnelle" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.</p> +<p>"Shaders (mode de compatibilité)" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.</p> +<p>"Shaders" offre tous les bénéfices apportés par "Shaders (mode de compatibilité)", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance."</p></body></html> Legacy - + Traditionnelle Shaders (compatibility) - + Shaders (mode de compatibilité) <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - + <html><body><p>Multiplicateur pour le rayon de la sphère incluant les sources lumineuses.</p><p>Un multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.</p><p>Ce paramètre ne modifie ni l'intensité ni la luminance des lumières.</body></html> <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - + <html><head/><body><p>Luminosité ambiante minimale en intérieur.</p><p>Augmentez cette valeur si les intérieurs vous semblent trop sombres.</p></body></html> In third-person view, use the camera as the sound listener instead of the player character. - + En vue à la troisième personne, le jeu reproduit le son reçu par la caméra au lieu de celui reçu par tête du personnage joueur. Permanent Barter Disposition Changes - + Disposition au Marchandage permanente Classic Calm Spells Behavior - + Comportement d'origine pour le sort d'Apaisement NPCs Avoid Collisions - + Les PNJ évitent les collisions Soulgem Values Rebalance - + Rééquilibrage de la valeur des gemmes sprirituelles Day Night Switch Nodes - + Support des noeuds jour/nuit Followers Defend Immediately - + Les compagnons se défendent immédiatement Only Magical Ammo Bypass Resistance - + Seules les munitions contournent la résistance Graphic Herbalism - + Support de Graphic herbalism Swim Upward Correction - + Correction verticale de la nage Enchanted Weapons Are Magical - + Rendre magiques les armes enchantées Merchant Equipping Fix - + Correctif : équipement des marchands Can Loot During Death Animation - + Pillage possible durant l'animation de mort Classic Reflected Absorb Spells Behavior - + Comportement traditionnel de la réflexion des sorts d'absorbtion Unarmed Creature Attacks Damage Armor - + L'attaque des créatures non armées endomage les armures Affect Werewolves - + S'applique aux loups garoux Do Not Affect Werewolves - + Ne s'applique pas aux loups garoux Background Physics Threads - + Thread(s) d'arrière plan dédié(s) à la physique Actor Collision Shape Type - + Volume de collison pour les personnages Axis-Aligned Bounding Box - + Volume englobant aligné à l'axe Rotating Box - + Boite tournante Smooth Movement - + Mouvements lissés Use Additional Animation Sources - + Utiliser des sources d'animations additionnelles Weapon Sheathing - + Arme rengainée Shield Sheathing - + Bouclier rengainé Player Movement Ignores Animation - + Le mouvement du personnage joueur ignore son animation Use Magic Item Animation - + Anime l'utilisation d'objets magique Auto Use Object Normal Maps - + Utilisation auto des normal maps pour les objets Soft Particles - + Apparition douce des particules Auto Use Object Specular Maps - + Utilisation auto des specular maps pour les objets Auto Use Terrain Normal Maps - + Utilisation auto des normal maps pour le terrain Auto Use Terrain Specular Maps - + Utilisation auto des specular maps pour le terrain Use Anti-Aliased Alpha Testing - + Utiliser l'alpha testing pour l'anti-aliasing Bump/Reflect Map Local Lighting - + Plaquage de rugosité/environnement sous lumière locale Weather Particle Occlusion - + Occlusion des particules liées à la météo Exponential Fog - + Brouillard exponentiel Radial Fog - + Brouillard radial Sky Blending Start - + Début du fondu du ciel Sky Blending - + Fondu du ciel Object Paging Min Size - + Taille min des objets pour leur pagination Viewing Distance - + Distance d'affichage Distant Land - + Terrain distant Active Grid Object Paging - + Pagination pour l'espace actif Transparent Postpass - + Passe de transparence différée Auto Exposure Speed - + Vitesse d'auto-exposition Enable Post Processing - + Activer le post-traitement Shadow Planes Computation Method - + Méthode de calcul pour les plans d'ombre Enable Actor Shadows - + Ombre des PNJ/créatures Fade Start Multiplier - + Début de l'atténuation (multiplicateur) Enable Player Shadows - + Ombre du personnage joueur Shadow Map Resolution - + Résolution des crates d'ombres Shadow Distance Limit: - + Limite de distance des ombres Enable Object Shadows - + Ombre des objets Enable Indoor Shadows - + Ombres en intérieur Enable Terrain Shadows - + Ombre des terrains Maximum Light Distance - + Portée maximale des sources lumineuse Max Lights - + Nombre maximum de sources lumineuses Lighting Method - + Méthode d'illumination Bounding Sphere Multiplier - + Multiplicateur de portée des sphères lumineuses Minimum Interior Brightness - + Luminosité intérieure minimale Audio Device - + Périphérique audio HRTF Profile - + Profil HRTF Tooltip and Crosshair - + Infobulles et réticule de visée GUI Scaling Factor - + Échelle de l'interface Show Effect Duration - + Afficher la durée des effets Change Dialogue Topic Color - + Changer la couleur des sujets de conversation Font Size - + Taille des polices Show Projectile Damage - + Afficher les dommages des projectiles Show Melee Info - + Afficher les infos de mêlée Stretch Menu Background - + Étendre les arrière-plans Show Owned Objects - + Afficher la possession des objets Show Enchant Chance - + Afficher les chances d'enchantement Maximum Quicksaves - + Nombre maximum de sauvegardes rapides Screenshot Format - + Format des captures Grab Cursor - + Capturer le curseur Default Cell - + la cellule par défaut Bounds - + bordures Primitives - + primitives None - + aucune Always Allow Actors to Follow over Water - + Toujours permettre aux PNJ/créatures à vous suivre sur l'eau Racial Variation in Speed Fix - + Correction de la variation raciale pour la vitesse Use Navigation Mesh for Pathfinding - + Utiliser les mesh de navigation pour la recherche d'itinéraires Trainers Choose Offered Skills by Base Value - + Entraîneurs : choix de compétences à partir des valeurs de base Steal from Knocked out Actors in Combat - + Vol possible aux PNJ/créatures évanouies en combat Factor Strength into Hand-to-Hand Combat - + Multiplicateur de force pour le combat à mains nues Turn to Movement Direction - + Se tourner en direction du mouvement Adjust Coverage for Alpha Test - + Ajuster la couverture pour l'alpha test Use the Camera as the Sound Listener - + Utiliser le son arrivant à la caméra Can Zoom on Maps - + Permettre le zoom sur la carte Add "Time Played" to Saves - + Ajoute le temps de jeu aux sauvegardes Notify on Saved Screenshot - + Notifier l'enregistrement des captures d'écran Skip Menu and Generate Default Character - + Passer le menu principal et générer un personnage standard Start Default Character at - + Placer le personnage par défaut dans Run Script After Startup: - + Script à lancer après démarrage : diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index ab0b01af73..6c655885d2 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -5,672 +5,672 @@ ComponentSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Components - + Sélection des composantes de jeu Which components should be installed? - + Quelles composantes du jeu doivent être installées ? <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - + <html><head/><body><p>Sélectionnez quelle(s) extension(s) officielle(s) doivent être installée(s). Pour une meilleure expérience de jeu, il est recommandé d'installer toutes les extensions.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'installer les extensions plus tard en relançant l'Assistant d'installation.<br/></p></body></html> Selected components: - + Composantes sélectionnées : ConclusionPage WizardPage - + Assistant d'installation d'OpenMW Completing the OpenMW Wizard - + Fin de l'Assistant d'installation d'OpenMW Placeholder - + Placeholder ExistingInstallationPage WizardPage - + Assistant d'installation d'OpenMW Select Existing Installation - + Sélection d'une installation existante Select an existing installation for OpenMW to use or modify. - + Sélectionnez une installation existante pour l'utiliser ou la modifier avec OpenMW Detected installations: - + Installation(s) détectée(s) : Browse... - + Parcourir... ImportPage WizardPage - + Assistant d'installation d'OpenMW Import Settings - + Import des paramètres Import settings from the Morrowind installation. - + Importe les paramètres depuis une installation de Morrowind. <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - + <html><head/><body><p>Afin de fonctionner correctement, OpenMW nécessite d'importer les paramètres depuis le fichier de configuration de Morrowind.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'importer les paramètres plus tard en relançant l'Assistant d'installation.</p><p/></body></html> Import Settings From Morrowind.ini - + Importer les paramètres depuis le fichier Morrowind.ini Import Add-on and Plugin Selection - + Importer les extensions et la sélection de modules complémentaires Import Bitmap Fonts Setup From Morrowind.ini - + Importer les paramètres des polices en bitmap depuis Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et sont fort similaires aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. InstallationPage WizardPage - + Assistant d'installation d'OpenMW Installing - + Installation en cours Please wait while Morrowind is installed on your computer. - + Veuillez patienter pendant l'installation de Morrowind sur votre ordinateur. InstallationTargetPage WizardPage - + Assistant d'installation d'OpenMW Select Installation Destination - + Sélection de l'emplacement de l'installation Where should Morrowind be installed? - + À quel emplacement Morrowind doit-il être installé ? <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> Morrowind will be installed to the following location. - + Morrowind sera installé à l'emplacement suivant : Browse... - + Parcourir... IntroPage WizardPage - + Assistant d'installation d'OpenMW Welcome to the OpenMW Wizard - + Bienvenue sur l'assistant d'installation d'OpenMW This Wizard will help you install Morrowind and its add-ons for OpenMW to use. - + Cet assistant vous aidera à installer Morrowind et ses extensions afin que vous lanciez le jeu avec OpenMW. LanguageSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Morrowind Language - + Sélection de la langue de Morrowind What is the language of the Morrowind installation? - + Dans quelle langue est cette installation de Morrowind ? <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> Select the language of the Morrowind installation. - + Sélectionnez la langue de cette installation de Morrowind. MethodSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Installation Method - + Méthode d'installation <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - + <html><head/><body><p>Sélectionnez une méthode d'installation de <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> Retail CD/DVD - + Copie CD/DVD physique <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> Install from a retail disc to a new location. - + Installer depuis un CD/DVD et choisir la destination sur l'ordinateur. Existing Installation - + Installation existante <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> Select an existing installation. - + Sélectionnez une installation existante. Don't have a copy? - + Vous n'avez pas de copie du jeu ? <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> Buy the game - + Achetez le jeu. QObject <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - + <br><b>Impossible de trouver Morrowind.ini</b><br><br>L'assistant d'installation requière de mettre à jour les paramètres de ce fichier.<br><br>Cliquez sur "Parcourir..." afin de spécifier manuellement son emplacement.<br> B&rowse... - + P&arcourir... Select configuration file - + Sélectionnez le fichier de configuration <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - + L'archive <b>Morrowind.bsa</b> est manquante !<br>Assurez-vous que votre installation de Morrowind est complète. <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - + <br><b>Il semble qu'une version plus à jour de Morrowind soit disponible.</b><br><br>Voulez-vous continuer malgré tout ?<br> Most recent Morrowind not detected - + Version la plus à jour de Morrowind non détectée Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - + Sélectionnez un support %1 d'installation valide.<br><b>Aide</b> : Assurez-vous qu'il contienne au moins un fichier <b>.cab</b>. There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? - + Il semble qu'une version plus à jour de Morrowind soit disponible.<br><br>Voulez-vous continuer malgré tout ? Wizard::ComponentSelectionPage &Install - + &Installer &Skip - + &Passer Morrowind (installed) - + Morrowind (installé) Morrowind - + Morrowind Tribunal (installed) - + Tribunal (installé) Tribunal - + Tribunal Bloodmoon (installed) - + Bloodmoon (installé) Bloodmoon - + Bloodmoon About to install Tribunal after Bloodmoon - + À propos de l'installation de Tribunal après Bloodmoon <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - + <html><head/><body><p><b>Vous vous apprêtez à installer l'extension Tribunal.</b></p><p>L'extension Bloodmoon est déjà installée sur votre ordinateur.</p><p>Néanmoins, il est recommandé d'installer Tribunal avant Bloodmoon.</p><p>Désirez-vous réinstaller Bloodmoon ?</p></body></html> Re-install &Bloodmoon - + Réinstaller &Bloodmoon Wizard::ConclusionPage <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a installé Morrowind sur votre ordinateur avec succès !</p></body></html> <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a modifié votre installation de Morrowind avec succès !</p></body></html> <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> - + <html><head/><body><p>L'assistant d'installation d'OpenMW a échoué l'installation de Morrowind.</p><p>Veuillez signaler les bogues que vous avez pu rencontrer sur notre. <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a> (en anglais).<br/>Assurez-vous d'inclure le journal (log) d'installation.</p><br/></body></html> Wizard::ExistingInstallationPage No existing installations detected - + Aucune installation existante détectée Error detecting Morrowind configuration - + Erreur lors de la détection du fichier de configuration de Morrowind Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) Select Morrowind.esm (located in Data Files) - + Sélectionnez Morrowind.esm (situé dans Data Files) Morrowind master file (Morrowind.esm) - + Fichier principal de Morrowind (Morrowind.esm) Error detecting Morrowind files - + Erreur lors de la détection des fichiers de Morrowind Wizard::InstallationPage <p>Attempting to install component %1.</p> - + <p>Tentative d'installation de la composante %1.</p> Attempting to install component %1. - + Tentative d'installation de la composante %1. %1 Installation - + Installation de %1 Select %1 installation media - + Sélectionnez le support d'installation de %1 <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - + <p><br/><span style="color:red;"><b>Erreur : L'installation a été annulée par l'utilisateur.</b></span></p> <p>Detected old version of component Morrowind.</p> - + <p>Ancienne version de la composante Morrowind détectée.</p> Detected old version of component Morrowind. - + Ancienne version de la composante Morrowind détectée. Morrowind Installation - + Installation de Morrowind Installation finished - + Installation terminée Installation completed successfully! - + Installation complétée avec succès ! Installation failed! - + Échec de l'installation ! <p><br/><span style="color:red;"><b>Error: %1</b></p> - + <p><br/><span style="color:red;"><b>Erreur : %1</b></p> <p><span style="color:red;"><b>%1</b></p> - + <p><span style="color:red;"><b>%1</b></p> <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - + <html><head/><body><p><b>L'assistant d'installation a rencontré une erreur.</b></p><p>L'erreur reportée est :</p><p>%1</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> An error occurred - + Une erreur est survenue Wizard::InstallationTargetPage Error creating destination - + Erreur lors de la création de l'emplacement de l'installation <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible de créer le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible d'écrire dans le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - + <html><head/><body><p><b>Le dossier d'installation n'est pas vide.</b></p><p>Une installation de Morrowind est déjà présente dans ce dossier.</p><p>Veuillez spécifier un autre enmplacement pour l'installation, ou revenez en arrière et spécifiez que l'emplacement contient déjà une installation.</p></body></html> Insufficient permissions - + Droits d'accès insuffisants Destination not empty - + Emplacement non vide Select where to install Morrowind - + Sélectionnez où installer Morrowind Wizard::LanguageSelectionPage English - + Anglais French - + Français German - + Allemand Italian - + Italien Polish - + Polonais Russian - + Russe Spanish - + Espagnol Wizard::MainWizard OpenMW Wizard - + Assistant d'installation d'OpenMW Error opening Wizard log file - + Erreur lors de l'ouverture du journal de l'Assistant d'installation <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en écriture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en lecture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration. Quit Wizard - + Quitter l'Assistant Are you sure you want to exit the Wizard? - + Êtes-vous sûr de vouloir quitter l'Assistant d'installation ? Error creating OpenMW configuration directory - + Erreur lors de la création du dossier de configuration d'OpenMW <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de créer %1.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW. Wizard::UnshieldWorker Failed to open Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! Opening %1 failed: %2. - + Échec lors de l'ouverture de %1 : %2. Failed to write Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! Writing to %1 failed: %2. - + Échec lors de l'écriture dans %1 : %2. Installing: %1 - + Installation : %1 Installing: %1 directory - + Installation : dossier %1 Installation finished! - + Installation terminée ! Component parameter is invalid! - + Le paramètre d'une des composantes est invalide ! An invalid component parameter was supplied. - + Un paramètre invalide d'une composante a été fournis. Failed to find a valid archive containing %1.bsa! Retrying. - + Impossible de trouver une archive contenant %1.bsa ! Nouvel essai. Installing %1 - + Installation de %1 Installation media path not set! - + Support d'installation non défini ! The source path for %1 was not set. - + L'emplacement de %1 n'a pas été défini. Cannot create temporary directory! - + Impossible de créer un dossier temporaire ! Failed to create %1. - + Impossible de créer %1. Cannot move into temporary directory! - + Impossible d'accéder au dossier temporaire ! Failed to move into %1. - + Impossible d'accéder à %1. Moving installation files - + Déplacement des fichiers d'installation. Could not install directory! - + Impossible d'installer le dossier ! Installing %1 to %2 failed. - + Échec de l'installation de %1 dans %2. Could not install translation file! - + Impossible d'installer le fichier de localisation ! Failed to install *%1 files. - + Échec lors de l'installation du fichier %1. Could not install Morrowind data file! - + Impossible d'installer le fichier de données de Morrowind ! Failed to install %1. - + Échec lors de l'installation de %1. Could not install Morrowind configuration file! - + Impossible d'installer le fichier de configuration de Morrowind ! Installing: Sound directory - + Installation : Dossier des sons. Could not find Tribunal data file! - + Impossible d'installer le fichier de données de Tribunal ! Failed to find %1. - + Impossible de trouver %1. Could not find Tribunal patch file! - + Impossible de trouver le fichier de correctifs de Tribunal ! Could not find Bloodmoon data file! - + Impossible d'installer le fichier de données de Bloodmoon ! Updating Morrowind configuration file - + Mise à jour du fichier de configuration de Morrowind. %1 installation finished! - + Installation de %1 terminée ! Extracting: %1 - + Extraction : %1 Failed to open InstallShield Cabinet File. - + Impossible d'ouvrir le fichier InstallShield Cabinet. Opening %1 failed. - + Échec lors de l'ouverture de %1. Failed to extract %1. - + Échec lors de l'extraction de %1. Complete path: %1 - + Emplacement complet : %1. From 6e79064a57b89bfec0470fc6cfdb469dc651b05c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 12 Apr 2024 18:32:47 +0200 Subject: [PATCH 1312/2167] Fix editor oddities --- apps/opencs/model/world/infoselectwrapper.cpp | 44 ++++++------------- apps/opencs/model/world/infoselectwrapper.hpp | 5 --- .../model/world/nestedcoladapterimp.cpp | 5 ++- apps/openmw/mwdialogue/filter.cpp | 2 +- apps/openmw/mwdialogue/selectwrapper.cpp | 6 +-- components/esm3/dialoguecondition.cpp | 6 ++- components/esm3/dialoguecondition.hpp | 2 +- 7 files changed, 26 insertions(+), 44 deletions(-) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index d1af341c04..2e9ed6e150 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -65,7 +65,7 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { "PC Endurance", "PC Personality", "PC Luck", - "PC Corpus", + "PC Corprus", "Weather", "PC Vampire", "Level", @@ -105,37 +105,21 @@ const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = { nullptr, }; -const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { - "Boolean", - "Integer", - "Numeric", - nullptr, -}; - -// static functions - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Function name) +namespace { - if (name < ESM::DialogueCondition::Function_None) - return FunctionEnumStrings[name]; - else + std::string_view convertToString(ESM::DialogueCondition::Function name) + { + if (name < ESM::DialogueCondition::Function_None) + return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[name]; return "(Invalid Data: Function)"; -} + } -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Comparison type) -{ - if (type < ESM::DialogueCondition::Comp_None) - return RelationEnumStrings[type]; - else + std::string_view convertToString(ESM::DialogueCondition::Comparison type) + { + if (type != ESM::DialogueCondition::Comp_None) + return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[type - ESM::DialogueCondition::Comp_Eq]; return "(Invalid Data: Relation)"; -} - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) -{ - if (type < Comparison_None) - return ComparisonEnumStrings[type]; - else - return "(Invalid Data: Comparison)"; + } } // ConstInfoSelectWrapper @@ -269,7 +253,7 @@ void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_Detected: case ESM::DialogueCondition::Function_Alarmed: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_Attacked: case ESM::DialogueCondition::Function_TalkedToPc: @@ -457,7 +441,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_Detected: case ESM::DialogueCondition::Function_Alarmed: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_Attacked: case ESM::DialogueCondition::Function_TalkedToPc: diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index d8d108444f..b3b5abe462 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -25,11 +25,6 @@ namespace CSMWorld static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; - static const char* ComparisonEnumStrings[]; - - static std::string convertToString(ESM::DialogueCondition::Function name); - static std::string convertToString(ESM::DialogueCondition::Comparison type); - static std::string convertToString(ComparisonType type); ConstInfoSelectWrapper(const ESM::DialogueCondition& select); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 86d38f9cd2..c844c5a18f 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -605,7 +605,7 @@ namespace CSMWorld } case 2: { - return infoSelectWrapper.getRelationType(); + return infoSelectWrapper.getRelationType() - ESM::DialogueCondition::Comp_Eq; } case 3: { @@ -643,7 +643,8 @@ namespace CSMWorld } case 2: // Relation { - infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.setRelationType( + static_cast(value.toInt() + ESM::DialogueCondition::Comp_Eq)); break; } case 3: // Value diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index acf87ccc61..295d690ce5 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -618,7 +618,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con return player.getClass().getCreatureStats(player).hasBlightDisease(); - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: return player.getClass() .getCreatureStats(player) diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index cc07ec8709..02c9d29b59 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -26,9 +26,9 @@ namespace return value1 < value2; case ESM::DialogueCondition::Comp_Le: return value1 <= value2; + default: + throw std::runtime_error("unknown compare type in dialogue info select"); } - - throw std::runtime_error("unknown compare type in dialogue info select"); } template @@ -226,7 +226,7 @@ MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_PcCommonDisease: case ESM::DialogueCondition::Function_PcBlightDisease: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcExpelled: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_TalkedToPc: diff --git a/components/esm3/dialoguecondition.cpp b/components/esm3/dialoguecondition.cpp index ba8f9586ce..a6a28307c2 100644 --- a/components/esm3/dialoguecondition.cpp +++ b/components/esm3/dialoguecondition.cpp @@ -45,7 +45,7 @@ namespace ESM return {}; } } - else if (rule[1] > '1' && rule[1] <= '9' || rule[1] >= 'A' && rule[1] <= 'C') + else if ((rule[1] > '1' && rule[1] <= '9') || (rule[1] >= 'A' && rule[1] <= 'C')) { if (rule.size() == 5) { @@ -134,6 +134,8 @@ namespace ESM void DialogueCondition::save(ESMWriter& esm) const { auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue); + if (variant.getType() != VT_Float) + variant.setType(VT_Int); std::string rule; rule.reserve(5 + mVariable.size()); rule += static_cast(mIndex + '0'); @@ -189,7 +191,7 @@ namespace ESM start--; else start -= 2; - auto result = std::to_chars(start, end, mFunction); + auto result = std::to_chars(start, end, static_cast(mFunction)); if (result.ec != std::errc()) { Log(Debug::Error) << "Failed to save SCVR rule"; diff --git a/components/esm3/dialoguecondition.hpp b/components/esm3/dialoguecondition.hpp index 15ad4944f5..c06d50b601 100644 --- a/components/esm3/dialoguecondition.hpp +++ b/components/esm3/dialoguecondition.hpp @@ -75,7 +75,7 @@ namespace ESM Function_PcEndurance, Function_PcPersonality, Function_PcLuck, - Function_PcCorpus, + Function_PcCorprus, Function_Weather, Function_PcVampire, Function_Level, From f880ada633fc52b1006503054405dd595cbf1ed4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 13 Apr 2024 00:06:24 +0300 Subject: [PATCH 1313/2167] Don't flip the water normal map twice --- apps/openmw/mwrender/water.cpp | 2 -- files/shaders/compatibility/water.frag | 1 - 2 files changed, 3 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a28ca0b7b7..cbc560d5ac 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -715,8 +715,6 @@ namespace MWRender osg::ref_ptr normalMap( new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png"))); - if (normalMap->getImage()) - normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index d1324e01bd..749dcf27cd 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -94,7 +94,6 @@ uniform vec2 screenRes; void main(void) { vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; - UV.y *= -1.0; float shadow = unshadowedLightRatio(linearDepth); From 86b6eee62b2458e97b604f8946c5f2e2681dce59 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Mar 2024 15:53:26 +0300 Subject: [PATCH 1314/2167] Improve hit detection emulation (#7900) --- apps/openmw/mwmechanics/combat.cpp | 62 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index b9852e1b41..5d283214a3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -587,18 +587,20 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& store = world->getStore().get(); + // These GMSTs are not in degrees. They're tolerance angle sines multiplied by 90. + // With the default values of 60, the actual tolerance angles are roughly 41.8 degrees. + // Don't think too hard about it. In this place, thinking can cause permanent damage to your mental health. + const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat() / 90.f; + const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat() / 90.f; + const ESM::Position& posdata = actor.getRefData().getPosition(); const osg::Vec3f actorPos(posdata.asVec3()); - - // Morrowind uses body orientation or camera orientation if available - // The difference between that and this is subtle - osg::Quat actorRot - = osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); - - const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat(); - const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat(); - const float combatAngleXYcos = std::cos(osg::DegreesToRadians(fCombatAngleXY)); - const float combatAngleZcos = std::cos(osg::DegreesToRadians(fCombatAngleZ)); + const osg::Vec3f actorDirXY = osg::Quat(posdata.rot[2], osg::Vec3(0, 0, -1)) * osg::Vec3f(0, 1, 0); + // Only the player can look up, apparently. + const float actorVerticalAngle = actor == getPlayer() ? -std::sin(posdata.rot[0]) : 0.f; + const float actorEyeLevel = world->getHalfExtents(actor, true).z() * 2.f * 0.85f; + const osg::Vec3f actorEyePos{ actorPos.x(), actorPos.y(), actorPos.z() + actorEyeLevel }; + const bool canMoveByZ = canActorMoveByZAxis(actor); // The player can target any active actor, non-playable actors only target their targets std::vector targets; @@ -612,26 +614,40 @@ namespace MWMechanics { if (actor == target || target.getClass().getCreatureStats(target).isDead()) continue; - float dist = getDistanceToBounds(actor, target); - osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f dirToTarget = targetPos - actorPos; - if (dist >= reach || dist >= minDist || std::abs(dirToTarget.z()) >= reach) + const float dist = getDistanceToBounds(actor, target); + const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach) continue; - dirToTarget.normalize(); + // Horizontal angle checks. + osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; + actorToTargetXY.normalize(); - // The idea is to use fCombatAngleXY and fCombatAngleZ as tolerance angles - // in XY and YZ planes of the coordinate system where the actor's orientation - // corresponds to (0, 1, 0) vector. This is not exactly what Morrowind does - // but Morrowind does something (even more) stupid here - osg::Vec3f hitDir = actorRot.inverse() * dirToTarget; - if (combatAngleXYcos * std::abs(hitDir.x()) > hitDir.y()) + // Use dot product to check if the target is behind first... + if (actorToTargetXY.x() * actorDirXY.x() + actorToTargetXY.y() * actorDirXY.y() <= 0.f) continue; - // Nice cliff racer hack Todd - if (combatAngleZcos * std::abs(hitDir.z()) > hitDir.y() && !MWMechanics::canActorMoveByZAxis(target)) + // And then perp dot product to calculate the hit angle sine. + // This gives us a horizontal hit range of [-asin(fCombatAngleXY / 90); asin(fCombatAngleXY / 90)] + if (std::abs(actorToTargetXY.x() * actorDirXY.y() - actorToTargetXY.y() * actorDirXY.x()) > fCombatAngleXY) continue; + // Vertical angle checks. Nice cliff racer hack, Todd. + if (!canMoveByZ) + { + // The idea is that the body should always be possible to hit. + // fCombatAngleZ is the tolerance for hitting the target's feet or head. + osg::Vec3f actorToTargetFeet = targetPos - actorEyePos; + osg::Vec3f actorToTargetHead = actorToTargetFeet; + actorToTargetFeet.normalize(); + actorToTargetHead.z() += world->getHalfExtents(target, true).z() * 2.f; + actorToTargetHead.normalize(); + + if (actorVerticalAngle - actorToTargetHead.z() > fCombatAngleZ + || actorVerticalAngle - actorToTargetFeet.z() < -fCombatAngleZ) + continue; + } + // Gotta use physics somehow! if (!world->getLOS(actor, target)) continue; From 1930bfeabb522781d5825bdee123a07216e62b13 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 17:09:48 +0100 Subject: [PATCH 1315/2167] Support coloured terminal output on Windows First try the modern Windowsy way, where we can directly query if escape sequences will be processed. The function is available as far back as Windows 2000, but it just won't return the right flag until the Windows version is new enough. If that fails, fall back to the Unixy way, as not all colour-supporting terminal emulators for Windows use the Win32 API to declare that capability. The implementation isn't identical as isatty wasn't available without adding more headers, and we already have Windows.h in this file, so I might as well use the Win32 API instead of its POSIX-compatibility layer. --- components/debug/debugging.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e9e50ff836..bfde558c85 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -256,9 +256,17 @@ namespace Debug private: static bool useColoredOutput() { - // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. #if defined(_WIN32) - return 0; + if (getenv("NO_COLOR")) + return false; + + DWORD mode; + if (GetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), &mode) && mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + return true; + + // some console emulators may not use the Win32 API, so try the Unixy approach + char* term = getenv("TERM"); + return term && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; #else char* term = getenv("TERM"); bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); From a1438f65feccb3e7b2c58b2af99be86891b13bcc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Mar 2024 02:09:55 +0100 Subject: [PATCH 1316/2167] Set proper max tiles on initializing navmesh settings --- .../detournavigator/asyncnavmeshupdater.cpp | 11 +- components/detournavigator/makenavmesh.cpp | 26 +---- components/detournavigator/navmeshmanager.cpp | 2 +- components/detournavigator/settings.cpp | 101 +++++++++++++----- components/detournavigator/settings.hpp | 4 - files/settings-default.cfg | 2 +- 6 files changed, 80 insertions(+), 66 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 03f6062788..1ea7b63429 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -180,13 +180,10 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - std::unique_lock lock(mMutex); if (playerTileChanged) - updateJobs(mWaiting, playerTile, maxTiles); + updateJobs(mWaiting, playerTile, mSettings.get().mMaxTilesNumber); for (const auto& [changedTile, changeType] : changedTiles) { @@ -221,7 +218,7 @@ namespace DetourNavigator lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) - mDbWorker->updateJobs(playerTile, maxTiles); + mDbWorker->updateJobs(playerTile, mSettings.get().mMaxTilesNumber); } void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) @@ -376,10 +373,8 @@ namespace DetourNavigator return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles)) + if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; navMeshCacheItem->lock()->removeTile(job.mChangedTile); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index d35ecf499d..e143bf1837 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -480,15 +480,6 @@ namespace DetourNavigator return true; } - template - unsigned long getMinValuableBitsNumber(const T value) - { - unsigned long power = 0; - while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) - ++power; - return power; - } - std::pair getBoundsByZ( const RecastMesh& recastMesh, float agentHalfExtentsZ, const RecastSettings& settings) { @@ -528,10 +519,7 @@ namespace DetourNavigator return { minZ, maxZ }; } } -} // namespace DetourNavigator -namespace DetourNavigator -{ std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, std::string_view worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings) @@ -621,22 +609,12 @@ namespace DetourNavigator void initEmptyNavMesh(const Settings& settings, dtNavMesh& navMesh) { - // Max tiles and max polys affect how the tile IDs are caculated. - // There are 22 bits available for identifying a tile and a polygon. - const int polysAndTilesBits = 22; - const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); - - if (polysBits >= polysAndTilesBits) - throw InvalidArgument("Too many polygons per tile"); - - const auto tilesBits = polysAndTilesBits - polysBits; - dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; - params.maxTiles = 1 << tilesBits; - params.maxPolys = 1 << polysBits; + params.maxTiles = settings.mMaxTilesNumber; + params.maxPolys = settings.mDetour.mMaxPolys; const auto status = navMesh.init(¶ms); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index f4a82b850f..e6f831bb6e 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -181,7 +181,7 @@ namespace DetourNavigator { const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); + const int maxTiles = mSettings.mMaxTilesNumber; getTilesPositions(range, [&](const TilePosition& tile) { if (changedTiles.find(tile) != changedTiles.end()) return; diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 0470c629e5..5e555050f7 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -3,41 +3,81 @@ #include #include +#include +#include +#include + namespace DetourNavigator { - RecastSettings makeRecastSettingsFromSettingsManager() + namespace { - RecastSettings result; + struct NavMeshLimits + { + int mMaxTiles; + int mMaxPolys; + }; - result.mBorderSize = ::Settings::navigator().mBorderSize; - result.mCellHeight = ::Settings::navigator().mCellHeight; - result.mCellSize = ::Settings::navigator().mCellSize; - result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; - result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; - result.mMaxClimb = Constants::sStepSizeUp; - result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; - result.mMaxSlope = Constants::sMaxSlope; - result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; - result.mSwimHeightScale = 0; - result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; - result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; - result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; - result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; - result.mTileSize = ::Settings::navigator().mTileSize; + template + unsigned long getMinValuableBitsNumber(const T value) + { + unsigned long power = 0; + while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) + ++power; + return power; + } - return result; - } + NavMeshLimits getNavMeshTileLimits(const DetourSettings& settings) + { + // Max tiles and max polys affect how the tile IDs are caculated. + // There are 22 bits available for identifying a tile and a polygon. + constexpr int polysAndTilesBits = 22; + const unsigned long polysBits = getMinValuableBitsNumber(settings.mMaxPolys); - DetourSettings makeDetourSettingsFromSettingsManager() - { - DetourSettings result; + if (polysBits >= polysAndTilesBits) + throw std::invalid_argument("Too many polygons per tile: " + std::to_string(settings.mMaxPolys)); - result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; - result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; - result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; - result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + const unsigned long tilesBits = polysAndTilesBits - polysBits; - return result; + return NavMeshLimits{ + .mMaxTiles = static_cast(1 << tilesBits), + .mMaxPolys = static_cast(1 << polysBits), + }; + } + + RecastSettings makeRecastSettingsFromSettingsManager() + { + RecastSettings result; + + result.mBorderSize = ::Settings::navigator().mBorderSize; + result.mCellHeight = ::Settings::navigator().mCellHeight; + result.mCellSize = ::Settings::navigator().mCellSize; + result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; + result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; + result.mMaxClimb = Constants::sStepSizeUp; + result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; + result.mMaxSlope = Constants::sMaxSlope; + result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; + result.mSwimHeightScale = 0; + result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; + result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; + result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; + result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; + result.mTileSize = ::Settings::navigator().mTileSize; + + return result; + } + + DetourSettings makeDetourSettingsFromSettingsManager() + { + DetourSettings result; + + result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; + result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; + result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; + result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + + return result; + } } Settings makeSettingsFromSettingsManager() @@ -46,7 +86,12 @@ namespace DetourNavigator result.mRecast = makeRecastSettingsFromSettingsManager(); result.mDetour = makeDetourSettingsFromSettingsManager(); - result.mMaxTilesNumber = ::Settings::navigator().mMaxTilesNumber; + + const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour); + + result.mDetour.mMaxPolys = limits.mMaxPolys; + + result.mMaxTilesNumber = std::min(limits.mMaxTiles, ::Settings::navigator().mMaxTilesNumber.get()); result.mWaitUntilMinDistanceToPlayer = ::Settings::navigator().mWaitUntilMinDistanceToPlayer; result.mAsyncNavMeshUpdaterThreads = ::Settings::navigator().mAsyncNavMeshUpdaterThreads; result.mMaxNavMeshTilesCacheSize = ::Settings::navigator().mMaxNavMeshTilesCacheSize; diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 45bcf15dbf..1d1f6f5847 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -55,10 +55,6 @@ namespace DetourNavigator inline constexpr std::int64_t navMeshFormatVersion = 2; - RecastSettings makeRecastSettingsFromSettingsManager(); - - DetourSettings makeDetourSettingsFromSettingsManager(); - Settings makeSettingsFromSettingsManager(); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 73331867a7..2f236bf55a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -981,7 +981,7 @@ enable agents paths render = false enable recast mesh render = false # Max number of navmesh tiles (value >= 0) -max tiles number = 512 +max tiles number = 1024 # Min time duration for the same tile update in milliseconds (value >= 0) min update interval ms = 250 From 61c69c5563589b76b1857f0a9624f901e8f0ad69 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 15:35:35 +0200 Subject: [PATCH 1317/2167] Use proper prefix for CollisionShapeType --- components/detournavigator/debug.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 835f37f999..4dcd5e9857 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -79,13 +79,13 @@ namespace DetourNavigator switch (v) { case CollisionShapeType::Aabb: - return s << "AgentShapeType::Aabb"; + return s << "CollisionShapeType::Aabb"; case CollisionShapeType::RotatingBox: - return s << "AgentShapeType::RotatingBox"; + return s << "CollisionShapeType::RotatingBox"; case CollisionShapeType::Cylinder: - return s << "AgentShapeType::Cylinder"; + return s << "CollisionShapeType::Cylinder"; } - return s << "AgentShapeType::" << static_cast>(v); + return s << "CollisionShapeType::" << static_cast>(v); } std::ostream& operator<<(std::ostream& s, const AgentBounds& v) From d6f3d34f2f75078d8b897a89484dc5fcbc1ea546 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 15:40:42 +0200 Subject: [PATCH 1318/2167] Remove tiles present on navmesh but outside desired area --- .../detournavigator/navigator.cpp | 90 +++++++++++++++++++ .../detournavigator/navmeshcacheitem.hpp | 9 ++ components/detournavigator/navmeshmanager.cpp | 8 +- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index b61a88662b..6dcade083a 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1055,6 +1055,96 @@ namespace } } + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_without_waiting_for_all) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, nullptr); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_with_db) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, std::make_unique(":memory:", settings.mMaxDbFileSize)); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + struct AddHeightfieldSurface { static constexpr std::size_t sSize = 65; diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index ec76b56a46..120a374195 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -131,6 +131,15 @@ namespace DetourNavigator function(position, tile.mVersion, *meshTile); } + template + void forEachTilePosition(Function&& function) const + { + for (const auto& [position, tile] : mUsedTiles) + function(position); + for (const TilePosition& position : mEmptyTiles) + function(position); + } + private: struct Tile { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index e6f831bb6e..ea20f8bc34 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -13,8 +13,6 @@ #include -#include - namespace { /// Safely reset shared_ptr with definite underlying object destrutor call. @@ -179,9 +177,9 @@ namespace DetourNavigator { std::map tilesToPost = changedTiles; { + const int maxTiles = mSettings.mMaxTilesNumber; const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - const int maxTiles = mSettings.mMaxTilesNumber; getTilesPositions(range, [&](const TilePosition& tile) { if (changedTiles.find(tile) != changedTiles.end()) return; @@ -192,6 +190,10 @@ namespace DetourNavigator else if (!shouldAdd && presentInNavMesh) tilesToPost.emplace(tile, ChangeType::mixed); }); + locked->forEachTilePosition([&](const TilePosition& tile) { + if (!shouldAddTile(tile, playerTile, maxTiles)) + tilesToPost.emplace(tile, ChangeType::remove); + }); } mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost); Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << " playerTile=" << playerTile From 17bd571a65e5d445cea4b84eb0309fa0976f1da8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Apr 2024 18:37:56 +0200 Subject: [PATCH 1319/2167] Do not repost failed jobs Failures should not happen except for some weird corner cases. Retrying is unlikely to help in such situation. --- .../detournavigator/asyncnavmeshupdater.cpp | 23 ++----------------- .../detournavigator/asyncnavmeshupdater.hpp | 2 -- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 1ea7b63429..bb04c2af07 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -343,7 +343,8 @@ namespace DetourNavigator removeJob(job); break; case JobStatus::Fail: - repost(job); + unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); + removeJob(job); break; case JobStatus::MemoryCacheMiss: { @@ -608,26 +609,6 @@ namespace DetourNavigator writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - void AsyncNavMeshUpdater::repost(JobIt job) - { - unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); - - if (mShouldStop || job->mTryNumber > 2) - return; - - const std::lock_guard lock(mMutex); - - if (mPushed.emplace(job->mAgentBounds, job->mChangedTile).second) - { - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); - mHasJob.notify_all(); - return; - } - - mJobs.erase(job); - } - bool AsyncNavMeshUpdater::lockTile( std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile) { diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 9b95d4f4b3..09119556cd 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -197,8 +197,6 @@ namespace DetourNavigator void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - void repost(JobIt job); - bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); From 50f4471750d934c238244ae5e408d69bf681dc0c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 01:10:48 +0200 Subject: [PATCH 1320/2167] Use R-tree for dynamic priority of navmesh async job --- .../detournavigator/asyncnavmeshupdater.cpp | 271 +++++++++++++++- .../detournavigator/asyncnavmeshupdater.cpp | 305 ++++++++++++------ .../detournavigator/asyncnavmeshupdater.hpp | 75 ++++- components/detournavigator/changetype.hpp | 10 +- components/detournavigator/debug.cpp | 2 - components/detournavigator/navmeshmanager.cpp | 2 +- components/detournavigator/stats.cpp | 8 +- components/detournavigator/stats.hpp | 11 +- .../tilecachedrecastmeshmanager.cpp | 3 +- components/resource/stats.cpp | 11 +- 10 files changed, 572 insertions(+), 126 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index bc1288f5f6..51ab37b123 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -267,7 +267,6 @@ namespace updater.wait(WaitConditionType::allJobsDone, &mListener); updater.stop(); const std::set present{ - TilePosition(-2, 0), TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(-1, 1), @@ -278,6 +277,7 @@ namespace TilePosition(0, 2), TilePosition(1, -1), TilePosition(1, 0), + TilePosition(1, 1), }; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) @@ -336,4 +336,273 @@ namespace EXPECT_EQ(tile->mTileId, 2); EXPECT_EQ(tile->mVersion, navMeshFormatVersion); } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_tile_updates_should_be_delayed) + { + mRecastMeshManager.setWorldspace(mWorldspace, nullptr); + + mSettings.mMaxTilesNumber = 9; + mSettings.mMinUpdateInterval = std::chrono::milliseconds(250); + + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(1, mSettings); + + std::map changedTiles; + + for (int x = -3; x <= 3; ++x) + for (int y = -3; y <= 3; ++y) + changedTiles.emplace(TilePosition{ x, y }, ChangeType::update); + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 49); + EXPECT_EQ(stats.mWaiting.mDelayed, 49); + } + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + } + + struct DetourNavigatorSpatialJobQueueTest : Test + { + const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; + const std::shared_ptr mNavMeshCacheItemPtr; + const std::weak_ptr mNavMeshCacheItem = mNavMeshCacheItemPtr; + const std::string_view mWorldspace = "worldspace"; + const TilePosition mChangedTile{ 0, 0 }; + const std::chrono::steady_clock::time_point mProcessTime{}; + const TilePosition mPlayerTile{ 0, 0 }; + const int mMaxTiles = 9; + }; + + TEST_F(DetourNavigatorSpatialJobQueueTest, should_store_multiple_jobs_per_tile) + { + std::list jobs; + SpatialJobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace1", mChangedTile, + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace2", mChangedTile, + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.size(), 2); + + const auto job1 = queue.pop(mChangedTile); + ASSERT_TRUE(job1.has_value()); + EXPECT_EQ((*job1)->mWorldspace, "worldspace1"); + + const auto job2 = queue.pop(mChangedTile); + ASSERT_TRUE(job2.has_value()); + EXPECT_EQ((*job2)->mWorldspace, "worldspace2"); + + EXPECT_EQ(queue.size(), 0); + } + + struct DetourNavigatorJobQueueTest : DetourNavigatorSpatialJobQueueTest + { + }; + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nullptr_from_empty) + { + JobQueue queue; + ASSERT_FALSE(queue.hasJob()); + ASSERT_FALSE(queue.pop(mPlayerTile).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_remove_should_add_to_removing) + { + const std::chrono::steady_clock::time_point processTime{}; + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::remove, processTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_last_removing) + { + std::list jobs; + JobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::remove, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(mPlayerTile); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_not_remove_should_add_to_updating) + { + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nearest_to_player_tile) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::update, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(TilePosition(1, 0)); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_processing_time_more_than_now_should_add_to_delayed) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + EXPECT_EQ(queue.getStats().mDelayed, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_when_delayed_job_is_ready) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_FALSE(queue.hasJob(now)); + ASSERT_FALSE(queue.pop(mPlayerTile, now).has_value()); + + ASSERT_TRUE(queue.hasJob(processTime)); + EXPECT_TRUE(queue.pop(mPlayerTile, processTime).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_updating) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(mPlayerTile, mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_removing_when_out_of_range) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(TilePosition(10, 10), mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mRemoving, 1); + EXPECT_EQ(job->mChangeType, ChangeType::remove); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_updating_to_removing_when_out_of_range) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(10, 10), + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.getStats().mUpdating, 2); + + queue.update(TilePosition(10, 10), mMaxTiles); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, clear_should_remove_all) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt removing = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(0, 0), ChangeType::remove, mProcessTime); + const JobIt updating = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(1, 0), ChangeType::update, mProcessTime); + const JobIt delayed = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(2, 0), + ChangeType::update, processTime); + + JobQueue queue; + queue.push(removing); + queue.push(updating); + queue.push(delayed, now); + + ASSERT_EQ(queue.getStats().mRemoving, 1); + ASSERT_EQ(queue.getStats().mUpdating, 1); + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.clear(); + + EXPECT_EQ(queue.getStats().mRemoving, 0); + EXPECT_EQ(queue.getStats().mUpdating, 0); + EXPECT_EQ(queue.getStats().mDelayed, 0); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index bb04c2af07..fe6d0625f5 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -16,8 +16,9 @@ #include +#include + #include -#include #include #include #include @@ -49,40 +50,6 @@ namespace DetourNavigator return false; } - auto getPriority(const Job& job) noexcept - { - return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, - job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getPriority(*lhs) < getPriority(*rhs); } - }; - - void insertPrioritizedJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority{}); - queue.insert(it, job); - } - - auto getDbPriority(const Job& job) noexcept - { - return std::make_tuple(static_cast>(job.mState), job.mChangeType, - job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobDbPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getDbPriority(*lhs) < getDbPriority(*rhs); } - }; - - void insertPrioritizedDbJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority{}); - queue.insert(it, job); - } - auto getAgentAndTile(const Job& job) noexcept { return std::make_tuple(job.mAgentBounds, job.mChangedTile); @@ -97,16 +64,6 @@ namespace DetourNavigator settings.mRecast, settings.mWriteToNavMeshDb); } - void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) - { - for (JobIt job : jobs) - { - job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) - job->mChangeType = ChangeType::remove; - } - } - std::size_t getNextJobId() { static std::atomic_size_t nextJobId{ 1 }; @@ -134,7 +91,7 @@ namespace DetourNavigator } Job::Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime) : mId(getNextJobId()) , mAgentBounds(agentBounds) @@ -143,11 +100,148 @@ namespace DetourNavigator , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) - , mDistanceToPlayer(distanceToPlayer) - , mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition{ 0, 0 })) { } + void SpatialJobQueue::clear() + { + mValues.clear(); + mIndex.clear(); + mSize = 0; + } + + void SpatialJobQueue::push(JobIt job) + { + auto it = mValues.find(job->mChangedTile); + + if (it == mValues.end()) + { + it = mValues.emplace_hint(it, job->mChangedTile, std::deque()); + mIndex.insert(IndexValue(IndexPoint(job->mChangedTile.x(), job->mChangedTile.y()), it)); + } + + it->second.push_back(job); + + ++mSize; + } + + std::optional SpatialJobQueue::pop(TilePosition playerTile) + { + const IndexPoint point(playerTile.x(), playerTile.y()); + const auto it = mIndex.qbegin(boost::geometry::index::nearest(point, 1)); + + if (it == mIndex.qend()) + return std::nullopt; + + const UpdatingMap::iterator mapIt = it->second; + std::deque& tileJobs = mapIt->second; + JobIt result = tileJobs.front(); + tileJobs.pop_front(); + + --mSize; + + if (tileJobs.empty()) + { + mValues.erase(mapIt); + mIndex.remove(*it); + } + + return result; + } + + void SpatialJobQueue::update(TilePosition playerTile, int maxTiles, std::vector& removing) + { + for (auto it = mValues.begin(); it != mValues.end();) + { + if (shouldAddTile(it->first, playerTile, maxTiles)) + { + ++it; + continue; + } + + for (JobIt job : it->second) + { + job->mChangeType = ChangeType::remove; + removing.push_back(job); + } + + mSize -= it->second.size(); + mIndex.remove(IndexValue(IndexPoint(it->first.x(), it->first.y()), it)); + it = mValues.erase(it); + } + } + + bool JobQueue::hasJob(std::chrono::steady_clock::time_point now) const + { + return !mRemoving.empty() || mUpdating.size() > 0 + || (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now); + } + + void JobQueue::clear() + { + mRemoving.clear(); + mDelayed.clear(); + mUpdating.clear(); + } + + void JobQueue::push(JobIt job, std::chrono::steady_clock::time_point now) + { + if (job->mProcessTime > now) + { + mDelayed.push_back(job); + return; + } + + if (job->mChangeType == ChangeType::remove) + { + mRemoving.push_back(job); + return; + } + + mUpdating.push(job); + } + + std::optional JobQueue::pop(TilePosition playerTile, std::chrono::steady_clock::time_point now) + { + if (!mRemoving.empty()) + { + const JobIt result = mRemoving.back(); + mRemoving.pop_back(); + return result; + } + + if (const std::optional result = mUpdating.pop(playerTile)) + return result; + + if (mDelayed.empty() || mDelayed.front()->mProcessTime > now) + return std::nullopt; + + const JobIt result = mDelayed.front(); + mDelayed.pop_front(); + return result; + } + + void JobQueue::update(TilePosition playerTile, int maxTiles, std::chrono::steady_clock::time_point now) + { + mUpdating.update(playerTile, maxTiles, mRemoving); + + while (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now) + { + const JobIt job = mDelayed.front(); + mDelayed.pop_front(); + + if (shouldAddTile(job->mChangedTile, playerTile, maxTiles)) + { + mUpdating.push(job); + } + else + { + job->mChangeType = ChangeType::remove; + mRemoving.push_back(job); + } + } + } + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) @@ -183,42 +277,44 @@ namespace DetourNavigator std::unique_lock lock(mMutex); if (playerTileChanged) - updateJobs(mWaiting, playerTile, mSettings.get().mMaxTilesNumber); + { + Log(Debug::Debug) << "Player tile has been changed to " << playerTile; + mWaiting.update(playerTile, mSettings.get().mMaxTilesNumber); + } for (const auto& [changedTile, changeType] : changedTiles) { if (mPushed.emplace(agentBounds, changedTile).second) { - const auto processTime = changeType == ChangeType::update - ? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval - : std::chrono::steady_clock::time_point(); + const auto processTime = [&, changedTile = changedTile, changeType = changeType] { + if (changeType != ChangeType::update) + return std::chrono::steady_clock::time_point(); + const auto lastUpdate = mLastUpdates.find(std::tie(agentBounds, changedTile)); + if (lastUpdate == mLastUpdates.end()) + return std::chrono::steady_clock::time_point(); + return lastUpdate->second + mSettings.get().mMinUpdateInterval; + }(); - const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, - changeType, getManhattanDistance(changedTile, playerTile), processTime); + const JobIt it = mJobs.emplace( + mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, changeType, processTime); Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")" - << " changedTile=(" << it->mChangedTile << ") " + << " changedTile=(" << it->mChangedTile << ")" << " changeType=" << it->mChangeType; - if (playerTileChanged) - mWaiting.push_back(it); - else - insertPrioritizedJob(it, mWaiting); + mWaiting.push(it); } } - if (playerTileChanged) - std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority{}); - Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; - if (!mWaiting.empty()) + if (mWaiting.hasJob()) mHasJob.notify_all(); lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) - mDbWorker->updateJobs(playerTile, mSettings.get().mMaxTilesNumber); + mDbWorker->update(playerTile); } void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) @@ -310,7 +406,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); result.mJobs = mJobs.size(); - result.mWaiting = mWaiting.size(); + result.mWaiting = mWaiting.getStats(); result.mPushed = mPushed.size(); } result.mProcessing = mProcessingTiles.lockConst()->size(); @@ -332,7 +428,8 @@ namespace DetourNavigator if (JobIt job = getNextJob(); job != mJobs.end()) { const JobStatus status = processJob(*job); - Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; + Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status + << " changeType=" << job->mChangeType; switch (status) { case JobStatus::Done: @@ -366,7 +463,9 @@ namespace DetourNavigator JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); + Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")" + << " changedTile=(" << job.mChangedTile << ")" + << " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); @@ -378,6 +477,7 @@ namespace DetourNavigator if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; + job.mChangeType = ChangeType::remove; navMeshCacheItem->lock()->removeTile(job.mChangedTile); return JobStatus::Done; } @@ -545,9 +645,8 @@ namespace DetourNavigator bool shouldStop = false; const auto hasJob = [&] { - shouldStop = mShouldStop; - return shouldStop - || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()); + shouldStop = mShouldStop.load(); + return shouldStop || mWaiting.hasJob(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) @@ -560,9 +659,15 @@ namespace DetourNavigator if (shouldStop) return mJobs.end(); - const JobIt job = mWaiting.front(); + const TilePosition playerTile = *mPlayerTile.lockConst(); - mWaiting.pop_front(); + JobIt job = mJobs.end(); + + if (const std::optional nextJob = mWaiting.pop(playerTile)) + job = *nextJob; + + if (job == mJobs.end()) + return job; Log(Debug::Debug) << "Pop job " << job->mId << " by thread=" << std::this_thread::get_id(); @@ -571,9 +676,9 @@ namespace DetourNavigator if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile)) { - Log(Debug::Debug) << "Failed to lock tile by job " << job->mId << " try=" << job->mTryNumber; - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); + Log(Debug::Debug) << "Failed to lock tile by job " << job->mId; + job->mProcessTime = std::chrono::steady_clock::now() + mSettings.get().mMinUpdateInterval; + mWaiting.push(job); return mJobs.end(); } @@ -653,7 +758,7 @@ namespace DetourNavigator { Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); - insertPrioritizedJob(job, mWaiting); + mWaiting.push(job); mHasJob.notify_all(); } @@ -667,40 +772,47 @@ namespace DetourNavigator void DbJobQueue::push(JobIt job) { const std::lock_guard lock(mMutex); - insertPrioritizedDbJob(job, mJobs); if (isWritingDbJob(*job)) - ++mWritingJobs; + mWriting.push_back(job); else - ++mReadingJobs; + mReading.push(job); mHasJob.notify_all(); } std::optional DbJobQueue::pop() { std::unique_lock lock(mMutex); - mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); - if (mJobs.empty()) + + const auto hasJob = [&] { return mShouldStop || mReading.size() > 0 || mWriting.size() > 0; }; + + mHasJob.wait(lock, hasJob); + + if (mShouldStop) return std::nullopt; - const JobIt job = mJobs.front(); - mJobs.pop_front(); - if (isWritingDbJob(*job)) - --mWritingJobs; - else - --mReadingJobs; + + if (const std::optional job = mReading.pop(mPlayerTile)) + return job; + + if (mWriting.empty()) + return std::nullopt; + + const JobIt job = mWriting.front(); + mWriting.pop_front(); + return job; } - void DbJobQueue::update(TilePosition playerTile, int maxTiles) + void DbJobQueue::update(TilePosition playerTile) { const std::lock_guard lock(mMutex); - updateJobs(mJobs, playerTile, maxTiles); - std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority{}); + mPlayerTile = playerTile; } void DbJobQueue::stop() { const std::lock_guard lock(mMutex); - mJobs.clear(); + mReading.clear(); + mWriting.clear(); mShouldStop = true; mHasJob.notify_all(); } @@ -708,7 +820,10 @@ namespace DetourNavigator DbJobQueueStats DbJobQueue::getStats() const { const std::lock_guard lock(mMutex); - return DbJobQueueStats{ .mWritingJobs = mWritingJobs, .mReadingJobs = mReadingJobs }; + return DbJobQueueStats{ + .mReadingJobs = mReading.size(), + .mWritingJobs = mWriting.size(), + }; } DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, @@ -737,8 +852,10 @@ namespace DetourNavigator DbWorkerStats DbWorker::getStats() const { - return DbWorkerStats{ .mJobs = mQueue.getStats(), - .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed) }; + return DbWorkerStats{ + .mJobs = mQueue.getStats(), + .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed), + }; } void DbWorker::stop() diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 09119556cd..f3d624a8a6 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -14,6 +14,9 @@ #include "tileposition.hpp" #include "waitconditiontype.hpp" +#include +#include + #include #include #include @@ -49,11 +52,8 @@ namespace DetourNavigator const std::weak_ptr mNavMeshCacheItem; const std::string mWorldspace; const TilePosition mChangedTile; - const std::chrono::steady_clock::time_point mProcessTime; - unsigned mTryNumber = 0; + std::chrono::steady_clock::time_point mProcessTime; ChangeType mChangeType; - int mDistanceToPlayer; - const int mDistanceToOrigin; JobState mState = JobState::Initial; std::vector mInput; std::shared_ptr mRecastMesh; @@ -61,12 +61,65 @@ namespace DetourNavigator std::unique_ptr mGeneratedNavMeshData; Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; + class SpatialJobQueue + { + public: + std::size_t size() const { return mSize; } + + void clear(); + + void push(JobIt job); + + std::optional pop(TilePosition playerTile); + + void update(TilePosition playerTile, int maxTiles, std::vector& removing); + + private: + using IndexPoint = boost::geometry::model::point; + using UpdatingMap = std::map>; + using IndexValue = std::pair; + + std::size_t mSize = 0; + UpdatingMap mValues; + boost::geometry::index::rtree> mIndex; + }; + + class JobQueue + { + public: + JobQueueStats getStats() const + { + return JobQueueStats{ + .mRemoving = mRemoving.size(), + .mUpdating = mUpdating.size(), + .mDelayed = mDelayed.size(), + }; + } + + bool hasJob(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const; + + void clear(); + + void push(JobIt job, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + std::optional pop( + TilePosition playerTile, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + void update(TilePosition playerTile, int maxTiles, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + private: + std::vector mRemoving; + SpatialJobQueue mUpdating; + std::deque mDelayed; + }; + enum class JobStatus { Done, @@ -83,7 +136,7 @@ namespace DetourNavigator std::optional pop(); - void update(TilePosition playerTile, int maxTiles); + void update(TilePosition playerTile); void stop(); @@ -92,10 +145,10 @@ namespace DetourNavigator private: mutable std::mutex mMutex; std::condition_variable mHasJob; - std::deque mJobs; + SpatialJobQueue mReading; + std::deque mWriting; + TilePosition mPlayerTile; bool mShouldStop = false; - std::size_t mWritingJobs = 0; - std::size_t mReadingJobs = 0; }; class AsyncNavMeshUpdater; @@ -112,7 +165,7 @@ namespace DetourNavigator void enqueueJob(JobIt job); - void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } + void update(TilePosition playerTile) { mQueue.update(playerTile); } void stop(); @@ -169,7 +222,7 @@ namespace DetourNavigator std::condition_variable mDone; std::condition_variable mProcessed; std::list mJobs; - std::deque mWaiting; + JobQueue mWaiting; std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; diff --git a/components/detournavigator/changetype.hpp b/components/detournavigator/changetype.hpp index e6d0bce0a9..63a43e88fb 100644 --- a/components/detournavigator/changetype.hpp +++ b/components/detournavigator/changetype.hpp @@ -6,15 +6,9 @@ namespace DetourNavigator enum class ChangeType { remove = 0, - mixed = 1, - add = 2, - update = 3, + add = 1, + update = 2, }; - - inline ChangeType addChangeType(const ChangeType current, const ChangeType add) - { - return current == add ? current : ChangeType::mixed; - } } #endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 4dcd5e9857..5ce1464bdd 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -184,8 +184,6 @@ namespace DetourNavigator { case ChangeType::remove: return stream << "ChangeType::remove"; - case ChangeType::mixed: - return stream << "ChangeType::mixed"; case ChangeType::add: return stream << "ChangeType::add"; case ChangeType::update: diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index ea20f8bc34..3b62866ed7 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -188,7 +188,7 @@ namespace DetourNavigator if (shouldAdd && !presentInNavMesh) tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add); else if (!shouldAdd && presentInNavMesh) - tilesToPost.emplace(tile, ChangeType::mixed); + tilesToPost.emplace(tile, ChangeType::remove); }); locked->forEachTilePosition([&](const TilePosition& tile) { if (!shouldAddTile(tile, playerTile, maxTiles)) diff --git a/components/detournavigator/stats.cpp b/components/detournavigator/stats.cpp index 1d7dcac553..da56f91a38 100644 --- a/components/detournavigator/stats.cpp +++ b/components/detournavigator/stats.cpp @@ -9,16 +9,18 @@ namespace DetourNavigator void reportStats(const AsyncNavMeshUpdaterStats& stats, unsigned int frameNumber, osg::Stats& out) { out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); - out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); + out.setAttribute(frameNumber, "NavMesh Removing", static_cast(stats.mWaiting.mRemoving)); + out.setAttribute(frameNumber, "NavMesh Updating", static_cast(stats.mWaiting.mUpdating)); + out.setAttribute(frameNumber, "NavMesh Delayed", static_cast(stats.mWaiting.mDelayed)); out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); if (stats.mDb.has_value()) { - out.setAttribute( - frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute( frameNumber, "NavMesh DbJobs Read", static_cast(stats.mDb->mJobs.mReadingJobs)); + out.setAttribute( + frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute(frameNumber, "NavMesh DbCache Get", static_cast(stats.mDb->mGetTileCount)); out.setAttribute(frameNumber, "NavMesh DbCache Hit", static_cast(stats.mDbGetTileHits)); diff --git a/components/detournavigator/stats.hpp b/components/detournavigator/stats.hpp index c644f1db87..0b62b9e669 100644 --- a/components/detournavigator/stats.hpp +++ b/components/detournavigator/stats.hpp @@ -11,10 +11,17 @@ namespace osg namespace DetourNavigator { + struct JobQueueStats + { + std::size_t mRemoving = 0; + std::size_t mUpdating = 0; + std::size_t mDelayed = 0; + }; + struct DbJobQueueStats { - std::size_t mWritingJobs = 0; std::size_t mReadingJobs = 0; + std::size_t mWritingJobs = 0; }; struct DbWorkerStats @@ -35,7 +42,7 @@ namespace DetourNavigator struct AsyncNavMeshUpdaterStats { std::size_t mJobs = 0; - std::size_t mWaiting = 0; + JobQueueStats mWaiting; std::size_t mPushed = 0; std::size_t mProcessing = 0; std::size_t mDbGetTileHits = 0; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 0bab808300..3e3927bf65 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,7 +10,6 @@ #include -#include #include #include @@ -416,7 +415,7 @@ namespace DetourNavigator if (tile == mChangedTiles.end()) mChangedTiles.emplace(tilePosition, changeType); else - tile->second = addChangeType(tile->second, changeType); + tile->second = changeType == ChangeType::remove ? changeType : tile->second; } std::map TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 65b009deff..9bb90635d1 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -49,6 +49,8 @@ namespace Resource std::vector generateAllStatNames() { + constexpr std::size_t itemsPerPage = 24; + constexpr std::string_view firstPage[] = { "FrameNumber", "", @@ -76,6 +78,8 @@ namespace Resource "", }; + static_assert(std::size(firstPage) == itemsPerPage); + constexpr std::string_view caches[] = { "Node", "Shape", @@ -100,7 +104,9 @@ namespace Resource constexpr std::string_view navMesh[] = { "NavMesh Jobs", - "NavMesh Waiting", + "NavMesh Removing", + "NavMesh Updating", + "NavMesh Delayed", "NavMesh Pushed", "NavMesh Processing", "NavMesh DbJobs Write", @@ -129,7 +135,8 @@ namespace Resource for (std::string_view name : cellPreloader) statNames.emplace_back(name); - statNames.emplace_back(); + while (statNames.size() % itemsPerPage != 0) + statNames.emplace_back(); for (std::string_view name : navMesh) statNames.emplace_back(name); From 910c88325a69f4166a468ae30b643641f9410ed0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Apr 2024 15:22:02 +0200 Subject: [PATCH 1321/2167] Add a setting to wait for all navmesh jobs on exit --- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ components/settings/categories/navigator.hpp | 1 + docs/source/reference/modding/settings/navigator.rst | 10 ++++++++++ files/settings-default.cfg | 3 +++ 4 files changed, 20 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ec58f779d0..74335a1534 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -591,6 +591,12 @@ namespace MWWorld // Must be cleared before mRendering is destroyed if (mProjectileManager) mProjectileManager->clear(); + + if (Settings::navigator().mWaitForAllJobsOnExit) + { + Log(Debug::Verbose) << "Waiting for all navmesh jobs to be done..."; + mNavigator->wait(DetourNavigator::WaitConditionType::allJobsDone, nullptr); + } } void World::setRandomSeed(uint32_t seed) diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index d6d7adcd56..c65dd3392e 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -63,6 +63,7 @@ namespace Settings SettingValue mEnableNavMeshDiskCache{ mIndex, "Navigator", "enable nav mesh disk cache" }; SettingValue mWriteToNavmeshdb{ mIndex, "Navigator", "write to navmeshdb" }; SettingValue mMaxNavmeshdbFileSize{ mIndex, "Navigator", "max navmeshdb file size" }; + SettingValue mWaitForAllJobsOnExit{ mIndex, "Navigator", "wait for all jobs on exit" }; }; } diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 55b9e19b19..6fafdcdfd2 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -245,6 +245,16 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. +wait for all jobs on exit +------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Wait until all queued async navmesh jobs are processed before exiting the engine. +Useful when a benchmark generates jobs to write into navmeshdb faster than they are processed. + Expert settings *************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2f236bf55a..10c25bb430 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -999,6 +999,9 @@ write to navmeshdb = true # Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0) max navmeshdb file size = 2147483648 +# Wait until all queued async navmesh jobs are processed before exiting the engine (true, false) +wait for all jobs on exit = false + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From ea029b06eabc98d620ac63b9f6c60730708e601a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 18:51:45 +0100 Subject: [PATCH 1322/2167] Remove unused define --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93da5feec4..9e6e788c05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -710,7 +710,6 @@ if (WIN32) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$:_CONSOLE>) elseif (BUILD_OPENMW) # Turn off debug console, debug output will be written to visual studio output instead set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") From 901a17ab81a4814d51dc7b5531cfef3cbdcabec3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 19:01:34 +0100 Subject: [PATCH 1323/2167] Make comments stop lying --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e6e788c05..3360ac2e2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,7 @@ else() endif(APPLE) if (WIN32) - option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) + option(USE_DEBUG_CONSOLE "Whether a console should be displayed if OpenMW isn't launched from the command line. Does not affect the Release configuration." ON) endif() if(MSVC) @@ -711,7 +711,7 @@ if (WIN32) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") elseif (BUILD_OPENMW) - # Turn off debug console, debug output will be written to visual studio output instead + # Turn off implicit console, you won't see stdout unless launching OpenMW from a command line shell or look at openmw.log set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() From d6452e7d29d7b354b411a1cff8c200dd5653fb87 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Sat, 13 Apr 2024 23:16:28 +0000 Subject: [PATCH 1324/2167] Update appdata XML, add branding colors --- files/openmw.appdata.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index aacdc1dd96..764082df00 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -21,6 +21,12 @@ Copyright 2020 Bret Curtis

org.openmw.launcher.desktop + + + #dcccb8 + #574526 + + https://wiki.openmw.org/images/Openmw_0.11.1_launcher_1.png @@ -28,15 +34,15 @@ Copyright 2020 Bret Curtis https://wiki.openmw.org/images/0.40_Screenshot-Balmora_3.png - The Mournhold's plaza on OpenMW + Balmora at morning on OpenMW https://wiki.openmw.org/images/Screenshot_mournhold_plaza_0.35.png - Vivec seen from Ebonheart on OpenMW + The Mournhold's plaza on OpenMW https://wiki.openmw.org/images/Screenshot_Vivec_seen_from_Ebonheart_0.35.png - Balmora at morning on OpenMW + Vivec seen from Ebonheart on OpenMW From 1ad9e5f9e897cce9d67058c40e32a9e4b10f2044 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Apr 2024 08:17:10 +0400 Subject: [PATCH 1325/2167] Rework editor icons --- CI/before_script.msvc.sh | 22 +- CI/install_debian_deps.sh | 2 +- CMakeLists.txt | 10 +- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/main.cpp | 2 +- apps/opencs/model/world/tablemimedata.cpp | 2 +- apps/opencs/model/world/universalid.cpp | 215 ++++++------ apps/opencs/view/doc/view.cpp | 43 ++- apps/opencs/view/filter/editwidget.cpp | 2 +- apps/opencs/view/render/mask.hpp | 9 +- .../view/render/pagedworldspacewidget.cpp | 8 +- apps/opencs/view/render/pathgridmode.cpp | 4 +- .../view/render/unpagedworldspacewidget.cpp | 1 - apps/opencs/view/render/worldspacewidget.hpp | 3 +- apps/opencs/view/world/recordbuttonbar.cpp | 2 +- apps/opencs/view/world/table.cpp | 4 +- .../model/world/testuniversalid.cpp | 29 +- files/opencs/activator.png | Bin 562 -> 0 bytes files/opencs/activator.svg | 126 +++++++ files/opencs/apparatus.png | Bin 364 -> 0 bytes files/opencs/apparatus.svg | 58 ++++ files/opencs/armor.png | Bin 473 -> 0 bytes files/opencs/armor.svg | 58 ++++ files/opencs/attribute.png | Bin 326 -> 0 bytes files/opencs/attribute.svg | 46 +++ files/opencs/birthsign.png | Bin 444 -> 0 bytes files/opencs/birthsign.svg | 94 +++++ files/opencs/body-part.png | Bin 418 -> 0 bytes files/opencs/body-part.svg | 58 ++++ files/opencs/book.png | Bin 323 -> 0 bytes files/opencs/book.svg | 166 +++++++++ files/opencs/brush-circle.png | Bin 1540 -> 0 bytes files/opencs/brush-circle.svg | 51 +++ files/opencs/brush-custom.png | Bin 2475 -> 0 bytes files/opencs/brush-custom.svg | 56 +++ files/opencs/brush-point.png | Bin 787 -> 0 bytes files/opencs/brush-point.svg | 65 ++++ files/opencs/brush-square.png | Bin 761 -> 0 bytes files/opencs/brush-square.svg | 52 +++ files/opencs/camera-first-person.png | Bin 750 -> 0 bytes files/opencs/camera-first-person.svg | 63 ++++ files/opencs/camera-free.png | Bin 783 -> 0 bytes files/opencs/camera-free.svg | 76 ++++ files/opencs/camera-orbit.png | Bin 1096 -> 0 bytes files/opencs/camera-orbit.svg | 62 ++++ files/opencs/cell.png | Bin 1212 -> 0 bytes files/opencs/cell.svg | 83 +++++ files/opencs/class.png | Bin 588 -> 0 bytes files/opencs/class.svg | 186 ++++++++++ files/opencs/clothing.png | Bin 369 -> 0 bytes files/opencs/clothing.svg | 101 ++++++ files/opencs/container.png | Bin 421 -> 0 bytes files/opencs/container.svg | 93 +++++ files/opencs/creature.png | Bin 463 -> 0 bytes files/opencs/creature.svg | 189 ++++++++++ files/opencs/debug-profile.png | Bin 431 -> 0 bytes files/opencs/debug-profile.svg | 115 +++++++ files/opencs/dialogue-greeting.png | Bin 386 -> 0 bytes files/opencs/dialogue-info.png | Bin 306 -> 0 bytes files/opencs/dialogue-info.svg | 89 +++++ files/opencs/dialogue-journal.png | Bin 288 -> 0 bytes files/opencs/dialogue-persuasion.png | Bin 362 -> 0 bytes files/opencs/dialogue-regular.png | Bin 221 -> 0 bytes files/opencs/dialogue-topic-infos.png | Bin 306 -> 0 bytes files/opencs/dialogue-topics.png | Bin 282 -> 0 bytes files/opencs/dialogue-topics.svg | 89 +++++ files/opencs/dialogue-voice.png | Bin 294 -> 0 bytes files/opencs/door.png | Bin 381 -> 0 bytes files/opencs/door.svg | 77 +++++ files/opencs/editing-instance.png | Bin 986 -> 0 bytes files/opencs/editing-instance.svg | 51 +++ files/opencs/editing-pathgrid.png | Bin 2297 -> 0 bytes files/opencs/editing-pathgrid.svg | 83 +++++ files/opencs/editing-terrain-movement.png | Bin 1745 -> 0 bytes files/opencs/editing-terrain-movement.svg | 58 ++++ files/opencs/editing-terrain-shape.png | Bin 1399 -> 0 bytes files/opencs/editing-terrain-shape.svg | 43 +++ files/opencs/editing-terrain-texture.png | Bin 2100 -> 0 bytes files/opencs/editing-terrain-texture.svg | 82 +++++ files/opencs/editing-terrain-vertex-paint.png | Bin 2545 -> 0 bytes files/opencs/editing-terrain-vertex-paint.svg | 111 ++++++ files/opencs/enchantment.png | Bin 422 -> 0 bytes files/opencs/enchantment.svg | 54 +++ files/opencs/error-log.png | Bin 518 -> 0 bytes files/opencs/error-log.svg | 134 ++++++++ files/opencs/faction.png | Bin 389 -> 0 bytes files/opencs/faction.svg | 73 ++++ files/opencs/filter.png | Bin 299 -> 0 bytes files/opencs/filter.svg | 61 ++++ files/opencs/global-variable.png | Bin 282 -> 0 bytes files/opencs/global-variable.svg | 86 +++++ files/opencs/gmst.png | Bin 338 -> 0 bytes files/opencs/gmst.svg | 114 ++++++ files/opencs/info.png | Bin 1234 -> 0 bytes files/opencs/info.svg | 85 +++++ files/opencs/ingredient.png | Bin 490 -> 0 bytes files/opencs/ingredient.svg | 97 ++++++ files/opencs/instance.png | Bin 618 -> 0 bytes files/opencs/instance.svg | 126 +++++++ files/opencs/journal-topic-infos.png | Bin 300 -> 0 bytes files/opencs/journal-topic-infos.svg | 97 ++++++ files/opencs/journal-topics.png | Bin 273 -> 0 bytes files/opencs/journal-topics.svg | 73 ++++ files/opencs/land-heightmap.png | Bin 193 -> 0 bytes files/opencs/land-heightmap.svg | 86 +++++ files/opencs/land-texture.png | Bin 288 -> 0 bytes files/opencs/land-texture.svg | 97 ++++++ files/opencs/levelled-creature.png | Bin 521 -> 0 bytes files/opencs/levelled-creature.svg | 198 +++++++++++ files/opencs/levelled-item.png | Bin 516 -> 0 bytes files/opencs/levelled-item.svg | 137 ++++++++ files/opencs/light.png | Bin 384 -> 0 bytes files/opencs/light.svg | 105 ++++++ files/opencs/lighting-lamp.png | Bin 953 -> 0 bytes files/opencs/lighting-lamp.svg | 40 +++ files/opencs/lighting-moon.png | Bin 1590 -> 0 bytes files/opencs/lighting-moon.svg | 50 +++ files/opencs/lighting-sun.png | Bin 1804 -> 0 bytes files/opencs/lighting-sun.svg | 143 ++++++++ files/opencs/list-added.png | Bin 166 -> 0 bytes files/opencs/list-added.svg | 101 ++++++ files/opencs/list-base.png | Bin 184 -> 0 bytes files/opencs/list-base.svg | 120 +++++++ files/opencs/list-modified.png | Bin 457 -> 0 bytes files/opencs/list-modified.svg | 101 ++++++ files/opencs/list-removed.png | Bin 134 -> 0 bytes files/opencs/list-removed.svg | 108 ++++++ files/opencs/lockpick.png | Bin 317 -> 0 bytes files/opencs/lockpick.svg | 108 ++++++ files/opencs/magic-effect.png | Bin 356 -> 0 bytes files/opencs/magic-effect.svg | 194 +++++++++++ files/opencs/menu-close.png | Bin 438 -> 0 bytes files/opencs/menu-close.svg | 202 +++++++++++ files/opencs/menu-exit.png | Bin 257 -> 0 bytes files/opencs/menu-exit.svg | 101 ++++++ files/opencs/menu-merge.png | Bin 219 -> 0 bytes files/opencs/menu-merge.svg | 126 +++++++ files/opencs/menu-new-addon.png | Bin 282 -> 0 bytes files/opencs/menu-new-addon.svg | 124 +++++++ files/opencs/menu-new-game.png | Bin 230 -> 0 bytes files/opencs/menu-new-game.svg | 111 ++++++ files/opencs/menu-new-window.png | Bin 213 -> 0 bytes files/opencs/menu-new-window.svg | 123 +++++++ files/opencs/menu-open.png | Bin 241 -> 0 bytes files/opencs/menu-open.svg | 111 ++++++ files/opencs/menu-preferences.png | Bin 385 -> 0 bytes files/opencs/menu-preferences.svg | 232 +++++++++++++ files/opencs/menu-redo.png | Bin 323 -> 0 bytes files/opencs/menu-redo.svg | 122 +++++++ files/opencs/menu-reload.png | Bin 447 -> 0 bytes files/opencs/menu-reload.svg | 111 ++++++ files/opencs/menu-save.png | Bin 302 -> 0 bytes files/opencs/menu-save.svg | 70 ++++ files/opencs/menu-search.png | Bin 408 -> 0 bytes files/opencs/menu-search.svg | 157 +++++++++ files/opencs/menu-status-bar.png | Bin 188 -> 0 bytes files/opencs/menu-status-bar.svg | 131 +++++++ files/opencs/menu-undo.png | Bin 323 -> 0 bytes files/opencs/menu-undo.svg | 130 +++++++ files/opencs/menu-verify.png | Bin 487 -> 0 bytes files/opencs/menu-verify.svg | 109 ++++++ files/opencs/metadata.png | Bin 389 -> 0 bytes files/opencs/metadata.svg | 173 ++++++++++ files/opencs/miscellaneous.png | Bin 466 -> 0 bytes files/opencs/miscellaneous.svg | 118 +++++++ files/opencs/multitype.png | Bin 1708 -> 0 bytes files/opencs/multitype.svg | 117 +++++++ files/opencs/npc.png | Bin 465 -> 0 bytes files/opencs/npc.svg | 316 +++++++++++++++++ files/opencs/object.png | Bin 447 -> 0 bytes files/opencs/object.svg | 118 +++++++ files/opencs/pathgrid.png | Bin 407 -> 0 bytes files/opencs/pathgrid.svg | 175 ++++++++++ files/opencs/potion.png | Bin 442 -> 0 bytes files/opencs/potion.svg | 146 ++++++++ files/opencs/probe.png | Bin 304 -> 0 bytes files/opencs/probe.svg | 134 ++++++++ files/opencs/qt.png | Bin 405 -> 0 bytes files/opencs/qt.svg | 180 ++++++++++ files/opencs/race.png | Bin 451 -> 0 bytes files/opencs/race.svg | 160 +++++++++ files/opencs/random.png | Bin 1892 -> 0 bytes files/opencs/record-add.png | Bin 238 -> 0 bytes files/opencs/record-add.svg | 134 ++++++++ files/opencs/record-clone.png | Bin 275 -> 0 bytes files/opencs/record-clone.svg | 158 +++++++++ files/opencs/record-delete.png | Bin 151 -> 0 bytes files/opencs/record-delete.svg | 142 ++++++++ files/opencs/record-down.png | Bin 150 -> 0 bytes files/opencs/record-down.svg | 144 ++++++++ files/opencs/record-edit.png | Bin 220 -> 0 bytes files/opencs/record-edit.svg | 132 +++++++ files/opencs/record-next.png | Bin 248 -> 0 bytes files/opencs/record-next.svg | 143 ++++++++ files/opencs/record-preview.png | Bin 334 -> 0 bytes files/opencs/record-preview.svg | 178 ++++++++++ files/opencs/record-previous.png | Bin 265 -> 0 bytes files/opencs/record-previous.svg | 143 ++++++++ files/opencs/record-revert.png | Bin 323 -> 0 bytes files/opencs/record-revert.svg | 154 +++++++++ files/opencs/record-touch.png | Bin 289 -> 0 bytes files/opencs/record-touch.svg | 143 ++++++++ files/opencs/record-up.png | Bin 150 -> 0 bytes files/opencs/record-up.svg | 144 ++++++++ files/opencs/region-map.png | Bin 428 -> 0 bytes files/opencs/region-map.svg | 155 +++++++++ files/opencs/region.png | Bin 605 -> 0 bytes files/opencs/region.svg | 143 ++++++++ files/opencs/repair.png | Bin 367 -> 0 bytes files/opencs/repair.svg | 119 +++++++ files/opencs/resources-icon.png | Bin 194 -> 0 bytes files/opencs/resources-icon.svg | 178 ++++++++++ files/opencs/resources-mesh.png | Bin 586 -> 0 bytes files/opencs/resources-mesh.svg | 153 +++++++++ files/opencs/resources-music.png | Bin 354 -> 0 bytes files/opencs/resources-music.svg | 216 ++++++++++++ files/opencs/resources-sound.png | Bin 222 -> 0 bytes files/opencs/resources-sound.svg | 142 ++++++++ files/opencs/resources-texture.png | Bin 288 -> 0 bytes files/opencs/resources-texture.svg | 154 +++++++++ files/opencs/resources-video.png | Bin 277 -> 0 bytes files/opencs/resources-video.svg | 155 +++++++++ files/opencs/resources.qrc | 324 ++++++++---------- files/opencs/run-game.png | Bin 642 -> 0 bytes files/opencs/run-game.svg | 135 ++++++++ files/opencs/run-log.png | Bin 294 -> 0 bytes files/opencs/run-log.svg | 83 +++++ files/opencs/run-openmw.png | Bin 388 -> 0 bytes files/opencs/run-openmw.svg | 133 +++++++ files/opencs/scalable/editor-icons.svg | 32 +- files/opencs/scene-exterior-arrows.png | Bin 511 -> 0 bytes files/opencs/scene-exterior-arrows.svg | 145 ++++++++ files/opencs/scene-exterior-borders.png | Bin 429 -> 0 bytes files/opencs/scene-exterior-borders.svg | 135 ++++++++ files/opencs/scene-exterior-markers.png | Bin 497 -> 0 bytes files/opencs/scene-exterior-markers.svg | 148 ++++++++ files/opencs/scene-exterior-status-0.png | Bin 1932 -> 0 bytes files/opencs/scene-exterior-status-0.svg | 167 +++++++++ files/opencs/scene-exterior-status-1.png | Bin 1912 -> 0 bytes files/opencs/scene-exterior-status-1.svg | 168 +++++++++ files/opencs/scene-exterior-status-2.png | Bin 1969 -> 0 bytes files/opencs/scene-exterior-status-2.svg | 168 +++++++++ files/opencs/scene-exterior-status-3.png | Bin 1946 -> 0 bytes files/opencs/scene-exterior-status-3.svg | 168 +++++++++ files/opencs/scene-exterior-status-4.png | Bin 1943 -> 0 bytes files/opencs/scene-exterior-status-4.svg | 168 +++++++++ files/opencs/scene-exterior-status-5.png | Bin 1924 -> 0 bytes files/opencs/scene-exterior-status-5.svg | 168 +++++++++ files/opencs/scene-exterior-status-6.png | Bin 1980 -> 0 bytes files/opencs/scene-exterior-status-6.svg | 168 +++++++++ files/opencs/scene-exterior-status-7.png | Bin 1957 -> 0 bytes files/opencs/scene-exterior-status-7.svg | 168 +++++++++ files/opencs/scene-view-fog.png | Bin 298 -> 0 bytes files/opencs/scene-view-instance.png | Bin 1774 -> 0 bytes files/opencs/scene-view-instance.svg | 155 +++++++++ files/opencs/scene-view-pathgrid.png | Bin 569 -> 0 bytes files/opencs/scene-view-pathgrid.svg | 136 ++++++++ files/opencs/scene-view-status-0.png | Bin 3547 -> 0 bytes files/opencs/scene-view-status-0.svg | 158 +++++++++ files/opencs/scene-view-status-1.png | Bin 1774 -> 0 bytes files/opencs/scene-view-status-1.svg | 154 +++++++++ files/opencs/scene-view-status-10.png | Bin 777 -> 0 bytes files/opencs/scene-view-status-10.svg | 148 ++++++++ files/opencs/scene-view-status-11.png | Bin 2424 -> 0 bytes files/opencs/scene-view-status-11.svg | 171 +++++++++ files/opencs/scene-view-status-12.png | Bin 923 -> 0 bytes files/opencs/scene-view-status-12.svg | 148 ++++++++ files/opencs/scene-view-status-13.png | Bin 2287 -> 0 bytes files/opencs/scene-view-status-13.svg | 171 +++++++++ files/opencs/scene-view-status-14.png | Bin 1302 -> 0 bytes files/opencs/scene-view-status-14.svg | 152 ++++++++ files/opencs/scene-view-status-15.png | Bin 2701 -> 0 bytes files/opencs/scene-view-status-15.svg | 175 ++++++++++ files/opencs/scene-view-status-16.png | Bin 2580 -> 0 bytes files/opencs/scene-view-status-17.png | Bin 3077 -> 0 bytes files/opencs/scene-view-status-18.png | Bin 2947 -> 0 bytes files/opencs/scene-view-status-19.png | Bin 3470 -> 0 bytes files/opencs/scene-view-status-2.png | Bin 569 -> 0 bytes files/opencs/scene-view-status-2.svg | 135 ++++++++ files/opencs/scene-view-status-20.png | Bin 2491 -> 0 bytes files/opencs/scene-view-status-21.png | Bin 3009 -> 0 bytes files/opencs/scene-view-status-22.png | Bin 2872 -> 0 bytes files/opencs/scene-view-status-23.png | Bin 3394 -> 0 bytes files/opencs/scene-view-status-24.png | Bin 2731 -> 0 bytes files/opencs/scene-view-status-25.png | Bin 3200 -> 0 bytes files/opencs/scene-view-status-26.png | Bin 3125 -> 0 bytes files/opencs/scene-view-status-27.png | Bin 3627 -> 0 bytes files/opencs/scene-view-status-28.png | Bin 2695 -> 0 bytes files/opencs/scene-view-status-29.png | Bin 3125 -> 0 bytes files/opencs/scene-view-status-3.png | Bin 2169 -> 0 bytes files/opencs/scene-view-status-3.svg | 158 +++++++++ files/opencs/scene-view-status-30.png | Bin 3096 -> 0 bytes files/opencs/scene-view-status-31.png | Bin 3547 -> 0 bytes files/opencs/scene-view-status-4.png | Bin 753 -> 0 bytes files/opencs/scene-view-status-4.svg | 135 ++++++++ files/opencs/scene-view-status-5.png | Bin 2097 -> 0 bytes files/opencs/scene-view-status-5.svg | 158 +++++++++ files/opencs/scene-view-status-6.png | Bin 1104 -> 0 bytes files/opencs/scene-view-status-6.svg | 139 ++++++++ files/opencs/scene-view-status-7.png | Bin 2462 -> 0 bytes files/opencs/scene-view-status-7.svg | 162 +++++++++ files/opencs/scene-view-status-8.png | Bin 298 -> 0 bytes files/opencs/scene-view-status-8.svg | 144 ++++++++ files/opencs/scene-view-status-9.png | Bin 1990 -> 0 bytes files/opencs/scene-view-status-9.svg | 167 +++++++++ files/opencs/scene-view-terrain.png | Bin 2580 -> 0 bytes files/opencs/scene-view-terrain.svg | 145 ++++++++ files/opencs/scene-view-water.png | Bin 753 -> 0 bytes files/opencs/scene-view-water.svg | 136 ++++++++ files/opencs/scene.png | Bin 401 -> 0 bytes files/opencs/scene.svg | 178 ++++++++++ files/opencs/script.png | Bin 256 -> 0 bytes files/opencs/script.svg | 143 ++++++++ files/opencs/selection-mode-cube-corner.png | Bin 1422 -> 0 bytes files/opencs/selection-mode-cube-corner.svg | 62 ++++ files/opencs/selection-mode-cube-sphere.png | Bin 1428 -> 0 bytes files/opencs/selection-mode-cube.png | Bin 1391 -> 0 bytes files/opencs/selection-mode-cube.svg | 58 ++++ files/opencs/selection-mode-sphere.svg | 72 ++++ files/opencs/skill.png | Bin 318 -> 0 bytes files/opencs/skill.svg | 143 ++++++++ files/opencs/sound-generator.png | Bin 429 -> 0 bytes files/opencs/sound-generator.svg | 142 ++++++++ files/opencs/sound.png | Bin 222 -> 0 bytes files/opencs/sound.svg | 165 +++++++++ files/opencs/spell.png | Bin 319 -> 0 bytes files/opencs/spell.svg | 264 ++++++++++++++ files/opencs/start-script.png | Bin 359 -> 0 bytes files/opencs/start-script.svg | 155 +++++++++ files/opencs/static.png | Bin 208 -> 0 bytes files/opencs/static.svg | 154 +++++++++ files/opencs/stop-openmw.png | Bin 252 -> 0 bytes files/opencs/stop-openmw.svg | 147 ++++++++ files/opencs/transform-move.png | Bin 879 -> 0 bytes files/opencs/transform-move.svg | 60 ++++ files/opencs/transform-rotate.png | Bin 1391 -> 0 bytes files/opencs/transform-rotate.svg | 62 ++++ files/opencs/transform-scale.png | Bin 436 -> 0 bytes files/opencs/transform-scale.svg | 60 ++++ files/opencs/weapon.png | Bin 394 -> 0 bytes files/opencs/weapon.svg | 142 ++++++++ 341 files changed, 19217 insertions(+), 373 deletions(-) delete mode 100644 files/opencs/activator.png create mode 100644 files/opencs/activator.svg delete mode 100644 files/opencs/apparatus.png create mode 100644 files/opencs/apparatus.svg delete mode 100644 files/opencs/armor.png create mode 100644 files/opencs/armor.svg delete mode 100644 files/opencs/attribute.png create mode 100644 files/opencs/attribute.svg delete mode 100644 files/opencs/birthsign.png create mode 100644 files/opencs/birthsign.svg delete mode 100644 files/opencs/body-part.png create mode 100644 files/opencs/body-part.svg delete mode 100644 files/opencs/book.png create mode 100644 files/opencs/book.svg delete mode 100644 files/opencs/brush-circle.png create mode 100644 files/opencs/brush-circle.svg delete mode 100644 files/opencs/brush-custom.png create mode 100644 files/opencs/brush-custom.svg delete mode 100644 files/opencs/brush-point.png create mode 100644 files/opencs/brush-point.svg delete mode 100644 files/opencs/brush-square.png create mode 100644 files/opencs/brush-square.svg delete mode 100644 files/opencs/camera-first-person.png create mode 100644 files/opencs/camera-first-person.svg delete mode 100644 files/opencs/camera-free.png create mode 100644 files/opencs/camera-free.svg delete mode 100644 files/opencs/camera-orbit.png create mode 100644 files/opencs/camera-orbit.svg delete mode 100644 files/opencs/cell.png create mode 100644 files/opencs/cell.svg delete mode 100644 files/opencs/class.png create mode 100644 files/opencs/class.svg delete mode 100644 files/opencs/clothing.png create mode 100644 files/opencs/clothing.svg delete mode 100644 files/opencs/container.png create mode 100644 files/opencs/container.svg delete mode 100644 files/opencs/creature.png create mode 100644 files/opencs/creature.svg delete mode 100644 files/opencs/debug-profile.png create mode 100644 files/opencs/debug-profile.svg delete mode 100644 files/opencs/dialogue-greeting.png delete mode 100644 files/opencs/dialogue-info.png create mode 100644 files/opencs/dialogue-info.svg delete mode 100644 files/opencs/dialogue-journal.png delete mode 100644 files/opencs/dialogue-persuasion.png delete mode 100644 files/opencs/dialogue-regular.png delete mode 100644 files/opencs/dialogue-topic-infos.png delete mode 100644 files/opencs/dialogue-topics.png create mode 100644 files/opencs/dialogue-topics.svg delete mode 100644 files/opencs/dialogue-voice.png delete mode 100644 files/opencs/door.png create mode 100644 files/opencs/door.svg delete mode 100644 files/opencs/editing-instance.png create mode 100644 files/opencs/editing-instance.svg delete mode 100644 files/opencs/editing-pathgrid.png create mode 100644 files/opencs/editing-pathgrid.svg delete mode 100644 files/opencs/editing-terrain-movement.png create mode 100644 files/opencs/editing-terrain-movement.svg delete mode 100644 files/opencs/editing-terrain-shape.png create mode 100644 files/opencs/editing-terrain-shape.svg delete mode 100644 files/opencs/editing-terrain-texture.png create mode 100644 files/opencs/editing-terrain-texture.svg delete mode 100644 files/opencs/editing-terrain-vertex-paint.png create mode 100644 files/opencs/editing-terrain-vertex-paint.svg delete mode 100644 files/opencs/enchantment.png create mode 100644 files/opencs/enchantment.svg delete mode 100644 files/opencs/error-log.png create mode 100644 files/opencs/error-log.svg delete mode 100644 files/opencs/faction.png create mode 100644 files/opencs/faction.svg delete mode 100644 files/opencs/filter.png create mode 100644 files/opencs/filter.svg delete mode 100644 files/opencs/global-variable.png create mode 100644 files/opencs/global-variable.svg delete mode 100644 files/opencs/gmst.png create mode 100644 files/opencs/gmst.svg delete mode 100644 files/opencs/info.png create mode 100644 files/opencs/info.svg delete mode 100644 files/opencs/ingredient.png create mode 100644 files/opencs/ingredient.svg delete mode 100644 files/opencs/instance.png create mode 100644 files/opencs/instance.svg delete mode 100644 files/opencs/journal-topic-infos.png create mode 100644 files/opencs/journal-topic-infos.svg delete mode 100644 files/opencs/journal-topics.png create mode 100644 files/opencs/journal-topics.svg delete mode 100644 files/opencs/land-heightmap.png create mode 100644 files/opencs/land-heightmap.svg delete mode 100644 files/opencs/land-texture.png create mode 100644 files/opencs/land-texture.svg delete mode 100644 files/opencs/levelled-creature.png create mode 100644 files/opencs/levelled-creature.svg delete mode 100644 files/opencs/levelled-item.png create mode 100644 files/opencs/levelled-item.svg delete mode 100644 files/opencs/light.png create mode 100644 files/opencs/light.svg delete mode 100644 files/opencs/lighting-lamp.png create mode 100644 files/opencs/lighting-lamp.svg delete mode 100644 files/opencs/lighting-moon.png create mode 100644 files/opencs/lighting-moon.svg delete mode 100644 files/opencs/lighting-sun.png create mode 100644 files/opencs/lighting-sun.svg delete mode 100644 files/opencs/list-added.png create mode 100644 files/opencs/list-added.svg delete mode 100644 files/opencs/list-base.png create mode 100644 files/opencs/list-base.svg delete mode 100644 files/opencs/list-modified.png create mode 100644 files/opencs/list-modified.svg delete mode 100644 files/opencs/list-removed.png create mode 100644 files/opencs/list-removed.svg delete mode 100644 files/opencs/lockpick.png create mode 100644 files/opencs/lockpick.svg delete mode 100644 files/opencs/magic-effect.png create mode 100644 files/opencs/magic-effect.svg delete mode 100644 files/opencs/menu-close.png create mode 100644 files/opencs/menu-close.svg delete mode 100644 files/opencs/menu-exit.png create mode 100644 files/opencs/menu-exit.svg delete mode 100644 files/opencs/menu-merge.png create mode 100644 files/opencs/menu-merge.svg delete mode 100644 files/opencs/menu-new-addon.png create mode 100644 files/opencs/menu-new-addon.svg delete mode 100644 files/opencs/menu-new-game.png create mode 100644 files/opencs/menu-new-game.svg delete mode 100644 files/opencs/menu-new-window.png create mode 100644 files/opencs/menu-new-window.svg delete mode 100644 files/opencs/menu-open.png create mode 100644 files/opencs/menu-open.svg delete mode 100644 files/opencs/menu-preferences.png create mode 100644 files/opencs/menu-preferences.svg delete mode 100644 files/opencs/menu-redo.png create mode 100644 files/opencs/menu-redo.svg delete mode 100644 files/opencs/menu-reload.png create mode 100644 files/opencs/menu-reload.svg delete mode 100644 files/opencs/menu-save.png create mode 100644 files/opencs/menu-save.svg delete mode 100644 files/opencs/menu-search.png create mode 100644 files/opencs/menu-search.svg delete mode 100644 files/opencs/menu-status-bar.png create mode 100644 files/opencs/menu-status-bar.svg delete mode 100644 files/opencs/menu-undo.png create mode 100644 files/opencs/menu-undo.svg delete mode 100644 files/opencs/menu-verify.png create mode 100644 files/opencs/menu-verify.svg delete mode 100644 files/opencs/metadata.png create mode 100644 files/opencs/metadata.svg delete mode 100644 files/opencs/miscellaneous.png create mode 100644 files/opencs/miscellaneous.svg delete mode 100644 files/opencs/multitype.png create mode 100644 files/opencs/multitype.svg delete mode 100644 files/opencs/npc.png create mode 100644 files/opencs/npc.svg delete mode 100644 files/opencs/object.png create mode 100644 files/opencs/object.svg delete mode 100644 files/opencs/pathgrid.png create mode 100644 files/opencs/pathgrid.svg delete mode 100644 files/opencs/potion.png create mode 100644 files/opencs/potion.svg delete mode 100644 files/opencs/probe.png create mode 100644 files/opencs/probe.svg delete mode 100644 files/opencs/qt.png create mode 100644 files/opencs/qt.svg delete mode 100644 files/opencs/race.png create mode 100644 files/opencs/race.svg delete mode 100644 files/opencs/random.png delete mode 100644 files/opencs/record-add.png create mode 100644 files/opencs/record-add.svg delete mode 100644 files/opencs/record-clone.png create mode 100644 files/opencs/record-clone.svg delete mode 100644 files/opencs/record-delete.png create mode 100644 files/opencs/record-delete.svg delete mode 100644 files/opencs/record-down.png create mode 100644 files/opencs/record-down.svg delete mode 100644 files/opencs/record-edit.png create mode 100644 files/opencs/record-edit.svg delete mode 100644 files/opencs/record-next.png create mode 100644 files/opencs/record-next.svg delete mode 100644 files/opencs/record-preview.png create mode 100644 files/opencs/record-preview.svg delete mode 100644 files/opencs/record-previous.png create mode 100644 files/opencs/record-previous.svg delete mode 100644 files/opencs/record-revert.png create mode 100644 files/opencs/record-revert.svg delete mode 100644 files/opencs/record-touch.png create mode 100644 files/opencs/record-touch.svg delete mode 100644 files/opencs/record-up.png create mode 100644 files/opencs/record-up.svg delete mode 100644 files/opencs/region-map.png create mode 100644 files/opencs/region-map.svg delete mode 100644 files/opencs/region.png create mode 100644 files/opencs/region.svg delete mode 100644 files/opencs/repair.png create mode 100644 files/opencs/repair.svg delete mode 100644 files/opencs/resources-icon.png create mode 100644 files/opencs/resources-icon.svg delete mode 100644 files/opencs/resources-mesh.png create mode 100644 files/opencs/resources-mesh.svg delete mode 100644 files/opencs/resources-music.png create mode 100644 files/opencs/resources-music.svg delete mode 100644 files/opencs/resources-sound.png create mode 100644 files/opencs/resources-sound.svg delete mode 100644 files/opencs/resources-texture.png create mode 100644 files/opencs/resources-texture.svg delete mode 100644 files/opencs/resources-video.png create mode 100644 files/opencs/resources-video.svg delete mode 100644 files/opencs/run-game.png create mode 100644 files/opencs/run-game.svg delete mode 100644 files/opencs/run-log.png create mode 100644 files/opencs/run-log.svg delete mode 100644 files/opencs/run-openmw.png create mode 100644 files/opencs/run-openmw.svg delete mode 100644 files/opencs/scene-exterior-arrows.png create mode 100644 files/opencs/scene-exterior-arrows.svg delete mode 100644 files/opencs/scene-exterior-borders.png create mode 100644 files/opencs/scene-exterior-borders.svg delete mode 100644 files/opencs/scene-exterior-markers.png create mode 100644 files/opencs/scene-exterior-markers.svg delete mode 100644 files/opencs/scene-exterior-status-0.png create mode 100644 files/opencs/scene-exterior-status-0.svg delete mode 100644 files/opencs/scene-exterior-status-1.png create mode 100644 files/opencs/scene-exterior-status-1.svg delete mode 100644 files/opencs/scene-exterior-status-2.png create mode 100644 files/opencs/scene-exterior-status-2.svg delete mode 100644 files/opencs/scene-exterior-status-3.png create mode 100644 files/opencs/scene-exterior-status-3.svg delete mode 100644 files/opencs/scene-exterior-status-4.png create mode 100644 files/opencs/scene-exterior-status-4.svg delete mode 100644 files/opencs/scene-exterior-status-5.png create mode 100644 files/opencs/scene-exterior-status-5.svg delete mode 100644 files/opencs/scene-exterior-status-6.png create mode 100644 files/opencs/scene-exterior-status-6.svg delete mode 100644 files/opencs/scene-exterior-status-7.png create mode 100644 files/opencs/scene-exterior-status-7.svg delete mode 100644 files/opencs/scene-view-fog.png delete mode 100644 files/opencs/scene-view-instance.png create mode 100644 files/opencs/scene-view-instance.svg delete mode 100644 files/opencs/scene-view-pathgrid.png create mode 100644 files/opencs/scene-view-pathgrid.svg delete mode 100644 files/opencs/scene-view-status-0.png create mode 100644 files/opencs/scene-view-status-0.svg delete mode 100644 files/opencs/scene-view-status-1.png create mode 100644 files/opencs/scene-view-status-1.svg delete mode 100644 files/opencs/scene-view-status-10.png create mode 100644 files/opencs/scene-view-status-10.svg delete mode 100644 files/opencs/scene-view-status-11.png create mode 100644 files/opencs/scene-view-status-11.svg delete mode 100644 files/opencs/scene-view-status-12.png create mode 100644 files/opencs/scene-view-status-12.svg delete mode 100644 files/opencs/scene-view-status-13.png create mode 100644 files/opencs/scene-view-status-13.svg delete mode 100644 files/opencs/scene-view-status-14.png create mode 100644 files/opencs/scene-view-status-14.svg delete mode 100644 files/opencs/scene-view-status-15.png create mode 100644 files/opencs/scene-view-status-15.svg delete mode 100644 files/opencs/scene-view-status-16.png delete mode 100644 files/opencs/scene-view-status-17.png delete mode 100644 files/opencs/scene-view-status-18.png delete mode 100644 files/opencs/scene-view-status-19.png delete mode 100644 files/opencs/scene-view-status-2.png create mode 100644 files/opencs/scene-view-status-2.svg delete mode 100644 files/opencs/scene-view-status-20.png delete mode 100644 files/opencs/scene-view-status-21.png delete mode 100644 files/opencs/scene-view-status-22.png delete mode 100644 files/opencs/scene-view-status-23.png delete mode 100644 files/opencs/scene-view-status-24.png delete mode 100644 files/opencs/scene-view-status-25.png delete mode 100644 files/opencs/scene-view-status-26.png delete mode 100644 files/opencs/scene-view-status-27.png delete mode 100644 files/opencs/scene-view-status-28.png delete mode 100644 files/opencs/scene-view-status-29.png delete mode 100644 files/opencs/scene-view-status-3.png create mode 100644 files/opencs/scene-view-status-3.svg delete mode 100644 files/opencs/scene-view-status-30.png delete mode 100644 files/opencs/scene-view-status-31.png delete mode 100644 files/opencs/scene-view-status-4.png create mode 100644 files/opencs/scene-view-status-4.svg delete mode 100644 files/opencs/scene-view-status-5.png create mode 100644 files/opencs/scene-view-status-5.svg delete mode 100644 files/opencs/scene-view-status-6.png create mode 100644 files/opencs/scene-view-status-6.svg delete mode 100644 files/opencs/scene-view-status-7.png create mode 100644 files/opencs/scene-view-status-7.svg delete mode 100644 files/opencs/scene-view-status-8.png create mode 100644 files/opencs/scene-view-status-8.svg delete mode 100644 files/opencs/scene-view-status-9.png create mode 100644 files/opencs/scene-view-status-9.svg delete mode 100644 files/opencs/scene-view-terrain.png create mode 100644 files/opencs/scene-view-terrain.svg delete mode 100644 files/opencs/scene-view-water.png create mode 100644 files/opencs/scene-view-water.svg delete mode 100644 files/opencs/scene.png create mode 100644 files/opencs/scene.svg delete mode 100644 files/opencs/script.png create mode 100644 files/opencs/script.svg delete mode 100644 files/opencs/selection-mode-cube-corner.png create mode 100644 files/opencs/selection-mode-cube-corner.svg delete mode 100644 files/opencs/selection-mode-cube-sphere.png delete mode 100644 files/opencs/selection-mode-cube.png create mode 100644 files/opencs/selection-mode-cube.svg create mode 100644 files/opencs/selection-mode-sphere.svg delete mode 100644 files/opencs/skill.png create mode 100644 files/opencs/skill.svg delete mode 100644 files/opencs/sound-generator.png create mode 100644 files/opencs/sound-generator.svg delete mode 100644 files/opencs/sound.png create mode 100644 files/opencs/sound.svg delete mode 100644 files/opencs/spell.png create mode 100644 files/opencs/spell.svg delete mode 100644 files/opencs/start-script.png create mode 100644 files/opencs/start-script.svg delete mode 100644 files/opencs/static.png create mode 100644 files/opencs/static.svg delete mode 100644 files/opencs/stop-openmw.png create mode 100644 files/opencs/stop-openmw.svg delete mode 100644 files/opencs/transform-move.png create mode 100644 files/opencs/transform-move.svg delete mode 100644 files/opencs/transform-rotate.png create mode 100644 files/opencs/transform-rotate.svg delete mode 100644 files/opencs/transform-scale.png create mode 100644 files/opencs/transform-scale.svg delete mode 100644 files/opencs/weapon.png create mode 100644 files/opencs/weapon.svg diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 269cc44707..3919507bd1 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -347,6 +347,16 @@ add_qt_style_dlls() { QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" } +declare -A QT_IMAGEFORMATS +QT_IMAGEFORMATS["Release"]="" +QT_IMAGEFORMATS["Debug"]="" +QT_IMAGEFORMATS["RelWithDebInfo"]="" +add_qt_image_dlls() { + local CONFIG=$1 + shift + QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -913,12 +923,13 @@ printf "Qt ${QT_VER}... " DLLSUFFIX="" fi if [ "${QT_VER:0:1}" -eq "6" ]; then - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll else - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll fi add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" done echo Done. } @@ -1112,6 +1123,13 @@ fi echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}styles" done + + echo "- Qt Image Format DLLs..." + mkdir -p ${DLL_PREFIX}imageformats + for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}imageformats" + done echo done #fi diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index b7784cf3f0..d29f16f55f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -36,7 +36,7 @@ declare -rA GROUPED_DEPS=( libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev - libyaml-cpp-dev + libyaml-cpp-dev libqt5svg5 libqt5svg5-dev " # These dependencies can alternatively be built and linked statically. diff --git a/CMakeLists.txt b/CMakeLists.txt index 93da5feec4..5de8934968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,9 +249,9 @@ find_package(LZ4 REQUIRED) if (USE_QT) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) if (QT_VERSION_MAJOR VERSION_EQUAL 5) - find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED) + find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED) else() - find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED) + find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED) endif() message(STATUS "Using Qt${QT_VERSION}") endif() @@ -834,6 +834,12 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) + + get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c2f249171a..3353a4586f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -250,9 +250,9 @@ target_link_libraries(openmw-cs-lib ) if (QT_VERSION_MAJOR VERSION_EQUAL 6) - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg) else() - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::Svg) endif() if (WIN32) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e7f980dc0d..62fb29e600 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -81,7 +81,7 @@ int runApplication(int argc, char* argv[]) Application application(argc, argv); - application.setWindowIcon(QIcon(":./openmw-cs.png")); + application.setWindowIcon(QIcon(":openmw-cs")); CS::Editor editor(argc, argv); #ifdef __linux__ diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index a966a53eee..a40698bc27 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -58,7 +58,7 @@ std::string CSMWorld::TableMimeData::getIcon() const if (tmpIcon != id.getIcon()) { - return ":/multitype.png"; // icon stolen from gnome TODO: get new icon + return ":multitype"; } tmpIcon = id.getIcon(); diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 9daf87e20a..0ebccd6253 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -23,158 +23,137 @@ namespace constexpr TypeData sNoArg[] = { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", - ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", - ":./dialogue-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":gmst" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":skill" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":class" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":faction" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":race" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":sound" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":region" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":birthsign" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":spell" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":dialogue-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", - ":./journal-topics.png" }, + ":journal-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":cell" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", - ":./body-part.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", - ":./object.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", - ":./region-map.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":./filter.png" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", - ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":./resources-icon" }, + ":enchantment" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":body-part" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":object" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":instance" }, + { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":region-map" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":filter" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":resources-icon" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", - ":./resources-music" }, + ":resources-music" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", ":resources-sound" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", - ":./resources-video" }, + ":resources-texture" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":run-log" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", - ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":land-heightmap" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", - ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":pathgrid" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", - ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":metadata" }, }; constexpr TypeData sIdArg[] = { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", - ":./journal-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":gmst" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":skill" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":class" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":faction" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":race" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":sound" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":region" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":birthsign" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":spell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":dialogue-topics" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":journal-topics" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", - ":./activator.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", - ":./apparatus.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", - ":./container.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", - ":./ingredient.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":object" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":activator" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":potion" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":apparatus" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":armor" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":book" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":clothing" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":container" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":creature" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":door" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":ingredient" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, - "Creature Levelled List", ":./levelled-creature.png" }, + "Creature Levelled List", ":levelled-creature" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", - ":./levelled-item.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, + ":levelled-item" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":light" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":lockpick" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", - ":./miscellaneous.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", - ":./record-preview.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":./resources-music" }, + ":miscellaneous" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":npc" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":probe" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":repair" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":static" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":weapon" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":instance" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":filter" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":scene" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":edit-preview" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":enchantment" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":body-part" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", - ":./resources-sound" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" }, + ":resources-sound" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":resources-video" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":land-heightmap" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":pathgrid" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":metadata" }, }; constexpr TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, - "Verification Results", ":./menu-verify.png" }, + "Verification Results", ":menu-verify" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", - ":./error-log.png" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", - ":./menu-search.png" }, + ":error-log" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":menu-search" }, }; struct WriteToStream diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index e1bf7e6ac6..f5cdb1b8fc 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -71,30 +71,30 @@ void CSVDoc::View::setupFileMenu() { QMenu* file = menuBar()->addMenu(tr("File")); - QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); + QAction* newGame = createMenuEntry("New Game", ":menu-new-game", file, "document-file-newgame"); connect(newGame, &QAction::triggered, this, &View::newGameRequest); - QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); + QAction* newAddon = createMenuEntry("New Addon", ":menu-new-addon", file, "document-file-newaddon"); connect(newAddon, &QAction::triggered, this, &View::newAddonRequest); - QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); + QAction* open = createMenuEntry("Open", ":menu-open", file, "document-file-open"); connect(open, &QAction::triggered, this, &View::loadDocumentRequest); - QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); + QAction* save = createMenuEntry("Save", ":menu-save", file, "document-file-save"); connect(save, &QAction::triggered, this, &View::save); mSave = save; file->addSeparator(); - QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); + QAction* verify = createMenuEntry("Verify", ":menu-verify", file, "document-file-verify"); connect(verify, &QAction::triggered, this, &View::verify); mVerify = verify; - QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); + QAction* merge = createMenuEntry("Merge", ":menu-merge", file, "document-file-merge"); connect(merge, &QAction::triggered, this, &View::merge); mMerge = merge; - QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); + QAction* loadErrors = createMenuEntry("Error Log", ":error-log", file, "document-file-errorlog"); connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); @@ -102,10 +102,10 @@ void CSVDoc::View::setupFileMenu() file->addSeparator(); - QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); + QAction* close = createMenuEntry("Close", ":menu-close", file, "document-file-close"); connect(close, &QAction::triggered, this, &View::close); - QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); + QAction* exit = createMenuEntry("Exit", ":menu-exit", file, "document-file-exit"); connect(exit, &QAction::triggered, this, &View::exit); connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication); @@ -140,17 +140,16 @@ void CSVDoc::View::setupEditMenu() mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, &QAction::changed, this, &View::undoActionChanged); - mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); + mUndo->setIcon(QIcon(QString::fromStdString(":menu-undo"))); edit->addAction(mUndo); mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); - mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); + mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo"))); edit->addAction(mRedo); - QAction* userSettings - = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); + QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences"); connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest); QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); @@ -161,10 +160,10 @@ void CSVDoc::View::setupViewMenu() { QMenu* view = menuBar()->addMenu(tr("View")); - QAction* newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); + QAction* newWindow = createMenuEntry("New View", ":menu-new-window", view, "document-view-newview"); connect(newWindow, &QAction::triggered, this, &View::newView); - mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); + mShowStatusBar = createMenuEntry("Toggle Status Bar", ":menu-status-bar", view, "document-view-statusbar"); connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar); mShowStatusBar->setCheckable(true); mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); @@ -289,7 +288,7 @@ void CSVDoc::View::setupAssetsMenu() { QMenu* assets = menuBar()->addMenu(tr("Assets")); - QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); + QAction* reload = createMenuEntry("Reload", ":menu-reload", assets, "document-assets-reload"); connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged); assets->addSeparator(); @@ -341,9 +340,9 @@ void CSVDoc::View::setupDebugMenu() QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); - runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); + runDebug->setIcon(QIcon(QString::fromStdString(":run-openmw"))); - QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); + QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown"); connect(stopDebug, &QAction::triggered, this, &View::stop); mStopDebug = stopDebug; @@ -355,16 +354,16 @@ void CSVDoc::View::setupHelpMenu() { QMenu* help = menuBar()->addMenu(tr("Help")); - QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help"); + QAction* helpInfo = createMenuEntry("Help", ":info", help, "document-help-help"); connect(helpInfo, &QAction::triggered, this, &View::openHelp); - QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial"); + QAction* tutorial = createMenuEntry("Tutorial", ":info", help, "document-help-tutorial"); connect(tutorial, &QAction::triggered, this, &View::tutorial); - QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); + QAction* about = createMenuEntry("About OpenMW-CS", ":info", help, "document-help-about"); connect(about, &QAction::triggered, this, &View::infoAbout); - QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); + QAction* aboutQt = createMenuEntry("About Qt", ":qt", help, "document-help-qt"); connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt); } diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 50735bf703..0d7ab679b2 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -44,7 +44,7 @@ CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent) mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 7f767e19ac..1c84d886d3 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -12,11 +12,10 @@ namespace CSVRender { // elements that are part of the actual scene Mask_Hidden = 0x0, - Mask_Reference = 0x2, - Mask_Pathgrid = 0x4, - Mask_Water = 0x8, - Mask_Fog = 0x10, - Mask_Terrain = 0x20, + Mask_Reference = 0x1, + Mask_Pathgrid = 0x2, + Mask_Water = 0x4, + Mask_Terrain = 0x8, // used within models Mask_ParticleSystem = 0x100, diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 214618a627..58523ab595 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -160,7 +160,6 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::S { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton(Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) @@ -170,9 +169,10 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce /// \todo replace EditMode with suitable subclasses tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); - tool->addButton( - new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); - tool->addButton(new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); + const QIcon vertexIcon = QIcon(":scenetoolbar/editing-terrain-vertex-paint"); + const QIcon movementIcon = QIcon(":scenetoolbar/editing-terrain-movement"); + tool->addButton(new EditMode(this, vertexIcon, Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); + tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 5c45e2b31f..8550195e8c 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -36,8 +36,8 @@ class QWidget; namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), - parent) + : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-pathgrid"), + Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) , mSelectionMode(nullptr) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 899918c3b9..fee43b5de5 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -347,7 +347,6 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget: { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton(Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 505d985ffa..06470d2883 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -222,8 +222,7 @@ namespace CSVRender Button_Reference = 0x1, Button_Pathgrid = 0x2, Button_Water = 0x4, - Button_Fog = 0x8, - Button_Terrain = 0x10 + Button_Terrain = 0x8 }; virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool); diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 67270bd1ed..1333a4a7da 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -92,7 +92,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(this); - viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setIcon(QIcon(":cell")); viewButton->setToolTip("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4212e952e8..b7be2b90c8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -385,7 +385,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mViewAction = new QAction(tr("View"), this); connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); - mViewAction->setIcon(QIcon(":/cell.png")); + mViewAction->setIcon(QIcon(":cell")); addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); @@ -417,7 +417,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp index 54538a591d..871a5218d4 100644 --- a/apps/opencs_tests/model/world/testuniversalid.cpp +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -149,39 +149,36 @@ namespace CSMWorld Params{ UniversalId(UniversalId::Type_None), UniversalId::Type_None, UniversalId::Class_None, UniversalId::ArgumentType_None, "-", "-", ":placeholder" }, Params{ UniversalId(UniversalId::Type_RegionMap), UniversalId::Type_RegionMap, UniversalId::Class_NonRecord, - UniversalId::ArgumentType_None, "Region Map", "Region Map", ":./region-map.png" }, + UniversalId::ArgumentType_None, "Region Map", "Region Map", ":region-map" }, Params{ UniversalId(UniversalId::Type_RunLog), UniversalId::Type_RunLog, UniversalId::Class_Transient, - UniversalId::ArgumentType_None, "Run Log", "Run Log", ":./run-log.png" }, + UniversalId::ArgumentType_None, "Run Log", "Run Log", ":run-log" }, Params{ UniversalId(UniversalId::Type_Lands), UniversalId::Type_Lands, UniversalId::Class_RecordList, - UniversalId::ArgumentType_None, "Lands", "Lands", ":./land-heightmap.png" }, + UniversalId::ArgumentType_None, "Lands", "Lands", ":land-heightmap" }, Params{ UniversalId(UniversalId::Type_Icons), UniversalId::Type_Icons, UniversalId::Class_ResourceList, - UniversalId::ArgumentType_None, "Icons", "Icons", ":./resources-icon" }, + UniversalId::ArgumentType_None, "Icons", "Icons", ":resources-icon" }, Params{ UniversalId(UniversalId::Type_Activator, "a"), UniversalId::Type_Activator, - UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", - ":./activator.png" }, + UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", ":activator" }, Params{ UniversalId(UniversalId::Type_Gmst, "b"), UniversalId::Type_Gmst, UniversalId::Class_Record, - UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":./gmst.png" }, + UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":gmst" }, Params{ UniversalId(UniversalId::Type_Mesh, "c"), UniversalId::Type_Mesh, UniversalId::Class_Resource, - UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":./resources-mesh" }, + UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":resources-mesh" }, Params{ UniversalId(UniversalId::Type_Scene, "d"), UniversalId::Type_Scene, UniversalId::Class_Collection, - UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":./scene.png" }, + UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":scene" }, Params{ UniversalId(UniversalId::Type_Reference, "e"), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", ":instance" }, Params{ UniversalId(UniversalId::Type_Search, 42), UniversalId::Type_Search, UniversalId::Class_Transient, - UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":./menu-search.png" }, + UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":menu-search" }, Params{ UniversalId("Instance: f"), UniversalId::Type_Reference, UniversalId::Class_SubRecord, - UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, + UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", - "Instance: SKIL:0x2a", ":./instance.png" }, + "Instance: SKIL:0x2a", ":instance" }, }; INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); diff --git a/files/opencs/activator.png b/files/opencs/activator.png deleted file mode 100644 index ded6ab835b801a7f9b1972cccbcb57e54948999d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 562 zcmV-20?qx2P)2ail&r>6(So8D<98nKcAJ!2|93wA=j+{bP2-o?~!8moH=lbf;1&Gnq`vWIbRESj5&K)e*-aT_RMTz&k^i zW%O?J!(cF25}==eVXy-pfI9AfQ{)OyGq?dZcmz=jZ6Xc$Ihw>urBWT{890KA38yZO zpl9q}unUp|tRvNdpTw92eQc#bF_%+YphG=Lw|M)(hRbRnxCCY!@>!DQeuP#9v{-|n zpYpmBT{cpFl!p-ukV8OzVTmGXJs5rzdlA@dD$i;SCMlW_5_bWbKo*PxDHf)N6q8FF zp!+!q)tDKOfLjwz9#S7jvJn5 zyd3%nfp_g}!88a^P@b}Iaj-zLOrnk|pgd&?-5p#F zAjIXxd8xG&dH2HpT%$++%2V!09$5>??Rv8C-;>S6d-Ukz*8l(j07*qoM6N<$f>?F# A-v9sr diff --git a/files/opencs/activator.svg b/files/opencs/activator.svg new file mode 100644 index 0000000000..11a23003da --- /dev/null +++ b/files/opencs/activator.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/apparatus.png b/files/opencs/apparatus.png deleted file mode 100644 index 4e95397dfadb913e58762e6f3607a6d77c7da01b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 364 zcmV-y0h9iTP)m?Q`zu z8)G)TQm_4kc4NmOp-XBlfgyN1tT$$QFh*W9#`(eZcOOZqlLr5hR~vAIOB1-JV@mQ? zoqEz^w!1Gq--3FZ4L3C&%4IEaWC17s8!KH{AkWV6W1X7dZ|xUVC1G+8T!mf$0000< KMNUMnLSTXoZj%iF diff --git a/files/opencs/apparatus.svg b/files/opencs/apparatus.svg new file mode 100644 index 0000000000..1345efee56 --- /dev/null +++ b/files/opencs/apparatus.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/armor.png b/files/opencs/armor.png deleted file mode 100644 index 6f5cc83c592493c87acae0136c03088f34f25373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmV;~0Ve*5P)_DCOd#+$Fb7 zD6OM%K}pGVZ#!F(gM%Y+Q?AHCPLgjke*a;%oozJx)Nh{e=Y3}8H66$C4a77}sj9{I zeM1=GfT}yo3e}sEZ`-!?r?yDiBsi7gLZJ|>oKnDb-7kh{r8J2b4gF83OOtz2UcFXD z;dg}}=W1IWScCr1fD4>1Y3PO|X`|o@+NCw%9(te%bC4ts1?Ql{F15}9%)%6Ca*~Ww zFamq9q790cWexHnAJGlGPCc<$Ay-GDmD+3mRxkF6h4bop-WkaObdF#?w);1Dgj%F_ znm=$uucr+p6P$iXUb=ZP;Sc44BGc?y?ufh<``h|gnuMjvS8ZoqL5nIEXq2=$| z?qUx^2b3hTK^SF|V{BcRet3pcxMOG^IwFw`!VHCK)Hl(+xP|4oX5z*Vn$nOwhO&^k P00000NkvXXu0mjf$y3bG diff --git a/files/opencs/armor.svg b/files/opencs/armor.svg new file mode 100644 index 0000000000..e7925a23bc --- /dev/null +++ b/files/opencs/armor.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/attribute.png b/files/opencs/attribute.png deleted file mode 100644 index c12456717990aead1a152c0868643699df2b9afd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 326 zcmV-M0lEH(P)U02@)tmpWqV& zKTz-mo~?9d=M4-M*=)xvh~%pO!h0A8SluK_nZt + + + + + + + + + + + + + + diff --git a/files/opencs/birthsign.png b/files/opencs/birthsign.png deleted file mode 100644 index 861dbd4d108807eb704333665f0542a49fa99226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 444 zcmV;t0Ym8|8w+J+ zd%?<1Y;0UXNFvw>TG-f#wGUvewI=a9F*n?V2;#ux%;cQ;yPoN=APD;WzVJM6Qgl%i zZ@_ArrccUU^W8)-#RK2>9|+fzB=O|LA$|k53pPw-Z3yN_uJH+`6*W->@b82xaF3R2 zZ*T^&(N$a(EP$7`Sm@F9kd=qrX+V=@*%%EzU6C6{QG~QGypPHAd`}w;33hZV#VY)A z3)(T-Tw36OfydflK(`|atF9#~c1=_5md*xS4HeCnDLTi;IU{!)D2D$4`fx9nYc!}t zr{E?&gFhoy;XLsjenP$v)}|8{x#d=WxN8l;h=0?hSDZ1W|E&B9Y{T8R^+vBMkt__u z0>Mx6%eSrX=|QaN;dtRPZBNo>BHDU2n6+IwVaYbrS#cbfZ&YnIqKO)G>TO+(Dzb?k mU~?CHZsNv&p5zw&C;kS%t-~3F{lQ-V0000 + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/body-part.png b/files/opencs/body-part.png deleted file mode 100644 index 8baec720247c579b9f3dd1b1e36b0a1226fca66c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 418 zcmV;T0bTxyP)hY0fIqb2@_aCwg-TT;UH=GopooqTUfa_`F8&F`@Ws~$9NM~7=};Am{s{q z?6dMb_XKI077eImCOk_^d0i=rsE>AqdqIsekhvh0OP_U!(6KG9%6JRu&8 zi|o48fGMtg-#1KAvqg4ZY9K_maZA`#kshel{~AEN1RE-lTVSGTtz8W6gCLkv0R}Dd zs@5(BnUV+bj@Zy%**h3=WNcZCK2JCUuTsl$nc#wh+%4$dKC#Qq%we`vY5-vcyg_Pl zormN{2)D_ + + + + + + + + + + + + + + + + diff --git a/files/opencs/book.png b/files/opencs/book.png deleted file mode 100644 index fdecb1585abea4e4e5b21014be31d4bbb2f45465..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)l;uAOAr)6aNuK@efw=DJFC>MLrVj`?+-`|t#uu#EX%Ic(=<)@ z5AKpTu$s*^<|FWiW*E*7LxUW#*_YZFb0UTCi`008uTfP6L11LDDA;ITabcw*<2!4A zaDYrfE>Xs~JhMlq=g8%79Tnef5#nkfgc6l{zt~In(p8E VUU1*+fjIyG002ovPDHLkV1k1HfwBMq diff --git a/files/opencs/book.svg b/files/opencs/book.svg new file mode 100644 index 0000000000..b34827d7b1 --- /dev/null +++ b/files/opencs/book.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-circle.png b/files/opencs/brush-circle.png deleted file mode 100644 index 22e92c1c7f85ebd8e3c62d7e3805d0015dc180bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmV+f2K)JmP)WFU8GbZ8()Nlj2>E@cM*00n4CL_t(&-tCxwNYr;2 z$6w#?T|wqNoriYKGVL&>8AWT0(IDO0g!%*<2}>&2A8pwm$-@1haAOPmu|kqD&4MvV zgkco;vEgV~8^mZ4NiH-wbXP~Foal)u*xmQLKl*;hA?Nwy?(`bI;ke`OzR&Z#KcDCG z{yfhE3s}GcUT7$@c__2ir{sIP)~p?HZCf_#En50)bfZPnQUj_w@N`kaohJ$QmiwvB zYwh-&2kugv9ybIsYkjKdzP@Wif5$}yeeqQP*HOmrrseIG*#(!}r8aF|3G6s_J9lSy z>kX?GNCyZg>X3IyO5@|iwAP-aj5c?Y!;?AOn~^o_S-*UwztBE5SZvX?WB|Wv>1)a^ z+}d2RsdHWkeDY=UjyHPm$>+gXdtJ5_m5n(?=XwsN1SWeXt7gzr+|_cx>3ek4s`(uN zw;bz4-N_xzb4nmM_`ZbH3k|v354V?Pj7---ezhmDr1NT3`sl#^SddVrN}CT{*E#@P z$tyoQUcKkcY#kjbt1ovBJvvA4%{4$E)|8=}Snx0I;*W z^+sl`PmRQ$5JCw{Ky+W3g%Ab6#}i0h2}$5dg1rS``AC0ZEC`r(O+V8}p+w-P z|El!oC6ET32ua|6s`DCveQdBet7gy=)zz;VwAjZ6i-Y}y3EcEu39K~p-awO-@^(l9 z?e?4pI?A|3*OH67S`I{$Kyg>g0gJ9B>nP*x_M8Xd4z83kAcS}wI23#q416)^8E}`{ z^t)+!djXujM@RFkJ&Dl>KEK+N==4203UD_qZ*RDE)2wxXAA(ZHOrIa6lwDzqIhkE> z$*)@ateW3Z(s{Ki8U#u@uU1(#zr(Lu`eb&&rKx072=OWK+Jy2y08~v`PPdPYu{zzGUx!Ke-WfyLpW{=zs{5GL?IV+{SKP3Tx=88?7w;byPfOF{K z=@X@O=VzKu`PH7p6Qyg^0e>6H zgQ5Sw16fkaUrmy6JCzVZ>;@VpeEqGI^6M93E+mBb6WAQ2I>W$5DWxlJCv!pwRZ6J| zA>IQ1Fm77Kc)cy9Y&HKA{G4@G)nutncSC10bOPT;>5j7)L03!=m{U^9R^x8aqOt?A zf)L_O;AYTQH>H$YW;IrsCmag;><2y!(GSE20U?AM^w}nacm?>!(0hQ7q?Ddu$&6^Y z8|C25%W5@HV_Z}KKLfu2b}8kw$`@rGci?5G5(p}j(}1Fom-ll#j7tJRYH~Krne+oE=^;t42|~z + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-custom.png b/files/opencs/brush-custom.png deleted file mode 100644 index 58c0485502842819d68420eb1e6528d9a923732c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2475 zcmV;c2~_rpP)WFU8GbZ8()Nlj2>E@cM*00|FCL_t(&-tAd=R8-d) z|Gme|zzj1Ci-Lf_;1UIKX0GRd9QzTUOGi*aK??(<9E(G^WJ^;efRf!_kO?c zoBMzl_QD>^m?!D1|0a!mbj`PuBMsUqBF?5TNPI7scI~_8z?y{OPEX*qRry0kY0mGK zT3ZJ=`sEnbaH-2n^EUlqa^}+@;Ps@ykEuCJy=`s1IEFRprT$qpiqPFMYfFfPYg2`o z^%HxIo~u1~?%=r=z=FnK;#Pe2yMwOiKZ}G zm?<7it6A(*jI^9IfkZ!jWvs$tO5hmQc(H4r)rVG%C>4B^E{rg6n>4AjFa1p=Z!^cg zth=@(6DWAcO2S!0N$PXS{qz-USe}z}3~R74 zUd>|88p1PZtCI$1?~a?YT%D@q+xv^!-o(znbRa#jd7y?9#} zVRml>sNUU}F@4Kp5tx~llbEO}JOzN+*LOm6&dPz+01&!zn+pT|nS$v$n{6oiQ z>ZF0$dEJL@&0RS_?QEcSBmjWt!L+rAB-gC0s9s?)jpo6$7Iur9wqpZ1>~ReAY`QfL z0Am!(^2+>G-vO$c7huD-dv^g8RQrV!d7IhqzXSqmU4aJxb*hrvwY_O^a%FB&Kuc2} zq5Zfje)ByaKf|1h2lx56HYX3PyRk88U1^E%;+z>FgrSu34-inc8V3VFtt{w7`{htI zFThp>-1ao!)Tv5t6Ob9Ty@_#X;{hONZSk_7R-78-ZL{U1VCxrVJR@A~VtQ7!Cf@3ZKz~%X2=EngawY`abrzA5gu%&6L#Yfit(_t}p?%ctW#+1!_eXT7TZ<|HVGmOzBmKIxCk%nWv zOg6^LAmVKSkp^wDQS5vA%IPoexC=h^v%AtauN>Yu;!a7rpT+be09>UqBJGn;U;n(l zWR{hc0RZ~+>5~hKr>GcQZT+dK;N z*w5}tQ?4EPD9of=2!OXSUiH@kd#p%LpZZ59RkM{4vJ-#`0N-IT=gjQuvU<*nBSg&E zy7COHo)8#cm7fx3(kFQhg!~Y|uiKHzcAEhC;CyV7`s_}* z_Tmo!Fpb^{s`pXWx#Oev);dWqcWnsy`sH1%(u-vmt3+0DW!#O&A_0# z>ysq5w&+$b$<;Gq-K6p{VyM1e2)j9ySIxHPQ z`u{{JEpwZIx6Kj_02Tg`C)@=;#qK)$QK3zi<5(cN(ORWWDnBIc~Y5=~JA%X10_yrkMMeAT|b zKi=>NLtX-K62KUTq?WYy|>nFEB{Sb%ftG;x8TbqJ?hI= zvpn}d6{S%u%h$+5_GkAVv&-4D4sU-cfEWNX0mK0~06+oYw;t~cjbD4A=WAuhD*&KU z8L{&3y+&_#EFpgJ{>hPj8>*&;nDuY6JSVT0`e$n7-HsV0vic^8LZ?x5yVc<1XK)7I zfrJ9UWxMjguK%Y2L{myn2qZn)*jksm^YdVH!y62ENv$ks=a(^L@kmY4_@MiZvEioL zH$BEh+ z^Fz$~IjN;tL&cmml;t@&%X12&SoV!Z9&+$lbiy7_z}szpyQI|&;IB_~YjNqK{c~a) zDt8Fi_k8>g8N|Lz)(m{7 z(>)XSR4c;^h3i^bkhWPOH@XYFU2VfT1gTIZY@n2$vgZcwsqRict`R~;0662&bcRwI z@9G&Aoy+DsoLd1*6Qs4%5g>%H4(CcjNH~CM0FCzMS(H+Zqhvny4;*fQ7m`&^0NVhJ zwcpwg;3$ADl+yHP`hmj(5`h5D1L$cFUd}EmS^z3aspV1pJkJ=IX$LRh5EL;Kzz2`& z3IJixQxCulpcueK040>tYykII3WN4002ovPDHLkV1mV9x7Yvx diff --git a/files/opencs/brush-custom.svg b/files/opencs/brush-custom.svg new file mode 100644 index 0000000000..9eb59597cc --- /dev/null +++ b/files/opencs/brush-custom.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-point.png b/files/opencs/brush-point.png deleted file mode 100644 index 19b2354f8c2dd5a053dc937d8fcc004d2d855fc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 787 zcmV+u1MK{XP)WFU8GbZ8()Nlj2>E@cM*00MVOL_t(&-tCw_Xca*a z#((#cQV~=H5hRe-QZz^`1W`e!Nar6&Fuegm6olA_!B~Ve3L1DI+E@krKoBHd5ELOO zX+-c3qLmOu4TP6Gn^{>NFE1}}$=lN#IQI7TZf?HanQwL$N-3q3QvM044vp2TLu2*L z{%@t|6ITK#0sOB3%q%N307>za6u4X{8V!``GUe^^nk%rL8Cq?ob8@FHpk`)wfga%Rzp?_c z&Jke7yLGpHIxC3rTY!0BZ)E%m&{s*^@Gif-;l?jV!;dAs%nd-^1&x_yl9tWv05B58 zLXa!D<$(>}>)ej~KE!QR(%WQdG|;A*T?F<=Lz%nLL{aj<8{k<3V@^t%PZogte%;J2 z0cU_g;25yrvp<@2LF2}61ug)?z!y(m2Z5847URsEstcWR+6;0JI49|8V=5 zlKP8utTGba*MQSa>VY%>#Qtn%+stea@WJ^W;Jl>IAup_^C+m>$AzAeTcRW!6JOZ8o zJ0(rDqbuv+3Ty{ncyYesS2yq#=#jM4j-F1&z+gC8a63Xy+6`Q9S4k%U9B(nUFG&E= zHhC9tqRA^ESU)8RAYzlr7BN`^ZYBdD=5RC1% + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-square.png b/files/opencs/brush-square.png deleted file mode 100644 index 08628772e7c5f5fdb2a97335e2ef9e597f913419..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 761 zcmVWFU8GbZ8()Nlj2>E@cM*00Lb}L_t(&-tCw>XcSQt zhQHaQR0I`41c{`z6bTXwLDa%ir1Jq0Ei4xVQ4k`C_=rVFqhN%fXk!)d2O%JWMH)p2 zJ{l2xAXo`ed_dTT%^A2X%do*@=U$l~mYJQIy?g&T=ltjX7b;Y!P@%#^Z!S(+c zutU;(PBP{cIvh+MU-fcsHFWhF*nBk + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-first-person.png b/files/opencs/camera-first-person.png deleted file mode 100644 index b497198df53e72097e982ae69e8515f04691bd22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 750 zcmVaEbKv)>L=(;a!^5v2zvAj z1QCQD1@Yp^o4pj`p{LqwrFap&^_H802L%zEw9>@1hmB2}-Pz|M4aPQ&jcjxx`-eTu zn_=Jk&ztw=y@41pV#N5DP{aDMhya}48*VA3^hoT8h-5`%Rz$wbPwa@u6A?LOy7A*8 z@=*>)MMNe{Ca@yE$2(JvpXz6|*1CImH7dPMgdFbo5|&CO78e&urBcky%#h3F0B!?s zqEKo@NWk7N@O_{8`FR?R1{)h2EG#T^rY=UMG)e;genAif)a!MWQrNalv)SxSt(!<- zdA}f?PLt2)snu$1Zf-I?J>8jFGBJn~BC^&icq^5PRIAmFXkA27re>n^BJ#CQGOCHl zxhQo;c7)ubmr=9cjR(uyFIK;W2j)LlHIH6yFR7>Ab+_o9G_DqMi{4}9q#mvXKH3k- zXW&|{otRdtA+H)jGGUQ(93*HxD;Kf_)9!&u(LNJ|kbqA5U_Q7jlc<9c4Su_ezE?`yC_ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-free.png b/files/opencs/camera-free.png deleted file mode 100644 index d8e7ccae5d2f855382885f1061703b259d49ca28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 783 zcmV+q1MvKbP)(}D*$c+=mIbg;GGa6<9W3tkC6OGYsN{o zH<@oA$uP;YBm-_2?WqS(VovFPc%|jTN4^?S3d-UL3DR_qf{zEQ55)mKGT$_ zQ~VnMcN&=M0)S5>4>+~@$E}uWZEcO|bej2mzIwP^BgY7vC*`Vu8sux_$l3+&?Ch{u zEK=A1d>RJHgHDN9Z&1L3MeHfNpp}&s#^Z4g4Gl4wOtM@qo2C(`zJZqw`i)zRowf^_ znwrA)_BH~60J7OEW@ctg)1XrV9s~Gd<1;CQcwsT#WtV(;c^QhL0025VIrXd^-o2GR)T8VQ2E>>O*6~JpD zgkHzL9e@VFU(3fZ3`|T+pja%TzrP>7y}hQXPY5yRg*N2L9=6f%Uh1RvlPokTV9;yL z;H4(Eu+8$yWIfTqT*QDm+8-e#_lgRxZIhA(2QV5{aZfzX7uA{Hv>(xTpXC N002ovPDHLkV1g$)RCoXY diff --git a/files/opencs/camera-free.svg b/files/opencs/camera-free.svg new file mode 100644 index 0000000000..ee1ed5afa0 --- /dev/null +++ b/files/opencs/camera-free.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-orbit.png b/files/opencs/camera-orbit.png deleted file mode 100644 index 1aedf2847971c5fcc02a1083528d20aa56dae115..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1096 zcmV-O1h@N%P)xK6 zs``+so>A3`E^$RwUsu)b|L7G^)qdd9RYInK2Sj9Yt+8A`Y^l*u4m>Amr~y^o0sI2k zYlL37rYJYh&Hp8|6SxJ~2iyhh1g-{JYWkFc8Q=nN8u%daoSCgQa1wZWJ+bG0jt<@N zSvhi>=mmO#Iba%y00nLYihv7j6Jrais48MvXdETLG2mF>IhVG|zzk3~$0sKznVXxV zx3`yKvB-R+r;7z^U>#{hx)OH-j{*n8*zFoeh_TT)`W5I2JZCzS0ad*|&F{Z4F)_jD z=qSb*`uh4985u!DeiD(L8_j9g4=vybB1Zv4EHsHv1)lR*y?AQ_V^61fy4)CJV`H?n zwb9zzN~u($TrLC5Ze~{rJSPb}=Vesyrz##SYryrxYcm-Tkr?>8PCL!b%|uZ|rBb1* ztBaPF7SacC(*&L~P7=>l?_mJ;W?O-0XWOP`ld*c2hK7dd?(U|ir-#A8!Nncr?JNdd zKkPuHy{4&BFV0@)VEcjX9c|_vRow$TReQaoql4k$;o7D_5xJD{j5>~3pfRcmz&D#R z;QHY!z-@u&45TS@Kvk!Kp*rCq@Vtn;pKhhrcly&;ibTHjyAfc zasBXN;DlJl)g-Qpvurep`UB7Te5HP?t-y<^z3~PM-wV7U#@?+-T-`QBKow0QZ#D62 z8MqZyfE^7G-2~hRyeg*9qe+a$QSGeH0uKhBQ`&SbrpP0f`sG=xnQX^QFMidJX0R)W~04@VR0v`hJWfi^Y_W{=r z4X`JD7(0PozzskXFrWS|1K$A`f$xD*;5onL-%oOKa&mHVa&mJ1d;AG=w4*5!t!mr= O0000 + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/cell.png b/files/opencs/cell.png deleted file mode 100644 index 4c3fbe25154dfc8dfb1e6cbe8422c1dec6d932ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1212 zcmV;t1Vj6YP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Yuva2`@{nsjb36|t>ImSa(@1U38$KXp6LT(b? z!v!*mCZ8jlQ2X!SPXFK%F`ldo(I@W>E}LwUj7c=xwMjO1ea;v2GkMU{{)0fJkh;D4 zHtjR??&c8e)Gv>E3T4KLGtf`u8Bj-N!DQOS8AxT*^L(c?{j$AV&b&3OA$H^AyA1%N zIw)00nz9-^Wn@uQpiwhqTAyBqMlrXL8Miyfj`ryRynS}S&g7}rM<~enY2XhSO+CAF z>{f&hto>wu_#s9U%>8wTUGFva-nP|c>w%P~xu~C|tTPxcfKpdCj}dS?GeVD%Fd~*% z0D1+R3q~PMEI_-n<;s~mw^bG{DA7uj04Y*LS6$Q~Nypi59S?P|IEm zuX)p&uW&@>%CHnhFows|pflx_u?OmXv@WxWJ65PmMgyXZ!Q6Pl3WRW47wHOhy`Y|Y#|oMx8Bl~XN8?Jmdw`f9O?`Jh>+W%7*;S~Jerw3n;;UHfnx+e z8I_A#S-=20CzYv#8^ult5@qCuPvF&7oNQ_wEg-_F*8pm;bwHN24*e5sL=R=B&XRM^ zyWpZrE_?OXJMVq)#VvjbOI*^D3)-jH;)*Y!#F9#`Qnh0HS6>6wSX0f78(OK^=9+Jz#gt9=4ep&H<+VA-DK{W zw-eTCUcV^15;S(e@>p)%Yn8;EYeSnWd$*)kOH!y!J>3e?8Yo|Ak-L`dc z_)t&D{xlZd6!zc&MEsxjQUAEkJsW+|btmhMJ zH|)9}Uer?D&iB%^Yt`n%{dsQ=r`_&&^rH_Q-%5Iy=|!TW-BX~yR?(;OSm|Z3zpm(0 z%r1ld#~8g#L`PcjcQeudxU@{m!+ds<00006VoOIv08Ic*08J?m1UUcz010qNS#tmY z3ljhU3ljkVnw%H_000McNliru;t2)|DG{B3eS81_0DVbBK~y-)?UT_7z#t4nFVb}i zE@HNEif*He6dWl1XoV;dZ2j%~G^P{>^MKig3jGD%Y#}y2|d& a1zKE!{xaOcA~d)F0000 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/class.png b/files/opencs/class.png deleted file mode 100644 index 272f2630c73d086f867547ae82e327357ad42bab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmV-S0<-;zP)S5pWQIE9E{VUbhuDoLsnEUW~xE`}%) ziy+uUk2Fb<%D+GaJ-I6^G=X5D1n;g&5mE>u3N}F`l89ckQWO$`g}Gfn-(}x&Ug5xp z`SE7v&3n7gwra$6-8Mq+;kj+wty&I(;2O06xl*ZAR?LWF3~7q<6#d2=hvQUCG^)GX;gn2tue^W9CSDt%`?s>y|i zVdOlC?eGlr-9JGiBYS8aX#@2DhCu6U(yWigw?WcAV|6pCE>i_*{6~?0j=u#%>}(U; zGxrg{1p+C~q!%aIhOR=PU>=h`vHp|46n`+q_*OiD90`-?H+~1BOpu5sxWfMkdd`RF aSZ)Cr5YXn`)Nl6y0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/clothing.png b/files/opencs/clothing.png deleted file mode 100644 index e42988e749a5f1fac8802bd485d421ccb380e145..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmV-%0gnEOP)GZP~e*mBJJhvqMVpmy~Ss8eQZ51Qu6?;c>!EJ0!{6y6|k#GrD=A4sQlsaD% ze-kD=&Y>up~P!=?d;Z%3z$G?lCy^<`Vwn zoH*y?!dmnPRObS{xTHorwoLYCj>UW(wtIF5aZY0bRqr~%-`_8O*9m_IbCrMZ=6^?S P00000NkvXXu0mjfT;7+t diff --git a/files/opencs/clothing.svg b/files/opencs/clothing.svg new file mode 100644 index 0000000000..9b1ffb54b3 --- /dev/null +++ b/files/opencs/clothing.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/container.png b/files/opencs/container.png deleted file mode 100644 index 336a05dbfed774ed2ac8e9c425c983ec216986fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421 zcmV;W0b2fvP)bh=@ z#9mPprE)z@Oso*?=Xw5Quz8^+X=2QpZ#VQG29gMfOI5hS*`%1?{dCu(D^_2(3>5J-h7Af09NZJ@aX;pxdPn$ z8<({GmkZ4r+NtVX0f0O9sS + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/creature.png b/files/opencs/creature.png deleted file mode 100644 index 003684dca0bab3c8eaa63de379fb7e8698db2b9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 463 zcmV;=0WkiFP)-!I!h5dZSNYvLa@_9un?@R zEUXkHB1E(>g|WQk5GyN7!CC_8?Supy13ABUW@QzHr~{jMd2inA-0hw*{|wLi<2bH) zp0~v^!{P8S%d#D-DgUJVRwU22+ikDaYN_cghI45O@Db^&ej%-xAW4#3W(pfju$Jff zL=XghCQ*&?ecz;M`bmfDYqO6An0(BI13U@tsi_*!I9DH6r$J8*ibufkL_tO_jZCND zy3C_EBi}gsG1J{k(^zxlCM#u!ZIKi<9&f3oYkj%PGtI{^PR0z-pj(pd-@A z#Tv)Yqv=Je^hINCuxb!f9O0|jae|GXsoM)Woz6q0QVF?vw^0<8!H;mg@0wtmLEo=d ztJiF}tCK5knu-x4l0KjnZrQXrH%4P?*9NPGx$EkG#&5~!$U^oL`&a+~002ovPDHLk FV1o0m&g=jH diff --git a/files/opencs/creature.svg b/files/opencs/creature.svg new file mode 100644 index 0000000000..e7fe8cf5a3 --- /dev/null +++ b/files/opencs/creature.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/debug-profile.png b/files/opencs/debug-profile.png deleted file mode 100644 index ddb01da4373c24e073eaa8780f2a1925d8e4c291..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 431 zcmV;g0Z{&lP)x>!fO`r}B#0U}7m4vO)L ze%-f*hFHKny78>*7F6o@9Wyw`w8k^_qV`xoD_*dn!6nV&UgIg6aviSFfm>|H0>X%5 zQd1npNnrg&n7}Hkw7k!d?}ls~p#o=dHZco_+K(su!yk}`Ij#M#W;5-=wLhRn(*6;! zg%6!v)B=)qB(|seE1ch2 z2}Yb(WEZv+3nAUa;B+8qvZK_S`?_ojxuzu|$ Zb6>EKPtoppBdGuY002ovPDHLkV1knC#q + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-greeting.png b/files/opencs/dialogue-greeting.png deleted file mode 100644 index de6b22b42c4a025ab1f1674f20b1e7dfae20b3e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmV-|0e$|7P)g_X0E&S{nb=LSc*vm9boZbGbPdA>kqglYIGRXLjbko82{06uAI1#t2n5=kl^3 z&<2$&y^T_9z4gD`og38jvtbxInZveO=)b3Fnh&jPK%;k(Z4y&$x{3MF5%DL@`3)`! zo`5Nv4&VW5j%6c_pP_D+We;#4T!7rl8IZgq(xLbU{sI$`Sx7x&eNXNdj@Wwz=jJPr zz#*7{As7R-H=GfQPEpg+pm3=`7IdBi8O|qoiR0K6_vDrJBgJ*{ANWh#`$aOUVAJ^{ zSx(>wy-lQl`+FrPH8mVdvUnNGUsI3CU*j*}hQ#y&X};1EERMEkt*s|XvQo2h3flEA gqwt)V+>A7UK)-ht$8>{E1Ew{Z+ zIBXzN^;~9d=2DJ5&G$JXIM1BoT3~ux%beSpqjgn9@VTFs%nA>qJYDk1=oix!i_xbJlLj`N(U)D7#Qcs-tSlrx}w> zIDfDi6f?2Za_oq-Dwn&UA%9=?q1+PlyLR(5!#Ef|G3*havwUje67@fE56gZ0%O7jc z)UaFp?ZMvn$vumuihcHozUnIwKUOa}r6*7QCU@zL`0JSyyE=jXW$<+Mb6Mw<&;$VZ CHFqQc diff --git a/files/opencs/dialogue-info.svg b/files/opencs/dialogue-info.svg new file mode 100644 index 0000000000..a0b4f4236e --- /dev/null +++ b/files/opencs/dialogue-info.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-journal.png b/files/opencs/dialogue-journal.png deleted file mode 100644 index 086cb8a4221c2db4048edb3ab5e1251c3f79514b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;L%`1zC#8)K8w0qXQr#(Pk5}`F)@Bgt%PtAW9#m&?wF|79G6w+Ha`A*)UVcS zrG?IW+lAFrjh0)gH@)*Ip0Km|o6;hojm+$C=10FOI=A_Wm&2k$nb)=+7QUCc-@OVp z?B-^Yp5VJEkJW2aMrh>zH?2&e$@~$f$2{HyUs!SFI&BN}%=1vb-z^Y=hR7j!nE8PTZV#1@3uBGL- z5HI02ptm|+WCDbLxZ^smoU5FmEO-?w`!CeJ1^M2w8%+kB>gsBRlbQ~WhBPmux*x;a zlD&W*!X`=Ly*e+bmOnET@9@5~*1b56y_!}t;Dl=$?CUmo0KSf5wfX{!5C8xG07*qo IM6N<$g2phI0RR91 diff --git a/files/opencs/dialogue-regular.png b/files/opencs/dialogue-regular.png deleted file mode 100644 index 933afc595220dcbe46ba10a4582325a7907a300f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_WYcr-bcN;5 SZ!iHmjlt8^&t;ucLK6UMEmtZ4 diff --git a/files/opencs/dialogue-topic-infos.png b/files/opencs/dialogue-topic-infos.png deleted file mode 100644 index 6242eddf4f0858ab3300580784fc9ef193e42c11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FST zi(`m|;L;#NzC#8)IZUq(o!NXTf5S(yBPaAH+4OK4DhR&o>A7UK)-ht$8>{E1Ew{Z+ zIBXzN^;~9d=2DJ5&G$JXIM1BoT3~ux%beSpqjgn9@VTFs%nA>qJYDk1=oix!i_xbJlLj`N(U)D7#Qcs-tSlrx}w> zIDfDi6f?2Za_oq-Dwn&UA%9=?q1+PlyLR(5!#Ef|G3*havwUje67@fE56gZ0%O7jc z)UaFp?ZMvn$vumuihcHozUnIwKUOa}r6*7QCU@zL`0JSyyE=jXW$<+Mb6Mw<&;$VZ CHFqQc diff --git a/files/opencs/dialogue-topics.png b/files/opencs/dialogue-topics.png deleted file mode 100644 index caa6d7e7cb15c81562831671e9c04d5e06a4a284..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|;N4(PzC#8)K3v=9Rfc`y^V`E(=w!3_rxeFZ_Cr2LUEVDd>5JI(#>6<;ys)On z(0a!Ad5N2!+FY13`9Rt-#!0)cM>%a14t2bQh$+S(6oCEG2WVCo7-OPbundXxZ0Wukc_xTgUSQI~(jL$`u^ouiG5)jz7G| zaV9VSL6bLgAKLDD8pU)bb#)4(!#)N3L(w5`cCV8zT(!O7o4-Q*!|zL1uHkApVeT|h d`t!=YjE_44d}XR8wE_Lb;OXk;vd$@?2>>MvZcYFI diff --git a/files/opencs/dialogue-topics.svg b/files/opencs/dialogue-topics.svg new file mode 100644 index 0000000000..57dd0b9444 --- /dev/null +++ b/files/opencs/dialogue-topics.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-voice.png b/files/opencs/dialogue-voice.png deleted file mode 100644 index 1d67745e55293ba97bbb01e45f0418ae4458f278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FJQ zi(`m|;M8DmzC#KEtc&tus@D|pmd`u7k=gW<79 z27A5>?Q++YpIMxBVXexd4Cb@XLmCn##5T?}QT4rK(WkGvUeb)$<;?A5XVYmJJP`*} z&Wc`-kkt4%Kf~(4#hmL$y0X_;9k839bHHV#_PUKm35MKty*&N(9gJ3Li@s)UD4Du( z)8X$8*BNu~&(xlgVdlYf;APVF(~sQd1@2XR$9mRswtKSL)<4mWKbqc1INew}rRSJT q@@WRyc@44A73H#XXIypt!M5&q{5KJe)&4*~GI+ZBxvXn|be6?^HEU#c@1Bo-3uA zGA@eZ3z}tFwv^s2r>aO;QWnS~y_N|K}y$Tiv( z{06qbmOTM?xj`L+E=NCd> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-instance.png b/files/opencs/editing-instance.png deleted file mode 100644 index 7349e8f66d16e18748f4e8b9bde1c84fd442395f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 986 zcmV<0110>4P)CV&*+YdCX})M$nSl?H356&sOe$6?D28}%+6R)*OMzJ!l%|CHCNY+=(afNoFp*TH2ifNg-ujvj%#1t&W zUJOJZ48l4Rrl*8Y}TAHezVWx7}st zJ2LZlMD)To%+Jj2%}nAE=-o(>nI~{J&f*T7%*=5mp*P@OY>9}^@d_5>?TDD%Oa|7V zEi(r<&<(|^%xo+72Qe};&oy9bY;kOCOG`^zS6A1B|I5Uc7s){1M(h=%Hez~>cq=C1 zqXtZUTrLsuGX^&+gs#T^vS8I>0~TcFfg0I+nDH-+uEzH`*(?Xnm1@(!#)#$Eotf+E zW_mvcm6oEB@w4z3cA@jKI}j25(S=jErpO+`z|4I4ABJs0dxLki;;GDRFNNVuGZ^TP zA4_ZxKfe-Z=JX4Uc?VB6kgvd;@_zt+YlZ`TN`rP5LyB=vUuZyC#D0y4VKuYA3_EM` zrC(iELU{OLc8d#PL^;vU?XnTu-l7u9B8Oc@$s~fuZ}j$fgf-~DW#)JFEc$N`r>vB!!x+4$PQzBX(PVEH<+E7 zN9ywGL{l8d%pDOi6I(K~Eh1(_#Ei^bT@t&t%CwY4?>5|wwi32uBBHY<^!ngPv)k#3 zcmXr929IUt;R`%JyR;6oO0P7xRI|kxgBjcyy!OWag zM4PcWB1Ynk!pyYp+L5(l6@JIo%v@Hdf1qq?#?|ea@58*3yxTH!Nw;n4ixY`f3_l-A z#rS!cn3-oHVsuIHu~>&qnc2}JP9;hy>_k^)jwmbio24^tFQ*iTurxDw_sC4O048i}|4DQ~&?~07*qo IM6N<$fb%7 diff --git a/files/opencs/editing-instance.svg b/files/opencs/editing-instance.svg new file mode 100644 index 0000000000..92e20fade3 --- /dev/null +++ b/files/opencs/editing-instance.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-pathgrid.png b/files/opencs/editing-pathgrid.png deleted file mode 100644 index 0ec024cadfd143c477ae6067bf1833e3cd0e44d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2297 zcmVc8Am&1B0ZWnCTKwGH-0%Z{m0-`d3umnOJvP@>a zd;3Q|VjKto0X;n@&wumH+mPu8GVrvpJpkiQOF$qPJ_i_UoG8Nb zBmvV-g^ZN4os_b}DQYMZwx_*uq5u`_KLM<2z2tWcrKxz@utiEaLrU5H1R9=4d>d1v9k8rnfnfNOQ_esj82$iZ^*b7kh5@z*zi9c#Ok^#p5?wk-g`D*av(H^ai?FmSqEIt-k>*%d%R#p8-|_JAtlXvUEcF z!O!!j{HXx*&WMLbD-IQWHIg|2%LI1dN_i-Db5>#)}P zPry|`U#;~cTI)l=C%{}F6*vzao4Rbw-0P|n{U;<}zoGTfEH+z0y|6tSX(9tg2)wa6 zQV#E`&Hmt?J;s~b30MMr2#f~W0v9T!YU9ev2mA|21YQ9y6GDt6dU$$}+8m!-2?6oZGdad;WU=K#;iC9I?NY3z- z9lgd-<4eYFWZeKB?cj^ntoVFv=73{L`IrMpN1qVy{3`!IUJ<2P7ZI~O*v7#THW>$Z zaPIn<#F8=>w0n2;=vTp!QhI@{#+ZppsmI)TdZDXdNIx8!WjT==p{rIIQimv|*1O+7 z35)_dtEJ1T>Yt@P+pVJHo>1O+_NU}Ftm$>?$lAS*w2voT-KAjA420*g#`Y`XPR^mg zbm3I5Hg?jZCMmOTMd#rh?$C|)d)CwDi#OSwH=$}n_Z}UQ;-Fg=>s@!5&^Q=5!EXj= z%Udk!52R~K3tKOb3)XW$f1sCA%Hh8zvFGw_2Lfwnw65$_Kt*~F+V5URyY2I#uKXio z`xheYlm`2CPj(@te8!DN5imz76#-zs@pW5Rx<9aGQQM@iUdGsMspx$_m1$k(Y#!g` zZkNOgpiC(>sxi51?s_Ki^NVgPL5N%cV<@@!_5AK{zq?k)iI@ZQP)c11&}-V(nJN1| z8P|5>M2ww6b*Ed{eZkNpCBw7R$Pp2Dj0OA=7!NG4fFnQ@h`7EDz?crg%KiB1O}>gP z>>99`FRopLRd?j;+~QmNNhyy=DOUoy@#OCX-;oYI6}C4QW55`MZFk#$`IG;i6j!oJ zsk;EblyaMt@<_+!w+(bG@2>3y#e6w%J_!{iv5y6W5)&@tX0cXExfQ4ce&S9xRw=atVDO0BU)pi?)G<}| zcYWPw9J(%_uz1&BM5&Z(fsIP3o28UCFa&rN z*kDy}-d^7Km0$17?t@3ij&ZP#WgF?R@{ZSZV&^u%r7oFSN~x&Z1P<^kb;XLgwJ-aG zDA?!qb5s@wD*?r)rw95{^s!qy)rehM2A4(|`fZN zBRg(J>g#qGuo1WxC{Rkh9|(pY5w>@ddlfuEQ|84u(fdM${+UwBK5mP>1Q_IAmZqL4 z;-@7bPRPT+EkMzYN=Xus41oscu?=tg!UUVExs zzL$XGWaI{`=Ceo3`p?fU%jhwYruKkkld->Kc6RaAYn;~or#H64@5|w|1jO5~sM~jU z#if%IzsmU;j%7DAWsYUzb)saKZJ_;{zr>6;t*9wi`B4NkSm~gvl11J2iP6lF_aEw1 zKQxuzAdC}TY_)T>`*OMlb4{7kQ9oecqHcP5qF+Iie~ zd}wbXwssyk8|2TL02hxLCsuBpSh;)G_`iHDe|I1lzN6(2iWan>1uba7*~0$-e38f_ T< + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-movement.png b/files/opencs/editing-terrain-movement.png deleted file mode 100644 index 40777334bd2102cb5ca7af8e8acbb53d8d9144a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1745 zcmV;?1}^!DP)ZU)JZy0Y&#ho zRFY$*pha7-wia5)(c)BRm?{NQ1KcBH~i{TM7s z3P~Et$7Hj6&*>j~c3gKNrX@K1vCqu@vG=_9p67kv=RD`!3mIgPK?WIQkU<6)CbZUt zTI=xnoI{-_;$wjIz+AurF945;NXJOe<&7}$0_H6KOjOM?_ zIB`vg&(11PrB~;x(cv7$;cjC#ci5ViE?dK);U}VoyEuV0j(9Ebl!(OL*tcC$e~Vl8 z^jp5RWSqMCrm#m|C}BbUad@@M;47F>?9nv{E!~DIauw4G5)_nz(}RlU&cM9TX=@Cu zuOEiA)G#Z48<6kZK?64zR<0KVt;kyRqndDaA6#h*}0EwwETma4xzKG~l-HY*X=8;75tG*}B`vJ`clroo40f8Q-f+;cVnUk9!fkv?~= z4!9e*RYdlt%sC3!rIf0));w(_^+koeR#Y(Art+g${ zcAzNTewEgGldHyKz%wEeO_}7=z;@5`uCvzSD)1BF9T71zt91Q_Nq*Jzf)DhrfS83< zZw1T`d*p=_OWz4vUg@{jvA5o&CGiX}RYYdG|C51bp67iFz!xkC67kC*sVT_q(jQIjk4m_H6{K#^5DiISOwe<>=2P}ri~W^`+*}O z@{KC3{nG(e*@~YoDD=h^g%XVCjsJjr625}H9q@Qt@asa40$1HNz-AGNxkKPApi)Fm z4k5KAuBbJs#_~}%n(O3q8&E4E8@1M7b$0fkh&+{k&+KabZl7i1+6lg@C*`ca$vLTd~~D_bIFd(1&#nW0o5Y%$Mo1Ltt$sCd&l{T+ABT6 zIf=a?iI9TMLD>2)LrcGXE~L2G$>yvTPlx}%BoK*2Vv$Isp{%UzSzszq3cOKPR(3cN ziKHgFQbApo;z#WPO$WMdWwEE!Ww{FF&P6c;?+w78y3JsZh>mM*O^dmG}tz4FUIjoE=_hpH)(&b&(^!ZX}6+QXjUm nT~{*5AcG7t$RL9ZE=2qnRg&Tl5w(j<00000NkvXXu0mjfGE+`o diff --git a/files/opencs/editing-terrain-movement.svg b/files/opencs/editing-terrain-movement.svg new file mode 100644 index 0000000000..6c1417fdd0 --- /dev/null +++ b/files/opencs/editing-terrain-movement.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-shape.png b/files/opencs/editing-terrain-shape.png deleted file mode 100644 index a11bd95d54b2affd7f555875cc1e1a4f19cbf6bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1399 zcmV--1&I2IP)^@RA}Dqm}{_3RT#&AYrpQ~mJFsQQ%7$#<1&U+%8X1?ViD0K zUFd>|G`gEmI5IdLx7?;0B`L{=`lk_+GMYwAnxWVgrNop}hQ{UeuD(2HX1C`(ayiWT z@czH;y`Ht!v!3U_{?D^F6i`3`1r$&~0R>b|c<&o}@0*^@J=|Gxya7-G^Z^3!CNM$L z{>t7fs$|FOd++A~G4LJGOwt9w0br~5zSMhvewE9BMSL3YAg~wcAnBKsO(S5ZbFN_s zVJ1*q85wbvM3vrlfHlB4;CkR|U@TAuJO{J~RsmB04~zhoo+-8588|)}xEGi%X-sOo zC-5F{45(qRdjKmXg@oN?;4xq}F!^*2;!ZQ-y?_sZ%Ylo5eZV%L9dJbyMHzrxE_V=! zqA03o&j6nTJ8f9mlZ>SIzPs^s9`KB$GVgtLTq^0Xy_*RP25yzKHnC@IV1;w8YY1TmFe#Z4mq!jufQ`U-prdhqnQ8oB z;C4wX613QC>heUojR1Z#8EsPTXkvUW&;@8K=^uM{9q=yjGOz>~;+%UZgzyEh2xyd+ zU+2A_Wzm=b%$IaDu}LdnQ4~d2hY+v`ya?=)l=I%N1@=lBm~yVRQEw4Y7w87GDR(qs zHy>y%sk1#70i&ZRy3bT=4KPB|j>Mb@c+&38lE_kg+|qq4r98zz`&=&9BZSb-##aFc zz4z;ZmU*170jveymNZ(@NMJp%(R*JlX)9?MaGv)*_TC=?J_L5>a=A9ST<%)C)MHcp zuWr@;IPiNph3cA2-ewiGj#bpjCTB-ePHqSO0*Y+0k>$Mg-Y+%EbW6=y0hCGVZO^wD zJuk95)Iw78)SQelV>B=dIBb5-1*S+klD@=sfr-FKV6mh@sqx0Zr@$6Tci9HLj4EH6 z3bmBfC6BT>lhHqb+7{hOz-&p!tqJS~bd|Ix57mZRP^Tq@#Z@4qb&Nj!fl^7+z4v{s zz&t8xPM$eGIp;16A&iw&T*12Iopa+t2;WO8$}?VU{C)%|cY3_tyJ_j#ax#UioLU2% zb4^1Coh5yi$L4*zL&JeLfX2WLl73EPZk^F?m=!Vy+$iZlYQ1ZKmG&Jmy__m%+Dg>1 zNc00{0W%~8@BK}{Vx!xrg#8%nM4tlwwrrM2nwpZ)epYKXc<*bPOpmkXt~7o3Dx;Yz z&qyC&3ve^gQ_|`@wq2cbYeNVC9VKnbW7El0Z8^~0Xmi4dr;$081H^HBJdWd)*=+VD zpfzwAur-^_Zi?ghKU}w_?OE%QUd?8+b>lc*ld!32m$r*h<_<|8#c^DAE>FY zW9i=q+%0Lh*&QZt&CQ4Pc`Ca>yt7mzsn>IsK9O^FeslKR*An7707Qj>tatR zhgI#jN|IW&%<(Ce>G-+&u~oyeQa}L(6i`3`1r$&f@h_U)TWhmzf$#tT002ovPDHLk FV1klJek1?@ diff --git a/files/opencs/editing-terrain-shape.svg b/files/opencs/editing-terrain-shape.svg new file mode 100644 index 0000000000..8f16b10dbf --- /dev/null +++ b/files/opencs/editing-terrain-shape.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-texture.png b/files/opencs/editing-terrain-texture.png deleted file mode 100644 index 4a88353eeaac24d726a6a9f1e9f374e46149b095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2100 zcmV-42+Q}0P)`5VVS=bX89oOH}v?XF=|PH1F?2`lX)Z6vh~ z*^PuEUkGYi5oG(Pi?fu(x=O5Lm)*o%D~Rk0SueU+P}GZ{3*A7o>844I)2P!ljx(O~ zf3g3We`dj6TDFDxygg@r=llGg=leXr-$0l!VZww76DG_jiwTJPHwuqTOiZ*jG&BV2 z>gv?Y%*;w4=95<)6B84eotQ7O_|? zkw8Tt5Gc;e%hN4ewhT#dnf=^ zRWTZkaR#sM--ENn=y1M>QAj0o06|dJ@QBY7&oRgEIi;j*CJ$vxr z0ga7~+`4rOK@jkGJgi>5ntAi)q0{LA7#JAf+_`hq*4CoYXb=Pev)N2RK>;Z#DWSD` zdV08c@uJe$*jTM7O7*Ezr<{K&0~U)V(rh+Y0f+T^eb%^f<0M5<5Jiy}FJ7>D^Jen% z^HEh5Ns>?$g-x3_F?a6VAtsh2iR9#DCQh8l#fuk_B#GA6R;*SlilQ)f>{vV=504)| zrnR+Ic=F`Q3`J4O%x1G}Hk)s@wY7QwPzK&-P5{5(Pj`1W8jXg^%1UBmV?(CgvSkY$ z9UXW)9wtnfKuJjn>FMbJJbwI``uchKtJM-8 zACFe6{Y~8OF#Ee1h|I{ysM2b+hdmxomQJUWf^`kxcDqSSOQWu?j?~mt;^X5n7!1_Z z)NuX!bqodrdc7XE+f7qb6Mny+`}gm&e*JoSdwYq9h(HuYo;`cUix)4LJ9jRhfBrd> zCQV}T;>C1!cJlDy!y#r@6h-Lk>zm>C`^(0UA1^<8^k|4jgtyFDv32X#VvEJ169j>~ zckgoO&>`;JxkF@RBmgT`tYFEKC5WOJTpXLt#)%UrFquq4c0y4UdV713Wf_e|Gi)yO z_V$vSo6Fj@YeVJ_1Ol|Qv`|%5#fT9jaJgKhq@<9Smq%J!8lot2L13+x-0V&-C~AQ&3QV#bOzz zsi&uhef#z)r%#{Wr4dE(_1(L72Qo7=hXgP%FuL&3CMG5ZyWP&Zb?fl^{g};W zGBY#TyLT^%iHXBL(ACw&&Ye4%Jb5wz-+%u-Yu2pci!Z)-_vk@=eLc^gKc}OkgM0Vx zq0wlFii$#2RcdQ%Q51!ooE!`W0|0}AgLHLuVYk}@f*^DUw?s}#N-Cc{d-kf*(o*A_ zH*ct^siC8zgHfYK4J!?&)5(Sn8+iKkDF+W8M3&`H-M)PJlFG_Tva_>?{O-(|Gi=_x znK5I=0O0j{@p`@F<>g^AnW(9$Va%8@!?NUZxrm90p}f4Dh=>S&`spWMU0t0OumQez zyO2C>+O+bhsHjyVM~*ZGyHAinAb{KLX6MeGq@|^W1Rp=#jn5q%J}i)>FetY2?u= zo;_RkdcEbTsi~`qii(U=r%nan$&)AS-o2YESFQ~4ipS%@WHPaF<3@Bk9hH@pbar-z z+HtUrPMn7;2^E7t?byb!*~1kZEHkCgzen9bH0y#oR*W4lFH}InX@W8 zJKI=aU;o~k4Q9sWa-r2~k!2aZUO(&|pU;ORNvvMInv*9_`hNN4mykLC?)!k8ot<4i za^%QWf*=eryQ-=<91c=bQ?XjD==FLkDk`{s{W@`RaYMSy|Bebir_*V@diClb68^C+ zG%`>QtP%vl==b}Hjg6(Ww3LjDjL`H|Rh27OuJGfJKcXlK8jS{3RegXJur)U~`}ilZ zSS+%|V)YO=q`YjgAcNU99|H)nIzyA7bIVC0Khr+@__syF( zRaI40MN!my_wK2Mg@yfziHY9-tqCQO(x eVZwy@MENh + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-vertex-paint.png b/files/opencs/editing-terrain-vertex-paint.png deleted file mode 100644 index 2b3f0beac06ded34f85eec3c01b2e6ef37e1b1e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2545 zcmVrQ9s?yL|X0Rk9VgeW^f_AM;I z1yLeQjSlKK1ENv|*2prH%8m{&E{Kk!BghCO1QDEoVW)x$2!xOzBqW`5x|2?KI{mi$ z?tB==DzTZTA@o^8+$hS|D{KKUh_E(zRb z14C_N#yZwL(ADwy@Ju&6%!Ug=1BN0a22A!7tJO$sk9UgpBE@0HjC+TG5a7_U6R;$4y4NQo6aGJ1ca!>o* z@ja7~5bFXQ?ZE9R>_TloIM=9R$jH%1NlC$>g9qX9c#xNuhxqt-06-`dLTPC!Y&IKm zb8{o!#zyJn;Zk%kWb{l)hUTdQtF1+Y$A`VO4cHe7t=bFrGWLAZ^b>!h<7{Kcci%Yg zK50j?Q_2wtpxY8a6Eid~kJVpzV<$UX-zJV4Ga5~Pe|XE5Eh-_zJbU(RSgqD?qgX5! zWMyTcyu2LSwr$hgE|=y^Of;QuY(nwT6G-dSMQ`DvQRQt?8p3U+G_y2#80bRYIV0g( zR>lcbRLMW`3rvccHifL6+{g0R(Tkl9H0PIdkSnIXO850II60uw}~@WMpJu(4axsQ&fcMQ>MaZHpA=j zprW!8rALqAk-G<=vq53hH9)WT!W$0aK%@l+b$R`H!k5<+?rHij1}5A4+{;%b+sG43 zr&`g!8v)FAa2g9@PeN5aRo*JPZ2A4K^O4fAGax#^xJ6m>>Km5FfAhq*B@+w=@$S3t z!Y~Zj?RMz8j{EPwA1;>*0MOjrj8|TI2`@hTT*Rz139W6YsjS4--8;imGqZ$tvTjwp zK9FDtTBsGLiHg^a2q@?4_6x=`>?#i2WMHyAZ7^T`!ccMU#J+$<)KTL%VgDIT9@xWT z&CYc2vMV6|h=ltl4uIrL2h2%eVjN$&;Y~7O(lqq$-TOORq3b$k&U^@&nVqp>#R>?* zcdNCwwiespe;-fHoW+>GNnnkB$aNlhf7vOk5NbtNo$#;10plcBg>DQO%*`+niUWd* zeN5eONHCUQS8;%hnUJ?(Vz&6Z{Cj}*ZVli7=NyC*fB+vK6Y$nij*cz?Ge&kmuim}n z_|$G@65olw{^oYvJunx2`}X~w_{EDBVbaum5JGUY^bi&1DCAT-Y+AG$*E#|V!G*O@r*HS-n0>e zhL1wdfB^u2hDJYDuUbi;eeQW3fcWpVxxj)Q1XGGDW@rxeNBut*j=pLbcpk$JBMRwza;Y9ym2P<=|TZ928Iv|YD+`} z34HwrXUNbIBaky-2pDz1Q}07jVIk}`8{94zukp2z2MT@#UzkBR07?LhKoD!8I8e9) zRF8n^I?69qV&;s8(Xm}R5DKB>(|tI1rVIt$dm2(8L=~?O2!^1yhK(wL(M?(_>cAJB zqQ+Y6E>`}-Jy4;0uT~s5b3SWjVIwnpuCsxuafBqtQ(V3t(O3NCqA+>FJ;=!F0)HDv zNF^}GB%t5GVff;-!rR9V>JC*S&WK7X+Jg8K3#wZU`45K)n^0R+{5dXNW5uTM3V+iM~CdYpsG4_BBHwD z0zCWQQey|rFjGN5%b~42b>h>cLzgU2&R`Y=)K^`=wS8OAb$EecGsRL?TLV^o1{ba# zk(>0$5>UGSK+zuMmNs+kNl&TU5a)77`%VvvopRbos;gigw*WDT$)JV~1AxK+m6#Fs z`yqWi7X}v)$y;SOQaeyHS75nOw1OZa=x>Bt@;WTe3}{ylDs90l%PQr|Yp#|yD7Uqp zo;WnyMs4mF4fkEohwmytTS^u;i2^Y=hsHP@m4^-6#X{=u_Y@4#0{6-Fn%r$131y8C zMG8<#p=&Cn#!A>vY*uK?#U)1Vk#)siL-|<;n*8pN^t8<7ddigf!Cntj#bh&F<%LwY zr=hyX6v)&B0N@5=S=C1blcx;cJ0E~W3Bh@Kvm*M6mkD3gulYFmFY| zb&q)_kUf)LPr3udFp%oI1ou~eR>Z*3r9>%TyX){z7k+zQC2lai7bHC?bGe$FzknJl z#D + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/enchantment.png b/files/opencs/enchantment.png deleted file mode 100644 index 9bd54b8f0a97d6bad0ca2b8303eb8771339fffed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmV;X0a^ZuP)`EUcf$;B8Z*% z>};*X&LYVLY(x+QE6*WVi6-lB!X|G1@DyAKJNtbzv$LBGf`1O<>*F}yV|{ImnX7KC zeP%i=%koL>zJnJpXUM~T=g*bO6BCxZiW|*&po$;l5e;R(ODZ%I=4N0V$4Qu}T8x2XR6gQXh Q_W%F@07*qoM6N<$g5>ME`~Uy| diff --git a/files/opencs/enchantment.svg b/files/opencs/enchantment.svg new file mode 100644 index 0000000000..02849172f3 --- /dev/null +++ b/files/opencs/enchantment.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/files/opencs/error-log.png b/files/opencs/error-log.png deleted file mode 100644 index df698d14531014feb9f4bd4b8f6cb7e0a6e79fe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_pf zf~Tj8V~B-+?_^)^!wv#%!mLx9az&?fami_EaEbY^tq9Ps@(eq|wP?W)Mx~~T&#%=v zUY(sRp0D)6-e}b$AYYtbYGHswzt4=u5fR32!1AmELWs z{eA3L=(WljtVdH$X{xm^V->5+IK=Sgkn)^^oHhsN-3>n1+t6rjvR{>XPL~|}@x%6T`X{OjY>=$&eym3zu zI(3&ND0F-5Ukg1WQH_M2m)$Dg|8G&?`1QNr_-kbx_wQRR0lw$cZUBRx!PC{xWt~$( F697;y!(jjb diff --git a/files/opencs/error-log.svg b/files/opencs/error-log.svg new file mode 100644 index 0000000000..1feb39dfe6 --- /dev/null +++ b/files/opencs/error-log.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/faction.png b/files/opencs/faction.png deleted file mode 100644 index b58756bdc0c2e5edd09b119ba45125e99865a94b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmV;00eb$4P)Xe;-rdM9~DL6@;ujZ9};_xF|BVh(9Rstehb2mk|dd9PotQ3O$H6;bE<^S(pqsuru;sjPir(dKbhltOh0n0V%k|NOy|KU}wdjgE ze**TV!H=I>vAwR>vMT3L}+_PTX2bpU + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/filter.png b/files/opencs/filter.png deleted file mode 100644 index 55f442377c439062fa99b406f9c858c12f2b0848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gs& zi(`m|VDgvGpMRfcVCm`UQRvF}@ZUZ{nwfjb!JV0xm&v=lX4Cre^=m3y)US5V6M77f z_#GJYl6Vh$9375~ z+{F*273}4oq>CK=VX@&0V^H%Mx9iOhnMzn&el*`Fj(dzS2^3eyc`WZZw~+f diff --git a/files/opencs/filter.svg b/files/opencs/filter.svg new file mode 100644 index 0000000000..12ae65d398 --- /dev/null +++ b/files/opencs/filter.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/global-variable.png b/files/opencs/global-variable.png deleted file mode 100644 index e1642ac355598da6a2809471b43133e22cbe54a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|U~+;4YtIYE2d`hBW)g`$d*q17hxhmQ^E#C233M}mh72z!W5$YXnymt!?!BWKf*D_5_+H!?OhU$ttL9edHzhHcXx1a$|?WW4rO_}3N8 z7|_HyT_s>KYrs2o1DzcWIjJ$PHb^mCS-^GW0(;3eIRl0F%r_@@m@%x7OnD)ru&piY z6iZvHct8y6Y()({2CZ2fAzvgr@)-?vx?&sFF}&Q%F{PZvuz+a+Q^6XB%pC^}4Cl;h c+sDEXyxrti4!`L+puZSAUHx3vIVCg!0DN0y;s5{u diff --git a/files/opencs/global-variable.svg b/files/opencs/global-variable.svg new file mode 100644 index 0000000000..00221d24c8 --- /dev/null +++ b/files/opencs/global-variable.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/gmst.png b/files/opencs/gmst.png deleted file mode 100644 index 2605fb4b963e630449816116c7797b3f33375514..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmV-Y0j>UtP)f3SO9Tl1deS-Kw<-~48kGd zy%I-u3JR9M@$bE7*LARA7!tj6j;?7bE$tnAzcdR#b5b?P%tqx_N@9#2sv?*U^E}@| z2z&4SeH_Ok)c_D|nx;}@3?{lOemi=AGA`!JOFh7pL}E@Kk$d8OuJ7~}0CJriiU5c& za1ZzsV{NMygt=zIaL|MDHURbhY^rlmW^QBvYIYoK3nedMqtD6;hymc<++PpicpkaL z!`3zS833Y-3Kjv_V`%wq^-Wl(834U+X0#vuGh_Y@0NKgJRemv=X$iD!$+f)96a~WG k8f1N?5c`_T{{tdDZx42HY+xwFFaQ7m07*qoM6N<$f|hQAAOHXW diff --git a/files/opencs/gmst.svg b/files/opencs/gmst.svg new file mode 100644 index 0000000000..2d96e4cc39 --- /dev/null +++ b/files/opencs/gmst.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/info.png b/files/opencs/info.png deleted file mode 100644 index d7bdad6cb1699891de65fecd17cd3708daa198bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1234 zcmV;@1TFiCP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000C*NklHfC5|6kQxOCK;pF6$>yOUvN{{oXKg_O7%ev#Js}93B zx(csOlVdV55i?Q5)K$m3?{^#CuJqxy*#3C`_dL(*_4#~1pZjhE0P!S`t5jh^eD*-N zcFV7PMzbwE)nt`x^oKq9hWoP~!P$$lB*XCPgHzb~6<|0rjV;}Px@`!u#FvkGBjJ&N zJR$GRmHRHhcpTtb{sw9I6M;>3JoolrP!=435SNwWsf1?&8vS7DQ6Q|%2`BY-khZB6I$mXk<%-G=2IHYDvGLE@efXxh&qe)l=VwOOIwWkpO)FQm!lPH!ac z+X33z*r@8`FnlUAG`_{R6U=n}}?_frzFX2;XrXLenH7^asJ-@l--g zAoYp5Qx5pdw3E$~AeQSO<_QqfRgfyW4VAxv7{|fA^cfRKol}8DwS$hz*`ABR$)-!- z^q=ti*VJc2B= z{Vq6pd*+wTwF| zXK+$93~tQ_{u2m)^&5nk>H4|gq;SZ;0@AV*2&o=(RLgW!eVWg5bca2Dy?I-%SrSHiG1k`J&$cW3uW^)C>kdqZ1kSXtwbQG#ZmL&sNh^+4%TM8^4Su wuEH6$(Z + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/ingredient.png b/files/opencs/ingredient.png deleted file mode 100644 index f31e6f581301293160756054ba1c1f3a11eb40e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmVoHmlChIkX9kMIER zh5Vc(=5jexHXVStNVgMVKf=ePP&xfBw*RHPVF1lr@DGGTB3J|O0DG_1bHr2Sbps$> zft4YOU<_=SB#*CF5%Zkq=_`u|K-{HH{}4}rt$@uEW8*DkIKsIAGXU`b7q-Tkdn^lV zoO}y$#$t-#dBi2~P_^B`9^wH9NZ?!(0`R)~ z5JyK~SCR53{6S(3B_G5WIP)_79q@JP1~dNxpQV`q*$@=j(JRFbR_x{hyVZp07*qoM6N<$g8u2!4FCWD diff --git a/files/opencs/ingredient.svg b/files/opencs/ingredient.svg new file mode 100644 index 0000000000..af2f736e64 --- /dev/null +++ b/files/opencs/ingredient.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/instance.png b/files/opencs/instance.png deleted file mode 100644 index ce63e64ed66830118ed9c25e6b817be8d9f96909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmV-w0+s!VP)UGq2zJSnbXnj}@gMNyS!ybGM&f17qqb7rHS+D`Y8@40%+--0DA>#p^QU(d|223)fMBN$mC(w)k z4GvZmAEh}@Fb1OF2(ejk0HnZEnjQQCEEsFc_-E0<*y$JX(e8;4?*ff34gF>F=DUc>^9NrYBwnAHePvXegqyAk9fnD9R{FTuppn z(}j)$y~ah`(K!Qu0Ven-O30eiwe|+ZC-5(zmp~tw;HqP6`xZSy{wv%*KmnSxw(ghb z&ob;9JKJbI*%VEW;5KX9U~h&&@A0+jJdMG+d-I|G3k+|{pCe3}VE_OC07*qoM6N<$ Ef&wfNegFUf diff --git a/files/opencs/instance.svg b/files/opencs/instance.svg new file mode 100644 index 0000000000..fe344c905d --- /dev/null +++ b/files/opencs/instance.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topic-infos.png b/files/opencs/journal-topic-infos.png deleted file mode 100644 index 4cc4464897026d9042d46c8051d636549f86aeaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1G6o zi(`m|U~e!d-(dxw>>D3)7W6&Vn4;wLa9&W>tfPtd zS8aS>ExmC{;Ki6+sTUlIIa7A@Z(Q>Ai-xn8XVJO*C7dN*)-_Y+Dl}VKy9#dj+~BbL z;ln#ux2jp~DTrR4{PK;~`xZ|=12#b$%VS?70$EaSI~zV_I4Qku!3L-AU7Vlv*?X)f zM?uqfcy)1SS8^OY3XRXXoXXn!62%etxQq4ojuyVy<4PH%>7;_I}2_J=z_9{}X#o%~lUn1NxM~)78&qol`;+01)we2mk;8 diff --git a/files/opencs/journal-topic-infos.svg b/files/opencs/journal-topic-infos.svg new file mode 100644 index 0000000000..4351d0668b --- /dev/null +++ b/files/opencs/journal-topic-infos.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topics.png b/files/opencs/journal-topics.png deleted file mode 100644 index d4e58a288503ea273e85169fceede9099d0c32e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gy) zi(`m|;N8iu`3@=Yw1%72T33WWczdPcd4d<44|9F)rYTPP@sSgGAI&*^Mak)FXpvst zSD9~dJlVO`L9xeLe`N(8=1o51*8AR^>C@LK?mdA5KMvfKeSNx2#J0si#A1rV(j#Uq z=X`D*zP$ea;=2L`%^h#v_ncaiKD$nfmvPmb0yBjjYnC~t-xc`47v}o^5O3oj?knC- zF$Y=g1XUw~^-szROsOlZTz~Ue>4Go1Q#w~4JCgmx;blVQdwb#THHTlE6WYsoSGw$B TaB+Gs&^rvCu6{1-oD!M + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-heightmap.png b/files/opencs/land-heightmap.png deleted file mode 100644 index 5b460a0024e053cf0a97a0f720ad087d98025c2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_Wq3 z#WBP}@a-f=z6Jvhmd1VS|7^W)ypcn~Vn&AW9?9xRzBgir81~d;)TK@H>gzE1{`H=% zLuvPdP}zBN94?nBWv!am*ijIE-g0kw&%=K)ib1-Gp~r8DD2IQIe`p=fkrDD*L)ydt mN8HA3_go_D?Plsds9@O2wP?>w9q|gF^$eb_elF{r5}E*R0Yd`- diff --git a/files/opencs/land-heightmap.svg b/files/opencs/land-heightmap.svg new file mode 100644 index 0000000000..701b246640 --- /dev/null +++ b/files/opencs/land-heightmap.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-texture.png b/files/opencs/land-texture.png deleted file mode 100644 index a96c4bf8c7b8c1334857e79e33ddb9230423c3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;M%~8LWdPNR_}iI`OWcH&r74-SDXxte!pw?M+G587SGe0awbQ5alYG@JA1mX zFlSzPo6D7RJ=IDj3$!ZARN}k}Pbx789-Q;}_96Ep1^pIDHyRV#|Jd)DVYpY*@*z9Z z>kk{3q!`WLdQF^*H~Q?^T%O~PcO>N)|BBJcn|pQ1;lGjnFA7qX=j>Es5U~5GBK^Kl z=7>S>hIT90J15KLXuEC`3eB26@s0K$hY!|Yvxp`=!nrE9M=(02@;u6{1-oD!M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-creature.png b/files/opencs/levelled-creature.png deleted file mode 100644 index e6cb1f54c7235440dae1d180e59054655ddb11be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~ohP)UtAI8s#7eXfu@Ee@-ED*vf}K`c z2x4KQf|Z~)NuhQIkCaKa^#D@_1-n>iWuc9NM-g#%{pOjsY(NBcV3_0o=Xh`58pc1x z)b)U(3sdCaRJc_U4Vav?#JS7;>%ckLcAxj+Q4<)uv{*iB=c-A z6^3B~tOI=(64K2^x8Mihli*3qvR+E1l4*V6YDF+rT!rkDuh_O-hgioyrr5^zHu%xe zRRcQS19;e0iiaSd6Q_OO@02O{DRdWX5RTO3xTh-CkhO=gM`)30^8iWwKJ<}-Yph(L zklRofP7Tf>{x~`z4JtTA$eY+E0h4K`9Bq`z18}Z10c;xINdS%jj%h$FU<)RV7Qp+# zQxrc47Lx-0j=BRJxu8fO*U=gYDPI1j*dX^xbIXuV7*+n#ky^O})qrk>s1`0twhJW3 z7IM@NjT1{wsBiLMX3#8&b{^BaWVJ9sxvcBUg@Q-h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-item.png b/files/opencs/levelled-item.png deleted file mode 100644 index 3c819c56d0da9b019a056fa2088284d5231f13dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 516 zcmV+f0{i`mP)f*^`uVPz7?FYptr zQrOs8Sz4qJQ9(@O4@j=Cvk=kFP82L`BtZ*BNuzLgKC|vD8(kRY<9*+m*;_7~`E!cJ zVw%lnGh+Tp)6}H+TogsIVmS>wpFmn_Hk;>OEkol9dEFNa-aW=DN~Mw+r(4K$iIXJh zp~1JM8TW4kCY=bv23^gmi|_zj7hF%Efp|!DCap4Y!RC2tdCQr`uaQSKH_lA~7!88p zfi(wM^OgKQ48t*5pV|}bajcJ8D~{tiI9Jla-{1)1mK9%u4MFU0G#WCbfbD~|scjP1 zFmv2DVD)*XE!z4v{tdyq&F}EDHa^22+BoAHNCAQQ!uT7>xrS_bLGvk^Pf87}!z)Q> zPFDjhdj1yi1mXh17q9L@_YELDGjYZtzk!H%d@-D1YHj8D8=O+yz^)ZU=w3-{_$qd+ zx!+)qXZ!$IQIKiozhGiWvqb7#sY>3f9Crl>pFHC^qFn_cn^47IHN;Kwj^k*cUj75l zAS8EB!4QwI;Uh!xU|-7juV8A3D-c`da#=afBVP8!0{j5|jM1noWfZ3X0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/light.png b/files/opencs/light.png deleted file mode 100644 index 55d03bcd127699d3790a7161a24fea83a25011e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmV-`0e}99P)1R5%f>k}+xmQ4ogTY64=B!XkZ0v3Q7xbzpZ%Z)%gmGw3Y> zDHE{YUBS}U93agWAq0;Qq9E-0&EkN_?4po?#|$&`|MS0jujwi*2!f68`@dP1Eh}2e zbfcztL2U%lLrsq}2>?9;+NNoGi=UoF0Jwp@0qP2nxJ6FXnC4ko056{BJxQ%7iXlo~ zt-gQBnF@p%6%$m9B}PRP6}iMG8Q?MJQ<}ptEb2h!a#-9X&+~R2P-`Xuk{y!Q+N2&; zec}ZJ90OLwJ@X$Z;MlX11-W({$FjR;&hjh*l+7XeL&+ryXT;Ygfm}iDFa~s=B#BJ# zK=MxQr+h};G>r0OPQ8$5tX^~M;Dh+CT;hJp4ea02P-RT*QJc|sr9JuzA&IXjiYh;r eUIjl;u=pK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lighting-lamp.png b/files/opencs/lighting-lamp.png deleted file mode 100644 index c86517aa5743089b95ced316e29c48c1ffd2d940..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 953 zcmV;q14jIbP)WFU8GbZ8()Nlj2>E@cM*00SLKL_t(&-tCx8h?Z3p z$A5Qb>4f^GlEI*$PKw18Du@P(REvR04T^A)ZE7L4sR&(*l8SCLiqM5p`B9)2ilWO% zON`1^EgYK=g>pf5bo0;~j0bTlNS)LH8HS!jHuwyC?-{+h5EQF~&|0(F}@7|)KXl|~TQQe*sT^+Ju| zw=zSkxTJQdJF~f6^)|j$9f*&gjh(-k?e-Vy%j(nWVzpEKCY~8Y=c#A1gFU;R1m4JY zuT$NV8UK#@f_g-~7NPUCx>kKvy&G${2FWTm)|0@AZ0DZMyzzM$-#1G798_Oa4~E>O z&R2g9dGB~^X&Q&|%~7PPS*Hz{5%Lk>ZQ#jKRHp%N0{a1hxxjP4rFh<67XrYPsIUON zKs9!616{!KF?82NI=vZk3osnxHg!fl2s8oB*|=)JNbGF$7`pdECuMcWOn;;v1csuw z0eCEmR5e8A0zZzSdlp!ndGH3%8slN$-h>lKDLu@LUI7dQp#;1I90cx+qIwnh6j&E> zcj&DO`9ezRZaq9ebd>`_vte5;hZksedWLr#*Tifrr3F;5hJIN~!<9@>^v}wN*$d4VS-I1qB5K1qB5K b|1SOl59>{tmAIp600000NkvXXu0mjfg0iP7 diff --git a/files/opencs/lighting-lamp.svg b/files/opencs/lighting-lamp.svg new file mode 100644 index 0000000000..5d34d68817 --- /dev/null +++ b/files/opencs/lighting-lamp.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/files/opencs/lighting-moon.png b/files/opencs/lighting-moon.png deleted file mode 100644 index 36a6e9b5b1e449bda6101995cfb62a1e67ff1c93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1590 zcmV-62Fdw}P)WFU8GbZ8()Nlj2>E@cM*00o*!L_t(&-tAazOjB1F ze$P3#^tQEdTRsHC21C@yoTE<0N9J_kYDArxx?nQMHr-}|h9zuf(TK^WnHrtZWZBH3 z$u?!OWy>@J6Jn$UmkJC8Ma4}%CVmhRrmZWq_x7H%A1xEH1=?QBO#1xkpPqA`=e^H4 z?*~8_VT2Jz7-2lYAPVk0u2I+ynsX=e(ganGynnwGf&ycb+aM&%A@@`Sl(;4D7c1o2ttX z-10#l0RUOLwlu~VZA=5e+}-}Z)?W|p?F9fiB<}`8J}oO(yI{%kUA%^0KNLhslumYB zzI578afT2u>7)$ipj(H>HGPVXP!9|^k2hMX>j1z#A}6tl5P%l0eqZoqAFq!mkFlOL zTPpu)wp5-10L##q85ViGbZu!|bZpGb&MR%_P9HmLAFq&cTS%vm)FA{qU)y3rrYEP( zi5SN=)Eja4F!E^G+S18MDM@vL5Lq+JWcoV8yi%+99nk2s285C3k5 z{P2@k6l|0fO=Q&RbpGNugp2_uINE45d|$Y6Tc{$i;&%W5U`lG*9l>BMz!*pRJsD#y zuU6+zGi9A?JyUxVALc&lai&;@^7eA z5i4X_ZfdEoUuLbUuuX&j002_7al2U%^hX~qtNn@a9K#p`N?9+Zw0p=_7^7GKq$dQ& z03eWMxv%Hut=yl#+<9rd)(f@{=L$di)NC*a<(Ob$ED^p04e#bWr8RTdZMJv6-L>VX zVFliV4Yue7tEFrDob2u(18g_z!=9P zgt4c2LE!m)K-kmJ07#;A%69L5aa&9C-sLfjNGJw20`MAv`hnrlxC oddX6yCUaqg5k?qcgz-e>waqbso&W#<07*qoM6N<$g1ZCWz5oCK diff --git a/files/opencs/lighting-moon.svg b/files/opencs/lighting-moon.svg new file mode 100644 index 0000000000..0441eb3bd5 --- /dev/null +++ b/files/opencs/lighting-moon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/files/opencs/lighting-sun.png b/files/opencs/lighting-sun.png deleted file mode 100644 index a54d0ab12498af2b0f3117d17f3ff8738d46f9d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1804 zcmV+n2lM!eP)WFU8GbZ8()Nlj2>E@cM*00wYLL_t(&-tC!fj9gV2 z$A9PCdD|WK%*^f#&;eWHmODi&TC!+gBq1mXvGI-K7paAU))EtA_@YKsQvDzXKJ<%; zEoUkp7T7<|9QEvh(#=75sSD4h%klgV)||{lZfO|WxL~H=#ZgZet_?b=%@cGqumEE zqdyNzPb56w%CE_42gh&#o#;z(4XeWOIpq1^8&2hxE zel655BrgXCqAdS+XsfCEJI&PK{YDqnvkP^-F3LUC z;fGBv=rdEL9~fOszO9RVA@n3xcIi*r$qL`w@l?#*uSB2${IemVo&1f!9hk}WP;PG( z%R$Jzg=pKWY}ae?>l^RiW9?R4IaX>0*gMI?T$?4jejn7R)t zzmhWfaT)7@csD{=&=@1Ao}y7ZaSYM5P1&V=dVns(HQT`#5ZSH@6Gtw4E1{jdY$hz| zV*3vf*E}9ceJv)EHz5;Qgq3KW3@|lKQ0~Vs_j-uzL&`4g(@x%6C~bz)3+ZBNBO=}# z?c5l%I#+?PfZ#mBGeMPL_z-@jcNCG`qU_RhSG<|iPX0!Wdnl6LvMiFj9fUl$hRslC zf?CUn-=9V-S*L9Gz0fPMcJh&FPikgx`khjCER)Hb=6LxT^-abR=~Y;hpIX+am%aq_ zP19pdv);^BI~Ht!9^eto`}bi2(|W|3E^-(W+f-r7!%VK1@mmpzt_xhA5As3A)0lXH zhJU&T=+jO}qNS%p zfOim)fBlkKXhxeLr*pSAlJ%WgL&LfN@!PGGIGrI}ur|(gAY2SSGToMb- zV1pS#tyfL(?-N1g_$^j;9mwd1(kP~mDfbij{R4>nq510xX(Cg$JBH}|rC;u?G)n(~ zz@I~`1k)g>oWdV_WeU-UbZ0dEU`nP}A!4bPAR)Uj^gWA zrZAPqRiXOW6q<+cnzBcS3Lv zX5v=xSAz5)RzgHxhS(uxyF;z!Z%;h+)o&TmMyL+)q6_i3VK-q3xJNbt&lQ~EjRbXiDX2%aefE5Q!>_cYk=d_dm8^K7MS}~`*k8-GY zgXWOF5Y#VR3}Ni5+D;>)Qy3qDDv**3UAtV-?X + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-added.png b/files/opencs/list-added.png deleted file mode 100644 index 4da26598392003d3c6299dce9754157a4110db91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%y&N z#WBP}FnP+{pEDbU4!CrtO^9=1`@_D9F>y}A1V#(fLn>{Jj5-GU88T}cTYdWkmrQ?T z;84bR!$n2BafZR{jVBwY2EA!YOkgNdSkx*%qucmHLYF2VGsFF*Z0C&UbS(wi!QkoY K=d#Wzp$P!;nKK>$ diff --git a/files/opencs/list-added.svg b/files/opencs/list-added.svg new file mode 100644 index 0000000000..ecfa8595e5 --- /dev/null +++ b/files/opencs/list-added.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-base.png b/files/opencs/list-base.png deleted file mode 100644 index 336d4c59c1c75d3833f70a8bcd003bdae158519f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%z8W z#WBP}F!{^Zuc-@I3<4KPE}71Hvf;l+PST7X1=ka74L=eb1)3QeqTBKqJmh19+7&gX zB^Wa-;dd3B0mnGe(4V`9BK774`a&*;x%xM+6R$JwkwWp*N4 cz#KjXiEZrSZVbYXKwB9+UHx3vIVCg!07UFG%>V!Z diff --git a/files/opencs/list-base.svg b/files/opencs/list-base.svg new file mode 100644 index 0000000000..a050b15629 --- /dev/null +++ b/files/opencs/list-base.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-modified.png b/files/opencs/list-modified.png deleted file mode 100644 index 8b269a31d3c79efccadfdc2c816f49a8b85ca9a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 457 zcmV;)0XF`LP)GAh8oHtOUiMO0X1s z0l~&r!GML0ub?(!6Ks^klol$I7f4}gGV6D*?lQXsiG>%=&iy&}%$?aW=0B}>yKF)9 z+a5u&NQ30Iz#fQ!1<&9-T)L{U<}4>b^BC1@Te217661P_BX%~jk%8$xU4wWPp?0bw z>S{C(;nbxWVu$7x;$blAx~@vDMQxV&=7)=GjDNmu2rO=}DC7HcN7Dke2RJjV z2c(arludxYAqniT(5y*vA2_>mG;B?ie0+g8>B^DKeOm0ZHaVIoOfiw(pD&PJsH*g7 z_ERZ0H^EyFs$Rus7W{b|2Gab(k@PJ71U30p*fHactLJ^ljS!w0tE9`cl#K$RM6$^( zm$~IO{tkEqU9bpFsZCRBu=^abS|%V7A`>j~kjjPu`TsrQ#Cu3};!OnJS=Dj^l0ifs z)IL}TlR)0eE4TnBG|NPT8Db&uAKPG!e_?$A%cF++611oh00000NkvXXu0mjfI6TI4 diff --git a/files/opencs/list-modified.svg b/files/opencs/list-modified.svg new file mode 100644 index 0000000000..6efcab4cd4 --- /dev/null +++ b/files/opencs/list-modified.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-removed.png b/files/opencs/list-removed.png deleted file mode 100644 index 618a202bb9c7503e8aa98d8157bb57c3db3657da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP|(8D z#WBP}FnP~Ap(75Q35#5eJ>IWi4B=oF+Y<4k|Jd=PsV$xXGTblp8fNosZWQp{$nD0v cz{!b;Az%~Jq!PoTQlJ?Op00i_>zopr0P%Ap*Z=?k diff --git a/files/opencs/list-removed.svg b/files/opencs/list-removed.svg new file mode 100644 index 0000000000..157d4ab5e1 --- /dev/null +++ b/files/opencs/list-removed.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lockpick.png b/files/opencs/lockpick.png deleted file mode 100644 index 7b1865f50457b6a0343f95f2c686cf376a1e70f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmV-D0mA-?P)%d$vB zro97xu#ew?*80t}Sj&Jzpa`G2HJ}UQ7|+fC8)oaa8QidDkDYktEuO2kxic}#)`6}T zi8vToj^{-a^WB7BroxWC>$*-+6iJ$<9GbC=9k5;C1bBlTPexh)`4>QpE1KYzt%1*7 znD1HvzyWX!yaK-1a4UcqS2M=2mcZ9O_!|Hm0`Gu1<$GVub=5r>iy80%Qd4^5D_tZ$ P00000NkvXXu0mjf6UBX9 diff --git a/files/opencs/lockpick.svg b/files/opencs/lockpick.svg new file mode 100644 index 0000000000..b7e8ca1c3d --- /dev/null +++ b/files/opencs/lockpick.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/magic-effect.png b/files/opencs/magic-effect.png deleted file mode 100644 index 44b682bf4ff359ce3c8076379cf27ccffa46f673..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmV-q0h|7bP)lRa+2KoEs@SH42TG5Cp;C=F>5lI=2gq;(PM(4>e2 z=SgAnJ;T6yy^bg{lD#|g-u(CtcsF5XS#ClIpMu-A{b-tIH$lyZ>$>g?sj8}@pt*xH z47|wcx?8^U-a96U(EGKxU!9}NFa9g!BL&`U3O->g9{4lI9{fqZY)}CIg>FZPOGw}N zUL@Hy`G@O!AGp451Pel(Q{==1D@~$!l)NT(Fs{x5aHKY|71*9DYSVc~k#sw%k+)QBlC#AB{V&8Va@&b + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-close.png b/files/opencs/menu-close.png deleted file mode 100644 index 81bc986775089a2ab8b8113096c890a47785e84f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438 zcmV;n0ZIOeP)_eP4v08QYouW`+TlKHqY~h g-$r@ga{kZ#4H#~PogGl%VgLXD07*qoM6N<$f?Wu@<^TWy diff --git a/files/opencs/menu-close.svg b/files/opencs/menu-close.svg new file mode 100644 index 0000000000..6bd1e9f275 --- /dev/null +++ b/files/opencs/menu-close.svg @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-exit.png b/files/opencs/menu-exit.png deleted file mode 100644 index f583536fb695627bb749c8e742df32f37e845995..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;j%S zi(`m|;L)Iif`<%vyo1~x&RF8ZkbH=3ZcCYHl8VLdecLiWy^yWD(y6Q2cS+!%Y#@8Z zSM!a%V%(O;BsO`JOV0bbL}HSw`BWdBlPzbN0#X;7xgOJ;J>z9nzN|tI-)sxE$?|lw*K7*&LpUXO@geCy5 CkYic^ diff --git a/files/opencs/menu-exit.svg b/files/opencs/menu-exit.svg new file mode 100644 index 0000000000..e1256eab72 --- /dev/null +++ b/files/opencs/menu-exit.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-merge.png b/files/opencs/menu-merge.png deleted file mode 100644 index c76288ae168580eb28605cca6c82876f8a467639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_Wn2 z#WBP}@Mw@DUxNY<^VjMR;fMaatbV!a%-QCPCsLE&TJ4%}>Aw1B#;w;HZPGQD8ukf2 zx>Fs0vQ(K>N8;+q*)wc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-addon.png b/files/opencs/menu-new-addon.png deleted file mode 100644 index df137b2b204cb94cddfe6bc5bc329166f3bd0c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|;L=IFe1{ZxS{o~EAE>$cFN$M5XTG67GU&z$gXm3P*ne%c33&Br`ObTm(;sHX z2B&I1pA?od-|(3E`}>h>Z^Seb*T(MsKJ(4qc=OsMf!R|g86B&sI-#?mfc442-en9I zTI(~MChy4*O3V4?((D$wvpkC_sD5|Xw)DRo5Obi@&TU?oclyq6(>vU+-;`lY>2dcjb9C=}__=+$ cM*mmV)hqqBP323C1p14?)78&qol`;+0L{H`dH?_b diff --git a/files/opencs/menu-new-addon.svg b/files/opencs/menu-new-addon.svg new file mode 100644 index 0000000000..46aaa1e1ec --- /dev/null +++ b/files/opencs/menu-new-addon.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-game.png b/files/opencs/menu-new-game.png deleted file mode 100644 index 701daf34b179e4c2e507788fd38366f9d83c9c07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;iE) zi(`m|;M?Fpz6J#j*K1#|%-&+5~5+}}`+Zp=FKa9hVk!j1d-CNBj$lkWtcHpe!-tP?^(cYK51uD5Gc}kTEZqVBl zDLgA>p=XQoO?FjSQZyelF{r5}E+txm0xk diff --git a/files/opencs/menu-new-game.svg b/files/opencs/menu-new-game.svg new file mode 100644 index 0000000000..416a774d30 --- /dev/null +++ b/files/opencs/menu-new-game.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-window.png b/files/opencs/menu-new-window.png deleted file mode 100644 index 4a42da0d1288678a5bc4bd01a4713834f3aba7a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_W(8 z#WBP}@a&{$p#}vGSHs)$^uoTgA7tmVnEGvoX1z%3>!%tAt}Cx-oFeqH%A{SOp(AOn zXt?jRv)ayGv32JvZ#ABcopUPXz_;0VuQ|n6A9K0&wD|l2aS6jfwst<34O1?yxy3DK zydi13!awf=jv^LL@`8(RT)7eT%${Ml{*m{uI=*idtt{Qkm|Cl~`N^Y5DWH=WJYD@< J);T3K0RY~_Pj&zR diff --git a/files/opencs/menu-new-window.svg b/files/opencs/menu-new-window.svg new file mode 100644 index 0000000000..33262366dc --- /dev/null +++ b/files/opencs/menu-new-window.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-open.png b/files/opencs/menu-open.png deleted file mode 100644 index 3766e17549830ca741e42055f9de51faa0263226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;jxQ zi(`m|;M-tFp#}vGm)F(5Ch65K@NVz>D)x6iTdze%m~rV@Zk-L%icB>&T|bV^hKmsy{PJL1`?c#)bI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-preferences.png b/files/opencs/menu-preferences.png deleted file mode 100644 index 4644297ad0dd75a7847000ec23f824e5845c0662..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;Q%fF~q`u=_FgeLk2vplYKfC7fe@ae<-uT$iT3~EtJho@4%^2p+9SuczMr`2zYsN z?VtZwt(GqNkg;~^)^E8zmPaJcE4CzmEOdCk@QL=zR*Oykm)1@{T=zVtY0vl0No6|^ z$lX7BeG^x*)07%TTaOBPkK~VA-{g21IGArL|M+i`-ckvvC!zkq>6wkDCt@O)RmGa8<0ehNE3bfxb Z|CL*E(B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-redo.png b/files/opencs/menu-redo.png deleted file mode 100644 index 0cd0eedcaeb898e79a55e1f572c52256d9d5d481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gj# zi(`m|;MGa`e1{Aqj$UUIYI0C&WbywnC$Tt&(c9s(e*uFDyTfLmLKYo1jtHJ~g`W8B zIWKp$C1rm4R$G0)di`6Y^S`^F{;m_1$!!-kyL3#@plN<{nTYFqpU=e&Mgm3)-Z->R zh$s~OY9M~d^p1;H?c&h7l=#PCS+ z#x9>|~QaJOhytpOOywJKnq=q3<%szi> TOZHS?05EvE`njxgN@xNAl(>K= diff --git a/files/opencs/menu-redo.svg b/files/opencs/menu-redo.svg new file mode 100644 index 0000000000..f19f236e7e --- /dev/null +++ b/files/opencs/menu-redo.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-reload.png b/files/opencs/menu-reload.png deleted file mode 100644 index 2b780598c4162eed952e978ae557e465278f0085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmV;w0YLtVP)PGS7Qaa1b3$&sEeJkZubf1&Oe51JYd%SmYmz3wBv^o(wk+$D21{nnZwrGZXc+SW z?9#EOUGQ_ZZSO}>RD<6Z{?7v9$co&fRtdfkP6Xa(l2fKC!{2Ih3qDMZ0v}@AU{6Bz z4(jNO1#)#r3uRV}v;g-K{YpLs<(!;BrWnu@NS^$@qVk*tsTRUcr#b)47VhEL^(1Fbv;Z*HtX0>tt#fj9#%sAm8G002ovPDHLkV1nW%zJve( diff --git a/files/opencs/menu-reload.svg b/files/opencs/menu-reload.svg new file mode 100644 index 0000000000..aef27dd104 --- /dev/null +++ b/files/opencs/menu-reload.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-save.png b/files/opencs/menu-save.png deleted file mode 100644 index 4be88765bef48fc5b571e98b8c942dcca95934f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1F4L zi(`m|;Mw44p=Jf1Cl_Bo42|*+dhL7Uv*KOXLjgznjQ?h)nJCWA;Sg9_Cvx^rqJ+}c zYf(pM_~> z + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-search.png b/files/opencs/menu-search.png deleted file mode 100644 index 1ec63f0e82853a56d41b8678e540d32c5ad6b33d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 408 zcmV;J0cZY+P)lD$gAKoo`NXTd`Jc>ocFP49~&C^lABR^qz&11?yp zm59X#wel4#Oav`$7oWhqf`y2}HYV|#T@ohljE!EHopaB*_vG$Pg!p4fyFU!WE2Y#{ zS(ZXdDT<=FNYnJduK9^oMbP{VUZ$y?B#Gn>$mii-sv7T2QOvn>6~I4mZd{t-TIPpw zJB&STup`#sJ~*2WZA?W`RC+~(-}KQA%+eTh#$^IOi~i-(3|B4j41DV;I)(3fG*Q1s zp7cu56K8{0HzZ#GpKGnx^E~gsU6M~kZD16dj<`-(sxp6z*yb!@vX4m@lh}}UGz@IP z#u)c8*>Ra@yU>TTEPGsR2Xj*`eH;YA8~LEBt(GW04_C@zZ*?@c3lHwoJtl~mq|ZdV z69!sIKBzJ6!tY4zpo_^h(CnreJhk434>vphDf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-status-bar.png b/files/opencs/menu-status-bar.png deleted file mode 100644 index dfb72b1e13cf674dbce6c9d72f7bc493882cb6a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%zKa z#WBP}@NG~uUxNV$^Vh0^$am^ZoGB`5)ip2r|L{!P_R@`UpB@jh%aV)V{ajc=@47y* zVR|#&`010T^k<3ex@Fp0+=ulye8}|Mct*u|y8P;wPY*JcOjh<-X)^hUSoEY9dTuf{ hNj|3YCvUr#p|??9r90x`8xx?-44$rjF6*2UngED1KT-ey diff --git a/files/opencs/menu-status-bar.svg b/files/opencs/menu-status-bar.svg new file mode 100644 index 0000000000..a6b4d15d1e --- /dev/null +++ b/files/opencs/menu-status-bar.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-undo.png b/files/opencs/menu-undo.png deleted file mode 100644 index bd177ce65a62cc08404295d3c98e550cf8280b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)rf1ZvFO7F0=P1S)EDdcINxOnVs8R&-+XBegA~lT!yPk5k(d9JH8IX@Flsj zIBh_&LUh9?6d%h9vbZUVjRqH`RNEx13Hbd+QS>C-(RgkVB$kpUSFj<%vlN1N$TxTo zt}D4*jQD|%as&YQ0bfYY9GYe9&;(c^tXo8qOj(u%xH}XCftKpr1d`0gOx4g@za{=t z0}A)JbraSt$j&lm=N9=E?{h{2$)yiTf+nWOyodOI8QVZJipM5!Hkvq1=-jYK_yOTy VK0Na~gXjPN002ovPDHLkV1lPsh4=sf diff --git a/files/opencs/menu-undo.svg b/files/opencs/menu-undo.svg new file mode 100644 index 0000000000..1fffc9959d --- /dev/null +++ b/files/opencs/menu-undo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-verify.png b/files/opencs/menu-verify.png deleted file mode 100644 index a7878ebb3f6c4c77230f8c080c0115dd7aefb765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 487 zcmVQSK@^4eAtF{Hh@^M-7uZ;7z)DyvLBUEPpkRpF z2qM7;f{l%!2o|vj+KO8UHnC8_GL;ZWIxPf=g&`np*6&U-i&;|`yl`gbo_o%n$F5=g zGt9W(^SnX0ZPPT|9HtCNVdxbO(-+ev*uz{F?$1HP dUz_|ie*;IC(~hX{6SDvS002ovPDHLkV1m_N&5r;8 diff --git a/files/opencs/menu-verify.svg b/files/opencs/menu-verify.svg new file mode 100644 index 0000000000..d72d229edd --- /dev/null +++ b/files/opencs/menu-verify.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/metadata.png b/files/opencs/metadata.png deleted file mode 100644 index 34c78ffd61e30acaef3cc30960d59fe30979cc50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;8rdF~q`uYVbkc!wNh-S6BXe!Er$1XZhB5lilTFn|}$k$2?+bN|AK`_H##Y%0=Pi zGbfaqOl&Nte{=o3IEKSujtOtQv4#^z&}Z)2DEnZqRMCC6Ep$|)Sbu~y#5zhbWEi_K ztmey;%ICH@yCCoX4PA+OX$h@+&Mqi-oPY6NuH=Kdf`h@*dKZdWwa+U};AYTX&E{Hb z794Q)wJO7WhcEkXcD*$Ez~FEU%PZr9_qKJsy8l!9 b=5OW&4^?AKLb7;(eq!)+^>bP0l+XkK>c5Fr diff --git a/files/opencs/metadata.svg b/files/opencs/metadata.svg new file mode 100644 index 0000000000..acccf4f4b7 --- /dev/null +++ b/files/opencs/metadata.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/miscellaneous.png b/files/opencs/miscellaneous.png deleted file mode 100644 index b21f6e214104dea351f88a07146c7ba39a7a0ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmV;@0WJQCP)j`*G%o6Ip3@pIU+alT>M0xlkk^ZUQfIINLJm;K=`oy>00itedRicMbo*@=7 zu7g@)f$k;cQ4>5!M^r7#GHm;m{8@j1d^0e^rT*-I?nm%nL=K^a;evb)^BVaooa)W< zYXZzUDjGA5Fc6Jlt}yl#Tsa^EY<@uRM)-(uI55L4!41M?;XVRW7{gC^Lb!$ST>{M8 zyuSpOi84#qm;mu1c@5#dNbZ-wC~vmmw2#PRBhEAC2A#U3;(i76W6VanLIq85fO#52 z4f6|kO)?(R#HQMT#2uLH;2q|PSVv1y6Pyye#1Q66OFi-W4a7I1tasx* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/multitype.png b/files/opencs/multitype.png deleted file mode 100644 index 05676e2de06f52ed563842c0d8d5fe888e771a70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1708 zcmV;d22=ToP)Ekc{Xm)1$i(h%(W|y+oEr<7?dfwtd#j#`W=aP>d z{Klc7H5<=7{e+;hy*X@yKVZUq?i4 zwO3e@!G|P^!(or}?!k~`kEAO{$F}cz#3sg{3&0P1(d=|*h1f=?&#>o%AFaH$e$%&( zCcpYbeo!8L>X@CoEdYlP$VMk_e_E;SmyycuyWV|&ckAwbwN+#9acONB=QAe%`g85g zH&E@iod6;bn-{oqZ9N@%@ZheOe*Kfv-#ZvS|D|L3b4wE8w?86b)mZh-T_4^*G%~g$ zYpfX~Bq?T~8c|g$Bb#~Y@B^Is*HscP?7XYa=6&BGojHTWoS3(ZU(D&M1C7<|2DaS${;V-n#adHzn{ShMW>FI)Q~}3ey~f9%Ji_B&dzPWK zTZmWA5(YOQJ|NB^;t?Qdi|sq#n|cZRmn6b8Ngv;K--l|2#|kb1pCO@A7*DDgAeu8f z^-tQD|IXUGKfvTGFYw0rPl>*cq_qNsbBH(^!-K(v{_7~09e%z5%qC6DP1+f;|v)6^AAy?KhWT4UYl2y54^&(gnT2X6_$JOAlM zqY+M>_!B2zyF^81@e)xSleP&p2O_Mh3{a5_!=VIBk|G(47@`O&fp%EUwdr>_e(D?# z@7+z_?e2c&A<3S4!P*Pd0b5n8X576Y<({$4xFkh`+!8k(t3f2FBiuD-Sav3;*upcBMI}oUzpb00mn@`^GyL18u-r*S=7+t zfR-MD|0e#6fK%<<`a~om0t!CNqd(3QWSnrDo zP6WmEW+KjEIE)goD41YkD2k~s!)Il&fMy3LB4TD1ZuB09h&Z1i&f_DP12I9=VNSs! z4#Pw+bAY1iu7|DY;g~nX6y}ImPym_jTxUMa^FCmRgcK2nx&pB?K}$ZwT;Yi*Vg{y& z7>Xh0vEp!69?}fM3h|lt1O85n(BpJIfPSKi^GIpb6uei=9EO#Ki_Iy|1me zH!ISwq&|qlK(4k(04E{^fRcUzQbPpI5pg&dzzp$L9K}S6%?yfuXGn>mXT+h!6tf~6 zW_`su?=eHJG^vyc5a%-Bfr=Rp%jshhK?Iz+UZcPSyjmZSi2yoBt%{j|7;~`Uo$qNN zEA8cBUP>nIs1`d-sv}!5F)YSDomea@RV+iHn3maMiii?b3bCnTQnW}ZQ#y89#2Ed= zH0@pq2sidYy1#ohKjVM!-5uA7TfQL}uU_XFlKfBGWrvVStfP3p1E6w$lx?ICV zh*z6j0Zs!`K;HjjA|kE?k(A6VKLv%?InV_nVE+cordT0F3*6fP0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/npc.png b/files/opencs/npc.png deleted file mode 100644 index 5b5b199be6186c4f92444bc7c541ead7c432b1e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSWDP)n;ZJAq-;U_^C_I~x44It#4 zD2aIvJm^_64{n7djq_agg1#oa*#JW73TPs<5sfH{ri9D~Lk81x1>dW9%?1p(Lzop2 z6TQ(Q<)NJa3VeNRUN;+HIskPb?^=eOkObD?<`7{h{nvjy)oL|Cn6`G5NPJ&P>1%ZO zTx(3cM(kG6meG6Rb#o^hz}lBQiz7sVHE8ZYkY{tMhHOAe?2;E$%x9V}_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/object.png b/files/opencs/object.png deleted file mode 100644 index 4f552151dbd78992f83ed7939f25356e38fc4f79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmV;w0YLtVP)7PS= z*9Sq+2ez%0S|W<&BuVxuw!$#{C_oa&ED+Zm$9V#)q-nZ_$RkFGD%iN|y6VL5Km^w4^@K7r`^EyY56&bcZxpns_ca?oa) zMDuE1;wh>l8)eQ}WImHzx3JEu8KkGNoX~NCGXr-RLQP27?L|@a;`_ehVj7<3wW;ra zb3i-_M_%Iw$G;Iy@g+(Dp$=h({#uj=#D(CDDPn=OA=c&)A7?0!F9rf);xR1~RRmpv pA(9{HoY+O&6wG>H_+t}O{0UfliPDznHH!cM002ovPDHLkV1iEIw{idg diff --git a/files/opencs/object.svg b/files/opencs/object.svg new file mode 100644 index 0000000000..717269b9d6 --- /dev/null +++ b/files/opencs/object.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/pathgrid.png b/files/opencs/pathgrid.png deleted file mode 100644 index 710ff1357671fa93ec555d6aa6d8371cd3b7bc93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 407 zcmV;I0cie-P)TYau?D8ExwHVX%@F@`6X(dOv@DZHsZ3MQacNQhhSp_>k)V5${9(#QB-cc&txT0a}923tyX z1*C%Z_s#@ue_?ZoO9R&48}V_XsJ7xeC2p@#{R7`+c|HR#b1?t_002ovPDHLkV1nXj BseJ$d diff --git a/files/opencs/pathgrid.svg b/files/opencs/pathgrid.svg new file mode 100644 index 0000000000..ba16efcacf --- /dev/null +++ b/files/opencs/pathgrid.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/potion.png b/files/opencs/potion.png deleted file mode 100644 index cb173bb9ed177e2f3023c14e1f5ebb287ce4b6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmV;r0Y(0aP)jWg=2$J kPKlGAKL|sL9m(EaKL|R9ppk7OQ~&?~07*qoM6N<$g6eX=b^rhX diff --git a/files/opencs/potion.svg b/files/opencs/potion.svg new file mode 100644 index 0000000000..b59d12683f --- /dev/null +++ b/files/opencs/potion.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/probe.png b/files/opencs/probe.png deleted file mode 100644 index 2e405d365324ef5da69e824106dd73ff5523bacc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Fqb zi(`m|;M~c(c@G(gxF&04WH4wZF)d4IedFjW+0r1|&^AfZh-LQ&;Svvz8O4iLxaQZ) zQMa|+c7Nx4t4+PnB^(7LCTuobmcRDvExj71`-#_{FitF7z0QH}o?GCShTRVzC|Xbb z$l8A9xr5M0ZPrr>b7H(%g?_lG-;6Vw|8Z|n`oW&Kg$_*dKLTwbLfU z&3hL{#Bx7&S71L~7U7{LEzh)WgNae2T7lXHkv`Uj(z*Rl<{A5MeIc~#)U-E~WW^2E z=3e}I^oY9pj+;xROOO9BR9qx(rr=(BaLYSh-Y2oUx#tDc0)5Ni>FVdQ&MBb@09{0M A!T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/qt.png b/files/opencs/qt.png deleted file mode 100644 index 381cb2251fcb072234d49752cdc5e1a79ff6e675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmV;G0c!qPx#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy8gxZibW?9;ba!ELWdKlNX>N2bPDNB8b~7$DE-^7j^FlWO008Yt zL_t(IPhTD(84M=q?cdCl(i2XFH5286JKydjx#l+M23-Aew2+z^VBE z*Ka`BFzj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/race.png b/files/opencs/race.png deleted file mode 100644 index aeed2fdf3e9d06145a1d295db4de35a82c218159..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 451 zcmV;!0X+VRP)SNxYz;AK(%dS)qf! z;&WQailP#BIwFi4WR+^hNoMKYGq4f>*u{6OFfD3~(l#v#1vJhj>d*MKH3adBF&w@I zc4~Mb0AU|sgzNSvjG?BQvAb$7_`rLA3};IXj$}Yw#TOxF0z;!-++K(&MrFjsldLtw98R&h7uk$c%vkwEJ4JXmY(by9VWY`U`51e?PKoxJv*4002ovPDHLkV1jxF#RC8U diff --git a/files/opencs/race.svg b/files/opencs/race.svg new file mode 100644 index 0000000000..cd7778127e --- /dev/null +++ b/files/opencs/race.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/random.png b/files/opencs/random.png deleted file mode 100644 index 2667630f5c27ba416a3810aab36cbf9d8e872b3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1892 zcmV-q2b=hbP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000KlNklpRqG7~cP6+fQxn#$KDE z5D_zj8DqvFA*T*9MJ1UWMOQ{X1xHX-0i@If6F$PdRYx^tNRh0cR&iiKn}ps=nqHFr~ue=tqpc zf2|ihDwU1^dmYjbB(uu3!5=r)L2<8!{HoFVuo36;Pr;FEuj2DK;o5D{5HMY4 z60j-Ie~L)P)Z{oOl<#H*uen(Uu}B1|REpr>VA$B$;M$c-8s%#6<~vnrtgS#p%?&iw zRicGd#nnYH^^37lfSQDcfLJI{*4N%fU3DeKhX-Z_r-o}){0&D39>iiXcsw3fnwg=g zzLG7wKJdG@uA#2tPh@ALAuTB$HddBO9Rgw7w=qyn;zB_#c5IUnkj6QCo`32%aUPs512Z)V~KuTgfUOaoq&bfNj zwB4?+=h|hjC@aQ}tw9L%^+Nv1OdLu{KuJ+9e7!sv9RhpfVwkf9`B-IX38N)T2;g%x z)K+0za1bsQ=CX5c2zW+XGS(5*Co>P?Qb7(DFIohXrAx6hR6?sUsY4(>Hk$GB^h9`g zIGhCngh;kv_;okCaL$}K2C@@yucmxfx2u(3{p}pY&V0zCcH^&-B3!*($P(djI1mxz zuC`i*b?#za0)Y1VogPkr6iddBf99q>n%(GfEZ?-Q_?3v>3l%I;R1D^YXI?UUfc2 z)wNQxSORb=D}$YLsyy>hD!e`2k$YT@>sKyJ1E)PtNr=Ob3m1aNu|r`_CN-fhfw<^B z%*fDNP1)7TwZVJaTZj)Hu`Bn+)nYtss6h=;e&)mxlwZB97yNQz4ld+nqbN6v1<%Wt z!$U08B@i36hxz#a^M#uWoNm32miz4Ht0Yc01Uxq@9quA$T>>$YQbzgiwL#!zB^PHc zJ5_#$sMS_Z!E-Xx$TsBbA+RTc86J4W8g90-Yb$S6G?1lr8b{Lh>-4;Ul$xwtn3$N5 z{>QRqSA%EEQ{gJqLm*nZn|c4Hci!MnoA2U$UJm33lGstBZl{K$%7p?aaLL#Sg+e%R z?Wcifr6-dyb=0julr)?f?C;jNUE6Rol~eGRrg{|RokV(S;xuq-I42&Loq6LgV@JzQ zl~eG{w0&^lJLnQ1_n&#&*Ts(7e83+!*5Yh#HV*EO*C@GVBzWGIFKY`%RY0|w0o{rPQlZYW#HM_=-vW9AFr`CGFm-t z|3)kIs8PeI%TAS3@CSFRk)M-=)WleWz|;1{z}DJwOy?GG?bk@cLbu{2Sv0ftTpRp; zeHFQIN0A&KrFYb3mYpi6;Cu&rn3^orydkDVGJhYhIO1ScaZ&!4cr{&lMOg{f*xJIH z9F&@ybOUB0Nft@^d=RF~mtlpODG3}hot{(hom)4-Vuh*7+H$4-NT!|BoE*3c$>t4H zx2~76ZdWU(r#U1f1hyoCkw*wwLK#SmmBLNr3=49^1bjY;<6Mo~Y0vjY?E;@?KV@#V zeC|-HO@c;g8mXmWp_-m2#zn!B53 zfR)8c!(+KN5rHF5?!d8EQNuOWRgh5eD;rqI6Ad{>4?`BYi@k(2aL_AC!FPvlp(CiW zGGF=a50jZM8e&p~*IGBwz;QnB1PPgCIC(T3vC^Gv&uOWX;-ui(+0b79cTcmKZE+V1 zhqjZPcjJ#sC_Hrx2{F5II3=Eh3>R>0SO0&HQ*9#Ft1KioR;xY{!;P<5Z8dIfX|7zc eeAzcW&;J1sm8E}EEyuCpMi}?|hzR%iZHk zJe59AX}ey$?yK3>CC^Kq_8)j-tzNL~$78dD%Fo=6@~nULg0EP3bK>nYJH0jpDfk_B z?N<6cd*eEXmt}%(33VU%8@z4aefoEgOJv8ax$HYvzI8WheW;ccdfct`nvGk>|L>+n jOj2c*S;ykkx*NCy4{KiiuC&J#=vD?#S3j3^P6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-clone.png b/files/opencs/record-clone.png deleted file mode 100644 index 262a67a42400f360345c578cd216bddb8bc6e06e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gay zi(`m|;M-tBzC#8)J-36*)UMfS{*<>-{wXG%fEMn*;m#ro6@SrUh1_vcAE0Tc zo3F>>yP>_`U1R2<|IB}q*e`!Pl(xH_%`E;O>rVdblLdLtFr08|GMM+OYG=shhdPJ9 z>E!Zueml^#>)eN0IYy?;uvN_=!Ha5mc7IVz2-PzbunHCMnKCiyh=lvf-Hncway!EP VX4GnG8Ua1T;OXk;vd$@?2>{spXMX?y diff --git a/files/opencs/record-clone.svg b/files/opencs/record-clone.svg new file mode 100644 index 0000000000..3014b2b4e0 --- /dev/null +++ b/files/opencs/record-clone.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-delete.png b/files/opencs/record-delete.png deleted file mode 100644 index 817988a5d1a4cf18b48b67d4b74fdd4b6fd3ed46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP|(xU z#WBP}FgZbj^{@hq#KgY^r&WL0cQI^InR52Ti4z~*|F2KmYMcGPu_a={RRd;KPDVF2 uH@2y^*#~ABUSW!LxWU2`yO@bto{2%Zg@;pGV;L{dPzFy|KbLh*2~7YHqbe@| diff --git a/files/opencs/record-delete.svg b/files/opencs/record-delete.svg new file mode 100644 index 0000000000..9a21973542 --- /dev/null +++ b/files/opencs/record-delete.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-down.png b/files/opencs/record-down.png deleted file mode 100644 index 92fe788602136102ed33598031e7493325540320..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrD@=n9jLfMwywJ zxjA1t`04)tJv}Rg{(my}cbakW(Qm$^Xa4W2d~t2d&$@4|8{>bAB^7(_ejmvO1ov;B xxTNvzSDAr@K~2Jq-8-eX*H_hev&U;NGbG-S&@#X6+Yhve!PC{xWt~$(695OAIXeIV diff --git a/files/opencs/record-down.svg b/files/opencs/record-down.svg new file mode 100644 index 0000000000..9febe57065 --- /dev/null +++ b/files/opencs/record-down.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-edit.png b/files/opencs/record-edit.png deleted file mode 100644 index 8b94e163410880ebd3efbeb68b3aa23e595d2c65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67JIrlhG?8mPEcTwkd%;+`18+M zl81-q|HO#dKz@Y69AAULw*Lit|NRHa&fti2+{NUk5t6#=+QaYj4>GoMYWeW2EjKeX z`0+OVpyuBr?{BDSi`uizFn>6SN6Sa+Ezixm|B{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-next.png b/files/opencs/record-next.png deleted file mode 100644 index 76866e4734c63dec80da28d7ef38c1def25bfa37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;ix} zi(`m|U~+=QmIK$XoA*l`H+&d$FVdQ&MBb@0N+hhmH+?% diff --git a/files/opencs/record-next.svg b/files/opencs/record-next.svg new file mode 100644 index 0000000000..bbc44e7459 --- /dev/null +++ b/files/opencs/record-next.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-preview.png b/files/opencs/record-preview.png deleted file mode 100644 index e3adb6ede41e99738350327d8717ed043c7bd82f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 334 zcmV-U0kQsxP) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-previous.png b/files/opencs/record-previous.png deleted file mode 100644 index 2009d84e04a2571e0f1ea41facdc882b0a50b24d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;j@W zi(`m|U~+`PtbXg~J7mVmU$%XL}t!8Yo;lrq-|1mdFrLEF93td?C#@rFF-R8+#P^LUdZZ znRi*VF*?k?+}Op(7+b_0YiMS7j)6(afOo|~&JI%vNy-2Dx3+Muj^YtNki)o3h5N)k z-Xr#nYzJBlGv=^0gfQ`*WDPPlD4D3m@a0^HLYi8reC-ux2AP!_#>qu$4}sob@O1Ta JS?83{1OOiSTl@e3 diff --git a/files/opencs/record-previous.svg b/files/opencs/record-previous.svg new file mode 100644 index 0000000000..2479c8bd49 --- /dev/null +++ b/files/opencs/record-previous.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-revert.png b/files/opencs/record-revert.png deleted file mode 100644 index bd177ce65a62cc08404295d3c98e550cf8280b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)rf1ZvFO7F0=P1S)EDdcINxOnVs8R&-+XBegA~lT!yPk5k(d9JH8IX@Flsj zIBh_&LUh9?6d%h9vbZUVjRqH`RNEx13Hbd+QS>C-(RgkVB$kpUSFj<%vlN1N$TxTo zt}D4*jQD|%as&YQ0bfYY9GYe9&;(c^tXo8qOj(u%xH}XCftKpr1d`0gOx4g@za{=t z0}A)JbraSt$j&lm=N9=E?{h{2$)yiTf+nWOyodOI8QVZJipM5!Hkvq1=-jYK_yOTy VK0Na~gXjPN002ovPDHLkV1lPsh4=sf diff --git a/files/opencs/record-revert.svg b/files/opencs/record-revert.svg new file mode 100644 index 0000000000..11920a329a --- /dev/null +++ b/files/opencs/record-revert.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-touch.png b/files/opencs/record-touch.png deleted file mode 100644 index 808e1b6c4b40b233d4ccd470fc4958386d33c8bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmV++0p9+JP)PEE!@CU zBv}dg!@$fxGe5Jl3>zXWow}|Qgb-FXq?AD0w%-ev0ww6yZf*L6{}V{jEJd?F38ZM4 zqTL+!vbjHR_fj-0nP7}biZ0PdKl_uM`%+wiGu)x+a1GvZv37z`dBh>+9eR#~P + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-up.png b/files/opencs/record-up.png deleted file mode 100644 index c8bfa4a34274561e236984ebc4d486d05017bd71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrDHr=sETEZQ%9& zr98`-?(?x(vuT!khNyD9EW5v%2MG3UujSt8x&3F|x7LmE$r>MXKi0M%`u~15d!nIJ xiE-$^-{((stZA9}F#g{&PlK;MJkHV#46n~i6gX-JZv|S!;OXk;vd$@?2>`lgIN|^R diff --git a/files/opencs/record-up.svg b/files/opencs/record-up.svg new file mode 100644 index 0000000000..351cb14d4a --- /dev/null +++ b/files/opencs/record-up.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region-map.png b/files/opencs/region-map.png deleted file mode 100644 index 7631847beef75552d7e2b1949259627987600c98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 428 zcmV;d0aN~oP)lpYv1?F3dfp;iHt}kX!rUZtv1xl#xXO414!0&U0@qnv?^=M45CNIfjVPRmCG|D z=mLT^S;hHfLLl#18)`DqYXqKo*N)?uae^rU-w_jmXWp|$${Fe2eqvU#Q+!enb~CdV z>Fp&?Y0=FWStmpSrPGJYDWGKpkaKCBc{u%y41luSs=7vL&$5c%37?6htE z`_NyTN&o>+-#$39Yzk W4f$`6a&e*n0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region.png b/files/opencs/region.png deleted file mode 100644 index 2ebaeb02850fe497f5ae24daef767139916e2fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 605 zcmV-j0;2tiP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9a%BKeVQFr3 zE>1;MAa*k@H7+qRNAp5A0004?Nkl3&4 zKk!Qef*>(s(8?|rTKK37b!F(cSmp!zE>IcSXc12RDJNUdYGI>~`UcSoF2AGl1)26x<_0EyjB|^!!pf!M z>)cWwoHYiq|4+acSk7--Kn1ID3zeS^5tqFuhlq38@G1WKjCq@%?{UFO)xUpD$%3c- zhDan^H}>i80qLnU1zlN@r>{~O#F^p56I8BeGMVv_eIPwO?yoe54P_3&-00000NkvXXu0mjf|El^g diff --git a/files/opencs/region.svg b/files/opencs/region.svg new file mode 100644 index 0000000000..4458c7e376 --- /dev/null +++ b/files/opencs/region.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/repair.png b/files/opencs/repair.png deleted file mode 100644 index 1b5a9ccc11a5ca12b2589ac4700a344148164d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmV-#0g(QQP)Y01WK2NB3Fo%#|vBX=dNN*`3{4rS9G=&-1ipSxQjH zwAPv2l|mK|VHn1BHV~~G?ovD>9)gjORYpOR{400?ecQHG);EbChz00HQS^(yF*J{k z<9sJcav=E*KETEZ5ln(0(D;ra3jzEW=oY9fym~nVE!a<5Yo}?tCZ2-^h~WlsjlViX zb1~od&rf`zR2e5}yPz>)osqhxihwKhaAR6s6?uu7)=|2^_AEPGMXqS3@JuW(x&$(b z9lq2! + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-icon.png b/files/opencs/resources-icon.png deleted file mode 100644 index d84c90d5dc841c6b893970f5d791205528ba7f42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_V?) z#WBP}@a?3Fd<_a5uBy@X%k5u3-|6^bilFZ8FAJyF2=2MpY&0i2Cg6{u+a(#LPnxPV zmyaxFwA#k7TC`U9RLX|(_~p!3;=exL)xYKR(U~kx9DS=Nt?lVxXZ}3-nD4ou35ws# p%}bYXCaC^+p0Kp~?C-xab7HTCtlSkQDG0Qm!PC{xWt~$(69B`5N6P>J diff --git a/files/opencs/resources-icon.svg b/files/opencs/resources-icon.svg new file mode 100644 index 0000000000..2054ba0b2e --- /dev/null +++ b/files/opencs/resources-icon.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-mesh.png b/files/opencs/resources-mesh.png deleted file mode 100644 index fdfa3528b1314612ff417ff703f78f534d905b75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)H27(lHr9~iiGm;ivAZw9}R+b4B)AV`9W>6O$_i8TlG zy5S^WiCxkBFv%D_EY}|`zl9zq4`M#C=74zQ-G1qsqMjth)07MDq|3-_?DNsx;NPOH@g+vY{!P!?vx+x6@1Y-H z2L=s4ZIh>Tzx+FX>3{?@W$pZbBN|R>_)X`@E^8X%m)MdG7H154?wnQcXl$&zIN#KN Y0mg952>$ihJ^%m!07*qoM6N<$f+7YBbpQYW diff --git a/files/opencs/resources-mesh.svg b/files/opencs/resources-mesh.svg new file mode 100644 index 0000000000..ba93da8091 --- /dev/null +++ b/files/opencs/resources-mesh.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-music.png b/files/opencs/resources-music.png deleted file mode 100644 index 53775109c5bfe6b2f7981aacd9bcf9526ec74002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wk0|TRo zr;B5Vg%$VZ%>fhhr<&qv-{{t!633XgThcg)hE;}!1OLs8UZuru3cDDI_woC@b z2@iXE1dcy8VR+)QKrORFY=fGJJ_9eS_d`>Lj9|NfP`$`F21~&gT|x^OS6|r6u<6$M z^ZYIk`IslDT;N{t;t= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-sound.png b/files/opencs/resources-sound.png deleted file mode 100644 index 86871611f5deae184cf6b986797020563c5c3ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;i2$ zi(`m|;M7Thd<_ab&YYdcn@< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-texture.png b/files/opencs/resources-texture.png deleted file mode 100644 index a96c4bf8c7b8c1334857e79e33ddb9230423c3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;M%~8LWdPNR_}iI`OWcH&r74-SDXxte!pw?M+G587SGe0awbQ5alYG@JA1mX zFlSzPo6D7RJ=IDj3$!ZARN}k}Pbx789-Q;}_96Ep1^pIDHyRV#|Jd)DVYpY*@*z9Z z>kk{3q!`WLdQF^*H~Q?^T%O~PcO>N)|BBJcn|pQ1;lGjnFA7qX=j>Es5U~5GBK^Kl z=7>S>hIT90J15KLXuEC`3eB26@s0K$hY!|Yvxp`=!nrE9M=(02@;u6{1-oD!M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-video.png b/files/opencs/resources-video.png deleted file mode 100644 index d86bc6025e729f7eee1a5e4d3e9fba20d30dacc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1G~? zi(`m|U~<9&0fS#ZfBt2kdGN#uk4b_5|2J zp~Sbuu*A+oUwD^7&-o)qN_aCoqy>r(9NBQug7?Sw_wpJE+KsdJG4pyJUM8v1c9~&< ziq4WowzDi!MdBT~4v$VStY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index eef5c01bf8..786040623c 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -1,109 +1,102 @@ - openmw-cs.png - activator.png - list-added.png - apparatus.png - armor.png - attribute.png - list-base.png - birthsign.png - body-part.png - book.png - cell.png - class.png - clothing.png - container.png - creature.png - debug-profile.png - dialogue-info.png - dialogue-journal.png - dialogue-regular.png - dialogue-greeting.png - dialogue-persuasion.png - dialogue-voice.png - dialogue-topics.png - dialogue-topic-infos.png - journal-topic-infos.png - journal-topics.png - door.png - enchantment.png - error-log.png - faction.png - filter.png - global-variable.png - gmst.png - info.png - ingredient.png - instance.png - land-heightmap.png - land-texture.png - levelled-creature.png - levelled-item.png - light.png - lockpick.png - magic-effect.png - probe.png - menu-close.png - menu-exit.png - menu-new-addon.png - menu-new-game.png - menu-new-window.png - menu-merge.png - menu-open.png - menu-preferences.png - menu-reload.png - menu-redo.png - menu-save.png - menu-search.png - menu-status-bar.png - menu-undo.png - menu-verify.png - metadata.png - miscellaneous.png - list-modified.png - npc.png - object.png - pathgrid.png - potion.png - qt.png - race.png - random.png - list-removed.png - region.png - region-map.png - repair.png - run-log.png - run-openmw.png - scene.png - script.png - skill.png - start-script.png - stop-openmw.png - sound-generator.png - sound.png - spell.png - static.png - weapon.png - multitype.png - record-next.png - record-previous.png - record-down.png - record-up.png - record-delete.png - record-preview.png - record-clone.png - record-add.png - record-edit.png - record-touch.png - record-revert.png - resources-icon.png - resources-mesh.png - resources-music.png - resources-sound.png - resources-texture.png - resources-video.png + activator.svg + apparatus.svg + armor.svg + attribute.svg + birthsign.svg + body-part.svg + book.svg + cell.svg + class.svg + clothing.svg + container.svg + creature.svg + debug-profile.svg + dialogue-info.svg + dialogue-topics.svg + door.svg + record-add.svg + record-clone.svg + record-delete.svg + record-edit.svg + record-preview.svg + record-touch.svg + record-revert.svg + enchantment.svg + error-log.svg + faction.svg + filter.svg + global-variable.svg + gmst.svg + info.svg + ingredient.svg + instance.svg + journal-topic-infos.svg + journal-topics.svg + land-heightmap.svg + land-texture.svg + levelled-creature.svg + levelled-item.svg + light.svg + list-base.svg + list-added.svg + list-modified.svg + list-removed.svg + lockpick.svg + magic-effect.svg + menu-close.svg + menu-exit.svg + menu-merge.svg + menu-new-addon.svg + menu-new-game.svg + menu-new-window.svg + menu-open.svg + menu-preferences.svg + menu-redo.svg + menu-reload.svg + menu-save.svg + menu-search.svg + menu-status-bar.svg + menu-undo.svg + menu-verify.svg + metadata.svg + miscellaneous.svg + multitype.svg + npc.svg + object.svg + openmw-cs.png + pathgrid.svg placeholder.png + potion.svg + probe.svg + qt.svg + race.svg + record-down.svg + record-next.svg + record-previous.svg + record-up.svg + region.svg + region-map.svg + repair.svg + resources-icon.svg + resources-mesh.svg + resources-music.svg + resources-sound.svg + resources-texture.svg + resources-video.svg + run-log.svg + run-openmw.svg + scene.svg + script.svg + skill.svg + sound-generator.svg + sound.svg + spell.svg + start-script.svg + static.svg + stop-openmw.svg + weapon.svg raster/startup/big/create-addon.png @@ -112,76 +105,59 @@ raster/startup/small/configure.png - lighting-moon.png - lighting-sun.png - lighting-lamp.png - camera-first-person.png - camera-free.png - camera-orbit.png - run-game.png - scene-view-instance.png - scene-view-terrain.png - scene-view-water.png - scene-view-pathgrid.png - scene-view-fog.png - scene-view-status-0.png - scene-view-status-1.png - scene-view-status-2.png - scene-view-status-3.png - scene-view-status-4.png - scene-view-status-5.png - scene-view-status-6.png - scene-view-status-7.png - scene-view-status-8.png - scene-view-status-9.png - scene-view-status-10.png - scene-view-status-11.png - scene-view-status-12.png - scene-view-status-13.png - scene-view-status-14.png - scene-view-status-15.png - scene-view-status-16.png - scene-view-status-17.png - scene-view-status-18.png - scene-view-status-19.png - scene-view-status-20.png - scene-view-status-21.png - scene-view-status-22.png - scene-view-status-23.png - scene-view-status-24.png - scene-view-status-25.png - scene-view-status-26.png - scene-view-status-27.png - scene-view-status-28.png - scene-view-status-29.png - scene-view-status-30.png - scene-view-status-31.png - scene-exterior-arrows.png - scene-exterior-borders.png - scene-exterior-markers.png - scene-exterior-status-0.png - scene-exterior-status-1.png - scene-exterior-status-2.png - scene-exterior-status-3.png - scene-exterior-status-4.png - scene-exterior-status-5.png - scene-exterior-status-6.png - scene-exterior-status-7.png - editing-instance.png - editing-pathgrid.png - editing-terrain-movement.png - editing-terrain-shape.png - editing-terrain-texture.png - editing-terrain-vertex-paint.png - transform-move.png - transform-rotate.png - transform-scale.png - selection-mode-cube.png - selection-mode-cube-corner.png - selection-mode-cube-sphere.png - brush-point.png - brush-square.png - brush-circle.png - brush-custom.png + lighting-moon.svg + lighting-sun.svg + lighting-lamp.svg + camera-first-person.svg + camera-free.svg + camera-orbit.svg + run-game.svg + scene-view-instance.svg + scene-view-terrain.svg + scene-view-water.svg + scene-view-pathgrid.svg + scene-view-status-0.svg + scene-view-status-1.svg + scene-view-status-2.svg + scene-view-status-3.svg + scene-view-status-4.svg + scene-view-status-5.svg + scene-view-status-6.svg + scene-view-status-7.svg + scene-view-status-8.svg + scene-view-status-9.svg + scene-view-status-10.svg + scene-view-status-11.svg + scene-view-status-12.svg + scene-view-status-13.svg + scene-view-status-14.svg + scene-view-status-15.svg + scene-exterior-arrows.svg + scene-exterior-borders.svg + scene-exterior-markers.svg + scene-exterior-status-0.svg + scene-exterior-status-1.svg + scene-exterior-status-2.svg + scene-exterior-status-3.svg + scene-exterior-status-4.svg + scene-exterior-status-5.svg + scene-exterior-status-6.svg + scene-exterior-status-7.svg + editing-instance.svg + editing-pathgrid.svg + editing-terrain-movement.svg + editing-terrain-shape.svg + editing-terrain-texture.svg + editing-terrain-vertex-paint.svg + transform-move.svg + transform-rotate.svg + transform-scale.svg + selection-mode-cube.svg + selection-mode-cube-corner.svg + selection-mode-sphere.svg + brush-point.svg + brush-square.svg + brush-circle.svg + brush-custom.svg diff --git a/files/opencs/run-game.png b/files/opencs/run-game.png deleted file mode 100644 index f5038654fa1a3238be2f3124e12c06e75e206773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 642 zcmV-|0)737P)e5SHDZcKoowLq~M@~ICt?c(50iZn{;z2E~1kPPC5%Z zI_RRKh@*dmx=4jiPNIvmIw%Nj?#}PhL{m&lle=6fT;P(+{d(W`<-NNXB6m3joB~b( zr+`zyDKMc5)XzSrs)p`q0buMKBsR3x(45S4=GV`^Bx62F5x!uZ)z{6Lw$6G*=4ull zDI>eg4@mN`gO>(~ghjvG|&9nJxE-j)Fz5R0#@>#ijNlJqYDxT zNxO-sv0Z3vFrH5&_#4JAW4C%3d)|!sbBK>NzJ`jAh0JkdHrgi;1N)5+ zFX({8AxfdNq%s1Dz&OR#w!4p8p|r$)3?#@ip3K8(-cL)`5Fd9LL;f{Y{sjgRMQO<_ z+NKBFhrb2GyDauF=qQE2DZT^cPxDx=m;fUd)JTTiD}YvRlRF8lj6j^d-T?eMfIsC? zJw4Vwf&M|JQtuXkU#7tnsoNvKu;EFhtAcOm_4850tPnsl2gQ500MZ0_l|~r5N+l5Q z>m7hs?(%31b!wq<2=x72;!slC;z`P$@v2z@%FhW!@Ld2uHjXx9L1P5|{9LX8{6~hZ zQkt|0;3q}>2fxG5m&)? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-log.png b/files/opencs/run-log.png deleted file mode 100644 index 463ead176d4c5e367262864fcbcf4e5a19d23ca8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6;>1s;*b z3=G_YAk0{w5XjGVuN& z)o`BQMRLN785QCkyc;B4)-e8`JbCheX5LKZB)*j94l{-of+-9<%MbD>o@Q8)?Qn0C z;^~GZOH?L!-(%d!ZprqCHS3JxO>Rw1O@-*`OorT+jTHhb0;j&oP_b4FoFlGb#4yG3 k&)>hDhYYqVtY>04?6v!2)}vJ`fv#onboFyt=akR{0N)K>X#fBK diff --git a/files/opencs/run-log.svg b/files/opencs/run-log.svg new file mode 100644 index 0000000000..404f74a6d4 --- /dev/null +++ b/files/opencs/run-log.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-openmw.png b/files/opencs/run-openmw.png deleted file mode 100644 index 1033d62baa120ce5c3ae4e2da94d122f88051a7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb==d%F~q_@`Op9V_RKs6Mn;Ejwn!AzDzNdKKKJL0mxr>C+%oock}3-3zi-zsP&|7? zqUkEbf@l9X2{_+l*vM(uTp-P0#M1HfSq=Ne^2QqsvmIG@8FsU!T*%qrnCoz+F-PDc zTLbT`uc8?z#3wjrRxk^uY3oV7lA5qR$D^3>j`ahEI`$)SOC%Y-9eTdsVUF`6@qK@$ z&;BjEq*7C0^8}HO3mG;XS5|mMnK%gvaU7p8dBQ)%-;-sSTRKm&E0{(kGg@W~CkuGk ZGJNsgqSE=Fa~{x344$rjF6*2UngEJofxiF% diff --git a/files/opencs/run-openmw.svg b/files/opencs/run-openmw.svg new file mode 100644 index 0000000000..f0ae78619e --- /dev/null +++ b/files/opencs/run-openmw.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scalable/editor-icons.svg b/files/opencs/scalable/editor-icons.svg index d41fdbde60..921c5f6d8d 100644 --- a/files/opencs/scalable/editor-icons.svg +++ b/files/opencs/scalable/editor-icons.svg @@ -8973,9 +8973,9 @@ height="100%" /> ;4DjL?#JK{<}`s9f!E^74Ncv2KxA?P znqS4>A5Mm5XZ+3RAsrteZUE@6ruIe*{@%QT2Z+HR#NdaTy6eD4apiOpRVR;q>U#s< z5dl4!@m)>rgBbjmf4MAT@Lz#jRoz?zw#1dInmQ0b+3!ohi5sB#Xd!|BF_B`hs`>b+ zZw+3k>gMJWZ!#-Y-K^MlYm70*n7>43YK=AEIdD4$-z`}kbRKvHY{lT;pH_`tk;1Jh z+E#V518gihk#XQpDcoa0(A;hJmDA@`6*Pm$IwA{8PjHzYIJPH+Y0*RP(g095rQkfOn@hH01mOI6nDCH0_U@xo^N`RX2O% zMq^$-6^6hEP3;bF3AoB6bo4Cn*|siYj4@_e`~+R?wcq}e9b^Ci002ovPDHLkV1jzv B=%)Yx diff --git a/files/opencs/scene-exterior-arrows.svg b/files/opencs/scene-exterior-arrows.svg new file mode 100644 index 0000000000..1f923c91bf --- /dev/null +++ b/files/opencs/scene-exterior-arrows.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-borders.png b/files/opencs/scene-exterior-borders.png deleted file mode 100644 index ec5040dc88138bb66b1aaabcd88783a2c365d44e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE^nP)$3h|I3oKieQ2xAZ*62h3o`-Ct?@h&0sWquc{c$*OV%}M|O zA}3W^-2ag!@feN|5P-hSpP9G?L?Df*EnQ((7G!h|QaAGT4x9nF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-markers.png b/files/opencs/scene-exterior-markers.png deleted file mode 100644 index 6fffcbbcca32de0ce0ebee5fce5e48dda8bbbed2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 497 zcmVK@@=Df8_46t`^H?Z3BXZt&%2)_NV`VG) z3>H`H?6g)81e;X*0x2X!(9+U2AU2B1F08wg>RxhF#)SlOzHVS<&N*{t284KsdSr3E zUjG7ocrb@=zVCnj)5T8@27d><2MEKE`~6NnpGRxW^?Jp!EDXaS3`62LCYQ@`xm+*| z1Iw~r_W1Eg0-3a0S*=!?PNy7?N0d@5mrHEhMr%!@(V*Y&v)k<$4u|x5J(46zC2nd3 zY&IL#>ow2M&rBv0Mxzm77^0M7KA+=x9^>(tLZQH5Fd&mjC-hTV1=zMtx7%gE-@g{) zI1T`w=h1GrS*=!7DiunlQfiMlwGN!mXJ)e*+wGQOu}G~}BaY)YbF|iEv)NRCp;S7M zBni!ClXAICtJT8uJQj-uolb`!2mmlm6QvZUY5r5+=vUyA)*90^IUEkSu8Y2$&{j9<6;?)(17zjcKY0HqXB6ydrqFJs`%bukQs+wJyy@~OT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-0.png b/files/opencs/scene-exterior-status-0.png deleted file mode 100644 index 6fa47b4394f11ea2b334f8ce599bd5ad1315836e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1932 zcmV;72Xpv|P)vn>RZ-ZHAlsfOLT2;bBHbM##y@!CK3}zyN_j0KebQ@bED4c%1C)Z2J5A@%#M* z0)eqLGG+om&sHc`u3SlTb2GiYy(pz6dMT=OydNsARwd~rpi*x7Bp|z&1t&N(R8mgcxbRL%Xxy^0ve>d^3#U$-=VPr!DaFvx(74BA zu^4%Id0f7H8Nc73xXhr4{QVu3kWz|REJl8Q{`hC$8oBuWeg+2zlflo}iSzb2V1Zvf@@Wnt`` z5J@IbhElmn)dx^0Um7+Z>jVB74FyM10Dfla<6Y%|i8PdU0!yYMQoOf64OpgpX}LB& zl9=Y+hHK4*Xebz&j6eZOFU6YiVF!G`0l%6e@Rh)ifqwx3U;*$zq@nb0DH6$WM6$0X zqQRQe(NM5=G6H#MUkS#{1b7S34}4lgBCg;q0e%Gpf%TDw(vvquL~AgnD;f&+O-A5d z${%=-jSb(d$Ik$VfqGy!@U)v+4?F@q3Vaf{8+dTqMA9;{?a0;6XefAPG6HEpS~L_K zo(Av(P9{|%a>jZ5PT+@1se7!o06ztu5RrB_z7!B37uYZjA_0IzeCj5$N}|Wl0$YL4 z0&87=u}Et@cF7wAtkycmT8kso<1UfSKp6PkbR#ll9k>DTGN9FI`92Zpa31{u@FCz! zfCqdO*eoJl3BB`kz(L?R@DvaM9u|?fi^$JiMAlD-h?)lQ7lDh;5fE8mJ zcc6{{e-x2#yU)!)uZVopdHfLY25KA>OTa6@FM$t;$Vs>1ZlFj+)+O}|JwRMU7AJkb4|r2Vo^+q@0@{GD z0TsXockri-h^jpH)%UxK+u2^#bj9<$mz|#PPV~6js1Jw%7aW;qfQNxKB2u07>``E! zb3&tt+?h1~8z-9=fX7^V&2vT1KNb!5>}|lSK);B5urgeii&kY=&{%7gRw%8?D#LZR zpWa@w#-+GFd!F}Ux4;h1^SXe0MWjGPD%@fIEwBT4#q+#C!iLWS`$gnOB60|5aDWr- z*IMh{!0W(H5m_!Gg+LV$_dM^rZmmxMe*#+ZtB;H-hBBoTSUY|OZUYvpG_9~;v1lu|pawU3I((=OtF6_IZw zJ^wOrFVJK&)Q;2NDk(y#LgiD~5d#oyuWCA;){{`(yWPqPgMu5BU1S4`JT)_ zT-m9$t~17jL}XtQh}WD>pLGYlSwz0)#-0E^4y<>2{}@myBL7L4|7E4r9&7C(G+&5p zFU`IY@Kh`D^+bNx+mreGzWvTq4*^#l;IE6wfdoJet~&PthehQ331e$qhN%Yff&D-` zn$M>Se6qVxHc$-wBN__Y6r^>)T_YY4k)I_+q$%Ng(IO(-lg2k_ty_&TDS6!8(-s4l zqM=~_q}OSseI*tP#`FW7QxO5qE`7Y~ITw)!0l&L4>=BWt5+qdXYDxBVJnn!Op?!r4 zg*79mqM_jBsT&%lM*mVI;v&-Q^Z9Dy@%a5BayZH3>$KKCF~+=z=8@?D$C}a3Fok|# zAzHtCw2geb3mR?AM+O`bb0Z@8$1imUN2N6uk&J12+z%8fpMN3NV6C}U@z(T2L%}nX z6Bykv@zxh}|Hpc}!3i3j>mw3=t2^fxZ#@YF(L9#Q;VjF0pKXefAb zPF|}_K;#_Yn@(1_Ro)AY67gvTVB@n7{D1XkuBb9xS2+KUa^B``-sbI=wf_QPyj)Vl Sbo&wj0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-1.png b/files/opencs/scene-exterior-status-1.png deleted file mode 100644 index 2e1ed0f6503621aec50b177487c8c906a7e33c8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1912 zcmV-;2Z#8HP)J3 z{$a>(n{2#@$z@ihIp6O&=Q-y*&xJ7=lTnF`W^xb&JAt}U zVt7qeKYOKP?d>HJi4YElNu^R`G8sxsOX=z9Ash}9 zi9`n1I5rvprckb&HER~l&CPUmbs-|Ow6suCQi5}idGqGc(9poag9mA9YGV2F!v5^fMHn3*R8d9kgBEt6V+lj?utY5#L%F0Ssty+aKg;8BpC;}xV zB`jOEjKhZypC?mZUJgJk7GuehCG6R=hnX{HQd3h?uuWVr4|I2Tvt`Q`_U+rpv}x0r zJ$p8pOy+`joO6sHKfa(@s30D&*0N~PB4*5(!Q#b>iN#{<*s+79OPA8u*9SmxaWNu7 zadGiAl}29$<~ZjlE-t3Cvy+O73Y>F@2z`Bh=PjO2rztNl=k)2*gu~(NW!_NLyZ%=p zWC28kbUIB%MFoS;zy;wF4u?5=_G~Wrg6_jH`M;CeM7`7|>dn>EAy)}Fz!Z#Bi3l!Z zJAh*c>ejleN?^*S9+L6s=z)t=Ns+;&GyejRaHs_9t_qP{0@D$hkW+mCK|)2iOu8HR zdomvF&ja|;sZX@s0@(JIHLbwp;fPGx)KdgZlTgtFm&sBg@*Z4gPbA~f_Mr&G5Hl5L z&kuXxy&m|vJb}*wehmBri1@Dcf%cU(f6kN0I8UVXLLvs7J(7$^yM`iAjtNy`?P!2+ z0eXP@RJGj~yve|?fGDt_ePzv|D zsSUuRz+=FtfIEN(M@*z>T&e3n*P4t+&kRMN2q;R%qp1-9-{!-lPF0V3i{A$PP(gs+#d0`MLMVf{_rB5dePyIO#3E1o$*iB_iXzWe*u+Znf5)0D6Iis`{oUFvk;! z7-K4}wFiM)jWJ@aO?j{E_Fi29)aL6E$piTFKshjTFvdNoJ-{DS^*jE#8R$~gZ+VOF z0^R{m0k;E-RJAn-f@gurs(Qblw;q`7*SG$+8+_Ip)+17zs2|Sa%Y0aT26z#88Tcje zVO2fk6|Ms&s_Oil^Fk+(QPoK~zi$Tqrm9c*=i7nTfv*EIfQ5eJ59<-B-S@Q*`HI_m zplTfu*qI#}Rac=}caF<`8F$jV- z;2u?tsp<^BnSTp30xt(a5Xn;bJg`+&f269rfR!F_w*1-{vkrI*Sgoozt7?_MD9Z#v z@IAlQy})Zg3t{=#K*TUzM8LW8d*FItk`x)ify>yA>-L}ODASYMfJMNImk5uIK5p*} zg5bNpSbY_^1vsgyO_zB8ZJP{<9|}sZ|1!J5^xW&){T?KBj2u`h)9)$ z1lMl?k_YP69?#7}LlIf+i9DpLuLnVp0Co<>aF){JB2wv``+%zcC1>n|BJ!4V?j2RF z&KYY0<^wnT6x|NhKsV@$oZHm<6hb3nZ5_<$&i~i7zJdYFFp@-*s>G zmU;*{=K+61RkvjUTIs8EH?Uh(*JO>&^%>@FpaR$m9Ki5}e1Q+u3zY&>fWIc=QI|(( zZS!r!1FHJ79FMHcYA?2^>WZB4g~pf`Yi(W@_kG$V;8Zdm?HRJ2789y=IIy+{XdR9S zaBS)mZO?g+JP3q+%dk;ZpUw)QhkY$69f`#~@QIjEl^{6Ve>fSBo*w={BQo&GlZf|7 zb0`#gIFrfTud2IqEI!{D^Al_B3m6_932>YpI2q<~9;n2a8wZY&@124M4(9#6o`}61 zk&6ANI-&!iHRzFXBeZxJm?)ueCC=iUy)fdP?M%j_M~5abpfJ1ZOZ)G^v)$QC9CR-B zNaEd&vR}OW6cEMmcs`qR%BG&dKTTZLtzGt2UdZb|c~!1eEnVVRfsIMBkqa2el8Iv)&X5~Lbb6jeDWvBN50000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-2.png b/files/opencs/scene-exterior-status-2.png deleted file mode 100644 index 8ccd356aa985d5ac2accd33ca59e08b23ed6ad92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1969 zcmV;i2Tu5jP)=iJ`D(O=V)Qb1@3YoS$V4GcCV6ZfK9 z2Kk9rUK*A##Fq;LLpt4C9PK1_IyNoC7y{E4LWp$+L>}jKCi7(k#9T;*gBjk;^)hIA z5o*EK-QIKdN6*E}mC{>UCRzIY_xqjSIp_O*p6_{{^Za1Y2CZMF`ZL-0{Zb&epBT=H z$cL9ZIisJO2Y^g~&dyG{y1K~A%fnjB*|TTK&dw$n4AR-zNjx5B$dDnlx3?1v2FcFO zPOOpD9{_rwLOF5bL>e0#IdS3yN-3I~o5{`1#ac^MRTZ_hwd~!ym-_m8mMmFZdZ2$iKC%-HSowwZFTox~0%%MYv2#3S$+_{s2f&u`Fii(&&e?Hr`ZDYcO36zzU z4cH+bFbTD_wb9hn#I9Yt7&~?>Q>RWP9*NqqXU4<%uJM0WM*bwRcUlQ;8|>rKKN24q?95Si&0ouc>WoxkaOqGrGoFb69?`8+A6~} zsxn-oujC%;3IPWwL91e=6gF;JfTMdRHP|akpkz(Ej)pwbZ{SK5s|<~e#Xkj5!9Xs? zUJ)Xx1YSd_@26BBK%oK|*mz8U1<{aqG7aEI#yonk959i(vID^A-iVZ}X)gkvSAmS% zY`iNu&3z8nnm0*}u{x z@)e+21u`aINJN7*KaPgH)jbio3mw>uG5rDF1hfNph)Bd0ywSjGfCtQu)RpbKEFxNi zF?U2mp7caurV3`iXk(q1>+vJNR-hJG1w7@Z)&lnf4*|CWHv)I}nMg*~WZU)G_o5;1 zjh+Y$1%^jM-o8EnU+-ixNkon~k6#ZwtCYIQS_|+Z@R*3~apPkE0rG)4eIQZ+5Dj@B zq?Ja~@c2<+3GhRp%Jmn+wbqGC-Z{W(t@Et4I5Ms764?lZfgklXBE8muO8_4SG&?Qd zDkA%xNB;nP19$-Nfo}neMC4#n@4Nxn2F=j7toz_|zV>(?_wz{Y;11i&v zh)M(a3qS!dA)#>xY8&u(5&5NiZUjz<$j_a}w*ZHM)4&ZtwTK+>eg7F?w1~`b^PUE# zy7i5FH_BzLUL&F^!!^Bme6f?oUBFAg>%d=t$s)4PZMX^;DI&8{`h`{?E+W^Yyk84^ zC?b!$=No_>z{5ZVFvlJIUL&F^cm3pRuHqipGpXSd-}hg2dcHB)<8GriAO@UtWS$1@ z0j7({l$39G0&ATU>P6)Gl<_y5Y&HRpxb#}!ie4}g4fpK`;7y=iM7~}buE|HMaahn; zYn4_gt;SV`Yes#vtaQ3daqs%Rf3sU)x$pZ2fm=kRNJJ{!Vg4Ji9C+RL{p_R-UjWvL z$a5mH1*mg?lkKls>s7$}zzPu=FCxXjBp~kl{%_q{-vzb<%>>mqyA{JYr4(3u{tS!& zu2C6UVZp{t%h27YTk_=8dY~G3=_2x2?R0yS@B6=U#p*$z95^K+^%wbmKai)CT5hd< zNJO4;5&x%%{4C}BSAkoA2AidpfAovektkKH0t(w@0HS*)H5^aPLOl^#;fTx>ksZG8 zhk;EA4JU1STq!loT6=A3pX>YnSCvxlTWb%CNNLJgJunLx?^19h@DCLCMQY1(F9y7~ zI`9P|#cv*O&3WgO6^45vgoVVN7IZw?5K68M7DkAHX0M)tb+y-nFk>4kc zO?Me)3Q!2F1NNXL}XdY_#CZu zvoR(ukC&`zF9d!AG)F_;TRjn|QaZ57V!@cVf!lf`0vsLl=)nyxB6kBpcV$>DB2Oeq zXr8MjL;B)z2mIIQz=H~fHC;2JA#ZC>1Qww5b6C^e1u2y_5f_ohKp-$L9*@ruk*z5n zpQW|_lQCv9n)~|#9BaBe!|r~-kA}QgdLpn8tyg!qk)L-#-HmzIcHs7C$ouqCL<)DG zZt=RMm54}IA3Z(-s8fO9Laf1BbD`p`c`_RE9`1?2VxZI=%*1%28lQ4{-X0BkhkH%G z;g4JL{`~RdfQROhbPi|9ns)8ZR2(qCFklpr@Mu2pZZza=xFWw*E<)rO5a>%*xmx}U zbrT6_1z_U?5Bz`iXKrL=xTbjUALXD8+Mo^ERcrqR%`9J1x}cwm00000NkvXXu0mjf DE;gUX diff --git a/files/opencs/scene-exterior-status-2.svg b/files/opencs/scene-exterior-status-2.svg new file mode 100644 index 0000000000..2d1ab9bbf8 --- /dev/null +++ b/files/opencs/scene-exterior-status-2.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-3.png b/files/opencs/scene-exterior-status-3.png deleted file mode 100644 index 70fdc4111e7ef5edc5a6dcf03c356f7c5fd45212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1946 zcmV;L2W9w)P)0oO{S+0uC?~BPAk&OWC8qkv(IU zxXVgl=*lLOh(&CVfs2$#j={C0J^_$$C?D%C3z2jJuOaf?wC)2463W4)S`?U_h(%6h z0Q~TP|mF`)!G52d`2Rb8gFJhj-*>h?S z{B7XAL@csDL*S!<=YW3#dH&RTM}1Z4-!mlgMW9YXIb+W!V!+uSC1Q~koe{Vb6WWBe zJpsNNXaa6m)q3CXh5)Yt5nx(smkkP9he0qcZ{nfU6R*$i8j>U+cqUjH({?7QY^N zMnrCM&H=m#Jf^C9ynYy$J@Sh8$h7Vdk!}Fr1f291p9OpmC=ro9-m?3QG1pmZPXK3t>8kp! zCosVi$TP+iTWj|M*BN8PTATD<+2Xyr04UGYBa#8|=Yc|CRC|njP}_jNsOm5LcP(&S zRe$C!z8N?KoC0nDW~l1^APAlYhN$W!KW`Z@-mh={zrnt0b?Ff)k5_bM@p(Qh?gU-} zUI+dNj8)Zre#51}AXS~3c3)@!QmT4&+W%F+KUDQ`|9u1S4)8Eg22A&f-=#;SeAkb^ z;ydpCJ!6)990b9uKAvyvXmP($BhUhz@MM+&_W%=B_2#s(JAqZ+2sNsDeVYCaA2u6- zM|^oL^i40^?hQY7An+#8q^e&nk5}}^$VeO*oO5CXF*33|UNQK?1tTW<61O7=f^mL< z>L3UX0Jo@WR8`A-GXEK<23`+>Ag{xQ&jV{z^;uQj3{-i*9pz8Pn5Dpbz#>(>MpaAv zLs=>ag5UVHz71>#>Ilo%+aiXMA_C5x%YlKw)skZb2QFog4%mI_Xn~$w3(NpsxWs)DPh^U!z7qsN9N5?%!yPs~CL+boxi6{e`)Rr_i^zM< zxkIWtB28BVOa-p-CAb#&E5d#Cm8JO?0^U_0_#BavH;*;+ef#4@#+V9gZA?{HrGa?Y z$LZ5P(Q8%pw_f)c@GW4PkN0l^<*NGc4)f=T$O`A&W(@b%FDUJIG2oeY;?EP=dc2|U zTa9bHrKSL%dcZ$X)wLY}Rr&7R2y9W+#T|4LeTBIhC<4|1doaw-6nJN6p>e>I!1zQg z(waeNt@X!bz>S&}bwAww=r<)cZ2vkWZJO^iS&YmCf z&OVulMIP>qz&v1tPt10GdpADm<9T}`7CF>q`Hg>cwBQdPJq|=LJd#Or4qe$~d`_kO zt6woN7-+X>e_%%<7FmB;o>eYD3yT@ gm0szUUb$lBKd<9oYUzj4#sB~S07*qoM6N<$g0j+%I{*Lx diff --git a/files/opencs/scene-exterior-status-3.svg b/files/opencs/scene-exterior-status-3.svg new file mode 100644 index 0000000000..401f19a5fa --- /dev/null +++ b/files/opencs/scene-exterior-status-3.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-4.png b/files/opencs/scene-exterior-status-4.png deleted file mode 100644 index 2f2b907fc843d8534d1128b17d2f312ece43f287..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1943 zcmV;I2Wa?-P) zSbh%KfCdv}&UqzNn{K{faMU(yhHona7X-20Vu+n)8}gXFNTPp8tSq-iJ6V*M&C9Kl z4#=ptO)uQubNr*{vdg8Vx3qP{^huiL-t#->e8120JEL^yd6)RSdPNy$>##+m)S+k}z3r$G`j4{;J)lpMZ!}|5> zDK9T)|Ni}K+O&zGp&D)t26L1CWM5}tG6gF+P15G{4+AdBjfyzAz;*s#=fh$$73N$vA?gmhP-v=>v zT8QKlcrQwoK^z{7CtIAGjKk$~}n! zV4?CAl-TrecA9$|t~F=lk#KA*0_7;Z0BbG{JKzHj_=P-y8{ikfzkm=h8(0-^8w9M@y4YHaBh%+Dk?lYf_`*aZGHxBX3h;iQ(`orm5jo~O z`ULP1;LCsqd>q&$A}6wX=br=nfjk7US$qz79(Wme5%`dZyyiCC3Ct6bH97r4ACMN28*{$j3%nsB zkGapc0$sq@ff`_)JNV;9MAddbWL?F*t7lo;OP=Sw;PiZ3w#VH@{Xhyh>&WZ^?g#D` zk>xqh9s%|`C$x&lEjiWZE}6Akz54Z!b#gou2&Hrn`Uv^sh_B=0?wc#_sQzG&+ z5jg-fIl$TWTdnm@;8o!JBC<$C<^aopwC8!>b8CGH_#@D1W*2;HL@^vvN`bXDa|X5m z&8k2Luwc_>d$2#%e8Y~|KA;YGeiV7^cDlXY^Stl4V)a#EF>p>qT1R>Q7*MQ~YO&Tn zEFzD)i2qGQzM1p<3&0&fTY7$=rT2lG+EA)q`4o28Sm0|t%i4aNn}x)5vxSx*Etbs%er24h{z*3 zhi~^gSYSFvsI-wAQ~g z#yn?(>cNQs$C{DOFpqwq0d)Vy61Yliv)+GP?}bK)_;dikrl%hG|LV=$*4k)e!^}I%8Jn>g do3ZQG{tH-lb!h)yw=Dnw002ovPDHLkV1g+Rt~3Au diff --git a/files/opencs/scene-exterior-status-4.svg b/files/opencs/scene-exterior-status-4.svg new file mode 100644 index 0000000000..aebc14b932 --- /dev/null +++ b/files/opencs/scene-exterior-status-4.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-5.png b/files/opencs/scene-exterior-status-5.png deleted file mode 100644 index b294c1b15a24c28c488c804da184b9402cbae4ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1924 zcmV-~2YdL5P)41!SOM2ni=rYE_%`Q7LDJm>p8=RD_}=eaO0<1!|Zu}ltvU0)h}G^LPha_wU#AImN0YXOqMQPN<1EC`}XZDU%s6F{(b-=kq9C} zBoeu-(&!(6dCoZ^kqA9KJycay;haN6=su+3(&I*@sILyhDC-cD4y0<6bKy@=ql zb}i7je@^S^OG;qsrW9R?*w}%K)JuuMWwQGKBpmuM)?E@J`2^mF$i%$r0|*i-!DTbO zz(2bZv4IhQ{~35-defm4U^^RYD}f(NIP{?l5t+IvRRYYAP{~A>%~2upE?j4ibtPh* z!x4xhW;)KE8TP>YJn&Nm0=K{~fPVo|-?h%~Y^<#)kVvT~QhqiO1I`}lO2m#1N1zfD zs=?Z^0N)CvfX}IFr!RO@fER!mu&A@Kw)~=q7z5TG>Pp0Vha>PF2}iGSne@e4yc^gB ztOnKtkNT;rf%||5fzJXr0(Xv@NJ(kA8#r~aD-rwma0E(#lCDH7JqqAY`7oKIs@>k= z*8^KbMQdu5mR>O(+Xp&pR}fIkgX0<(r<+=JQ!{83fE>z~_! zqpJEHZ}FYLo4`B34ZsprJs1SRVEy~EFI1Lpemt^e)4K5LEW5vfZyw2ff#6+SFJ z4?GLJ2s{sbL{(q+3fBXZRdr$Bd7%f$s_NBwzi$Tqp{kGg=No{Rfo}pcfyI8~kLVGp z+xLL;75BFNb6Q^rg5WtH&o|~;+$-t@GQcrUrVY3UxLs9m$$NJ%u-O}-MOClQ8{h52 zW(Tm!r`Jkf^uj~l@b6v){2oZD>PPF64WGftZX6h#J1s^KBhS|*8y@Lwtex*u+$%v4 z-0T-<4uaqiaI31uRduG{%)bMgffs`yh~_AK8hBDwf2OKCfkqEFSAJ`ZSr5Dh{7_Y| zRnQr44hu70As#Te6I ztxc%v<~$Iu`Z#^uZ}fIm{gEGg82A*h$jAF9fjU(^oiqPyBC^prx6_r%y+>BoG+hXI zp_TYtB5$Xgt2cK4)LZH<;FJgaEmeIg2T-H0&b`1cRb7`ecDv6ow*XbZlfZsgDqk)X z_;9^YIWQIYdsian3J9$&zKxi#s=vLLZQ2}+3X#vx+~A(3ym?qwAMc3O69)M0LR(ElVJhpfohDI zHh7GD_Y^dEFdyjiL~MbfHRO@fQCd6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-6.png b/files/opencs/scene-exterior-status-6.png deleted file mode 100644 index 872568b266adfe23d89f24a761119fb1d6a27110..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1980 zcmV;t2SfOYP)=iJ`gOWWHtrC2uU2x|kK&N^7xw3)ac zxJAq>i2P_+!VuI8YaOY&jfF{VV<+3hBFuCkwi-jMGa%zJiX+)i0%9)9hJ%<2m~z=j z1u~k#TDiUF?2n#{mn)^Ww4G$>^WXcP_nhRX$l%ln@m8`5RthLOYJC`+U)^O<1AsQMQsIIQY7&9#U z`i3%j%9JT%6CP}DZ>O-ZkXf^4v3T)f)~{dB$&)A1TC;E8J}N6Ksi>%+sHljQD_4@6 zo10iB*4EZ``qProMa#;{qN=KjqeqVtiA31He?NJ7c>oj?6tHB;687xb!_=u$DJdx# zwnIE@66);iq@|^W0|yQ;Y0@NS%$Pwu9>3%pYb|5Oj2YG}G%OJ?#!y*VNoi>*ixw@S zprC-QTeq@e#R|H+y8#FU0w|>j1OnGo8l4P;thEFJ0XjN5$j{HmT8mPO?(Xi39*@Oh zm10J4Occcq2?X@L&z&hpked$U>iZ*o> z052e*~7H^cJkSIP8G`8Mr?h4!xfya09#y{1XTQV}UvC zwIw-e68SvPs(cyKFD0VEn(s%$p{D)_+=KRQ$C#l2Zvnc1yG5kk6}$<+E+7OfXs<2F zzA7SGgE4nU!y)O9z&zy-zG`DVSL^W;z)oNdumO15O}T=LEXR%@MOt;Lb)aF@ttAOd`Eun`%s4qO3v zC(!D&e7lIWIgdUAd=+>Q@PMxaD@5c-Qt$i{uod_#uoh?s?h}!?i^%IPA`1pXL=6J? zcHoTj_!8hw;5wyLrt|D!t@RDYnA5;HV4;Y-;|SdD2n4m(d${5q*qO#LP z^)a9<-H50(fWHFd0aFticcAtFe-V+NxaVfzl!*M;d3*ujRSbR%E{s$;5Fba;7wq!fYmO&=DDKhPej9gJ0AE0&?O>YDT~z1 zM62Cc&{%sxX@%12&9X?%lkK%7b6kph+w;6z-2(NV=N$oV6OjTDDRqbW_dq@HmgjlF zqzzvIUJ#MzMPvt1>i{R)@3ht%fOmmkh{$9Sxeh1?;-2UI+^zL3U@y>W#%6r2PciIP zN`bXDaRycabt*##uwdh6ZDwbzZv6W8EkGsk+GXU?TeOhk%P#u|Y6z+{(#n}NUE zOm+Y9RmG2A4){QI;1fi$I(q8I{rW_M*1EHQl(nTT9Sn!j8r)nu*RVKddTYO8iM8ANJIbYdZ0q^VB@xBM110BJyGq zpjua*JAs`d@>J5;T`t2^0QtZRz(Jd-mZl55zq`<_z&c#y;16rfu&>#CFFjr~cHj4#g{shb%hzL+SvF=E- zi^#oz-(4A+L}YD}gciG6l06uYJK&F_eGe%V*7VMfhC@61Bd`>upU0ZME=Z}giMWVt z^7(v=2+$HxP;%I9B>HCSsdRlGIpqT$e^{Sl}Firv9Xj3=t`8K>vH z(QxS4K!-he_Cj6ZGso+Jkj+%9(>a_)o4T|+Q*pom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-7.png b/files/opencs/scene-exterior-status-7.png deleted file mode 100644 index c19431025c42cbe98696a531473daf375240d7a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1957 zcmV;W2U_@vP)&$+#~m(pvRaw}|t#L>7;XB`YSF%$NJ zTa5V$B5z}fLr^b_I#YEU3zOPnC!1g=#dIJR#SrTZ$asw6NH(_-1XGp`2QdmPRW5jYbKF!=zFv(&;oqhYqE+wUux!`+wJt*R6MS0Q8pM1=PC zb_xp%>G}rF372p<%<0poGr3~he{jEhg`}%`bQUasawwh!jX8R3Xq)c)Qu06dMK*FIfW8Ebol1X4YBHzjAK7b&h99+6xf%(Zq z?07H0{|Y=by7FKvV4Et63xHJ;4t?oDL`JP`jRP-9C})aGcXUH3pTKqY*<>Pie@_G! zAZ9(zo>hC`e**4JCSo6C3EToN0sjP|{?t0HsiHVPOCp~G8YGl6;anmHoc%#E5v%Ko zz@3=T7Od?L@C`sKaEGck`Gz+V*bc;iSxptiLobSmF<|W-$wW+hA}~Y3(N|o1>S8T^ z6xaqV16Bi1`Kil*`+$dmTY(#ayZTHdCwGGD`1sq&L~KV-1cm{_lZn`YJ^+8ghsk(V zJ?brfJ+NLxZgS26yaGI~s{6ctG@!s0!0bK{DFH|(V((>DMzgT^hd>$dJzzQz0)}^; z^{ut10cVWKch2F-H2dR2J;`z&dY)YE`{H zL%+j^&1PV!FRul@>4m$z;m3{u{s6S9>Q_n|%co#u2M!F*oe?95kvB>k%b#efD4ym^ z-0mO zzf{$0RrN~$P?ipY;1_6p4iN$8TvrY(0V*ZOL~!8Jc13PWd*z5< zHLVBc0I!}WJnDSh-V_AE&wR6b0Qfv`LRG8JGyZ)bUqq^$a}TTPQ{Ll$SJfY9jDH=t z1*l1nj8q+6aCHqLWfBrxhjqXY_l>VvlUap&B2w*%%uv<0gCJ-GHh0Bvw@p71ks{~Z z7gY7V4BZz+N2#i!x}(o}OU(d2_JDt+sxNc{RN=dG3$RU9S9a6g?kmjAKq2rvu+Qbn z{A_{ubQYQjtO6z_6S0mgLTkN0Mod%H-)4BErrYsigQ`B7p`UGxX|UF2W${sKTML1o z0}aVU?5&;%OcxW{>~LW1AAu>o5dkVjS01eQ9=Qt$`y)f0s;=l3LU;RKGPExi_rM>+ zgdPwCXFH}Q6R~YQ5txsd=Ww?379^525$}<;p-|}VbUHm%RkvkWe5NtxH`dxME?4gB z3vit6ycu@h2ZCfG_F7K_7Gg|Y=QZ+^TTti4yki$|Ycdi0cb1{m<&oSzT6_dhA))X> zoW(hNZp1sgDw&8q(i4Hjz!;yHUHYzWe8R``u4E#1xVL1Fo;*`I{OKcAK+NUJ(rl7* z)Y?|#b1Lm${fdCAfG&$(0qjmDVlQ5jXO;61sRr^9c`)0ma=E+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-fog.png b/files/opencs/scene-view-fog.png deleted file mode 100644 index 65ea108ac54205654d7fff77793bd02a80e49274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0s)y z#W5s<_3bQ2zGed+7U?7Z>+guMU!A)6KqlYWu-V)kC#TtN-hSa;q1>T#;dy^$YR|Jq z&8Z2ma4VQ+`21V!0{*o^%lY3J+x{0`C^b9u@sy1#H?NYM@ocW$mA4}I_8wTTd$WPp zfvHb`rQ>uigK`UR4g=GXr)CX;huF**7#|f$AK*x2mTq7u+{v5JvZ0aJp<%}zHUp;{ z2iO!GV)B?J6mt@o1r+qQGxi9WZD8aOn9l$G$W7fm=J=0#g&%9h6+0hVe>iwx)%U|Q tdse4w?-&2FKP=_Mb0bryqQl=+`Cbc3rY~%JH6Q3t22WQ%mvv4FO#nbqZubBH diff --git a/files/opencs/scene-view-instance.png b/files/opencs/scene-view-instance.png deleted file mode 100644 index 6f5e7cb2ac6c688073347515a1b874df3cbb33cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5m|JWVRUF2@GdsIGyR*C9ZttyKE|IpO7D1?h7TR(X zx+N&YHzht1HAG`H#>Hfs_OPUkBK|lD=Yt%Rt}G z78rX#&)^isKspTqYpHX)yY7_hUlXu$gKMFVv8}R>xv~tpB8O3gMaQ6LDuzQ*1mZwc zQPCZy6P2#{Lofc;cyQ=KNE3Qr({5y1dewxDQs0kSgE zVASg&84U=A!kiu9XcWBzL-6_oXllOj;@y$9XB1IVNtW$6Vl#$XTyTna?d7f}#r0^%x{g=g#C z?hhu`$XydqwyuU{io(t!S-*W*wu}(Dczh^~B}*2dcve0{oeqN@FV1!LqNU{mW=jkO zHVJ}2u>L5z3D_5n!yy6`Gpa4nK86<|d>@rIN$>Qxb*|dQB4b}vWy|vlEqXK$1>t;PKDMsPrnJ~Bl?Cs_-tYeUO$5Ep%6%qO?ytKi&8zTHm5?ZC(0!s~eo+ZtDG1!eV+tH7#LD8KEZ zjOt~J60Y}N8OF!;hwF0^8l zT3KDsC@Rtfn5YleQgOzosOQ6KUeg|T-_1VGuemmEY@$QwmDu%-bF%vV3xP?}{$L$43%j7C!GWs-Xm?*2)yGV;lH zyY16)bgIu>orYg~jNlK}P;7EcBK8l2QCnFKQPiQ4!*_LJKEZ$AIf6VmK|U3=rqo?Rz^<}~kCW}2%DWIgs$qkP&`nV5wAa_TIi96r;69|*q01lX<)Ov!lM z{lEQj5A`@-Ctsh*^Kk*>{IU|O;gynf35_&g*|K3pqQUa{42Lh}@Eu9;6R7wI%iF*} zO#Nm;(Tx5aZ^Sgh9$t|nqq*OQvWLpxuvzg}_hp=KZl&JFfd&#)Y`*Gz64LkpME!VM zNcpmURDC|Fc*ai?0%%tGUPYGK5TN?s!*N)@kKuzsN7K%i6 z72lV$O{ErDDJy6l4na~Ppnr8tIv>|wbgz!%0yKa{k5;i9yJgpXX8pF2NF0SmhO>bi z7ZTe2_xgB)01b%O2pRt>tpY!!etmoeq?MR07*qoM6N<$f~KHTxBvhE diff --git a/files/opencs/scene-view-instance.svg b/files/opencs/scene-view-instance.svg new file mode 100644 index 0000000000..771432bebf --- /dev/null +++ b/files/opencs/scene-view-instance.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-pathgrid.png b/files/opencs/scene-view-pathgrid.png deleted file mode 100644 index edb350b8b5842e835fd05cb269890e896f6ada8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DX&fr0V0 zr;B4q2J73Yw%*K+0&V5yw^;tL3LQDN-aa8QHCcJdrp+5frkDiQHz_zt*>_FZwrFZX zwz}9xe<{b_)(PhvM2ffXD?XWXZu5r+3B^Y_p1(PFCiQ-8S(%^aPhln}1>we^zmHkY z2Q8C7`C-$^4|@A|H6PUVuet3b&G2*9rDY4ag^eYq*LE-eS~AO3F;B44fBo&?_&NOd zZCcE8FUapOiMVk=;doIW|LkupdWvcGiZ5=kH5NZq$k_8rW**!Bc?=%<&0m^%6|db= z;`r_%{pa$j8QZv=r`>X1b3!=Y>@4S<9Y%q#x>_CINFSO1m`@{T!kyPLpEK`rHoRaI z%X7FP%iXY^?NsQpHQbX=vEA1`u~YtON#N>oNCI*WBW`TX0bMhPcgULq^@TJ)2HV zt!6uorW2?cu#X*iAq&D*QJv}JJv7{`b z)k3KM*eZYFL)TW66f~aPJF}y=)@%9?f9oU5wna#G*e`x3zp>f2=Wcn!|x{ B>*N3c diff --git a/files/opencs/scene-view-pathgrid.svg b/files/opencs/scene-view-pathgrid.svg new file mode 100644 index 0000000000..b1a0c8845e --- /dev/null +++ b/files/opencs/scene-view-pathgrid.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-0.png b/files/opencs/scene-view-status-0.png deleted file mode 100644 index 1906fd89cd46cca2821b24049ba5e9312ad6cdd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3547 zcmV<14J7i3P)e5SP5`cug9Kkj}3aQsSn5Km9D%`uz1gN7w=qK6Yf| zx;#L@UjiqSb8tBzVdqD`WL@+eS5$t5)@Z}9VjAC7~YB7(PtaGf^3QyXznFqIZC{sP~qgtr8GBJ0!T(YNnQaNSWV}L zrTGy@w!Uh_^?&_zWFh;@pN|Y;KFUNg>?lxTW~w6CRuJRW^m00r3T2rc#Pq6(7Gv{8g@tbw&d*+_CX&8D8zicfF_R^Yp7|ktiMuq`Sp=|NxC1E zF?*c6ko$p$$zbSN+RXO2exw0OBpu7peL+J81XJnA#qTXD+87rZwzgnqHlm1iKV_)S zo?aY1cNqhH{m2f}KrIufYJ1TqQ$f(_kv=&AQ4vO{l}c#TD!2y;2PI*T&xh8|9ynYB z_;7E@`U_>}MMK6lU{i@(_)`^2hePR`1tfv}1p)o11dzPIB(ja>UY6#hBa=$e{F!SR z<1!hC0b+COu7gkocuWu1AdH-8b&%>z;fPC2fH^i22Avkpfk8C1bfC)8jHcF3L@GH% zDJdZkfhvUzEdxHZdwB%N^8sEEnwo5D%g&$Q#P%KK0jFCKroKnbj4&k6WLoIx>Tf*2 zULXTv=+H$5FiXXe|HPKhKezuOuMJ@tnV8!2P)Xg-VAOT?DeI$}z`lC`RLxb~DV3^-_K6UE8kT>E? zq7IqC;wYr7QjnjuhGmg?xl;!t!VH=KF;`yQfcmOB%ptkh2RyK-jaW202O%0Y+9`P7 z-+LT$!_|lnRpVl}8_g~+vh*^9P)snEM`M+CBiR^@g4M%ukui~|t*(DRQ^SY&1#WrM zpn%!SAH=FvD?u4}w1n8}qXL62Mh2&NpZ+)~@Q#$0!|8Slx5#PKrgJa{!eMO^7h) zkeV1LM$e_1E2y|si-mDIRChTMnmz@&>63{`0mn(_j)n%ziVs5t#et=N5ZR$JD2PP{ z#Y*nqle!)cB*=sXk@cK{3*h|4N>p0gkUKRskdZt|*5hz-jcuKn6djI8lTlokUgfPV z9h|kblgpo$20h8s-tWThqo?29ymi~7%s}po-MfX3nldECM~N><*2~FiR!C|a+iGuY=Q> zXg9H%V}a!}VV<#uOG_Gx(cqq9s_hr6wnnKCy84{lty5E>57B_doiv!d`bo~}xHQNk zLbcE9G)hDmLwUJe&Voa9F>{;U;q)9TEmx$I400)x94A@KT`(8sxF|7DH-CNzOJlXv z;BffJ>c9*tA0`#+orcCmCyzq~Z-dhshG&NESRV1^wYE0w+_N9ttAG5hK;U6%Zik*KWOiCI)4Kmc zjaAXv+lNdgF&d)8PMZS{7u^Y+R)g|d3(C)4#GU3a)KNaK>~$m0AQug15Lx}P4Dk4P z{$_AW%`o_@53K5Q)WWP&RSA0gDRi{fa5IQ;R;d}|L@2tb_%%|co*W;IuuvV&P!VjS zc$=1*i29~>C|qt2B1!8>agm~ni%*<|f`-fO7QshrOH&Q8E(qPV#naTFSSAXXdc}ioNrOFO%a8*%0)86!5)>ak-Er?}UtUzAQur zWr_;h+bG^kUc>z9DTt2@6SEYhQz-6OvK-sCzKfjXI2iOH@KF!bVedk2h+LGzS)UpY z7tVuHp+L#`%kVc^Q9$4ixAwzC!8nOhq@;HSN@Fyls@IF@dL5or*W$0wq~WbX1due`Y1-iA)25SCLd2+yatQ62;2NC}oU`foJc-g&GS|sKu7c zIKe`B+@1bQY(pB^K;Ipb+ay0s0DL;dLs;Wpv{)}}IC^mZE0S8IqRUcqwI@a2%#4ES55oahUmI4gm}DGTC!1{mS^^ip_E~B_Y49 zy-RVWxkIedkw!h(_kxoTL$xmzA9&NyDP!F%X@9I#M8>SSk zu^TNeF2)_vI@JL6;|@bO78CeBrwa!TpTvq=B2n7mf}R34j6OJ|fXz-xfKjKx!INdO zm`D?{Qq4RiFP4s3u-YP$=Wp+l*X<#^WC+D$RY zq+HCHr=PdE)2^y(>OkRwStLC@HFm?FsKdLnEY?GxmYa7TK}KSfKp#2Wi|%{ipwG)m zgWX8)H~?EinJATs08i+F)sL>mr(3r`VK6{PW!plo&rtZGpv8|8`wL9sf87wEsI`*r z&$QXM%WMuyXP@)Ayo_WDbhRoz(ln^2moL^dAe0Kxut&sBErWO)_ZyY2q`j3h-; zYy?jR$)~vcp|>7Do2~rm_eV%&A_K;o55@qMXxWY(pYR8Ee+q$gJYUD-vV9O5-U}7c z&iZi%{9JkYlkUFW9%-?~cQ5%vQjF>O>1k&Ab_KB?!aU-!wfCr5U19A&3w@wxCMSp= z?#&%`iVG#mY8r)_mab=xA3E@gwAObW$MZnYfRT}X+q~P~&Q6Gayw4jzqMF00)~kV! z-Rz7{Op`*w}>8~%|I=-KusYR6ERdd9TW@4F4XW{eXb1$_U#P@=h(F-y1r%u zm@#bR<>mdRvbOH;t`Uq+w74|?z^aumYE+7Kv$Ik)ZjYCzWn^lKsk4<0-PmjzHN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-1.png b/files/opencs/scene-view-status-1.png deleted file mode 100644 index 6f5e7cb2ac6c688073347515a1b874df3cbb33cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5m|JWVRUF2@GdsIGyR*C9ZttyKE|IpO7D1?h7TR(X zx+N&YHzht1HAG`H#>Hfs_OPUkBK|lD=Yt%Rt}G z78rX#&)^isKspTqYpHX)yY7_hUlXu$gKMFVv8}R>xv~tpB8O3gMaQ6LDuzQ*1mZwc zQPCZy6P2#{Lofc;cyQ=KNE3Qr({5y1dewxDQs0kSgE zVASg&84U=A!kiu9XcWBzL-6_oXllOj;@y$9XB1IVNtW$6Vl#$XTyTna?d7f}#r0^%x{g=g#C z?hhu`$XydqwyuU{io(t!S-*W*wu}(Dczh^~B}*2dcve0{oeqN@FV1!LqNU{mW=jkO zHVJ}2u>L5z3D_5n!yy6`Gpa4nK86<|d>@rIN$>Qxb*|dQB4b}vWy|vlEqXK$1>t;PKDMsPrnJ~Bl?Cs_-tYeUO$5Ep%6%qO?ytKi&8zTHm5?ZC(0!s~eo+ZtDG1!eV+tH7#LD8KEZ zjOt~J60Y}N8OF!;hwF0^8l zT3KDsC@Rtfn5YleQgOzosOQ6KUeg|T-_1VGuemmEY@$QwmDu%-bF%vV3xP?}{$L$43%j7C!GWs-Xm?*2)yGV;lH zyY16)bgIu>orYg~jNlK}P;7EcBK8l2QCnFKQPiQ4!*_LJKEZ$AIf6VmK|U3=rqo?Rz^<}~kCW}2%DWIgs$qkP&`nV5wAa_TIi96r;69|*q01lX<)Ov!lM z{lEQj5A`@-Ctsh*^Kk*>{IU|O;gynf35_&g*|K3pqQUa{42Lh}@Eu9;6R7wI%iF*} zO#Nm;(Tx5aZ^Sgh9$t|nqq*OQvWLpxuvzg}_hp=KZl&JFfd&#)Y`*Gz64LkpME!VM zNcpmURDC|Fc*ai?0%%tGUPYGK5TN?s!*N)@kKuzsN7K%i6 z72lV$O{ErDDJy6l4na~Ppnr8tIv>|wbgz!%0yKa{k5;i9yJgpXX8pF2NF0SmhO>bi z7ZTe2_xgB)01b%O2pRt>tpY!!etmoeq?MR07*qoM6N<$f~KHTxBvhE diff --git a/files/opencs/scene-view-status-1.svg b/files/opencs/scene-view-status-1.svg new file mode 100644 index 0000000000..3dbfa1622a --- /dev/null +++ b/files/opencs/scene-view-status-1.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-10.png b/files/opencs/scene-view-status-10.png deleted file mode 100644 index 1216c180fe3976e85e0377406721156f179de0fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 777 zcmV+k1NQuhP)e5S-)!(K@@&FcL5352o@qSf?yD96|7Xm!hnU^rO+-G zrqcWkijcy>Lb0(`Q>Cy7qNO4M5sO?S{;mWCiD$gq9ly7GZ=BszJo32x0W;k3-n^MN z`@QeInO$}xi$)D@roq@#=xqrgLKD3S5`_~3r*Q!==%$p@fb6t-)Frg?vPa*3RG2en zottDp^id&A-TvOk|Lv&rHS>Ti7JMwZsWJfIOB9(_)M`&dKcfLRqVEvih@)Un z7X-Ki=)d_ezkO5eu+0GYx{dppDZ<(|F_<&9W7)?i`cOmyupsZFf3;w{VPifBvW7&k za(vJ&P2^aYs0O<{1p%gyfazy80a$A%!El!cVBbx~e5521uw9HzN@pC>$0zzI8CY>; zLiA980qiG$zlA+O2$0RQGFGN^0f5oP_g@^FDF)bKKP4S5OtK5GUMxD6)lc`pqgOqef8N7MWN7)s1RdJf zc8?WcyaO=%xm0k!0JX-AT0(GgfZAPNof>g-#yKYW&=egl2XPojTgr<$0B!|~S3+BHuihsFP<0MM20{ju41^L=5~y|$8BkNG zTp(mXNub(2WI#=!a)FQmC4p-9kO4J?$^}9Olmx2X8=ryPcVeLfuP^vR!JqeyGEhVL zuM9K-JoA~$fd6f*hGhR0l6y0UQGRLK80IA!zl0hGo`d)e^!6c1cMT8=00000NkvXX Hu0mjfK>uAc diff --git a/files/opencs/scene-view-status-10.svg b/files/opencs/scene-view-status-10.svg new file mode 100644 index 0000000000..8263818403 --- /dev/null +++ b/files/opencs/scene-view-status-10.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-11.png b/files/opencs/scene-view-status-11.png deleted file mode 100644 index dbe8276f07bb8180ff786c28c5db6b5e134a52ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2424 zcmV-;35WKHP)e5SZiz)*A@O|cJ}Qxe&Bb#CTU|kI3`ZPfgr#b3@u(e z;SqjRO;HpTQdLg#qixhSQl(a?tturys4Clx23>(~z1iKSj?ckaD&=icvp=R4=#8BU^td0hB;j9rEtk|bJr4T{4|77?^ozE#yTsC2bB7BA?4S*!u0E!?)bYIN7y0? zK6Yj0t~@}%UjoPLIGs>H^~di9+%%uP124y{U=K50Y3tev3EYF!{=DZ|TR`cjz9IsC zNs5G{T^!9Vi9C&&wY03or_}GTNC6mk{U^157T5PjDZ&y#w2&iMrEhTj4?N!sQB=VC=$=Js%*IuVPpMDG1C6RofVUN31oj4j z|1-q^$UxTZQ0~p;RW*PZ{dxDG%BGBg`d%WIGvo$^3a8UoQ{PGjAQ@3dUI7`{N%a<` ze=e`ujqDGelK_4g-zKskJqPu73h?3sFo=m~n69k*ta zqfEzfJFGg+kzvpkrWpk!={V>)2_yaxycA*~Q9^G(jx~&U{Li0iJ+?6)tVt+bl!uJ;1u*LMFc}T-`3VOl;Xo*azJXylJzli6UwQt}fdjH32@R+};gfb> zV(BoNzF9yL*ry5T-xN^f1tyUAc$>gB8qa<%sTP(B(;r>nxz8Ay}35i`aph? z9__oOHF;JtncE4P0iTQ+Zo!C+8v zPgE!}v{4>2i>C1~X+2%wTfUQ>RADF9KkWCQ?9nnjke`iEID)1RK89}KHkM{5qhrK} zYpwtm6Wi8t;EYLDrZPec{IXAcb2x&!lLs4y*t0P~tOc#Dvw`uY%2Bh5w0hi;XVZzK z-!8$5$`y~utoIF$;I9WxV0%Fd!bES&EjR2&AlDR?WPn)g3rHBGKx8&#QnJW5a}*=7 zt0V$`>QLh_8>Pjk*44@s9@yZh`Q`mq!|y6{tmqtc!&X>?vcg4dQnZoIo!746(cBbt zP#ko*{3x~Yq*am&EqtBaf6gPm5CZnE(R;Ux;C?$K=?xxEk7dbhr>B=&jZ_rI72pg! zD+#eC>?C8Nk3|31J8J$?VlzIyGRuN9w_GSLe}p8HgD`nvU(<0cF-fF#6W;4}B10!4 z*UYQTo^?jVo*)*t9&BVgx>|mY9C( znU<#t7Z}k-vRJeBF=VBupnqr-ZyavJ)_fb>6lF~}+*q}MhnX_4qKfk_QMw$Ea@r0y z_A4Obua4SUmJYiI!>EgAJbr9KtO+~E4It)Q9XUy>b1ix_^f>X<6YF6$o6vrx3+-pm zVZA*CS1F%g9Q9#ovQE~vB8M)oi2u06>~n{kCIuf4aH@79tnxmP=s^X1rb&mx{XXp2 zQU$ZoD1$#w;H&JZ_-Md`U@(O81v*(W3iEE-H9Nf0Pwyr8L>O6LOzKoDtg5MfpD~|m z;L#p2!Leo;^n!pBpIkt=`#MT8Y-s6oA(f+@l(LLAldxd?*eQ~89C!xICvxZtNV~o) z$eBJDV?guKqZs_}G2~hGI7C7D14o5?2z&pNiwH6B3zP6JfiE@#$>ST7c0tDe=bp$Y zWgPYqU`~o^0BL^3JFo3UajFTebY9uEX|)`%>^#H3moV_xHQ?jmvBb+_U?430EAG~; zHq{`e9rncPEGyb?yHNV=Qlur@@LBIITyDQW*(MDwWKBpMevtE@AIMUpVRT&n8m(JtP35VX@wlkTefb+_v|(__l#oO6B}SM=uWs~eN9UTanDKa0)O_lw zeVpI5^M~Sl@9u|05m{jsabd&{lNbbj>X6UlbIo`>*hC&s4bW~5n`&xb`G!TmYb+Q+ zzLCR4Z}dDq)mbHe!F%1FwgJqT>Y7@Ph<}0J0$-)PE}zHmHK3W!HzNaPI{93TPh^0) zk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-12.png b/files/opencs/scene-view-status-12.png deleted file mode 100644 index ba6d1f3235bea33a17748c01fd6c16f9df53c5b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 923 zcmV;M17!S(P)e5SkH^oKop)yva+Ci7k?po76lhCdhieMq8AnM>fZF` z!HT;pD0E(BMOa)Mi?N5$JxxHO=(Y{t5RMO=PC|e{+ajmf*`a#=9|@VV`B&wnWn*Hs zEdo7wdkiK!8`>3tX-BDwKox<49;hT#(1C@gTOWbcWo{IA99;Q%8snd%K0$rBy)gG{ zl=#YgSoqqX0?d?;;X4%mW7^g7P;aw;`*0k#RhqCcdPdG%`x4 z8%jdNovA6mopgg3IQgGoxC|m|Nk@SOicy%E1ax}H#e=N%qT zf=Fl7_nPBbtgJBy0lg52Jd%!gLj?v__(MWkAoc*`mh)yT7AtEkK!AvV#47f}xny5x zcH#aA_sRv3c%jW%zgeuTu^<5g1QLD!Z;EBLg<%&#K)Vr%+R5x+-Y>^n=xQe6jJjctte}Om{JsxP1KjDNNdO^jyo4LzMa{=zW+R> z7YHQ5o%<@00?_|~qHwEcFLaN`8KV6791&t3-Se1ti8g-FY6$;(U5HLg1ZtmfyAouX z5)#mN4d6eAUhD=v=$SIs?}ljad=#B8n?kj5&7yS7?5a`;#2Al57Y%rH5ag(jfVY|e zy&*bZw}gV%rMHXv*3mxZxxL3zq9s#T)E>UtXd|#6R-`*~Ej%B(T^W1a|51;;jpsO1 ztz(s|JIklu(`szSWQ`>dAOIjir}Y)on*g|51<*dEG7LD)rNUxmjU^F?U__sXp0*CR z>mN2C8IR<}dE6XNZ5AtQoB#m=w0H4!6ToM2E~KPFeO%$NmYqg99H#9pz%OWxn;lu( z6WhdD0Sy|*MIb|N8}!QQ_f-U@9fK+YRRjupz2O07$UCzgTp)2TYc6z@ESOK0G xr6PbUkTt+{IFhRb4D@@)Is&GItOHL`_ybdd-_5S~u(1FD002ovPDHLkV1gW>mmL5A diff --git a/files/opencs/scene-view-status-12.svg b/files/opencs/scene-view-status-12.svg new file mode 100644 index 0000000000..2e16eb8256 --- /dev/null +++ b/files/opencs/scene-view-status-12.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-13.png b/files/opencs/scene-view-status-13.png deleted file mode 100644 index 3651cd609417d6b77e862e22d97af1f2fa9f45dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2287 zcmVe5n0stgSsll}_c5=|qc2+8LI*V1Hb8-8DKCMxP*6I> zg>E(*bs_#CU<`2+O^k}}N{lA{p;0zbffbD#-HmaDZOe9dODL2o3(G^h6qcn6ebM)G zrZdxd+kc!>9*2EBE&q5i~xPdiFaBJgQP z;DJQ;IAJz!cNuBERG#CO8aOy3QfM~vTzy4(dCO#3I4%3Ky;RMPw`sd;g}`xp ztODmsF$!5pCJ{D)gMm}fKOBOO4hA7vL2H02biVBO4!?M@=FDOJ_ zt_^m9`(=h%%*#zM!^A5X@C7l*8=+Wj$XYlLDar9L84NI+jPMT!7&`)?5Zb%@;P(1Z zTX*}#%2TIQLt?VVY$Rj!mQStz87{wg$>9ZN@xbyVGZI7&P3~bJF$w9J^WaRI1Bcau zA>T0Wwsqm7#(QXO@5UU`QHp^Fo7yPyX!C{88I&P$JS2i7hHq4ql^tO$)aSlhrfWdv z5zpsotiRLs8@hg;@aFS@IU;{J%W62VHr0w@^5W&b09LJ9hU^6y5Cs9f18&@EZbyCn zZ7ei%nCCEq=LyyuLMs7#LK2c_Rg%moTA*vhk3quskh1zi3chvlB^qo!=S^ zASg+gYvhnjmxW+i+*M!G1kqnv%oO%~FwEiXKzPHMin3a2!gO|_i}c>F={jih^WE;v!ec2GZ(T*F3u*lYSdTD+<>>rFy-+ryqYjbq>!hNI;m} zTir1PrwPa~kEEyod9gj9pobPBvmupsolFZyYlI=)FR{QcR+jg%9x>Gtvyts(QDv*E z=h4iG5-|=ED7q41%*NZl#IcF=7CDzQ&bq+n5n&Kv|0$FFdmaR*M;Rw;NG+lj_t=2)y_<^c^p)hythPCxeSDuTsi zQsJ)?e33H|A9Q;W42F;wFQ}5yJU>MFt=^}6hv0uPE|3&vk12!khbS=~$~-#6R_bMH zYoO~qT_N@uykM8Ty&Krh6}>v|@2BK-&BCIR9p{2^OICt#^!p%)s zl43`7y9bFJWm4K@^g9Xj{#Ca;_@@(P36!;eN^n1j=$?3{P$@bKwl;Sq-S9 zMY-FRuO7nAU%QDAgP&u=TLhm&Ytj)(OuDgD?cecA12u1#|5ew%oOI!3XQl}Y0}uKM z`ZmJdcc75hIgx$$;2PC)McN>u%V;P_HtVu&#tQ}MHk`fZ#@1~ck>s$e=NWfH1C|i{ z$tHr2i@-;Xq-pb9zjW+mS-BbkeHW6`5l%4dQ)JS8v@0VXmj>EKyiLC&3;Y_5vAg5Y z>UuVoCh?dOq6~X(<6JB1x;)5TnXAHo(%ONK>u#v<)pQ8Ujsed!FHvY4eF{$ZNpGC= zDBskBYju*-enko`B8r!(&C38;RL1nU6kbZ;D>Zm+oann6f%mGaaP5uXBgG`(---p# z7OuhIkQc9?s6y$YIk?p2rErEYUzea5xOT{zC;>%J%6>bK2N#CgZr&yXL}o}Vk4L{3 zD*~zkyGl#(Wv3lgEdzM=nXNDz4S2Pz8rzZ$=<$bftKW;o_K~gfzXjJrrM-N~PvB-s zY7a<7-$!4MWq_Io_U_$_za2jY(cyqK&Wy%;UC0r`5b4{shP+>pD5&EwsWeR@W3;cU z=kcjdIMe;7WWY>MJ`>}`GGG$vaVDmIbYna*;L-VZawE_B2*muLp~b>JTpvFAP0lHg zwbxh?m=*BMQx8Od?f;L}eKRoGXeOqy*!Rw?Ge9q)SqIMo_#er`s%*S$50L->002ov JPDHLkV1kn3TNwZV diff --git a/files/opencs/scene-view-status-13.svg b/files/opencs/scene-view-status-13.svg new file mode 100644 index 0000000000..1f2b3ab398 --- /dev/null +++ b/files/opencs/scene-view-status-13.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-14.png b/files/opencs/scene-view-status-14.png deleted file mode 100644 index 4bddb7ebc3fbdb999de2277e1837413dec770eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1302 zcmV+x1?l>UP)e5nN5fsMHI)YduBt3$wd$~@jGA)A_~R?jE5-VA&OuW zRDviuxE_oiG#*^Z7vw`Bpa+Fqym-?@4|@uHi2;^Yr8b_2a`33w!1ABGac>44LZ2?*$6 zF{en`#(Hv_(D=74b>Yu>+%wU-B)bE)J}9KC5B}aJ|1jwIJmbLDvG}Bvlxqh7en5GC z&8yX~rT$XGWLJS~;>+0RLWh+M&o}Z5{ZGDJ?7~ z@!5J8BMUH7zDxVXjO;btkw6e8HbN?QN6pHLl-4b)$jc1~G`$W@f29M!YP|#vH>Csl z3g!DhsUWamoAfQQjWWt4K3gAT2gYMFQRf8S?+W#2vJspJsTof)LJ zB7G`5v8uN5fe(+h)aa2Gk;qqv?-@~dtgOe^3N$_im>b1Zh%$%K^dnV)%f83jeKKPD z=Nno*Ga(Kd@YhzGKFKIpsrxV;abQ_*b9T}BZu{>|&SJyqaER8Qw7}5#kGj*Egux5c z<8#3F&v)tl(bRhNJ6vYi0ZG@7clOJfrf8H6sUI0u24DYEs*-Y36BewDtitwUaD za+#3vfsc>3)Rr&t0pC|2ZCs>oT3$;LNTLAv2HN8sTuaT_V0=T&OYh`SDiId}6JqYY zIkfSIFAdbHE4!i&xKil|tv8G$bZn{+@cf-Tnh@MUUTpd+cqGv3t=l&Ae=nvYC+;g~ zzlPcEF;@sTk+BN!tG-u>ejw1cC}B5|Y7A$OhYK-CoEAvNOX0zv;JLKDgg@xBCmUX{dqZ4;>6EwIV2&rAK5O5{HKUj z2>}5OiE*6z0UMhCA%eAq;d&D!K8WQ5Rw^SPcpzbpZWp%5tfuLE6e8ca#<*lI0*NT( znvzSl*W8~ROGMZ68z3?P(SW?2e3fHpUr}LB?0VD1b|Ez${|KWNextSQ$Vu`SWWpL} zs$W|cG=975%yTOqlCM|E%UtTaP-@X)8nO#7clkngZMjHx;@=``=w8FT$Hei2xrXYT zpHZqkrvklP)iU4T$}}N`eUAbDw9#S~^u^i|v1S{pb#_X%e>tgii=ZXp*x^;B5{NL~ zid{5%`9q3_0H13D`h;r#I;}KX_v(ve$WiLI)E!r}Qhgu^4u3evY5lZ0AF`^9G~T%| z6Kb1w6seA~#uI0EtvxPlxuQ^I1Oxyi z>smA4oxo)3VqPwdC&x?ue?>q5U9scqCcqbSE~HFF`drCHpzJW}!jY6s0)Eumc(P;A zdZj)vD5zfJTsu%j8103p5cvXuLe!XJk%b+|p}az-F6=-dYRs{S^B^PM%H_Q0*SQ?; zD|~7%6B-KeqK(uJB>y*7xT;E8FuqE#zWm-98qHgMBu!}Ozzd1L0kFZn;oU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-15.png b/files/opencs/scene-view-status-15.png deleted file mode 100644 index bdd43407c9e2539051fdea0b759500923bbb98f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2701 zcmV;83Uc*{P)e5SP5)Y*A+c~=FjHw7~5cj4IW5KZNeUc*~(_N7LQ3- zf}*woMNxsOB8}8dZE2ID?v;pIP@5WBx}YYg3WeAvB!Dnm!V;F$!GMDU#x{$=yFDJy z`g`7=`NJPG#zrJiOMTLx_x}CwzjyAv=idANND3SJa5MB_>?(9wQ$S+V#(kkDae~BY zh$Y`~L(8j%mlvUUk;8oSz`42LW*n%29(Tny# z9w6`ykPJr2C=ccMsRseq%Xja>D+w*c%Ys+>v3@~7vP17VTG zwUv;8b3U`QtjDK~@6t#E*rop$@4wqc_FE+=5<$4p5n_-RxqWC|jMX)*B2lh_prt3% z(#Mq$!754uE$k5wC?5=ip%$T#2G+x83EItP(d+SP>)Jii>M{ zULia~je$L7eylm+XC^XW#~c%KKQBj*6|{H-r)CSPFw5+qrJw2T;`(_r7rMq9C26JI zD7n(i#_Uv!j6|aZlcb=j(~k~@m_MYT)~m)En%tc)oj818qkavI_c0l>u|1@W-|iJO z2+!q_sAhGdUV)*cPF481VxD5fDoI57j3X4Hu)%KyqV1{B( zME0=3NbBDZ7Ly59iy59y;$RZ?`u(_ZyAiGK4je7J^wOT)yH!QvDo}XHqx}4$NQdF{ zEdr8YpC{12YoO=_L6O%fdsUNl-#;eJ${)3Hs8wD&F4c-;I`e8PS(lQEjI6*7(gDSnV1l0K(lPX^$tJkd?5tsc~O43-#1E%iVDTPF&=35 zD9Yp8c$iVbJb#P}9e&}C2gD0hU?97CDj+mf8)aYQ;NQId$m=)BJD&9pkPREN?WVO; z((UM^6;CyKF>&HpwxfK=FH&9-F3B#-s1}9mWBM8>*M=b%j`GZJhRFY=& z7I=k;;3NDt?%d>8@U3+(bIpl&(!X%a{&GKFc%L!A#rEc#F(Q*IbkhiYHXjwUa3!j> zffh=?dJMzMH(|~oJ2Pt#CmXz&oI4gHNRbeYclyF7I9FK>r{aS%*@i~)yrRVm9}D3? zvxNSL~^s247L6{GBKnOtMc}qaT!REm910_XA#Zxs4%^PI`-t%EuY6v+Q(ad`1 z?by)?-u|A~y2OqK3^}tI^G%nLBe8N$CLc@q;Cv-ces~eN{cXrgF{83I zh<(@FRO^qi3%VFpzyhp#bwy3N)&zJzNz%+8veFTiYk}e1-2UQ;iGgz_d2uC#eElGe6IfNLxFq=X*$$DYs|jX|tE{&rEr zU05{_^hSYsu7c_0>Ot3FhcQI{R>^kcO?g7K`i=S~{B8FkEFYST0L@!^vmH(gkZBE5 z(n~Ae@G7WhAPVcGGg)NWB*sXrrQ!iUv8T9E%!-ROt}u=V=DP}h_&K}zmw6d>RMfY_ zk)4C_*~7%5I7*(kUb%uvnaL<;98|S+VvNH;u97a?_$u9hWW=}OPRjh#(&Imqk57T16a@S?@V8dumwfw``Nzm9TPo}V@_0jF-ZVMOi|q)Y|^ z^uo51gBWX7$aO2;t!+h`F@#K;A!^yvZNQ0Ho8Yx2lc#Dm7g(0A*t0mNH6*V$Sgd9z zJyGLP&>e8`f@pEE#ue%m$jdL-mY1IJTy{STj*=ELXHUhz{>i9oXvXXB9L3Ts2ih5B zCD+<9y`KR#W?+pK=h_vN&e?*1$reTs`Hd9I%e;MdY{^%D$G*BgfVEvM9`h1!;$n>} zj3_|MA9iIV&d5wKp{S-6&pkaCcAFJtm#R>9`XkJBCZm%1{6ez_PbL{vZfkU?>Ih-y z?5zmN41{i4L=o}IPkC)+p{(s@sw$3$RjmW@Ueds)Ta9?9&V!Xp=D}vMsNl~L{5)q0 zKDh0M&*w*OKch++&GPN6n&lnJcL;t%ytOr1X=~icr?k4J;k<%HM}*~5%m$PNt#Hkp2$L-1(Ao0{RA0rYGzUs= zv>`=eC1sY;XA%*NXIeuo|JYfS%&Pqhg8PwnOwl&&RnK@4r{j>%jbPJtAmBsIPbR>~ z4G1-g^{%q}T45pb34WkW#@^dr6wJznEE{oHfUl{>NP^#0-GU4`g!C@p1u#!YLX}rp zEykq8I)blOttbS&Z3M1(-SFS6K%lM?il2u;amdtMtXn5Wfqnb;qyE?d47QuFhe5f* zHA_8&9XWdeegS?!BHkkS5sb;CNMX{grKZBm?qKubw~NJ}09qSIUbxi^q)EE#1#q8- zpf4eC`!W<-U6RFj56*HtMvikti@JienVOSmJ~lfe5eKieV*Y{|NKJC6=b6^ZN{k}- zT~`U7A8ac4E+8~+9%>41*;Q1mdO$ygbSmU`#cCzQ?~bmfj95I_d_8P!-bVubEZ10Z zbN!@pv6e>hxD;ZAJv3vW9c8!LFlNFS75pc)H}P@Vc@=yq4`DfRzzfa?>DsCe1^XL= z+jq4o-_!%w#-!64CFmbc6t7U`7664;y0v&PFq*(8XyCPVy7sCE_U_wZ}c zQEXV2HyJJMZoIK`AC?XufRne}bf+IfbOow6m9y(+`g`YA-!T963zw*XEEI`nas7NT zj^_bYfh9|q;ww%E_SH0F+0*l3HJk9~qEamAZ$iB%fQlwJMmoZ&@L6a1Q-PURbbb^NK0!RSr!QwI>P7ip3W+t2FQD@(Q5^SGWi9IBpUwv{0iK{ zysn!Q5(3& z{k!u3k7#|P#dtvE|GokH4$5dHrbvA6^f?9e6zX%}g&+P4(Tr+K?WFhf00000NkvXX Hu0mjf@MR^( diff --git a/files/opencs/scene-view-status-15.svg b/files/opencs/scene-view-status-15.svg new file mode 100644 index 0000000000..a8a3ce8573 --- /dev/null +++ b/files/opencs/scene-view-status-15.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-16.png b/files/opencs/scene-view-status-16.png deleted file mode 100644 index b0051eb1273c586e5ccfc4b3d371e7b8cb86127c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)e5S9@$z=M_KSz1NNtJI>pA$2br2hCl*sD72+4Wp#EJ8MJ8_(c zWBcBn^U>=Drj$}rX_|bc_*ysL{mwbR^ZWhIXMpe(Mj(tp7=bVXVFbbmgc10kML@vJ zv|nX*?5oNg}`=47^3Q351AQ%kgNI^tNg2B;o zxN5b-yw7*+?RWqBEqwgg9eL~mh$_3ES+}1M7X${g^M1J0&;n};EOKI83_fC*_(Y(& zqesfkOo5!tR9+l)#FUBJf8UwiA7ts80zvojzVlsQ#Y$R$aH1%D7uUugtU z5TH0G11#w&JaM9xdWoV4<5Sac^j~#vo;vl>>-hYUsrASOsEB7IC%_%M3$z*$3N0Ca zNs=^*ECVHV4{?{2mz4%-$%$~S$p$64Sr8u+4Kl6~uqZ5AX|#1PpFaTQMY+&7GzRsp zj(1Tb-+q)rzHk9A@7;5FYh@XyiYiV@1QU`QZB9X_mB17q>u>4o(PSf?RI1QgD#|l6 z2M$+1tVlCKcmFWV`4*s}I1eJU8eS9$osx0%41(3(b?i?^j(_Ki6e8lH&%LVARlfVm zo)0n43Pf~1DpDsAiu}UlBvE78-H52F?nOM7L`<6H2l2S~qHd`(JYe}&$*#VU*Bu=_$p=Wj; z&fmNPm8{xojVT~ zTQc@<-?$b~nGjUbrXp}tjYfhtOz=jt9rChL;OX)b@Gbc76R`#@(>pr{?|*UyCcU%4 znmtvbK_3Z$z-RG!S2P-E>2N{!z$ip$1lYWO6^2tnoufR7ru28>>@^bn;lI59kMHB- zP)9-qsEFrer0}6*>*|99qn>k_5c|{){m%YjjXBi>nJI~UUERHo_>|fh9wj@XWBDp;5@dj-dUjfXJ?a5#|~k zWfi!WK^Fn;>6!cNm%a4$7vI{ssiMZ5mc(CDc}w=^nVCgV4IzDGWzH91D_5j}2{&In zq-fm?(&p^-CKRy|kzqnvL3T)-gd(jW(Kfa_!HAvc=oyreDngz)o&K#R%QY}QISsDC z5zuKQWo*jJ))wY~0gDeV9*hX+G}8Ixx$Ex;X{Ll4kJn4>CBE_v>65etw_9EIQ zri;+Rl*b!#3|dI74WJ#ic61|ZMu;^U5J@jg&&>MOy=WbMpP8FSo=?hJoDsNIpn@=| z&tl>l6ts-FEX>aNASEe*#YXFCY6n$N)tE>#=y3J>5V%*_AAk8{C04KV_YO~Jh^HmF zW_f(lBU|k*sjR>P$tWa}M68}{@q(DS968nYvX4p_xfC++7 zce5F2;E_0a68Mk`xY^?19l@ANFVd%Xc$}?4#HmXZlU#|!-ZMDL?Kj2{C1XrsXv~e& z%TS#CBaHp#C$A~yJdbA&iGy!L7nL#u;|qHH`eI7Yc26k*L=j(a~Mun@^c1VbgmZSAi z3`>=9n8|k>E~y|p6E>|Wf)woZmD@Ik?ZG*M>7xBqyevfx{OJb~%oJ34ZiU+he zOhFEM5drE5Ax+8Ao$Ro#zF96W%vNx43Ful(M*ha*`*eLOWFOJ1gJTm=gox`Sbx_yP2CMV3puWwi zbPc;hh(DO;k_D&>x9xc0XBg{^T~C!smLnX9xS#PRehBc+57E;lLPeEtb;)$~4T?@WE0;+Yb=%Nsd2cLf0rTr}Du@V(%YyYsuECpIr~}MguIiOnR5jN0i`^56(kDW|ER` zPUDDEab%-77Zk_9_>xpn<3D(Rr`f3qzb?mNr;kK82?Xaob3WY^o>ii8 z5EbWS@pBKYCvoDB7qn}4z-Z7*=9GjW79`To$_87P(mmpSC{@(B58l6Y0qRCoTT$%E zafe87m?xqiMP2dXWREc7Zv#5i4UFCsC)r{Dh#PgGfhK#8Vjmbgc=_BJZiniAk9vRX z0@Q%tczN${QOmDamgMpd5YGq4+zL)oM#m8nC)zVyvfAabshI<3KKb|Oic{V9VfSS# zKn=8S?@PbKf%d&8igR?xYkoAKpN&M7e5S7~e<+{^GnWNRlKU9LHgFat5!TJl_p)+B}oKJXNQLNyS0K zN$^JESWE(OQGzQ>DB|=8)}h3tS6F5c;*-IL2C~d zeF4;31?YGQ3yU%Mcr7HO2`h^7kdbDAPNRXK(;^&+GVF-PuUxk&0xlpTAm|O6pe{2%n-Ge9+IOLfH z3Yx%%;&2h*KstTL%1D~kEF_7!&VC1m z`i8NQPz=>39tO$@mZ&#l{djhr z#UhPHIW9db9fSSDuT=@6At7-Ox*`&;O?i*Bc62-&_ASgSPq3Bw%%MnyB_|u~Mn5Iq z*_I9j!(pjRC31F?1{bG8sB5f4c|kVfi6o9+Xobo%gLT5RfLwu@ zF_lJo&16)$B6#fAW~MWMBR!aVp76+ z(z$&baY~d8=drU^)=-%kvzSD5RQLvb;;SehaY_4@b0Gi{e_f} zN^-L3RTMQdlEJYl^b9*;N+hr;%Ro8|prT<7NhU`&t5=Vo#VSD}tqVBm@*_hfB3I8- z^f)hNWrr{^^$ za>$ypLXiwdY#$g^dlr1C(h#Et4c;F2GR-NClYGXf=eRY*IJ?FeXDc#G z<~K%8U6h+iL>R>DJwyByW3`2Aa<*Xd;NT?Br+-G-~45Mr@7N~p>xtTTXw9cp_KuJkww1c%3VZAxaQT2*n9 zp)%MClS~txgHH9OjsdW0x4p5Jtq4yzl-pz}vlY13>%fgZM{-AfCFQ$TR>+WR1}d#N z4byWA8#8TYwO-Ki&WV}itk2I^szuneJlYZh_T9Z1?4ffvdJr5M!unhb&P)biloBW~ z+zei@SkNEkVJATMZCQs6wJR_-;o&c~_aV)iBBmG()PF*Bx~IjC!BKTZNdcPHR3SUf ziVHV-Ia+-wE6plV&T!NN;abliTFJ@t(k&86i)Vq(Q*(H}eL(7TxWBzrg&y1Y(9bMJ z{qy6rJUesD$kwZId@78m9^6SR^7!yZFXH24s7g2CtuY@e^njU4p;Gap5{D_3UaDAG zBCVvV!mcaYmD#3u3{xM});}UkW7ozy7^$kTu2mUKGErL5i*3Esz=={g9N{X9^W;vI z-FLRN4^)(;;i>r-J~;i}FW7S`<>Bt0-uFboXxe4Z*i>xR;$2q|Pwd$SftdeH@XVO# zVP-(1qE^hWSRqxdDCVhZam*u9t!DYv3_K0SI9#OhuhrL5Rng%}M?Y+4Q^I64P_UAm zoUAEqBXZMu~X?F;C#NB$bh{gkq9_lAH{PoOoUz+6SB{ zq2*TboaCfBzP7S#sT{JPSnEACyCBP>v8G%aaZPiRv-7AT##x~_bG-+v%k4D3LmHX( zE-|hESI$c-(6nXq@SXy*s>>5ZSzRsl?HL#*8*jhbhV?n5dASLbVEKsovz`c6Q^gsUJ$H)heEV)8-_}F@0yIE}4@&Hw(fjXhqr=aIgODBGy+GaRszj&}h_{ zC&oL5-BSCo`{1!-Z{5VW0$e%E-Dv%WjX!V7G-?;AANQM6v5Q#s1w%M><}w~COGnF0 z2qrEGD{VNGYExU12a8d_>C0_=R=N$YeAp3ld%Rj!)|&5XB{WG!3eh6x3Ijk#WpI zGwnv2fs)xBK!b%$ih#2wZH#pPzRP=L+_P|;+_fo#l2u2$8mcG}n2>HW?6$Con-9 zbe5cqarcauazul+fiY=dV)lje@1K&(p#tbm&r1O)53)cSH*Pvyo0s{tFP21sj>DD7 zPj4iY`<3(8AKmv8@{8|mtSLhZwE`@usl}GaZ8)*h_k-X^QTUp_@(mf z7dd|_0Oe&Ykag?UeYbaT=(nLy?0}U2Z+Cud-@`u@wCZQpSCQbiP64z2DSYm7s6Zrd~Vc4L<+j7nI7K&cA&BG6TyDEHkjoz%m2>7YzIZmGX!F T1D;N|00000NkvXXu0mjf3R>E# diff --git a/files/opencs/scene-view-status-18.png b/files/opencs/scene-view-status-18.png deleted file mode 100644 index 7d92726876877243fc3ab99bc163adde658d58bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2947 zcmV-}3w-p6P)e5S7~fq)fGPP&EknY-uHDfUg9kwhByI-HPEmGaA=5v z)b2f8Sd)fC!66~5<6y^YJl^+xyo@~_&&+%M z&UNlEHl!gq%8$xVnz?W8yYHTJzVn^$+#y=JmJnD%U{MI9|7G$ed7xAuq z^{2SmBY#}(aZv#P`R7Sd4T`~N!+q)HcAo7i-^M7M%g4IbuCj2CT#vb2INqrHeRER^ zu+TLXYjnBha*W?L1OI|Ek1cTMIo z!+a{KYBy<`bk&V#q4LM&8ox~jJ`T`?=-<*YINJl~Uq%fO0_5pC<+IB<1_A)0kK;3s z0{HzrP>vT4={g)WZNl7o=^%o52w5Qncoz5Naz1F}{mntYFZr#2?%b}w!`O+BJ;=&g zg;dfhKofp66Z9On`os7-HGht%wDj(scVAz;JuW(eKK}gVYkzy|pATToyp0yV^Id+6 z8W2b;>J9~T&^6z~K=V&>Gdw$s`xQ(TJbVw&w#sMUG;S^}-Bo&HahW|e5y?SzICF|Z z_-Rx&;9Qq%_#zJ)1Z0 zOpc4%e)HN@6c5yW!3u?i2BVSshDPXUbra2aJg@)tjeq8`43nVT!zsxj)D`IWko?-?(-=Bd)3nP0x7gaAhrBnP=DHq9ZY-!0}OEO>?I) zJ3WcA(vwASoRLosRVUR zSp@N<0%Jgb8ytOC8d*3>#M2UE=}c1xnN0@Dx2JiHMx#kbPhh9!5brSNWTa3^VjLZ- zYM_!o!fQFGi77WZI(le%N&>a_ z43OJ1OT~pb6k;}spt#W48AoRyRX4T0{^wWUd=Nh`n2|sqI46hq{pkfZ@4ZZsIw11*oY!d%BQbw%gj>PsggyzW(Z4f?DTJj{Icq`dR>Y-gVc` zLVMcwJFdHmVIf9RUW~rUWTbjHc%r6>ax#+WhBYh6GwThY&1+4NOxLuV-ud(hjk%`% zWm`5HtYOQ@=ljY&?**ihkq&C_9ikAEN*mT)0du-RW{&sbtnlak1IM`FcmDmIcOSxZ zz^On067j6GB++yl+PWz=B1~|Z5j!V`-qxN0lPx)cX(HB@&t=3L9}{I_!?HLT@wF>+ zL|`Xs926dEp;b9H4aF{`b;vnRK%5FQ)BSn&G<9@hO6lw$qBLX#@+$$+I(i3{j=mwK z7<*YQA>^E#nkT=?z2CoQ*Im~am)TO{#Y-}JmcNOqX%tmI=%eXwk55^#JcSald2o~B zbyJ|tf#X#u;s_w4s6~01f#PHoc?}os>Bd%yKqi_y`!rBR&9SBOW9h6Y_K~qk>gXFJ zi^-@DkGqts^0UZ_;Qhgk5r9sD&L4bs{1r7NA+~J7<>K@*U&V&}&G*x2nPZbKpq-#t zLd-Ni;R+}QFXYtvAcu9$?LaMpq9Uw7(nXU~(_Xn3ujBhE_Y8PGrkT+ruvX=O;M7+( zv4#~5zRXU$J(Lt5t3-u|ao6@sP^C@KjG8-oJOJ(i<%MT{p+|*Tyj=sMCg$nNY@0SR zHlbBFbr_5C?39QCx=T>AgSdi zYls^j7pHInlN`XwT8GF8Z0cR0PuIYRawQPwEU`_NUoQ5}z9AvMP(ckg)&5~8sF$X< z_6#b@Z=ZNXw;32v2JeC*2DN-Oj&|d*Gfn8!iFEV2RYJ9bZ8H5Bm{Sd{ z^krkK|CZu>Lx|Zdd~5C=B!f93mIg+~N}%7yWtI@Ny?4-GXirt^Tf4{!IxscNa0pD% zE9)IfJEC4+luy^JT0zS|tNq8TVL%gwhn_&l9-lhN5@MoF*R2ujI(vuIqt%TR8x^jF zhlMH#RYNn=&Nw=Zd70^S{gnljgiIg#vO&T7ImZ$z84i^gC)nm#sS6CaAHoq=>?xD)R&ZwwaPgar9t4b-QPj ze0wUuFpG5uPdDP<3F$5UqmLdsuF^E2FGtGUIkSN zp%Z7$QgnpXXAKKQuyb9Q4~12YEi3~1;OMw=?dn_-bOaMl)VAooBNIP=@BQ~Tb+ok{ z7n}3`@@C<<@%B4@WlM;DdR<`_HMI58TJU|=>&028%LL`3jN}CUP-UHx3u%?b$&QG^ zLr~RC?ZjsygR1B_Vn7bF19Heg@zJ{<#}B5*x|KP4Tlav{H#|xOKs;=jg~}`IX?0Ep zRn)iYZ3E5#;tTyeZvb-P?VIlWC2T!)_q9bv)?>b)5c&s)zf?A?DWHPPbPZ0cl2+^- zHuZH43>%>m6bhOM)j}{_=8{${!8l84pp^R27ghR?ZoOJaHNVfbx4)uZcg@T`^4Y$< zFAAgQ`3g|zJkP1w;rQI)aQwz=vMOT}uAAaP4HUa#fT85LNUErHC@yF`4q+UKj?T&G z8?w?Qb9p>w%XA_9*;*9I|p&9P86q-krYFIpHJZvfGo}tEwwC4- z=krU>UoZo5p&Xf7_QaT{6QCM-wq()RxY&D!oMx7_(wo*%OoWw!C6nLf^AS7v>PH7D zFFjt*wWSEeIXIS4^ab6~J91vCNc#)Niw;0;aLcW?Kbe^v`|KJtyRcnMpT%UA$2^y?xMmL8?f*3&-yqfZS-~ zZ7BA{=%=~h;3v=z<*azn*+U|%q2d_bJ2Y3EEQdXVPSk~os+v0WrrzO4zxZswkVCoO z#g2;(KpOnPwyn>hmLD%!nJqHFJntKJ>gc2jA4j-2A;*|Sd zXuM9{kXe5S7~fq)fqkS&1%nhX1wp?*p3s&3j|_2iAfp~2#6&P zOIXyRsy`5k3W!2W)v7;QRjpK^(uKB4Xz2!}P$&=}4O<}r0trsy*pByo-{bKt_RQEb z@Acdl-ozL}L8&5D@{?xnd-vY=?)RO0zVm$-SiY7KSVrK25XgPp{}_KI7xa+}cv-%H zclB4l$(w`v!^Lhd$N?h%C?we+8H{0vM7yP!+AY}yA#6teDm66I9QAXTNIrt8^Pts6)u?&Ge24z~0Dklw$j72h{6dj6l^YS4ZB)t|8) z;4gpCD(wY2MaC-xzu z*=)8uU1_ew#IpD}t6QR;ghIiAz|2hZ`1ttX((j$`S&IP;X-R%aA{|sEazD}ht^NYX z+Ihc9P=$jpaBP=8_F3)DnwotzTPk+vXQy*>xI1F!Bnkba6L|KGBh3^(bGALMD9K`k z-5|jvsqlmrFvk#EP*e^{3A3h;ZO#qQWF}R=!0*(%5g% z(7);ia*m+L-`V#Iy-#liRd#Nv+#?W|WeIac^T}7=hB>O>(j+TfMxyT{>k^ZYnVSuF zMhYCZI0WWG=pC6rTi-B-MkkSCl8|bmAaJ+LMj0b>3mBhQ5arHC6;&M?8r;)RU;lf3 zUA+|!2273mvA}BO=D*lFhqm>3sEF7Ds7~eg^aZj z5OK{DTR&I!ET}luKlI?~y1HL!1N_rt;3stA2NFL<^lzu3&+8MGwmRZD>B(s9=!e;4 zKxAQFscr0%w`JI-Up ze{nRH;A<2TYo6u9xz&ZCV%!^%@Wj!^O@lq%ABa)S0dbT2c!2MyLJo_?^U~|~UAOGL zcH>%&Dtd6v_yRb1)TM79EY@cGrCBn4qsS9 zKa&va1}`=C`U2WaiM!IS z4!zZ>B7##~Vxo_Mceu6@fnZQwX;36LQU7p!7Hg~4q9`{53y~-ed{76&)5;{06BJ3FywXbgvsH@*1$!8h&=`+Z|) z-#+*MDF<%2@y5NS`8j*8+jP0MO7vWcC-KVpWlqV8t6lKBO8HWW~u;pR*X&`4!xD<5rlAM%1J6?!XG8su_QTw5YnW8b$6*El_{+kJqq`!fmCLKH2R-5go}SWpv}40t)V|xr)JW z!)!1lGP7bf8i=q%4uKtA98= z8=Ajc`qhuWr@Cy`g~9P@lbso|Y-OIpgrhWc^cyEitG3VhVkM`=s#wE#H){;(8k&H?>`2D=nVF4gi4J3&)gt$eOhl)=K6#B%L84hkJ@tLp zHCI5q^zNw^1iHGgKFf(iqkh=b2y){;X}s0xM0;3q6UK@X-$5%R;h}4hwIvSI)U0fHvR4Q-hT6!;yvvoE^eAzTHjQx zHv4vWYIVNDjDte~+_hsXtVI4ZjTecD?}-G|ub7pb;#F$Ns(hKMC5ar7Iq38 zjv;dVD;4Eb6$?IUY)7KQ98z>+rzi^Rnce-fbLzyjSGwZT)ev;{uw?nDsaqX6 zF;dnjPXOi0YHHuD_oai1dz+li_-FpIYi9m6dvQ&2lG3%eABiw6!}ZORL>;j~%t_OW_7JtYyP zg>FS`H>n~rr?H~62=2@@tsD`Vy|%ea%j_$eyo5v^Z|dLxD^%w+3f$>(1@uy$MgeatPNXn=loOpxrZzk_3Zhyk6FN64Oyq zm@ATx_{|Sbddx!BYObsz_3SL{&Pk?NOk`rA9)Eo0Fp6_iRUUFAaf}p+NysSkg4#K9 z=DbwV4PM%66iP}-$?(EdX-G&wMd?Z=jwzVNhmp)0{@}15l};upx+;pa0rLKxAu&P*FmO&IIEC%Wh#P&&3MkflLt@R)xsuEjD$ep23mSx{; z5FbzuJ4dD-eDS6IpG%dchU?F})VGhk z^Oh!ux*KUSiP@2JMA9td=$AB2_KDq`XHgt*W0h&zUvX@i-$LstwaJ# z0TI+Ju~lYLiHS**Qy*jD{e})@$~*heTW`GfYyI5|X+PzF{xRXmx^?Tm+uGUnhuK9Z zAbtFN?H#*r{khd_ymx(Rp_RPWN&`}i=i=V~3F1rFJ+A{xCw|#y;Rq$@eje)0W1hf6 w?_VHQzU=zT^Oq4=Mqn9%WdxQH_`g8lKer1kW{sycPXGV_07*qoM6N<$g2tA(c>n+a diff --git a/files/opencs/scene-view-status-2.png b/files/opencs/scene-view-status-2.png deleted file mode 100644 index edb350b8b5842e835fd05cb269890e896f6ada8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DX&fr0V0 zr;B4q2J73Yw%*K+0&V5yw^;tL3LQDN-aa8QHCcJdrp+5frkDiQHz_zt*>_FZwrFZX zwz}9xe<{b_)(PhvM2ffXD?XWXZu5r+3B^Y_p1(PFCiQ-8S(%^aPhln}1>we^zmHkY z2Q8C7`C-$^4|@A|H6PUVuet3b&G2*9rDY4ag^eYq*LE-eS~AO3F;B44fBo&?_&NOd zZCcE8FUapOiMVk=;doIW|LkupdWvcGiZ5=kH5NZq$k_8rW**!Bc?=%<&0m^%6|db= z;`r_%{pa$j8QZv=r`>X1b3!=Y>@4S<9Y%q#x>_CINFSO1m`@{T!kyPLpEK`rHoRaI z%X7FP%iXY^?NsQpHQbX=vEA1`u~YtON#N>oNCI*WBW`TX0bMhPcgULq^@TJ)2HV zt!6uorW2?cu#X*iAq&D*QJv}JJv7{`b z)k3KM*eZYFL)TW66f~aPJF}y=)@%9?f9oU5wna#G*e`x3zp>f2=Wcn!|x{ B>*N3c diff --git a/files/opencs/scene-view-status-2.svg b/files/opencs/scene-view-status-2.svg new file mode 100644 index 0000000000..656c5d9d39 --- /dev/null +++ b/files/opencs/scene-view-status-2.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-20.png b/files/opencs/scene-view-status-20.png deleted file mode 100644 index bb950d94e3cd482bda6b4dafdabd85c5f072b0fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2491 zcmV;s2}JgZP)e5S7~rl*A@QWB3ZVDEP0W43tQf>SqzxPO%t{ufdYmC z6K2ZKCWVkLlr%GKGijJk^QV(clFqbbrVt28X{OAyglq(c)eaC=8w@tWyDWK;ZQ1fF zOM0*8KI_Q}OMu|abUJ!7)|I7u?>XOh&iT#}5P2dDL>P!L5Mdy~K!kw^1OKxO@Z_2H z`}B_czAi5>U$bk?lcie|O$K~&`oiA#KKSQzWJ{WCdy@`eZF%{-<%>$UWTz)XmSyFS z<2dvW4&(EROM7?kKd^;B_$CX;qybpBcI~dzMB~<_^JXB4Soa44As?9$K@`#M>cQD+ z8%Dj}eSd%dA3vp!A3GwC4S=P}+C}rWGRAqH!|!f`Fd>0qp(wCKlw!V7TYHqgpV*{s z4zBl&KzMPoy=$s{8fLh1WTCva# zJVV1&R2`H)l04$|bNN%#V5ZB%n-tqOG&+Xk=c{0jHxM%%@5tGPkEPU5T};r<6idS5 z=0oxmaW*c`lCFFevdLt%ZvZXrUC?Pnxx0Umn={P{eTnSBF;#I~8!MQPFR84M`~v&=&4Cpo-P-%P9-U^M87$w55j z`?pUmYQ;nKC0OXEtsA)mhHY^9%yP&k*`tD%$d|F<5ie4b5;)_OSk|=zDnQjHr5Wm5 z99|;sIqvO0{#G`|>U{RjUX4CBn)KQt_4Ik9YFmp~QkacoDlmqgrxak_v#!~J#5g^& zDb+6DtXJ}1l|yETr5Y>t{q3`GA!**-QBc9q%Ibp)`0=8)PS(4>MpwIyn@x-x$PZ?d zsS+#p)^?X7zj68)iEQ#6-EK;~47t(K#c{9y?$@#c@QuRiR&n>y?-CDxN3;vsUFYO%Um%J-4{4#6V0z*DU< zn_gLqGaZ2D(wl*0rTIdXR;&0{-_`{|Ye)d;bD7y>5TkWbe6zDFP@J94-)ywQP3gc= zLrV@(rpT9X-sYMK^$$v>Vd0E?L{nNFJ6}zZB&F~~C5Jt1>Vz&zgA!9RE=Wm)U;^B| z;n4TN^lFlIsF9|iGLVK!iF(R#9L{u(!0weW*LQ=@NlQd8kyBZ1ldm^AeiKrmO&iz0 z85bM9gN3bZMy_Iq*dMEDXw#D1$@IdN%`8x4G(0M7?s4}I;?T(p&}l`vq+qICK&rw5 z!cyw3p$)hu#XJOvFlZDJ**k~3=s#!jB`36@&(#kp~dziyY;Ss26TQhQK z#1?o?ZYCWc$$uUPkf-E1ZB)&SulI3$medDh>lbjgRPRt3|K`DaMBvU1D}J=<4>ab# zy5@-zk?ApeNFn4y6<4_>bBj=vlPQtYyqZ=_7Ks(Vy|Y^+l|T$NA4RnY%%Eh%EuR%1 zjpf0`vuRekJ5?0M`@r2gjIo}&?I(^NdFM`_!vDW-1j0A5_mo;Yv&^Q~N~h&u3dvku zvlG?~GwPZgpt*=<{w#^94^5imos%X_1u(I#$}j8exzxOvM1FO91iSgA@ToZ-Ochh> zk#A-EPQM-+fO?RnR(5jyPBZm~LQASr3#`~3F1MD+TKVGnh&Slr^M{QRbePo*Q;$7| zPoXd~NiMLYac_*Qf``{4W3&hPUB_T=s=9BgsP^w%9~ppp!Lp}Uypofe@Ta-KV)y$4 zqn;73uAgQV7NKB7SmPkZCD4x-l54l$ppO+TDG31rB>c<00DAi|bl9&wFjZ8$cdp+V zfO^sLXQt;-$_WWzfecW3J3^ry4+M38dTX@Wt+46 zrOFe>6ggDyd(`!@0jLIl^6bVxQ!T$-Ha%B~08^y)ZnsRs9>>NJR@@#MMJlRoQg{E* zj$>bb@vX(F-us|+(g3K2Hf`MS2K9^=7Zh7{^s|u-h&=n*c)7M|QW#h5sOzKyP%mac zs5AV6CJ=3hdvHg^W2TCFZ)A-y5Mdy~K!kw^1OHbH{1*iRX$TX{yo3M%002ovPDHLk FV1iZi(t`j1 diff --git a/files/opencs/scene-view-status-21.png b/files/opencs/scene-view-status-21.png deleted file mode 100644 index 76ad0022c7f67085facec469df788cb98e833bbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3009 zcmV;y3qJITP)e5S8GsI=NW#^+53gPz;cI0KoCI?BcPaA?@3J}Ceg;3 zPA0$7=p<=PP21_ecKWl^G-)!ewoTJEH!2c`*JRwtlo#?o) zYx0JMO}jU(tl42P8}NQ(^B-S*eeV;rmXKUeu2d@ZMpK5#Y+j_*sI440gu@|+-|cQ6 z8yg!)_Pf*dD>qe>hvg`my7f?BUB0n)G8%H;RwTyNGygC=M=nwApUZ&ac67GgbF+6Z_sUU`|DLelDz878rCI_=92ChR1Qa ze+YvkPFNHi(iJ?|ZYkwFhJ!JTMFk`{9tlAd2L}gsw6?VTZuXA+#|G3q_3A!ZRm0|O zT01Hg7r==zoN4XEK;HnCXXp?n7C)Pcpr)n@3kq@|m&q{c_M*!%g7)?+n5X8DYfzKX z1U3-EAOZHp5=fN;I&v))g};gjVJ%9#;v>&;#Y9}N4}SmR`SUL(4f(|s{_To~H16DB#t4V_?^7AfF z1W|IcOeKdj`e_I(kuSLr14o#zYYZs+?um1F<#gLU1AV<8vkqkM=WCB{ePs82_0>D9 zxe=TX+wkjH8PVcI@wwmPevKXZRs-6mLa;6>$Kz{rXi^SmJ9^OEav8O08muuZ(c?|v z$godhzf#BCMJ54eV5Q&nsE6ovZW0{>A0rKO)1OkINSS`0yt&o2fmCH}O~rPmB6n9W z45yh4>I5;@)@4KQdI2poFAc_h- z4=u$63)Jhe{(E(m(J0y@9B0Y2;7V8To8@Xj8yC5U2g4#h8+GkH-`4g_$m5<$j$k`; z(>60WSqIk9OM!QyxefkMNSrSdIICWPPsReMuB%2#eimZ!1P*+B9x~@RmS!2y?h3)? zi=dp?)?EY6m}G6X5lVv*eIWtG#a5IR=1alM`t=Qt;>6iYe}DIbLr+J1-cj~$X1l?F z2OfNIcST{|j*a)+C9RSWrgIM_TyE?=d>R|G^$=(m`sF&TS-wPKePqIgKmD^2-zqR6 zPV^oh^~0(Ha?~stlQhyaBM}i36o_om3{stZ4c%#UaLToik&LdXtD@yZ8OtBZ#)6#v z-aC34lOEshH{O2t8TvL2YTAG#=y8F<6=?d0l>(Djf zM@s1eRF}>pCPg%npL=aK)a97aPI1uh3!^fHhn(nUQ0%n*9~AGzwp?CZZRzFYTmly^ zcB0chit2^MiL$~xe##r*9HUO;Wu(GlHcIm{p!VT$&OYMgY8I71PwwgR1#sZx+3C#A zZF%^iJrAy|*T#;)&2X#yN%skNx{_^onBLtTA!4) z)e5;d;qh~KEi8s!s|Jg^WMjF)NAA4u*qK-OoQ$;XUcVplcwAr~SY!X}J{XQf&bD`B zo?Mi`H<4S^tzLl=Yqk`qFTZmTYe~4(3K?GOb)kd|$tIUcg1_h&(75imSHZK12|R0! z#ap?z3{Sxs%Mhp6r1b=9ge4jbMUa)A#+gzmpF|>wq=9TKb0=~$^w@_YfpGK*?!}+| zSTyO>v4OEEg`P6xqWJ{^B^;r(+b(yyJt$WYqgn;_4|ws|=5^3%)M&fXkG69cvCe8j z59xWQJA|bMnPfO4p5>qZpn$)w`U5zHnyq`&#>wR15^+4Oye#TnpJIHZn_EJRvrNqx zm(h+)Q1Ww7rka+gAZslKouVMy!Wf7j`s9L~8XpLS zB=8w(89p2f;qiypL!(kj;4cyQ^;R>^Is=GCW2iOCB-WFm=cnDyDk-`xcxI7E;Mtg$ zW91xYsD!AGW3AvuW*~q9nG-x|KCk7WC{kiC>5t;0=f!n3rO2_En1^sw&_bp(q+x8* zU7uk#$Teye-#nIAC6AW48e8wZ8!GDDc%cLSo*pdAG2-}$4>~c9d|Dvs zG7Oa3Xhc_phm~rhYI7!(j$eY#Df@7LVRCaQ5sNI_E~2TejA8iPihBmxIW#+Js~ z`Jx;Prl>eq-Rd9#D=aHArr%5@95NXCyyR}fsK$QhqZTf9v0L({RV6Lib8`D9}N2O*rqj5 z6Z1C(&k_?2W(mkHD22STQn9?WkSEpRSdK_F8KkLe6gGSsD@A}~Rm&ZWV8Ek6Bqr?; zs7ztI!pRj_3=bAM_Q5%3+xfTm-n-v!>phdiJ-q|@g+7U5Qjf zid4^d$!#i#`F(a`p2##1cqO@tEhM6_C|}B)Y(l0MQsB8fXzlJt5xJ3!=fr-}@lfe^ z@D@r4Q=D~7u7CAv7gIc#90xIMZ{3w-{Q7}c6;pag894G-ky+VJNh-H;5y}Zz zFQxG_P0ge>sW?tWSQaUg@f!1|ydqXFEoc1%0?m^ENb@jRBv#@BV-s>x2`NikjC!#1 zf?qJ9D^`rRqa|?iteO>WbQJ<#MZmeP{fJIpe)ibm4}Ucq^vq|e8m(Dg|I3vbI%SaR zajzj28wk9|AHb30XYj~;3!29R&{Os_(Saiwu$4u*FzVDedZvZXw3ty*Y!yho&_6WB zGN-(zq6F6L42f-)*_+#Yq|9DZ;KdTnqsv3_BxUpq(J(E+s^U)c6b6zSOJ~{%1qbN_gDwRy*Sy>yW!i^nL%8T+?|LECE z-O8T9aWt&1lgf`6W$tA5WSO+SYJsF4&%bd1W%=nM9dbBrC=4$JXI^*`r9($BU~AcR z=vr{j270S!DTq@omMjkxwX&2H)Ktu;#4!QI*bvgR6wE_DN#|LTW<{EV{QlUWYwv*5 zeIO7DuS}<4RVfuRomv$(8&l*CD!ZA?(Yctp7L#i!bFz%y(dUR4=A`o_)+~W!nI8+M zV}WBoVwd+lyZ_xkZ=4yNw*ox-{d5qsmF}vo+f%n_!IPBVu}UYP!xe^Fh(bxU->M$p zaNowC6l9uzy12wjXID_*YuS!OD6^{!dM%={IELwfUS60h9o&b;T@)7zv~)Yf?%|2& znvNZjLu@9l7x!k?fzme*4j_Kc`s!Vf~`{ zNTE`IDH4^~A{B*PW}1{a;iXtO)!HpgcmlhQ96EU0;7rbYr|S&{BnL1bh;~mS3oq@~4+o6stp#C@T&pqR-3_lA!O> zpPbXFRA>e5S7~rmRTe()^^#7~WKU;LlY}fJ?1W7R0WB0nf&-LR zjen+UR1kH>L9H6ca-^oTX3COrsZpF7R8WVpWER{J3Xw%Y_CO#DAzP=jC!KWGPItdM z-+9SxngD`^;>T2ORrh`GZs(luob#Rgh{oC&0%Hh_3W2o0b-#{Z{-_Qa#b$LBd*#-3 zc=$rT-0Xf-1_1euq^JhPV6@}8=w>U=@|5cl3gsqZU6o_aIFDS9v0Nx#Cj7pkAq7|n zO~o1^*W8T$dOh$FlzD1|LN5uwKQIJG@bjX;z)gC%rU!uZ93XvIXi!#&Fd*E|x?wzp zRJEQoO{&_DxiI;1v-<1tz^8#QA^Mk849Ys8{2Rytf`L4|OwQfRK41V4eHy<@LICd@ zfpWaAkPuMRbT`IskqW|y$KVx$f#rDKE60O+KHnVle#0Mi=!Q@IGx|FGGJ>R(E2NU3 zK$!5p_nr6u`5@L@a?r>Y zU9TVdt7t*tnv9eLI0xQg&nXIZ_4Lsf z2adhF`Mqsxu+Mv?y+374fr2CGP$9>|)x!R^!jP%wcA=_x66VqiMG;my+%G~l^mLl$%#rG@6hl${nAK%9|w_x98IS|^Q9 zwo#3bH1*TiYuzOy!rNf4_}WVdi`gGBbPtZj_ytOXMd{a zciD=-Dvvy{@>3vJ2t)_0p=M(uBCg1Mn$pNZM}-YbA$m9jYr|4$pLsUf$w&!mgMJJv zXEvzb=JrlQeRDhc0abohs%8xfRoEl4v7yKrUwuo*jFn68crH3V(RQ}9bQk6?E|QCf z`+x^VjO#oulauFop}TK@zBzK5=1tA@CB#J=8=BjFr!UkS2Qwd_?V-~VeiuWS??%(9 zn;x)yN7dyAkrO^0aSIR!uTop5n;Zz6G+PYC$3{_WN0<2KSc#IKnM!#XX*%_?+^YH} zI#gV~Fru^2|`X%z$P0+_R}5BX#Z4pUnWOg8V@UF_I>ek~otMuh9M$A}Hnp|{G} zY_ccWC@nEgU$=M2Bi{I!C=&}l*gscn_T?xjhyi7JKNrb0hB7fH?4)8;{M72|A|Or$ znQ8vKj8wJ5-K*3!wNffP0{R`wFDP2u(4y2fwkm~_vdL-*Ay;?rCG@MT`uV+Y-7~*% zjXgPD-z2SP`g8a8A*q@WKAPzF_>`RS$z;Rf!9|MK^$rZup2Mfe783zv6xErzjy~3s z0(lXzWT=|c!kQi8*2oGa+n%CN1!OXO%9@jpNJp=5S;_W*61VhJ%*m%Asx7`%>M>jNF0t*ilR z5fl|+1(H24(1sIzU)E}6Tf{nQ_1Z*0CgdTZHRv+f*YBak_*f-sTsT*4 zzci4~VVMXss;qT-0NjJhtAG5RhzhrO9nBpkYj`O5+OD;Cy0zkpTI0053`#(PvGpp@ ziV8q z8Y`6PKs*9^uuNtpPWHOSR-Jw$tzjBus!eS!gkGwsa<(YSi_iR4*b`z9B)D^Gny;kF zX+)#NbEoQF2&OrwVv3*-H=@qQO7?5{PhN!QT;4zm{L&FyL@ z=(m^bhF08zhl-v@m}!3#k?Gh*{~d)nh7hw^*R8U?g$(A1SOP!KvqeW3LoFd{O+$;n zAR|RRSLGlV!hu7>422LW;`q7qN)4>OW!glVJ2{6!5mvho7sDh?PduX@!*0HHk|o4M z)1njTh{m527md5JIb1`Bh^1Oss6-n_2mQ%(P(w}Wi=_R{{p9dyG}C)l%}kD?4uEs8 zxI&z%ay}Jcq5D^_+!z@ix}M!OZ}J3P4$vPgxlnHg-3k0D^QPqLG#cy`zW2DgdT7Vq zBV;if#kAbs|hWDo=5 zLQ}_+`}gd6g?9uDklXKEvh?@k6JlPRf%p#(4bhY4*trX=oIfr)$}lP0P9}JSjbOx? z4)+}^(fF|vBG-d!KmVj8p^~K+n_3<20Y%M^AK&LY(37gP>LDqucG)=t&^hazNCiQ;`I{o zOE+G@10xqKxqI0kaLs>i`K{B8%*Wio5MsxHlggr*`IMiTra@^{h85EyQ1LmM+l*id z3PbVHSqsMWXi-$|&7uznv#C|(+pKnZsOX4yl2ZN&4Yrp(@%65quZ`3x`2DIL2wud` z2(1n2w&>>zCuY((&|Fzf17)S!sI1yaxEJBhpRO_c;HEiLIdRj}4F=khez~$wK=ERV z{Pui5ZB}n5uZ;2GP>HVF`Gbg$)a$wpkQ+I)G7@4o*iawx>`8hqaI!mFU1p}WqJ^_5 zCc;WyU(i(on>lZoJ>LH08_G+I7rFLi<>mfm)Z{pQ%}|kYNA^G20C~{jyO8V&(Ho{_r=wO7&<|s;cu?6xBCKKhHM*g-gYq*{ zP#etTY;hqkOmwQEPE<6sJ$dl!-8vo0^L}hUY6GOeU);C)CFJtc^CnHuJ;03A*ya+r z>?wR5;pBGXDsrH>LTl^lUBCOQFMlvO<+<1DZnU<`pV1jY~;L*V}kf&T$B WSqVTPHE?DC0000e5SZQor)fN8Uo82>>8QbIC9>;d&pOOpnuD)mRJsy`~!0EN;dEv02mAW%X`vy$DA#98gcYrM^j_kA|c{$9_0 z&Kr#55KvI0N`BJ#zIWH}eD|F1+-qR+nPgy+fjeR#bBFsi`W4>MBzG{Fzk{*z(5LBT zzx;8x%R4fFnBNMHS8xht7QL6>ZSZV8?z=2X;HFc*7RQFzfZU(zrV;RZF%llF;)n}T z;Hi%&_qiM8cin-H2+RxP0KFteBGD?^2==}-T5*?NZo31-^gLqvtSAszQH&vmeGD7b zr-SF)AqX;5AGO8EA9t(#E<5mf)J=eoWncuhgTTK*F#s} z^u3i>PLu$NA^}xBNOcQkfMmq8v@0M38|eMGTtB8{^Q*^v|KP6@bvI`H6P0%QWsg~< zfddZ_Q8!^t{xR+JDn3VlE_|~NC@){VZS|s(jg~kgK0jIcueaXa`6Ts;PNY<;)drI} z$s89qBSxpSa-=7Izt8R&9>2Zvo?^fSZ3`k6K{1%SfK@`Hz5u1ON2T)rBz2`Gm zp~g?BZJpdUQMsnPe0%xgk`3AE$+S7NJ8b7T4z{jd>^f9YL&B#{H|X->HA)y198{c$ zA%6%X6k;JkM0-GrH8i%iZEC5neQlyO#@2BJaGV@+a6Dy*1~;{5k?XXL#(}6M1ENwo zcA`httl5@oF>hQ}IunV+dYBZdZ*Ul=F4bbh>Bda678+i}@JJ9&UJX%iK>n0;Bqo@k zQK_KSsNwSmm`5TI3ZbiS5H9ZsK00u6Q`MErw={$q@ENf}NoZ8QSwIrlpA*oxWq{O& zxyT1p_p)3km&{exEH2&180UEoBgE!ApC5%fB4BpB7G@=}?;@{@i$`i^I;<%c81*`M zM*L{)>_vTB2im*(U{P^MRFNRiZmE?#I!8k22?~hN&PN1MY;SjLth#dL@AA5G$qdLK z_i&JV$aOc#A@xb7#(pv&D*Po+zqLm(t9;G+7#n7)E`t+8I9pW*M~egVll1Ttix&n1 zC@GnRX<2DdDijzPcA>%Eg_@e{n4;y7Zq$;|1lAiuI{|iwB8aD`)RSvz$YV7`2z!FW zt(#UJ3W>OEYyZ{NOP5}i8t9K41AinVe#_Bh#Qx(1bhq4Le7SV0;Ns=(aFzhPO5o|H9$%7FmpmCix|xd@Smi*?Po)?|lO48m&GVUYa1!5Ki1+$>qm zA%T860!!peF2vRx^@p!lXFyrIGtA-5Gu8JwTAIIPt&#!kC3A;|#)wP~3)uJg=kaxq zZF^wBoQ>A>bX@Y!#T%glh|X^0Ufh8Pbv9%gRHz>G!8)UmfTz)L4i{>hPur3pGD z#i-FluGHuXNbIK)#WLXWU~jD5L1ns(gKb8LoTM`#VsBN$s^*6JgRE_gjNVEP%eOBp zDcZnP#AJx^E4hfaSbYwgTX&(kz6lG+U4}*kTnKBiVr3a(v>NnK@VVOYbgH99~cq}w4B$gRy7O>+Vjmae10vu;awxnNgXnwy?E5w9F?opdh#D(smotLVs zpY=J12jy|m9gr*Ja%^z2I@Zxefp?;^8XlidoT?BwE3y7nj~8>w<{&RK1)*>R`@Xyc zMPDz9Q;et?@}booK%t(8o`RVHW~{S^y^qDOHk#1l6Ofy0ML~8Z^&9Qj(%y{|=db-| z_x?lA1l+Ff(a~T3y~%)w9)5URQFg}0RrlQ^trGeAtb_icVeCA72CGsH5XgVqlzJ?k zKUZSCtA7ap{_jcrB+HC2Av@mffmH*fX<0HRX{7D%qV4aeKxB(1k?LgX=uM|bpE3pt zlF_AQvuHU{!E%TEupno@Pmi9#fYZJ0ynIcv7EH4llZZlRtV5oIZ|-kg_?JwCxe^ zH8pa1S)(x;T~m@;x0JQDDy7)(^l8oe&e2uBB6DAdLT^b8CyNQyHmby^MI*4Z2Bce?oLN&#_d9#=@;*WG_FG}QUzjcV*~_uz@uOQ9v^Cj`$D6OCgD$R?nUXCUG+Ze%s2S*SYut9M5NxQ-+RhSJYrrYC!c=$psls}tTZ|OiS9sV zcFz3OWpmCfp(J&p!-bXiFG9RA7U$_a<07{yBIftlhvvV?~%*iHX zY9R&QkP}snZO9=vQt+JEMmp}x{{=ok9(fA=8%Ex?D2fRfrvRT#@nE(eK&P#0i_Ew@ zL@uKSPzDY>krSt`p(K?)a|Q|tSTm*ZvlW%3Ht{&#=|&1ElIaHXr(GhJ6c@670)gg9 z0HisYEYhT5M^C?!R6@$q7Lx(&zTgqeXb9zES1=EKJUh(_H@gY}pGCmAhP?<5)Nej^ z_~2h;uoIQiVXkEUf|nO1>D4229(Nhzv68?$Jzg9+ein~SwV<-s3j<|OGhI03fSs9> z4wGJsqi3)1$(A@2^u*|6}DawO2HA!NdW%kONCMmO*WVtX`eF0xy_u)-` z8Ny?He3meTxVD4e%J_IABmL@7;D!oU~-BNzc`B@Pl<=!O;pKh{39)6qA)&+GFqN~B=bs8tHRR^yK|#VYM|>}E1Y_hRN+Os=KO z$ufFvi#?p3mdNK>Qv{M_zQ>=4Y4*Jc)$iH7clX#{Ll(PsEPJ=I*?6t9$)#us^4WL$NheGo|W#dpumq|I})JGt}z;75DbOU zNf-3O>~!hk-qAZmaiPMMM!VS9+5ddSu_KZkjtBN#zoQ1o1DGLY3l?slnV$5NGZ;ap zhQqn8n=cY_f4ThWTMxo#7-E!p6 zfo}~?dEVQVHyI!gV1^VI7yr8Mdec9=V{Sll`>o3L>(>5Rt5!ZUuP9gR3k2B#ktY|U zdvWUz!ua@gZ)w2zj^Fp28A1~D99`e5SbI=Z=M_Kq?gJF$&GKB9Wm!Q4B!WgvT5D2#v?FBH znoiq4+G$grbUGb1nQ5kz#>uq*WIB1wwAD^aG-hh@U()!fj~W#cG4coq0xqvz-U|!6 zz{0}5ZqNDPTNk_$UzwDScLwg|+wY#o@BGg1e2XCA1xxr6mvHPXbR|zfz>7aGg*y_* zhjEI51SsgGLZ>;f8GHVJC=^Va|5ZA1+62eC)GBaCymu@FXSWodA1CllSLtyD7O4OM zq+VptLw!7NkUf;81uvJD?l`>TTSfcgq9fqsmFq{}egA`B;1>UaZ4dpYz^5;1fAI5z zS}rRsJyN= z_C2#M58@GZuaAx@vlkt*6iOuw43EIo1`|xr%pCjsd;k17F5g#0WMh?GB*^aZBC3>a zTDOl77exW4rae$u+XPQ!>-}-jk+>va;G^Eg_HJdSHW3WkB$gai9Kv{FUi`OjxXKZpWVRGQ>j>aX=7kj&bNg+GsT~5FZ-_ z!=q!;<(eiT2YV`8uf;9o5umeo5ULt(9V>tT#0wZ7>k<6ycHF^1)AN}#XW+`&ld%2i zwe%GY2uXgCQI$#w%^2VpjV3UrCBnB0b798q37}1TRVbOxDHnWr?sITBr+j;NmMFr* zmVwvn<)&DGh%{Ej0_Of<2vLc!d3`>*lN1<6U>{Y5em=Tb1>2w74O_Qur+4p6pscL) z2+5%#HIWUSmablijR<30CdB6C(9_vxRq2v6L=(2}(riUM@i9>heDoF=f`Ge(mHG(14JBFXJ~!d`c!drd{XEh9EMb^2+A)9#Bb>z5-bD5LJ`^u z4-EnP#3Ym7^5Z9dMp+7{Po0v6O!c5kj%P3N>`DH{C#SHf29Z8~$u;8@R;DL|28{;~ zDcU!Qw7Gb#9-BA<%O!{f*%^W6Bqy|onzpXB6C$t@?cD=@q>56%8KJN#;#5x7^RgfOVv zK5-8z8ph(Da?Le=djQh^R)*exxJU_HZ)#%|$<5SBJxHG(>xl3KB2HBzpCrH3?A;hF$uA{TbF&QwZFa~tXrZIm z3c?>>*)Qo5Vvr=*I2h!OHVYt6p=84vh8%*zobv3c;ODQ_3%x_5P?VE_M+O6P#@?UX zN$=Zn!X7_efk6oHe91;1&IpBPK@%gjyp3&UWz`K6PU-}BW_=!0tze%-KLVzvr4#Tt z0-FkGcBolw?Y%=FR8&+*CypP5e0>r`gfEBcmM+N8NP<b*e~?Aq^Em1s<=DO-?R{ z%dw$|UX^t%kf}|8mFcOB>qDas)|^}paZEr9sum5X4f!kmn23l{I zm9020lVOQ|+)W+5YBVO{FQGU;i^*s(LUiu2k2&GvOVNUnetF9xy}17+p{THs=yN-6M2YsyU&IY1+qS=^(?lOyzuEvTUHz~Q`R?|3 z@T}9LO4h_eT9QVpsJ$sk)TL_BO+C|s&-hBd}CsBZ3*x~%qt6&KIHB^#^kB6*m*wtesWzeiuM z?s%#|NqS5dA|`!Y`K7SAFb8rnw0;a)V>+YhB_qF*ao zl>sZTxm(TsU`W+KYgZrOxd_kvX*6X?j%Oz)o-|p)SQYy6Lh?(KJrjo)QHCUVRt{5f zz?tExHCe1VYg^3$#JO*NmyK0+F{lFkWqv1W=@ViOX>dMd>yp@DLm=IK!*(^vTIqMz zLrg?Cs@Cmu54}t0BjR%TCzl~x8!u()k{RMO3`s_DE=XVF*yxkVO5gw-0CC2(NAq7>Ywxvrl z583BYh5J}|myK0+@g5T3#!I$hvnNC!qJ~2eLG+hXRc3muqaid`Xm0cm&o(E?Vc(D) z`$7fvrf$j9Z#($erx)Jhwidg~#wxpb9|>@$KiaeV4eaHYi*qws1<)=G*z6KcQh|;m zWO5u4uWT^+ZDW%ME}Z*!pgFmX#qP4P$}T=Y0^IrYyLbHwXU4CdTx|$NUbCYCee)u! z+%MwXCcn$ZD!cd)32`b%KZ?KQVqNr0 z{iAUFb=r8Gz}JcHz1Wuhp9^x0zQqL2L-%6no7(eL6j~C&6KL})fa0@c0_PPEd@;ZU lI;t+Y1cE8F9P)1RA>e5SZQoj=M_C~c6-LNcwg{%!(fbUoYHBzNkmHMZuk}f}zw1HGa30;)Xf&(Um0LBf(24lSM9(!!O;<3kL z&*GW)dhdgo7_dVUq$aNNNze2A=DqJb_uO;O{oV)?4J_cHSisn2=&E1<0*&DLLii-% z1PG@h6aa%>C~~?+*(2nd=aLs({{Rt1AnfLf5?@XTPEHU=}Igg!5w&~t6wwimiun*KO@o!R(8 z=b@8(4B(N{($^BA&HIX1<|2kz_Xh$P85>7MO%tXkCXs8_L9383KJCMVLIX)}z>?G? z#6+2(RjZ-XYT)*G89Tf)GZ+{dgL7&c|2T2(g{!q!J_0;B z6F?+YrJkgvrSjJjAv}DPl}S>TV@AUBFaH9oii*B^071qV#YLg6tp^&l68;&VSXI}d zD2~@d6a%Qb?!~H=dB`G(5P7)V+=+&cURWg`tmZI`k3w7&yJ3A`*zAx-TkL}s@+O{?MvQ(IDKGG6naJ=#|vNTXUVl|>} z%nfT!Hhxf?$jhO+sRLEDEm#>9hD{a?I-CKVx;81ZpQ~rNC}qG5Rz7Y;-A|X3=#U44 zpCJz$r#~9CIwExaZW1aXS1B%B{w%x51NqB5QD&nqK+M&*cA=xC6KhB=j%hD!S`)Uc z%0swLiyh=f;1oKk@Xl7%!R2;KissXj&tS5uzlVA@+tu_)O#@M7{}ocFJOCu0U~9= zrqW~6>icBY2Sy$E>j&rXXo?wrqW8?83sx3Tqik<&cgH< zQH4WEHoB=OkCu~^EYINJBIo1CX}jZ_D=Ne^-(6i^Y145f$C zFPY5PP?#fHOh$5B0axmq(cCkL{AC$|th7anG3S)fJ2-+xv5|S%8{J5syB+32K!JH7#9o z59sI}5VO-4DKb-T)M$`#rEIm6a@W?WRnq8$OSpeo1`OdkvTA;ZzcE-|lO$(tLM9aD z$Bw}1a>4KSi$UFSiurun1wV7DXG^BGZ~67wVt>egeg0+&Z$-L!Ay} zQXmN=nIQNEmx#S3n_)8OG3EAfXBIUoB{6JNxV=6G_1T)Vo=AhR_@>=n#K%Ml<_PL1 zUT+{6Aj4-lk<4JD=6CSG0SLeU_0Od!Qv_`NLoiT>%*{+=t^2RG^{7V1Cy=csM#I%O zYIowv(#_C^=}^~hL*3!$;Uo)UAT`J=WEP3XJnL`UoU}8O^gKxbcveaAk|w=Mlw44 zh9LarX9k< zd^1QVmkF}Y00}!()>M4jWFrs)9^bk)5F2S$&X5d)gELGr_1CxCR2S;nz;0Jul*<`W zP$Gi2$x`MFTx#w@JvGA(`I*paG_pcE`i3D?R8&as9(oITX$deI!cpDa4TsZ-{BR{K z8VV?&!$aRGlr?un5#iXGw-v)tOAF9rt0yvUHhorBZ?BPCM1o z09NNM5h)M_S&c{;mxysCGj1VfUDIw{tZPL?xK3KJG)u*fAdS3ZJMK#f~alnmZhnaTiAyq55ff=r)VA1Y#~FnTYhus}c^%mf?SrFa zhf6SYeGCmG)zx`R{V|bd73-aglQ)ITIBoi*(eX)P^U54~jWODeUuo@;o_p~pLSaF{ z+}QT~OE)TGY#JF)e_^@ub^$om;*M+cS@^Z)80=EI430kwAwEkHL`%`Rzlas;>TFxl2cz404h3>NhdSj7wAtBq_zw z%3A5k2UiLFQp3+XoGzTIZjxLc--{oASpEkF&10?rNV4}A9orfv6aA2 zxTbLG%th>49F3~sDHy0=&Gf<%ber7tB$)I%oW5AAh>Nyhd4^SF_oCwD&Z#O~o(XG0 zth^oJ&R*5nA$Ru0)Vx?S)eUW2oQj-`MaW5w7npUn%*!pd5aTysfBs*&26pb+h3{D- zaLPW8-KATh)2Q+0(KC23N{vysAFYllWJf4v@a*xtt#w%9kWF69mPd!Lf5f3-TPxXE zMA9?R#_luAWC+}eS<45Go<&x2j6^RvGUKd$wo&Q}Qv0>*vQ*}SJZ}b`e(Gr)Idl*z zqY-*)+crvlmdXzmy}MQr`x`AvdCmZ7poesSY0z<4VRza_CR{HUWTla2wVK3ei^o6@ z*SB>ef*R4yjL17VF3yW}EmXV?e_>XNDkVNjq=1y1naj=Hl5N;=i&W+dV6N*S7a}GU z@4ffFc&h9OB+}sqy@)1<2RhLQ4bjf~aRy#fSN9|L#Q0cnvaQ>;|1u@c@+&Sl!b$9h zb4I-Op)oD@72k{>*XRYEi<33pKkT5oP@}f3S8BU9`cmb`r(O-tb+^yCG7t)YiYIiw z`hj(S&P|H_;e;=MWUYWt2kr!Z`(jT#w&%B0%RgO{vslgm7b|VCCF-_9T$EWZPA4&b z{%V^zIx)5X)X5W};=FyX`TqaH0dO!p3JMB-*4*Clm#JBd&o_DS`r~_c|4ye-J-=#s zhK_VCZw<6mQ!JG)%Xo0k;OCbLAo$Q%{p3K%f__Xd^_oG4YybH#A(i>o{GSFeABcOo zK34!Y-uLch*01^5Zv*(66EOD=TckC9qu@8oZ~T}~LJI;svwO==i|qa1c*<`B80x-s mop-wnE`Zq*T5#Yw8UFe5SP5{HP0v}5ZvQ>#=F#3^+;b+mGbcmP2GA;bV7_p!N2vdQMyWRvXi z_j$kMTQ*@q6vSqx-%R-a|NFo1J)ie^zeUk7Xc`Z78rv>R*GmT!4Ea1AW&p^U7krp;75E$|5XR-=y`g3O%u%_HwQLw(rFlK$OA2Ib zJ{5buFCQ zntJBzyo(xm|CF27f9?hosr2^E23lm*`>z#qsTk|&U%uu-SiQ%83X9X?q@Bcr3+UVY;q zKgaT9%mxY!@*LEG%?DlD(^X7|IeT$U+I8cVO)~xh7|Z%Gw(jJ;)JP zRfUE}+;r$fIbD-u@x?{Y!jb~Rdp)P?n)R7!iDXSn63%f(b`>Ar`udyy`~_rPk|RMK zaJn=t_f1@8v~d|0gF?mkoy!0l3I`G}+2Men_-|PL-B*6RYT5eSj8yV^eF7@Hmxo@j zqcOLa_I_4MI#s2))>N{jBnrbBR&wZcI_l{kq^?uMkSUd&)=Deulu@^b7UX48>sdP;KU4b#YUKV4IdVY<*tqn+57D}K zg`WFfn7CF*gXMhpx;tN9wX{$`1%gDx*$^1yE3Ir$LydYe#YDNw>zfT3fRls@?`5Y3 zhFnzJ)JC(DO=N59B-iK|Eu5Q0Ax496iVK~iR@>Y`CuH{ zjW>l#d;N0}SmlMM9@kRhqaU0V5jMh#*eD(xalKBb&Q6XW>9F^at)tsuLII~G#<{)t z=HG|?5urvm8(3b~Os+8xz@lZq@DJFa00JQ)%X{H#8kBy@4-hS;sNx71Tn#!@HF$#pEpP6) z9{~{=!MAgEPfg37J$vZj-kr4OhHDXGjX08Wuuy|RPZcom^V2nym7Yl7S&&boV{U)6 zd9MMK=^S>^TOWKzea>O;`a4(XBEn~o$1_3I*u%0AX=J2{Z0+3?Vo+)M;sU7CbTV@+ zi?hOi?|giO*4(j_fA%QE{=?1Eam9sPue)7*?SZ;mBZ z?Hw3Wn$LDqDl!84l>zZJwRb5^9o@=8$chLJA;;j5px-Uqcl?YURCe#$rFGSmkvTbD z9FpEM{S6Eaqo~dTK0eJg>QUy*P9_tC2R13*Hw4&xe6$Qj9Eo%(YGF=>zc^V5@8O~? zt!ki1WTLLQ!w0CSS>_b}ER7Y#-qSZoO&whnYS3%;ey4J6u9YI-yf?5hBA|nSb4XGa zse5<6Ljz7HrW8POxKS7nD_)H$8l!;A)7#)e&^lo%hYM9mE6uFXFqS&SI0 z6EI`Kpx3u-A?1aqext>LpPAYir}MHbWOwv=PS!N(3v(=#fC6LhRosQRdsf-nC~j5+ zSpYRIPR75aLsrC4<6=K}{4^~q%z=TV?mu>#xZ!be^58w}z_IdLk&*mNv(^Imv~>0; z*C67YCDzIG%f;RdW0`*0p^`JTbk^>md}|ujw|0{9hsPh$%n3073A}`nr)rzfN%gd1 z$$Swx1Vtznis3yceBa?RrM0V<7UpH(%3u}B#QBMxeBOZ$yM1>tj8N#F70bNn5jyvj zA_lZPr)zEcBd2Q6sT1ht#q$Jf1?pt@abQX+8wl4Cy0MUZhf&y8*V;u&adEM>WBXPr zuq07rL>L{fY@~vW6lw>rb4K#4>0k&0xjkOdIVBy2p`aLEhe|6cGcAGU%uW?p@9OOn z#VP3!ZGtMnTHK(P6wL9#5fxP9L&h~kT*ncQMr4__j-IOKV%O$pTQo3~3Y~;*Eh3(6 zPSPqG+LR^pvP4!&tDESdUq4EIKK;S>?|N=l_>2vlwx#njMJ%Cbth~O}2w@ujQI-~D z3mOfK5Z`+o{Z4v!|1m;0)(W#{YuR8GPN>8#*8n?G4=2j18wE60EG?vP1Yr^(KsQ73 zFs>fVr!Losl50sJu0P&)ptOo~kX6^wv*F-JABc;k-vKBQ&qt0NrEQ!4O4kDa;WI)+ zp_WwCvvJB)Wv5Dh_xXk4Jf zIn>$HOXYy-l7egx=vOal3{c@qAxFHYZ$Pv64Jx-R$`^O!Ucm8->L%@>M;=oaE?B_u zIcGPbBuaeJB3@RkUh|~c6uoKjTq{*Jw$mcuea!8~U8lv6Y>K7yB$HNr;*63FYL&&w zqD0}rsgpG};yaN}RWvXL=rGeyhY*jzD1b&E46die`B_?HYp2p-@1;CMJbXqd?LSdL z^Rv?FctwNO*y(t*_~U)g2emcmB5Rl!t8cySchL33?bjFTnU8rOV%ochzEGAg$fLZB zG#`vsC9L=;4trWU?fQncF5+4dtOaK{QWNnKnZ;eo2S};!eOjjdWaV{&s`vuC1tF=Uv6$LTN?4bDc_&(9Xcd7{eZkGRw==b*L*T@1*8 zJS=C1mL(x(lL`GH$DAY@8v;4j*6lDdtrgw0m|`L$z*^_-J$#n$M~vky?;W6=w0JGs zoGc>FjfrU#eL<^j?-5Xux~9%SZ4J735eKBuTUM_AZAMb;(+ehw-Q)3&47f%^`*By{ z3{7N2T-RlBcJ??tkrCl~b7HI)4(T{ECn_5?TbE-JRHTlnb5L7@E?$BIQuvBhDE5Tt zOz+X^Z(N$6DKY@L)M0mM=%fl?M_A+lB7W#(jnCdcwBf@K-uD-$l<{SA zP+Nm8ehmkt@_W|Z^%wMvUtTxY8VX#Cs{#M=VyJx8h)bDr4r*)A#mjI&Dq}}Lnh)cS zWIXF|ZaDN6pdw{VorBsMbnyx}Fje`Lcn@Z4z>oSXq2lVx_`d^JKjxD)2K_%5V2yvp z`0qoLkNj`FT?j(c!gvnlqzr(3rj6ke2mT)hSm3Mbv~wU3LetK8&cc5IkG8pvXC04x P00000NkvXXu0mjf1n>V~ diff --git a/files/opencs/scene-view-status-27.png b/files/opencs/scene-view-status-27.png deleted file mode 100644 index 30eb053b9b4bb475553ec6327b6a18f825d682de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3627 zcmV+`4%G39P)e5SP5(#=XL&O_9SJbg(Icsk#5dn6cM+Xc5rPZxmAQOEXdWxo`CO zn|Y3~#WZ|;BiLKz5nOBR?7cWTQ*3osZXMa*i zo2R*A0^Y91qVrP{t*t6DJ@W^X<;D24^Xn|q0A6!^o36ie&D;;v7z@Gtpg{1+j)lux zc39$d&8meg7a+*fo5<6rRfWK+Y9e_!z&B_<2eLc}MbW?p=w70B@rO7r#;2X%NCXbX zX#)Ja28_TC5%`}`4uA~g=?AoXxAGV_fIRx$fr+>`1qPb?5qUXHTu`b=I>VP}ZmR~6 zjQBQ11!Q0couAO=7ae(hT_0z?L>>vN(WDqaxTf%}42{KeHF)X+gh58g-pIJHw z)Yk5PVfWotPoz3+IC!ezwO3z%>l^e;d?CHjXtdfL=?K>f>z%dAx&OUdvP=m3fCey>F$(cz#c9?TfYB zs`lsSWN>8{Kguce50By3$Ld>1?~FOtgra1V9#)+MgQUV6j9`vZETX6w42fJrXP^7& z-uBjiS-h9V`z4Aw=~vW>Ux%0m^Uu;|o^M(g8<0oTanIjZ&167aN*6Ew;GR7%WTiTu zsM%PKH1c|s6l#2O3U%jNF*iMfa)%ivS*5BS#Kvq{*kLjlU^W>M2oeqw z;ZP)k;qgiMX6NwskyB4!XgV(pS=NA;P6gDzYiBu}mv2r;0{fQ)^j8`{iUL#QO`7|$ zHm4n#DtoqVe1bhL%M#|un+Fbl1Y=CW9m!@mD46{|Vx2P?S-ClIWv0SrNq~PYh`!5X zXm<}`aCjW41_@~f5(ILfQ7_~2Tm++G1u=?zOi|UrLC+IS=g+^y`!0!qKcK4XUZ-Y8 zou_9$EmZgT)d=tlWIzTTCddG`RE)cJ?0oUh@7ub6UBN2E2rSd(^o2HfdOg^jZb6W| zcxEz$s;Uhr&dY{gr^A&gA36qx(bCd|)n*AfHZvJbU}qy3B)~I~7?LR~EkrF7G1f#5 z;Sna^&d|=@h>9nl|2{TVS6`KX4$Frv5j_40kv zp_p^$8v0_~?#bGnY$a5DvdePn9gDpfC?_3COlN~!ZZ9t;ggi%V~ zH;&X}YqAO1NhVyp62Q=G80)Pv5-BHm$E8@c-H0}JyV@U;q|}U5banK+QEFBaqpI}K zU{J-ThaSbgeS1L)yp%%x`jW(;3ymtAoZ>$H@loQPY-ol*5K!0X6v<^Z;FHl=R8&`> zC^r+4XbgvbcMiJoF;r&S(BciCZzhCN^0s9GID3-EwMH0icJu}m6c)NrlAp_X#B;ra zBRJW3>D_-jeC)}fZ+e&~mtfT*piLe=d>E&W9>o23eL?IJyV~nu!aIexKKK;(XIY_8 z6x@0Xc5J>=czt-ni(mfc6duWQAWH5%G2(~I1Z11%DH)>h4~JAtP$Dw4bP|i)1UgHw zU|gRF9m(j9>J7A=s#_q1Fo2VsfA4)n`#!i2_x%)Y*%cOI_HJWkJV;A4zMAQL%Ql$c1|1xv#Ge)!7cTP#(Bvf+$atp(igg zRr)FXs@;KYRb`6PZX>moaQUeICBK44Ywv;GYQbzE$dy?!>UHE{n;Zy*8PwH1X+MPqVF}L#LdZ-@ksL`> zPeLL7j1dta}O*DW;A!X(R}tI?r}NLO?uuo6+op;C%Da9as6@6DcE@TX2ona$|o8w zf(srervO1<*?Is=;Il^BHc+I=Q;Mcjv_Ka#czPXuFg&e8+3+>;0Wbg zSz#`E21g^~g?ly-K)w=0x><)0$3uAZfo(`InFRPt1ir@Q zMC14@!r=%CNM$syGeE))y=yf-YjqO{318i{HI|<2&_#%b+TaKiO`{jP-1<+OJHcvK zQ(evxktq<`WBiPu^_jL_T%=;St)d7fqfs!VdvFX=U0t2}=7HC+AwLT?Ya$xk`r!5X zP?4yE(?|xz%y{5Wssx8moo_{Spdahgl5k>p22P18=_1PTu4cO(O;cgqv93UPT&I)K z*5jdE8ph@gYZWp?CaF;<;wpJu#~yc*XZ>>loNn$wQleSCqc~rsVkNUIpKI$D9xu+z zQd@h6q|N0;A}Z%P`thx&pF_MLJo?DqAKNVn&w404JF{%aOwi-dNC4k>XeW75#>eN| z5b+G4G}Vg#9GFH)0$?L4)aV&fC5-42)RN)?wU|^z=={P>;cZ%@hm2_I=o8e~wWR_U zQWfr6wZW7LQA9s!YNG~DQ3HXXv@SnKbgKN^$#d<{QRKRZuRMF|!(*bcj2l3kNJowy zRU6-W6>A+P9P-TIYqd3k^L6C;>%eniqMa8Ckg~RDHPVvpa#OpTI?fany0YVnMvne(Ra< zNL7`U%hv%{LE)C&)fJy^ry_N7$cJ6`-i>5ilIUlA-Q8GEp8t=2@;rU8*$BLmsL~gp zeyAWfo}8=}BJsS_xX|fF0r6TVODZV_$>Ex~9CAds^&Xj+BEUVUE-O;|21ljgiAj`_ z$5|*&oNdJ&MK0RESM3}1K3DhA2S1_p*=Nfe@a5gR!;cj>jV)B9a>~n4O2B%kjGwM= zAhk)x32MSJNs;Uep8wdVVtZvNKc^7rc;YcQ5}~MPbVA=dG$yKas@*D*ly}!8kv*U_v@R~$L`%E@kb56@Adg{ys=gF2gA?&_JgDU%%FKJXMpHNj~+dWyVEVkIqJuK zwq)!g@YDWT96xay53fl@!`LjWl&}u^;Ly~jydVd5iy0rCZjv)nomgM!QdqqxIk|G` ztJW96m6a~q5w7eFE#0EBuc6|_lxe)w$;l~~6|O?r>P(5f&XRey-5vM%JnY~9SLDD$ z4?m1Qb0y)pX9|1w?1I^7z{~HSz`ZF3Oa!9n@Xn$%NhiRw#&cWib!MQHw3sE2e&L~U zuaRY~c6&8Z&q|HmRmo%sT#1>>ue^T}CAn!TedGv_b0k)TFl9uKVu)>%0u- z-*CbaPV#;tM2}h#1Odsf+oXqi|F)!tX0Zp9)YUkyN=jwlZ{3qI4|J`x9 z2*eGbd2c=qdWBH;#Dy&wl(lsIte5SbI=Z_Z9x`?%id96Bf`@83S-+3>BOc$o`rJKUo73iW$K%h%~o(gwL91r2tc?mG+ zsgh1(&Q`ML|0jif)8^ZnPHNitVx4LcxDD@Z0$+Ah;rR)H#~r075x7eP*g?j-oO!5^ zZy96{rD?*;g@tQ&t$DU!bGRi4pPoLy=lu`=@l!e?O*r<@e+ugK<=*f6{GgiG78d@g zaB;z=j!`T35oA3BAQq=h3v9>yf{_XE}plUJ+9LU zxH;lN$;Hda%S@KSEx{xb2zZbCo4OlfT0%5Z6Jiu{9FarC7w#*yG_h(5?}0$UmiG_n za|_o#AJB+dhH?nNUN3KgbfLi9n5pKy5WuD@2TXdCcRDVtQgM zVxz)wwxS9-X^998o(4&hC{j9jJd#{iRVO%HZscdBp}DOK7p~NPKsB;)G9sE-HH8`k zHmv`_t`!UCY)*`eL)l0k{^-h73Y1ukx@e5Z;+_jwVmycRgmACN+0*?)!>Da+!SvWj zG_!mqksMTW%}IBXEd>D3Wii`voSos03b%;1 z_HOs3Yt5oFM`m&Y9U1K-*PGjLwz6jL?hp5GB&>=7`fR-APb6^U$Pt`A`YBdD zGgrxNkB^5~slg!P>a`|({f!mriP88@ehwT?7X~~b_;}=5l*pFR+F;gn33?F=q#nE& zaugo7b5zK3(q%LlaJjA#4Yp4B8FW}WKbOqOdcx#A$I;`T|C~IFRqNJc#mZIuu9tv* zJ6(Lbyu2)WxV5ez$|NH)G#C_ajEp!CMujJk0`4a5U3OPxO}#$S5{$+9*$6fV@^C^} z$TU}|S=Z<*`)-N=wNXTb1bG(Bnjr+4 z%|cB>i_W0eAuzySAwW2#Mq3AC5uPD?0iGe0pDRXSNNCd9?-%9qA>r_$Lvn|;0!gtE zN>Qrp8F2s5O)9E3N*_rccDRKZ(__KleTNjE8ya!q?;1el^A~>Gk zqlma;Nm$ZwKwcDV71ER96tUMPx7FSOaJ`ufqTJOtD9oLi3Uh#ox|Yvir08)i`NgC| zA2!W@d!Wg{o|dS%I5bMWbJ*jT5U!$qf7?vY6iL;O`+GVqNbJQm3B->I)x82p#H>gjJj}f?T z{QV3bf0Lh1CCN4^%~0FahJ#0rLwNhQzmz#mxV4!B{V^*&8C~{XccryaoRgW1C@L^! zucJ1})3dsv1>wPFB*#bN;?+7O|J8QLMtJgavF~f0jq?%nZjXWwA+8U>)gw104z{jdB_dg=iPRwsaJfCoXfXNTN1A{0jt3p~r6lP41R|>tOrx(-? zsn5q_Ft!NqI5X}Tg-Pi#Df*F|L-5#bGzpw2vi${5=OignDXVG3mX}|p6=p2Ce)Pkl zw}J!xw{h4O&Pr4ClJ}jLuQeN~-O2PzSeTokv{B!v@ZMwZ8^nPx&ckFBR{V*|X=c_s{QOF6Doqzn>Dq(yP}PoG088qR&ZBf`L+mN$}+CwD;k3>1By`orut2 zT}5>R5~IVAN_!9!aLFdTKDZ7YpaC|qE2)6hyB2V&lU&J;(57S9_TmN<c%jLR!t;rA@8HU7|NV)jp6(NIKD^E_o7_d@! zM5@x-fD~G>^*TXrXzdh+XKciNq$_z&2UW%$9z$TkPnoU3rq8}DAyCX{d#vAewAK!H??<(*IPQkwW4$_ zA~Pt3pvIMoe>&6AspD-{x;<4CkM}~TIS6NW_14diAKj^9(Zs4L^dX?W{M^b_+Y=)# zZxqZ-!8B@f)eSbJ#z&&Mz6G=w(axVJardEiEHskGxwR^U$=hoC4GRiH8VxUQkzdsg z<2~InxYQgUOBKtFqh61zw$-~PR!yM>0aXd7R&rG6_DGr!nMpB9EpV~7blQ#F)(V%* zM<@+Dms?pN)z_4|p-mIJ51vA1LWG==6f3+ryc+#FBjQK8F{9%In(8Z*RPnwv;XxCt zrtl5~RGr^_ZsqGKF=4;UA1ijZ+cVNX>@f8W3@H&BiwF-6^0);0@`CnmyF17nC?-XR zd8iTSKe9gsbJGd5HC?(TRa6@j@0wUOh4(-}Rla-$6?>Fr`|R{Y5Ki)b07u2q)ZXLA zbA{)It+NMN$#LLyqou=6bzwk-^@eP4kA2e*~nN2;hg zCf+r%Y6>3#fr-W+#ot<3l`iopls_&VPY66N&#QJd|GiGR#$RT=`;dD0+jyTWg{A~} zcIYh;pyo4WfUDWy{bE1@Uec!=0pApwa^N`%{{s#KpXNb?RmK1S002ovPDHLkV1njs B92)=t diff --git a/files/opencs/scene-view-status-29.png b/files/opencs/scene-view-status-29.png deleted file mode 100644 index eff610666206b1692cb06fc999ee0a870130b692..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3125 zcmV-549fF~P)e5SZQn&=NW!xclPqG_psMDUSm5pj={#n1_Fd)97sx1 z>~Q306h%suHZ-InB5KvBl^XSD73J!Yqk&YU2~pEr!~uiLVIU1LmoXUMc-QOu+QYlv zd#3LaPJ-0L6`wRav$Heb_q@;hyvMhkNCUHY$YwEi2|6D&fTIz;o(*RbPK0pE zCJbPpXN#Q1$(Gdf|0AKvviYIPDJq-ESZA9DuEBfFjmg%{hU=RKeBWNW>46zMzz9;# z@XQVUd_^IdSKfx0Ed|KmxHIjzlmx;>6K27cu444}!{jjrB7m=zon6f&@-mAni+ zV`(Kam%fS3DSFLxJKfI5C71=Ne(Z zWXJ7^TKI{@QzHSCloVoKMk;u@9K%jGnmc>Z(9nvxY7S|7H5pA{y&-fF;L%V7aTJwW zl9r0XUqytl5hSmSlCm5j5nG@89hNRx^4$$s{mei14Cmdu-YHi>6lhjrNYi#4i(3AQ zvScj;x>q|Kz|zHq$Rmjmc{ttJjtgy_u!uoej2euP&YN8U1W9H|N)GXKXb3FxJChI_ zSMRczDeQMwn8RCN*5A?DPK(m`##!ir%^P34ds)#IOIiSD{2lm9D353vME2=7aF51@ zbc-JKBR*IbYh=F{Q@nMi(CTCz)HuJsJrQQ z8XeML;6s$dob;hoC}Jn>&m^I8$|_|g^Pgl{WJS@f{&=Ha9U*0n)P`jw7sprt zR+Ry3mKGvLtwKKq@4NesV|knksj(`YANHZ!8^nB_3^5cFjO8n_M&Xcb3sWaDZhlzIa$`2=KVTacHT z&H`0>?owADj-0&k(Fc3?Z}q!fz3k?7gLv=Wy{M`B4D0T=OIh@#i;7hDa z(Lo?DSa~g0-@Zs-uW8Ym8|qka^` z%D@wg42q51zc+gu9!!&orRH{y=OQ?Jz7dVKJ`~-W9m&g_BO7shxz4^p%t?%c*<_IB zrB?-8&j4rZ9pp+Dbw|EgrCPgY%?^GT^p;{!ZtFgb!jRd)XRE>zqd97N3ctflpm&;jjNG@h>bGSW$L$&pMF3F&XGRbk0#q!eSu%skNfx6?< zL%1hdLk$jx4{UBM(s3{;WfK8<-Xq{p*_+@r`th|dTV;!1z}fi%%;z1Vjvu?cCi9jk z3IoHA(1^<;&zzIO1w&zIREmg3O*MR6Rty)(46XJ)?Adz&+{=IXjY!~O?e2$;Dr7-U zCewQOT#Jn#bh?nQAVy;p*kgC&k%~K^)u>V5YDN9&^SIMuL>uMvMyC(OdbwmcgUITS zbxgp&R_y?%P)CWo=FG`?aFH;!Dw{-|<0}mGws4DxaaO4r<3uQisQ7hKrJkFb2xF`k zr>F?_QM@h4PDguJKX|V%05fSlBgHJaxay13;Awb$J_&rHT8^rIA0FMXhS*R^;4cvP zH5L<24tfy`hOpQmmn1W;8hFUt$vIiq1<$Mz2|Rn}PoaE{(-%P0g;6Frk>vFvLp}%@ z<$PI;42mozcK1=d*SvyN3$u`FHcDBF(kb|r>o(xMU2mf>GX;8G3_{ey3^;~R6eE}9 za5|*IBjqcg;Ca-Xy@+t94P^xWaPKHg6pV8yMMm|`K&cG|G&zG8*GcK-&?%(vH zL95wrC-%HoQw^|xDmR76)ZqL0c)o+e&Tp7Wt%u0rY03OGRSb39fk$g2PSE96C_-O4QP+43N6f zs;VliOw=mJs2_LhDlZwe&HNkik z`1Q_G3~boA5kIxWqT24nri!&tD;0Qa&tcpZufUKmjAn-y`LUN%CCl-wt#z1^kWaao zF;7RZd(fe5>l#3L=@OEjjv70X%A~=v*a&ScckDTWyz~T-HaXl2zI)-O?d2T*FmeZ~ zVehDuq%!HXD=w$?z~hfSj*oZk1h3aaOJ&Doy5r}`Yb zWp=l9(B*lqI4_d|U8PJloBTR@`Fu+UVyO^~dqnEwB9tOe)Hk7j*by$t%iuGT;{`HE zKF>D_ovj*u_WEbuog|ejP|xsXA`4N8mVNl)M?!Vw#}G-!tF!_xI{Z)zK`4oK){isb zXX@*p_PLxR(RtV2z3%rJNv7u)=2&R&3SvKoc_d)(A5pQo!rFlz+MwrWrb!$3?g0nI zg#vXgonlMR(6h%6RlgLSYo_y59+)s-a%A7W;*K{9(h`5}3PzBw;_y}PwZN}l?BNGC z|B+((SIZXMB6)xo1GU&9HHBPK{N>8&rdT+3u0Z-!~DQSI;%w_itCXe2KaPy`Y)JlHq()rnHUmz&ZG%^*%)ak~KZ_Rjg&gl7D84!Kx zyPlaLl!AUqn|fuR!?XR^w~)$oYyM9Sm=45^+@H#T%lo|>ne}^q_oe~gvje97V~e!L znschTKm0MBgk}YJM(-7WEwbGI!{_`;gcFsRZnGwR))_EaLbDD$^TmGw({d3B80v-i P00000NkvXXu0mjfdX@eE diff --git a/files/opencs/scene-view-status-3.png b/files/opencs/scene-view-status-3.png deleted file mode 100644 index ddf27c2db1e4ecafbf5bda486daf7c5084c0adfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2169 zcmV-<2!{8GP)e5SX*oq*BSoK?CkFB)$98WuOTY7YsUl%CO{0f1Ej%g zk~V4jlty`?ZA2P1O{2DXYK5w*RZxXmDM=rCiJJ5lwaMDxhBgM*i3l-CYcODNg0C3g z?OpGEcc%Z0eb(zG#&#_`eb|w9&zW=1ob&zP|NZ|tGeGJ}F_2^(uh@>NZ; ziid?&Jj)+_iYh1h&3c!sGJu$W5lqlAomfouht?asx*l83q6F^qH13oBuiHX=Jhhb) z@T*EJI@`pE3q=-ajKasPN4Z=N{E5IkmjvjiN-P#{B1fq8x_HAnRhIMsF?}yFeNK@H ztfH8S;ed*b+6y5F1CV77H9&o4ezRWXay;OU%LMof2S#8A3H&c920#X?%`V=zo@Lwv z#OQZv;dTPn!U0g*i^TG?oMoodLy1N1qcQKzkC`FOD_zu9#@p7j)TotCbM3oA4zCnFo;e9J9_Z@O=)iL&jMaNv(23<*pQG`{; zpl1pugAw>C#3HhSfsht!nDhmodB63Y6HBeZ2w+k=WavL)O_WwKNvCHv4it$Dh^Nw} zCg1g-tJKdF2_O`SU}$s# z9-kj=9oL^Z)zq}8A*umy(sn?b&}{lv1Cqc#NkD(g0n!*%BCk=~ao)Cgxrx=(SDYx8 z#H06QNN|V@y&h6sdIs`~3gOJlg56@mj6aAQH%D-_XAlEJqsSs1W$P&+Fb0Dt;HE!< z;joMtc|NA7X5kx6Zucw6>#7E9AbIqYJb2%2>5#_cQs)>MFrWDO=()1;fqIrD3MWb| z`lGvYEC>>d?GqvF*s%qr#RU*`I*d$e^es znUS|ZzgZ!K3BH$Bo{T8C6;ZzZcBA{}e0d%N$H<5m7)?g(f0cm#nGaY_d4GMw3#98~ z_vcwCVa0HMJcN6zwxEn8A`^KRu6&G3-FVK^NoOpwmIrb7tRMwn}0$fTc%z!LeI zgw!=>^)SD>3RLZ%jxwB?idMHYx!+ZLH3QU2(aS4OhYz?Kwu_9tUYldtTx8XwV6{q3icmV&I zm_~(Fz#fNAV_5~QDLWZy(me;>8&Gg9Aiq8l!_oIoxhMFrxCeN{Qsq6Q^-NJ{`C^{6 z#!0NdH|)dKom;WFC=Zcn42|!82;Jxiw&&T=IT^qWZwTcU0Tv2o6|l;>da3n0G0%Yy z1V0`4>?{cq;th8y54aktNvmgEg*Kf``t1=dsN1zoV|{3B62ECWi^qx`h!V1vTQhJP zfdXlEiwcqZhsgb76o{$~Ih1vZOpIb=;fl)z&YxPg2M)R#zJHg+@Z-9C3%bT;U@Ixb z){>3Hq=Gimx#z|W>@0Aglj5Mq8$_i|0L6#KaxbxX(I-D20p8fB_dh0s{b*d#8+=TU z9d>o62UZGvMF#A5HT=55W_)r_t{LsOyeO;MMv}=#l)Uh2;~8v`6cpGb{C&WKY@Lh( zlMrY2f+r^Thf&^o(yi|3@#_a)+W$R|EdEe1N(Lu+qJI&M#~)avvZM#<>Kk6I%P~J* zl4e93$zosaZscY+s<=>gyYeE)34a#wWMm(Ewt;)s=7ETVU4Xt$q(4e|SGECJ7zad(d&=BKA8S=%##r zWh#K}cAcheE{7h!jDLFNyo=3^i-J!E^S|6Dq|t?sbMQHm4$Z>>Jn`iNFd2;+_)7%- zfHNH*jQS7`M^Kff(fI?asvOizG>u(GIEl@tFr`ThMtgeWPZlAtt~Ks&NM%($uid!TJ$(YLHRXT zjdlq8*GE?nQNd?f@g{*UGXeIwjVTeH%Kj(5*c4?P^&`N#TyX=4`CVl;!*gZn5?XJ1 z@#vwwTEKol=NT1zg$lkm0eliX?Oa1`qfuo=2f?IiR(u-|V%lNP?#;EJW5kQfFH|DK zZo|g|w{WfFGG&_#w2)Dy^vywIK4F>o21I$hJ1GC5X-0V}p?l4%ngMjGJYJn`Hu&j0 z?y+a!;9W(Sp7G&y%Q-yqxh$L?@xe*~>!2479o#|!#@gL{#;KmH}dFd4}NL#6GHdX$=0Otg02GA?pE6s!6Z;Ez1EM^HkgCi&t zqY&xcwUyYfP1nA<(W@QsSu@Uo^D@2<)HfVv!EpGc(ha#s6pF|ii;T;YK}d2K^r-_& z9c%f7>s`hT;0tfNua@OHtS@aa>yJ!_V<<8*T=C!jBEiRh*5z#ma6qa@i1_C{G5m`1 vdi)KD_ovDf11Sbl45S!HF_2;)#Q^YME=h*-U2L6s00000NkvXXu0mjfJ2DHp diff --git a/files/opencs/scene-view-status-3.svg b/files/opencs/scene-view-status-3.svg new file mode 100644 index 0000000000..56b471de20 --- /dev/null +++ b/files/opencs/scene-view-status-3.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-30.png b/files/opencs/scene-view-status-30.png deleted file mode 100644 index 63ce7d0d254ec549590fd8a9d12e5f57aa0d250d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3096 zcmV+z4CnKSP)e5SqX4d^%?)Y-M5=fvN<;Qz2pu_2*d;wwMFQlAY;&i z+ScieW2<=7s#s^*aaw7|PCHgbrylhLv89gIiin8d0f}-*2nhl?A@_Z3PLj>;zSr-2 z+5fT$8;nA=ndzI^{NMZDd*Ao_eb@hcAgOd<79YAbWo&wPh5W3TfeA|TFZ3Mui0At(JBX8QbvJV)4E0w1q@Tu=lE z_!E$HdPy%^>AdJdz|HX8bMQ*cf?wvm@;>)gNZ{tu_37qyL3SZ0StbFmQeDmokwm&x zMMuy4*V?i-K5>2yBLQ&A`X*ifYKrVJ)y0iq;-EnA$x`>_x3*03x?tt5at#D|`cm@r zK~*8Jsv1BZ9_1S(kB3gz0!0yEN9kUGxcG0Z-uT4%Od{}}CryBV319@amB2qsH2@;W z;#K0_x4C)hX9}!$7hp$jg!11(k}VH(X1y%LPCp@e7Iyh+s9He=6i&E9*CU zU0?K54*}Br=RfTon`)q1g7+iZ?I{nb_mcG1sV^24UHAM;fBC)ZotHP-Z~N(wU#0u! zD4P1tH@Vho;DcihtUKhOPNc)z%M4hUDtpEX@_02V&frv`F7q~ddhPiPtu8A1ThWrj zb;yX$gkYyF<}~dRaz$cQ5u=)L_hR-~7^R z|M(e+C+DoULLcWo3B0gxRDHCH+u=m}=7J=!w-V6Tgbs>ADV=X6*^@#hR!)`GOBUYA z9@pt4oEdYVbictA8{r=T5YJf-&Ca$G3r&f#~bDJ zq&UPyN8mttC34e}5Ec?domZhq>ELoHYFTBSWOq1Gke!B>jvgF7QTrOz$n9jvCy{9h zFtVf&VgyR76e?S<-hHR~Zi? zJu$-LarX4!$S7(XTQMgt3JtBD7`2b1Fee>;MuSF*3!S}I+tiMVn)=Ppy|U#Fx|~Gh zD+mnVd+5f1zs6c1(c01nwH1|rb^@) zxOAw0lE+m=ag2#eg-+3P%Zszn}8xdm_e=Tq^Ybr{b^ z`53}2rv0d|{)}qEsd?N)9w!f>r+*O5ls1V`VTcS5MR(tTy0`SWl%0}*tmH&?BAdnO zmJS@KsNVd_>sxOp0-AwISf{euxpOD>?RpO@ue^k3AGo|c#7YeY87EIQk6YyDx$(dK;g#N zm>uC%cwF4vO*(sQj*9AfeUv2xOA2xjVh-fZ3E`nZjxe*N&o|Z}LJ3fhs#HwwuEuMN zWWqLk9%2-enqcgfWZDyZDFW0+5g8inx@=yC6l^w2)eWsWgItgXKg#m=apjwWi#Sca>OAYi51gF|nKDs)lXq6(KcJx;0)C3y*7U zwSNd)ZzhAR_6!V5m&{FtIlx3+%hSQN=y5Ii#im0a9^}6v&}3jwE2Mx&3oYL{Y_`E+ zJLRdIVU3qYC>rcdOR$;auNmfNxE&s=IW3v|jIe~5rM7MxE)VZ??3N0(4}=Ni;_pOi zG=}F&&GJ0t$Trn1pP8^pI?1-u)oXL~4Gim(6JjN<8-G88%irXu6C~Ltr5S3QJFtD{ zZb(o4;nyms3AZ+Lpg-oNC!@#K@2se4lowj|USDw#gCUVsC2i z*4l4qP#{WA)S{!uhTPOdoNDQW^vB;kpjxBDC`rgW1oBvIBV?LemR~ks3zc`9WADMBXoti`) z!T^WUrR|)e9R`^E&_6VclCnyqB}OA-PJ&i9U48wac1V3b0mE@?@XUDTd805bJtjpz zWqbrKTN~{J<5<)7A}-IhYEdbxZp7X9JxC*rJGp*z*cQ)A)AW+}$B&5sHHFH>uy-cjMX$2KsGZJ&MtlTlU|WX@4DlM^UsIaVq*j;z5kg}*evW2ur9wH{A*Yr3jPYowM6EfPJOM6`pk>!$NfBRSigr2}df;K}^7*n(*-O z_2>d^V4}4Opi@_0z^P916dS^tKg9aGZbD%}0jJMd+#&*#gmkfd#mYylQI-vha#B%Q z--d;h@8b@KIwCMx1HCC)I{&@FBpIChgiGZ-M;@VZeqcQ)t*Iltd=6=iv zbqIA^$x&%(K{n-jBDY`9OvA6z%g*M`9{F@@7r0imu0>>ql~7bWQt`%kI=Xc{%_`@n zistctNY&dh-dlD5C%bn&EwK2+>Qm@NK)ihQij^CZqAZUT&P_oOwYjQ>Hl!v*p{l+W zG#Am#pQLd2p>`}alE=BVYJ|z#qWy*k2O^C&FK&^a*N)<4-7+{tj=NLE(zMItaba7m zKC$`~`XC^baB3w-hi!$;*HPad=JwCb$gE}J;#(I&_wFk}hZaMW%Z7#h(cEEmDf|Wmgz=l#u6dmHjC(H4Nj1@Dqe7~5{MmS< zvi@s(T<8?5Ppm$L7eGK5Vg0000e5SP5`cug9Kkj}3aQsSn5Km9D%`uz1gN7w=qK6Yf| zx;#L@UjiqSb8tBzVdqD`WL@+eS5$t5)@Z}9VjAC7~YB7(PtaGf^3QyXznFqIZC{sP~qgtr8GBJ0!T(YNnQaNSWV}L zrTGy@w!Uh_^?&_zWFh;@pN|Y;KFUNg>?lxTW~w6CRuJRW^m00r3T2rc#Pq6(7Gv{8g@tbw&d*+_CX&8D8zicfF_R^Yp7|ktiMuq`Sp=|NxC1E zF?*c6ko$p$$zbSN+RXO2exw0OBpu7peL+J81XJnA#qTXD+87rZwzgnqHlm1iKV_)S zo?aY1cNqhH{m2f}KrIufYJ1TqQ$f(_kv=&AQ4vO{l}c#TD!2y;2PI*T&xh8|9ynYB z_;7E@`U_>}MMK6lU{i@(_)`^2hePR`1tfv}1p)o11dzPIB(ja>UY6#hBa=$e{F!SR z<1!hC0b+COu7gkocuWu1AdH-8b&%>z;fPC2fH^i22Avkpfk8C1bfC)8jHcF3L@GH% zDJdZkfhvUzEdxHZdwB%N^8sEEnwo5D%g&$Q#P%KK0jFCKroKnbj4&k6WLoIx>Tf*2 zULXTv=+H$5FiXXe|HPKhKezuOuMJ@tnV8!2P)Xg-VAOT?DeI$}z`lC`RLxb~DV3^-_K6UE8kT>E? zq7IqC;wYr7QjnjuhGmg?xl;!t!VH=KF;`yQfcmOB%ptkh2RyK-jaW202O%0Y+9`P7 z-+LT$!_|lnRpVl}8_g~+vh*^9P)snEM`M+CBiR^@g4M%ukui~|t*(DRQ^SY&1#WrM zpn%!SAH=FvD?u4}w1n8}qXL62Mh2&NpZ+)~@Q#$0!|8Slx5#PKrgJa{!eMO^7h) zkeV1LM$e_1E2y|si-mDIRChTMnmz@&>63{`0mn(_j)n%ziVs5t#et=N5ZR$JD2PP{ z#Y*nqle!)cB*=sXk@cK{3*h|4N>p0gkUKRskdZt|*5hz-jcuKn6djI8lTlokUgfPV z9h|kblgpo$20h8s-tWThqo?29ymi~7%s}po-MfX3nldECM~N><*2~FiR!C|a+iGuY=Q> zXg9H%V}a!}VV<#uOG_Gx(cqq9s_hr6wnnKCy84{lty5E>57B_doiv!d`bo~}xHQNk zLbcE9G)hDmLwUJe&Voa9F>{;U;q)9TEmx$I400)x94A@KT`(8sxF|7DH-CNzOJlXv z;BffJ>c9*tA0`#+orcCmCyzq~Z-dhshG&NESRV1^wYE0w+_N9ttAG5hK;U6%Zik*KWOiCI)4Kmc zjaAXv+lNdgF&d)8PMZS{7u^Y+R)g|d3(C)4#GU3a)KNaK>~$m0AQug15Lx}P4Dk4P z{$_AW%`o_@53K5Q)WWP&RSA0gDRi{fa5IQ;R;d}|L@2tb_%%|co*W;IuuvV&P!VjS zc$=1*i29~>C|qt2B1!8>agm~ni%*<|f`-fO7QshrOH&Q8E(qPV#naTFSSAXXdc}ioNrOFO%a8*%0)86!5)>ak-Er?}UtUzAQur zWr_;h+bG^kUc>z9DTt2@6SEYhQz-6OvK-sCzKfjXI2iOH@KF!bVedk2h+LGzS)UpY z7tVuHp+L#`%kVc^Q9$4ixAwzC!8nOhq@;HSN@Fyls@IF@dL5or*W$0wq~WbX1due`Y1-iA)25SCLd2+yatQ62;2NC}oU`foJc-g&GS|sKu7c zIKe`B+@1bQY(pB^K;Ipb+ay0s0DL;dLs;Wpv{)}}IC^mZE0S8IqRUcqwI@a2%#4ES55oahUmI4gm}DGTC!1{mS^^ip_E~B_Y49 zy-RVWxkIedkw!h(_kxoTL$xmzA9&NyDP!F%X@9I#M8>SSk zu^TNeF2)_vI@JL6;|@bO78CeBrwa!TpTvq=B2n7mf}R34j6OJ|fXz-xfKjKx!INdO zm`D?{Qq4RiFP4s3u-YP$=Wp+l*X<#^WC+D$RY zq+HCHr=PdE)2^y(>OkRwStLC@HFm?FsKdLnEY?GxmYa7TK}KSfKp#2Wi|%{ipwG)m zgWX8)H~?EinJATs08i+F)sL>mr(3r`VK6{PW!plo&rtZGpv8|8`wL9sf87wEsI`*r z&$QXM%WMuyXP@)Ayo_WDbhRoz(ln^2moL^dAe0Kxut&sBErWO)_ZyY2q`j3h-; zYy?jR$)~vcp|>7Do2~rm_eV%&A_K;o55@qMXxWY(pYR8Ee+q$gJYUD-vV9O5-U}7c z&iZi%{9JkYlkUFW9%-?~cQ5%vQjF>O>1k&Ab_KB?!aU-!wfCr5U19A&3w@wxCMSp= z?#&%`iVG#mY8r)_mab=xA3E@gwAObW$MZnYfRT}X+q~P~&Q6Gayw4jzqMF00)~kV! z-Rz7{Op`*w}>8~%|I=-KusYR6ERdd9TW@4F4XW{eXb1$_U#P@=h(F-y1r%u zm@#bR<>mdRvbOH;t`Uq+w74|?z^aumYE+7Kv$Ik)ZjYCzWn^lKsk4<0-PmjzHNe5mc2_HK^Vs0*x5dg|4;!y$M9CDxE-i1fo zI}mAtNQQrjohWGD0nH#t=R5KOVYLuc=J!ku5J{?mm^}sU+EFz)Ofb1$QSlfcTeXIa z3mpSQdH`}4<#!N1?4OOWL75h5*V)D25r8;R|Ezv91Be6xNTi>f>M%>{r30fO(=rYQ`o12j$oWV>mngq3*$ zEMOOAFYH20SO~(PMdVe$9L1Uw6>|gN)i2wHI&}!G;MT&q(VBN;ANd0^t~E$B%mJ_b z>w$f^JNaYj;rUgE4i<8r=~b&;@E6BBM7!a8glH6XqZl{E2j8of$vr6%IaM#~G{Sj4PF)2EmJFVQtJOPp>GK{! z#^Ufw>Uew(F8? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-5.png b/files/opencs/scene-view-status-5.png deleted file mode 100644 index 63d37f8be5b5c20e1e45fae96325ee73faf5ed43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2097 zcmV-12+sG3P)e5n0ag*R~^T{bL@R&eP8j$t%7aEv6HlMj@F5fw59PH zS|vh3sHqTtB!D8S0zyC$r9vPf)Cy7wrKuDU6)m)agxbcXiR#30p-Gb%+&E5ZH(9T5 zJGR%m-g9$RW>e1kV#}Ktdk@`z0BvB0x5y(E{aZyeQ%O z$ypPUiXlmT_T7g1U&RAibpQljMUCq@Ua+bP^-2n5_WTp;J6=Xjwgn26T^x&GdwCg( z$s!7gce(QuTRF;vY=a5F(M6e{jW+4X0B%hxcD0@B`<8am+ zt7vYlf1f?4cvqb+P>Wwve$E;k!`dsy@u==5@|||HkB8vgxB*Ysc``i*vPCZZF;PhOB4C#&u3dJomL(GF&x0=ZJY7A{A@@u84d4W zfiOCP2ea+yml}!Yyv;*Xa2kMI7LuMg_CnZgC%|} z82~BY;>xk^$~EiocCQalJ+TKClM(G*ZnR(i2z#6kbklm?=?&pQyP$cS!81F!g9=Vo zoq!_JL`aZk0%1*lLit4RVOw8mF57GAx@!P&_!K>lcG(Em?yb;?BF?p4L(J2Ik_;P~2mMIlXeXsrWnh73iUL{~e6I%^2>z635;>xR%ti35myp829Z?Uld$PoH1n*hpYOPInH4=a{ zbOW*BZm1F+25H_got=0BoQ z@X~K9+36Kd&>fob`TQ!gip$=CX|NK$4WIN4;p6se8hkSy!iw$!&s3L*+#OVLW?cT`DZl#7x$fugD3HUdEUhOaKTo9r zwgRzHNvOCS+eqLKECtVc*F5!@tL~+mEX%VQ1_A$8P1s+(9g|Z5ym9h0_N`CH#gPCF zr-ZfhHYf(Z)FEpQ{qA?p6VU=`ptFkt5LqBIOKKa{UQ+SJy=fNG@!YrWqf_JQUN81P zv6Ig93jTEF13X@k3Xh*Y0mD&junF2~69@14lsjh}!1Fb|JNx;IF|vxd605}9Exu|2 z&?npRcl!O`5bbtY%tpBTM^G%rAkw#M3#ng~B53WHRsLJ>MF5ux{_t`(S=461Pt8KC zq_*x0TsRs%RNXT3i?+^!t?lo-tNJV*#P42-tur2m*ub?TWi)G znvBZGH*n|--uWRBuYa$vcLWdzVk1Jre~-QbU!`?@_6sQfJb6tjkW?V4KvIFE0!amu b3IP8B)7_VjEAYZ800000NkvXXu0mjfiKg~Z diff --git a/files/opencs/scene-view-status-5.svg b/files/opencs/scene-view-status-5.svg new file mode 100644 index 0000000000..5a74f8e829 --- /dev/null +++ b/files/opencs/scene-view-status-5.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-6.png b/files/opencs/scene-view-status-6.png deleted file mode 100644 index 051aa64ae7bbd4d2e6bd51697eeb7be5063b1f0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1104 zcmV-W1h4yvP)e5m`!NZKorL(*(zG~=0Om@5c~pBu;`&4^x{FqgNRj% z=t)5lp?(}(!B(uOP!PYM7s0DmJrulp5Ja>^5JXFB!B&f(C}>^nZg%{=B$KAw)?LkR zDw04lGjAp{|Nnp9dy`$Ik}FXlQ6Q26b6US%48>pJ_nj~}Og%UUO#eie5(5WNeT%O!L7J0vc)b9aWSEa(8zgIK-{!u`eU5hu zVk11da&;2}r}6Q$WfKEVZaZLZj<+Ph*U{I`!8P4I8{F6s_tK3#VkhDfU||lX($XOW zrZOC|skD~3Aa>kqf{26;GB+LW@z9L_WOu~HPN04Y@XhWggqNXs2|||L$Rh*M^!T%s zuP4}nznv1GBEl&$RhnQArV(P0e2HNvgdYbTWv)afVDLa>A@w$ZuXJO>?~u<7;_4~Q zRS@w%UCmq3%d!zLP#|)_va1fcXNoae7Ku&{Nk>lmCWw?RW0jqNV1`6Hv9LJ^@PLSW znoDadB<>hdY#dH?3B#)bM^75`*< z$yh%JRrYO{%DwARI!n^>e)Wy=n2HkcF|WWc22r#)1pz*k0d%*@edtvh)vc<@^4=5b z%ilJw{$4oAC`N??%GD+wS~;!vy61!U+hTZXUvcNlfTuQXKfs?bXcxer4G$h!i;aLR z3kf-`>$#5saGK^H4RMvf0WR~&6)OR6N$)xL+R*&$!1Hcgrb^taRK>${NPWTx$l|k3 zKFR=m3g1daT}8%^byYfzjB>IK` diff --git a/files/opencs/scene-view-status-6.svg b/files/opencs/scene-view-status-6.svg new file mode 100644 index 0000000000..4321be4269 --- /dev/null +++ b/files/opencs/scene-view-status-6.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-7.png b/files/opencs/scene-view-status-7.png deleted file mode 100644 index 6b2e5fdc16cacc0ea7449461bd470b6eaf3b0b51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2462 zcmV;P31Rk$P)e5SZQoj*BL!;=FL81Y~$V7KtyeqorEJMlm(kL{o+EO)*x~Pjl6=^FV5?!K_ZYd--fd&+_B?ZFLIv6lG;N8Z?>v(L> z_Fm6@<2SYk>@l_E{PC0C-21+}o$uUpzkBZhlg}grlMIZDfvmlr-F#<{tCMjwHjJaK z-t>89-qk-QIvtk-WPT?kgHbZd)0uy2qQQ&tq_GT2aGzzpTXkDqKD|C|xdeV)4F$s{ ziCn0Pfpt{9&P1eRoq@j*%*#=LeyN5+5fdjul*J8SWF< zXwQVfP!C1Xq4u%Nu76BaI+htIiO2-_hYpNj8wmclTmztk!tB<|CNho802%!@2fjdH zWjX-bc9P}oI?IyL1T_>`$vPhn*6E?<)k@m(blXIx8g&B${+wwo-H{T<(5#J{!4mn0i@ z=QvE;mZvz-PZlq<`>=TNeB@5gfNV6Pt+N}IwN0p~xQQ8737K(L#t4G-22fAHo`*csXX5`q3*uMnLQcmZuC z+_;?D-av*@YW|H`6|Z4!nu7r=g!64aEL$=kd6bAk@-ANc1XrqRk*)fX9dAQBbza%! zgP$8=s#(HRzBYm-`I>~pm|;DLT=)?{MR`|H!imn{s}FI^N;kTF8vE&_ zSO**pR^W+##AF00l1873E4|A4W2GfWMV%oIc((Cd>;q?`8{hA{!2Qe*<~R*MUO~BZ z^!iY3nS#fb&4=A;!JYO_{Q1ZkJep`hhRcGF+xpPp^@H_c=b8|T9t&tOt`5AuU&W0+ z<<0gGww*gt(yn8QIG`J1r3;=YlBUXrvd^VC^RvnNhj+YKxO5>tla+=*Foe<%F2UH^ zf=ANgP|?wc+a4cgI}C6P02i!_CzcbjfQNe-G6T9{wDd7|@hWQdgget^RH)x-*@434 zi!|1o?sni0hfm^*)8i2&y=Bck$hH6(*6**8@nfvZjz<;a`L)hIJQ8Qrw5`jbx>v!$ zg8firE`$-D0Y^lWAMxGK2v|Qt)e+4jI-rA3u^Mr_)rT*;^I@}CH1Jmlerbe_C@pcL<3Un{Cvj}4;(MBJK#Ma5qf7C&tqRnY zZ)(|DMBCtVUk7yX31$PzLsq!gEQU#zaq{w21Z!(BC)tIvCJz!M?xc(|L&1wEa;aPC z`qRO8;<@Sll*qn6)n>yj+)J-E#L zJhI;#-86C|BlF91o#vPG60A7U(2b4jS8EIQBc5jj_&EfBsD|KqZP&m@^`xC^Xlo6s z!w!O+g-)8Cl+#|`E4B>ie0XaCoQ#J$8^df1`4Bh{mY4Z%Ch&^k4FkS7Nr^X__z>=z_x|GH^X887fC02c{WwHfvq z2}?!ApO^am{yn+VQn#pFk@*ep?yA-vAG9=QSO<&)_o1CtC)GBC-&BmbfkI8Ef^AOHXW07*qoM6N<$f_NmQ#{d8T diff --git a/files/opencs/scene-view-status-7.svg b/files/opencs/scene-view-status-7.svg new file mode 100644 index 0000000000..bcaefa0bba --- /dev/null +++ b/files/opencs/scene-view-status-7.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-8.png b/files/opencs/scene-view-status-8.png deleted file mode 100644 index 65ea108ac54205654d7fff77793bd02a80e49274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0s)y z#W5s<_3bQ2zGed+7U?7Z>+guMU!A)6KqlYWu-V)kC#TtN-hSa;q1>T#;dy^$YR|Jq z&8Z2ma4VQ+`21V!0{*o^%lY3J+x{0`C^b9u@sy1#H?NYM@ocW$mA4}I_8wTTd$WPp zfvHb`rQ>uigK`UR4g=GXr)CX;huF**7#|f$AK*x2mTq7u+{v5JvZ0aJp<%}zHUp;{ z2iO!GV)B?J6mt@o1r+qQGxi9WZD8aOn9l$G$W7fm=J=0#g&%9h6+0hVe>iwx)%U|Q tdse4w?-&2FKP=_Mb0bryqQl=+`Cbc3rY~%JH6Q3t22WQ%mvv4FO#nbqZubBH diff --git a/files/opencs/scene-view-status-8.svg b/files/opencs/scene-view-status-8.svg new file mode 100644 index 0000000000..5f6107f774 --- /dev/null +++ b/files/opencs/scene-view-status-8.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-9.png b/files/opencs/scene-view-status-9.png deleted file mode 100644 index 72d0d9fb774b64180d1c73eba960683569ad160e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1990 zcmV;%2RZnOP)e5m|JWV*BQtEGdnvwyW?H2@3(rPQDZDSCZyO2K?Y+m zw0PGI5b+QtMR^FQs!;P#DQX+3Qmd$sec&aciQ1}Bo2qS;#5R}2DF(-F6B3fjV2Hsf zzJc+5z3aX2&h$HjXBn1tuw5In6(gN27IK*0UZ(t`}# z?+o0wk=;v}ExX+!%~!~C+#_ZVu6ZjAi9FZa-qtp}SQbv-eOc`{wii3VV(;6!+r7cW zaR+1*=PnRU6}c7xStbrLY6;;qarm5}X2uL0=fi$wf(d8C=EraL887 z1;e;{eH>p5T*Jud1oFv81!f*>YEj^EJs87SR6&B{kswH7_(6MX>ruu+_uO*JN(rdh z)y(A!{E-UTe0Wo#j4(y<)9DCmYu97lnomwwJQ$@X5t~oc;F~sLQjW zF-JtdFM*Ehel7YnGP8?G17@(=E0vlN&R+~ZrT||0L$0(Z`Z4~utyZFkJWS5y> z1Ot|AG(E8%HpzmqX)peGq8poYEhx2HaDFO;Yk?@Lvv}BOb{6HYVAa{Qcjh7nJ`z^3 zdH=K6yJrtXQCz--9wo2m34G^winAJA6!m|O1+d}q4S2Y`7_oQ)?f>`;ripQEEOwxG zCWNd02&yRB@;o?;B)je=!U%pjq`Wzuz~PUMw@$I&E_)F-xjpsd)zj`WyGbGc4hS+D zH$A3BeROgLzdv>c``0)Tr|@>(n1jm#lu9?Zs0c-IG@@dX79!J7NL8oY%F!BONVl$Z z;2)o8n`U>UbxYb#R?AYZoo>%hACkpiHI~Teo1BBaVjVVAtYw3un|$uOdKHhCI?+q( zV89q>|Sqyf@-Qfk{EBl}|?YoG+maM^V*v zysb|MVedcNv*RZ|Mfe48kwh0|VmPEiw~8Kd zwr+b8MR`t)O?mOBlik=?ZpR$0vi56psL$qMr5dig;(}jMFGtjp?&ED^sWAFycS{Q^ zheMNb99{@qzc(#w+Rk|a6!|W9N!FH9n;EUcK0No-4#-vsJy!zK7X=(L3kZ(vG3_v7u*MQ_(I8qlVc$q*xLlF#iGGqAowO% z4nCO(AR3LKF59G8Mvwd)?V7zo^~Wm-zmUjV^^~HnD{S(#e8?i7Bl74;NN{hhg;@}A z=JSh)4-KNaz>dyQKXN$QNvX;hD+!DKXFg^24{x_R7n*dr{W>3^CRy)zB761dQ8W{L zkyXI^6A^eC>L3UvbTRnhAyg6kJ43T55fl`rz%#Ir!eJny9$GHsQh#@wfQ}D7z~sM9 zp-eX81TD(%yBoAa*oU8ALX5%ZXW?Cfue1V=`HjgqA&dTh0tqiwoWB_RSF@cIK%U?9 z&g-wDGDkueomZaTy+vEF>^#HZs~P;D0X~I_?Hp_kOvKf{rhK)kACrricGxppie&VR z`%&}l8ss|c_;Tb1F85rdYLkmj61C1YA9({(as#3|-XB)}d~8nrq2cRQZ)yVG@902% zflUn3dEDp7#m{1-?dgT|1AjG|*DVTatjK4t_80PbVO`k=+Y`)tKXffYpfVQpU{q z3?$D&M%A=f=zj>>1D3J-YuR2X19Y~`jO(@xu=<}F*taN?J27>!<9x;mFj^?%;8`C2 Y3skl6XO~B$%K!iX07*qoM6N<$f>T$({r~^~ diff --git a/files/opencs/scene-view-status-9.svg b/files/opencs/scene-view-status-9.svg new file mode 100644 index 0000000000..5d101ad1f4 --- /dev/null +++ b/files/opencs/scene-view-status-9.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-terrain.png b/files/opencs/scene-view-terrain.png deleted file mode 100644 index b0051eb1273c586e5ccfc4b3d371e7b8cb86127c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)e5S9@$z=M_KSz1NNtJI>pA$2br2hCl*sD72+4Wp#EJ8MJ8_(c zWBcBn^U>=Drj$}rX_|bc_*ysL{mwbR^ZWhIXMpe(Mj(tp7=bVXVFbbmgc10kML@vJ zv|nX*?5oNg}`=47^3Q351AQ%kgNI^tNg2B;o zxN5b-yw7*+?RWqBEqwgg9eL~mh$_3ES+}1M7X${g^M1J0&;n};EOKI83_fC*_(Y(& zqesfkOo5!tR9+l)#FUBJf8UwiA7ts80zvojzVlsQ#Y$R$aH1%D7uUugtU z5TH0G11#w&JaM9xdWoV4<5Sac^j~#vo;vl>>-hYUsrASOsEB7IC%_%M3$z*$3N0Ca zNs=^*ECVHV4{?{2mz4%-$%$~S$p$64Sr8u+4Kl6~uqZ5AX|#1PpFaTQMY+&7GzRsp zj(1Tb-+q)rzHk9A@7;5FYh@XyiYiV@1QU`QZB9X_mB17q>u>4o(PSf?RI1QgD#|l6 z2M$+1tVlCKcmFWV`4*s}I1eJU8eS9$osx0%41(3(b?i?^j(_Ki6e8lH&%LVARlfVm zo)0n43Pf~1DpDsAiu}UlBvE78-H52F?nOM7L`<6H2l2S~qHd`(JYe}&$*#VU*Bu=_$p=Wj; z&fmNPm8{xojVT~ zTQc@<-?$b~nGjUbrXp}tjYfhtOz=jt9rChL;OX)b@Gbc76R`#@(>pr{?|*UyCcU%4 znmtvbK_3Z$z-RG!S2P-E>2N{!z$ip$1lYWO6^2tnoufR7ru28>>@^bn;lI59kMHB- zP)9-qsEFrer0}6*>*|99qn>k_5c|{){m%YjjXBi>nJI~UUERHo_>|fh9wj@XWBDp;5@dj-dUjfXJ?a5#|~k zWfi!WK^Fn;>6!cNm%a4$7vI{ssiMZ5mc(CDc}w=^nVCgV4IzDGWzH91D_5j}2{&In zq-fm?(&p^-CKRy|kzqnvL3T)-gd(jW(Kfa_!HAvc=oyreDngz)o&K#R%QY}QISsDC z5zuKQWo*jJ))wY~0gDeV9*hX+G}8Ixx$Ex;X{Ll4kJn4>CBE_v>65etw_9EIQ zri;+Rl*b!#3|dI74WJ#ic61|ZMu;^U5J@jg&&>MOy=WbMpP8FSo=?hJoDsNIpn@=| z&tl>l6ts-FEX>aNASEe*#YXFCY6n$N)tE>#=y3J>5V%*_AAk8{C04KV_YO~Jh^HmF zW_f(lBU|k*sjR>P$tWa}M68}{@q(DS968nYvX4p_xfC++7 zce5F2;E_0a68Mk`xY^?19l@ANFVd%Xc$}?4#HmXZlU#|!-ZMDL?Kj2{C1XrsXv~e& z%TS#CBaHp#C$A~yJdbA&iGy!L7nL#u;|qHH`eI7Yc26k*L=j(a~Mun@^c1VbgmZSAi z3`>=9n8|k>E~y|p6E>|Wf)woZmD@Ik?ZG*M>7xBqyevfx{OJb~%oJ34ZiU+he zOhFEM5drE5Ax+8Ao$Ro#zF96W%vNx43Ful(M*ha*`*eLOWFOJ1gJTm=gox`Sbx_yP2CMV3puWwi zbPc;hh(DO;k_D&>x9xc0XBg{^T~C!smLnX9xS#PRehBc+57E;lLPeEtb;)$~4T?@WE0;+Yb=%Nsd2cLf0rTr}Du@V(%YyYsuECpIr~}MguIiOnR5jN0i`^56(kDW|ER` zPUDDEab%-77Zk_9_>xpn<3D(Rr`f3qzb?mNr;kK82?Xaob3WY^o>ii8 z5EbWS@pBKYCvoDB7qn}4z-Z7*=9GjW79`To$_87P(mmpSC{@(B58l6Y0qRCoTT$%E zafe87m?xqiMP2dXWREc7Zv#5i4UFCsC)r{Dh#PgGfhK#8Vjmbgc=_BJZiniAk9vRX z0@Q%tczN${QOmDamgMpd5YGq4+zL)oM#m8nC)zVyvfAabshI<3KKb|Oic{V9VfSS# zKn=8S?@PbKf%d&8igR?xYkoAKpN&M7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-water.png b/files/opencs/scene-view-water.png deleted file mode 100644 index 246c5aae929d28b3f7e8b86c0ab7b3a89da14bfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 753 zcmVe5mc2_HK^Vs0*x5dg|4;!y$M9CDxE-i1fo zI}mAtNQQrjohWGD0nH#t=R5KOVYLuc=J!ku5J{?mm^}sU+EFz)Ofb1$QSlfcTeXIa z3mpSQdH`}4<#!N1?4OOWL75h5*V)D25r8;R|Ezv91Be6xNTi>f>M%>{r30fO(=rYQ`o12j$oWV>mngq3*$ zEMOOAFYH20SO~(PMdVe$9L1Uw6>|gN)i2wHI&}!G;MT&q(VBN;ANd0^t~E$B%mJ_b z>w$f^JNaYj;rUgE4i<8r=~b&;@E6BBM7!a8glH6XqZl{E2j8of$vr6%IaM#~G{Sj4PF)2EmJFVQtJOPp>GK{! z#^Ufw>Uew(F8? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene.png b/files/opencs/scene.png deleted file mode 100644 index a9fc03f7699a715592edd31db78ad47ac514f006..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;Z-gF~q_@IYEL|Lh-@t*V$Z0!W(|@3;h57`SWl7MU6jL1fn1C7%foY7O>~x;9$^< zV?46nan?dM8J17ifB*j7{6vn)P~L3ze@4}JjVcW#`~vyxj{+3ZSROGu*l=r^OTSW5 z+@a{usPC|cS&bod0`rpcLq{C+94D=3J@wbg?14$Z0;WjA3G&=u&!s9@b3Lg^NoPKq zx442?}xUyxj@&tLlBR3uPh;*bY?0g_T;e2NvgD_{~i9UU^lm<3m=10dDY-l)k mC|qH;aK}@(V7oL928Qm_H3#l%PWuYpUXO@geCwN2ZUMx diff --git a/files/opencs/scene.svg b/files/opencs/scene.svg new file mode 100644 index 0000000000..a516d6b596 --- /dev/null +++ b/files/opencs/scene.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/script.png b/files/opencs/script.png deleted file mode 100644 index 29d622e19f742602321a65b981b63b35a845d401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;ir{ zi(`m|;M&QFe9a0xtw&dGZ{E$cJ(#^Zb@G;FhmxN7OJAz1%{|Ctac-y2H3{cs)kS&_ zncu8tk#ERT1(t)Rzy*9WmOoDA;y$eY3YfYl_PA)s{owN+d}Ufd6E zODZw+YwuWfDs%xd`aABT4!&+SdkJ+6JHv3vv zuiCSAGn15p?Xv3V1u2W}-(vc^?i%}}O@^y~eYJ1@R5GndeerRi>lr*<{an^LB{Ts5 D&n9F^ diff --git a/files/opencs/script.svg b/files/opencs/script.svg new file mode 100644 index 0000000000..76f3b7b28c --- /dev/null +++ b/files/opencs/script.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-corner.png b/files/opencs/selection-mode-cube-corner.png deleted file mode 100644 index b69ef1782fef094243f5e5f68b96218b2699bcd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1422 zcmV;91#$X`P)Z~{#<2u7WO5QIbr28@YH;v<5TiAK0TNO{Ep#7Be#Qfs8hpsg*BVoT|Zw)dRv zptniE0)3nd7`w)2@3Yp~d;fc_|G*#yF^EC*6(W)&B8&Z)%iIC7*}yX3S>Q1s6DSV^ z0^?lQeN|OIxn~4K$u0%P0`rV9+1A=}padvZ)nB5|8NdaafT^l_=8g*xkx{@%UDvZsy6k9N3fS<dDnc;A_+^=n?s(VCarihfP>LYy}@ATrC;Xo^} zMpZvl)pEZNx~i@QE{MqHSO{=|kE35dW>D2xK#_>N=C^=2Rows_5s~j>Ty^t+4@4x_ zPXS$DXcbT(B5Px~3Jt*H#+V8b8RDye=nF;wTSY`-D5A_-+f|wT6%a+XF(%xXW$sU9 z0*;BueqZi^8z!$2kww6tKzu(rTEOd8s;2<6ftMY}ndo_59q=`vR7m>pvvRPHv^})!U19o|ycTH83 z2fE#Y1Kg{#M5IH#L!pqh)~*MNfrG%i z;c&Rh@2wjX0RYxofcsU|yVK#w@o}z%yOHi3{^Ph?0y^%gGtk=v=d{;kVOdIB^&H|e z5s^4m?YLJ2xZM++X}^?@lduBs+5&=UW8Ks|8b+<6IW3pQAz381TqgJEic`RZdqF^( zi8Q=bIH#b{2GggxDS0&IuB0t7oh#`N(Ude4*935^hhsYnRa0UrsZ-XCFEm^B>^*p5 z(H#>Y3+HAVw|R+)G)%Wn(({oKGg3|6NouEV+EPC(?>e%^S_~FrEkPT(Zk~YWa%Rjt zCbrZThD_qxaLMs)u@cY`n)_ri1fKGe^Kv`V^J&PQ&XtUZX-Z6^H7=3q-z}zQLq8!zkme{7RU22*v`1-M!>qa z?#~zp<2YE)1M5{|?QX35qs2Y>S5ZpkGjLy1DAX(>52$LTuL5*mi4@MhSdo=ADYrIb zG{)ZMvW=1jux_LZD8qV(tn2NsDoW`wR~JKzSUU|kDWr-m$KC6ezDMO6iNI`c*{U1ImEWzFuUi`x-E&VBF!A#JTOa`1pZL zAW&75(kJrWL~H<#ipT-~L;b!3zvHzn8h>mPp6Pj*^;}FK_|d5CjZX}~-L3>Ch{&5j zyRTLvy#MgtK;4OL`K84~oSt`|bt7P$pGR$POdf!UTwr}~`1Sc72$vjxqtI*0doW|{ z!it=!H}ls-8)4b~h?~oR8PRTF_3g&=yE|Pt`_YQZ=XT`QWR7Lpo<)={*p2H1uB%2^ z??hEm%G6tsU3!xQSKK|p#l%W-+cP3%tdTu)e6AFfZ-oEbX&*Hy`WJ*pna&~pM zyIS$RNpSwJK&P#kH=elEGTQ4o75Wy}2@vOYFr>AfVHZx5RCk7i%O|lNjrEv7F>n?* z;Aew)z<+2&Bov^1@3pKMBwYFl+jhRnShvguGY;86(*9Uk diff --git a/files/opencs/selection-mode-cube-corner.svg b/files/opencs/selection-mode-cube-corner.svg new file mode 100644 index 0000000000..63d5f73c56 --- /dev/null +++ b/files/opencs/selection-mode-cube-corner.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-sphere.png b/files/opencs/selection-mode-cube-sphere.png deleted file mode 100644 index cb5432eea6f3e668540da5ef909db8937546f3ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1428 zcmV;F1#9|=P)->{SrNnt;ZL57-nC3mRb+C|GFON@;gz zt`D7JHEn6Pv(<;5r<<8ObN~Ny&pG#=KlEC!^;)lW)kI{dh|KllU#bVd<^hiZlYqN` zT%gLf?b{s3c~Wcr?*E2>2X;PC2+UAQcpHIfCk_xt@T@4>ngt!U2DAxI4dGAr-Oh4yyJO4ZKk!J3WP;uh2I1+wAL>I`$S|{ znyc>3zy=Y?_me=2hn524Mday@=7l2h;!q@hFchgbp-9|>-mf>__+Tg!e;6P$rcHZE z4UAJtofMINz7p^j7zDf_BGLx%iJ?gRg0k!bKoNKtjlmliDJ!r&6p7ayTlnh)m8^B! zS>uQ?rlm6ZOTe={rBt-_Hishd*OZ;HAGqGQPP-o(HwiG{VsJ?L`SCCP*->Qz4v5HS zzMKR9`uS-QnFE{vGDDI0W@QH!8Yj{D#B-CJEVwr~cvIt!>LpooMC1#KtkJIP zUI5B~rM?sJNbD4O=)GBwEID~7kd<#I8{7A~Qn*+crLc@kPGg*b=Z=tFQ$=?5x3if& z?+@H7XTjP6%yV70N^9M62CYa7fo2k)uAZ{&?d8Kx?Bkc=lL)v;45}HhvRZ*XN_NdL zvT6=vH=H5Se6m=N+>Hfw6nnke?OgLT(|s z)kn#^e4d=s-w>#+pegqrE)OZ9dc-v9#+H(_nwPU(2jwMs7G_u|62C7N4({(!0>E7>=2F86dgMqv5EgWtyv_ZJeu>v=cVH$3qBkTMsSQ#aOA`&Xr~NOBIBbGJK`_`gMQk=?f-j|BGq7X&sb%kI9aU~R9Ia4_l^Q{V0QcajaUaBxqLRUuD# zD8+3oZm8JWuX5buse`vOxb@JM-|n3Gz}o1p^4HU~0|^n?E+W+;;&@HGiEMm{RB{CsFq0#&DsrbDnGKitg7Almm@g>%ncktXUjbqoz+-hEjbx4?Y9qBwmSy zgU@@9hAE}$MC2x|^(kL>Kx;hwF+0yS;JJ&!)ahy=ph)l_!8vBJvE-*-Goz|YsLXx&(>23VxE)*@nnY^`-e7Z1ew zz%9Vc_N4f9_5;I#a~_aufo+lE7x1^b-6F;B0oFyM(O61%sqR)Lpi(KNqS2`Go@D~J z1B-$Cl~Q9oDZUIi4(#?*AXYxaZHaB$I~~XQ(XySL zrl4^F{@?~oGzcVWqQO8A7A7j_Lfs02nrNi+wNfbR0-_O1QY0u;Bmo3k3T+1pZD;1* z@E|Y*Xb1!X zV_esLSyex|V*pZS7Xzb#xyG0xYi$Ei18h^(UsBH*zy;cYDXMzpmIH{$2w*s{1gJ=v zZ3Q+1dx2f5df_JThJnk#AY)9MwRWJYw)e(Iu$yKxfpNf#z(Qk8v9&e^Yyo}*-i=11 zCwlsERgIBcidfev7W zs(z@d4SrvARb37=ipctO0Jy-%slT5#sp>4?B@ubWZ-5L{T?6bEk?+zRbxVQwMWn<} zfUAFK88BHyR;F#iS}fhdlQ*} zJt9)=OCGpk@zWx*05}R{_R^yTylX=BMBqu_dB<_ad7jq-d=4y2#}1&XmB6PWa z#&XAT91*!BB8|Y-6x7q6=NVNkP}LPchVMIY4aBP=QY#|sRdsa_3c0_4t)Ay~s%my$ zk6W;hXLXi{G>b?R@HOxpFbbG=Lm{{JY9W^b3ju5WxGzB;F5J*aBx0?#Zv)$aoxnTM zX!NAtM>j110M=T7yHwS?)yVf<31b{A+ zjJ;Mddvb*h4t~fTSW0`zQo6DR(>C}X+QWlzO#sJwIQB}QYR}3gcjD@@3bSE*<<7bV zw+uka=M)+D(jt?LO|?$=iR7^9xu#`5%~RHGXe}tc&aAN(gT+`&&?c{2C*Zjp88w%2 zmzyghCTnH1X78qS0VE=G9^M9lX!6K)c$+w1v^ zv6ep`d6RKFUQj8Yf45dupYSz|YE_No9(wOY;rXKktVh6e|26NI;=vHdrhkM@Tm{5^0|1Ct z*H#8v>Nbr%xQ(Qfo+R#AkNQX7!p$lD0IcIj7`+~%X78#Buf23+{^;`K#Z&rLhGVeC z;28(UdSrK9q@ejQ1KN&ZLxbS*-$&Dui7R*2O9d;BcRs$ktS@jff3i-vE?TwoWUnGvpDN7qxn*SJ9wOdKedHTlCqRaqAg801 zg2n@cTaFNF+Joslc?$T_1`Cecz<`~84v704)I}r`AYR#7IGxbhpRrwMP8#bTvcdda xHW02($28klfE2_tzyY8^Rq0p1`qi&)>mM%IDJw43e1iZ0002ovPDHLkV1oE-h|mB4 diff --git a/files/opencs/selection-mode-cube.svg b/files/opencs/selection-mode-cube.svg new file mode 100644 index 0000000000..e03138bafe --- /dev/null +++ b/files/opencs/selection-mode-cube.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-sphere.svg b/files/opencs/selection-mode-sphere.svg new file mode 100644 index 0000000000..9f8ed79340 --- /dev/null +++ b/files/opencs/selection-mode-sphere.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/skill.png b/files/opencs/skill.png deleted file mode 100644 index 0ef7cb1cb7eb771b6ffc154a04239e908d7902d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmV-E0m1%>P){+JVsf%67$h%QQlms+#U}%j z#7H7U3cxh#Ad=t%R_J4es#gSu1{@atS6qNBL>0i-0pr{Ey*(b1U#Q5DH5HG&MVx^} z3P#YJuE3M#$E2A_igwhONPx|Ypbe9Z?~7=8`cu7t6hN-EzRu@)JY!V-0AP@yMw+z3 Q1poj507*qoM6N<$g3nBYc>n+a diff --git a/files/opencs/skill.svg b/files/opencs/skill.svg new file mode 100644 index 0000000000..21ae4d12c8 --- /dev/null +++ b/files/opencs/skill.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound-generator.png b/files/opencs/sound-generator.png deleted file mode 100644 index 79833df9c36be3f7693f32e60ae3bf39bbd9ff21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE^nP)H1=6R;S0V^yyQTqL8bXP7v>qmG4(T=OJSw8W(~k3Bxd5 z38d+2E^Vw;>L+4}xFXJLXHY#X$kxF870sdu>JMtCX3*;R1Mn{cwz$q=zR8z*p87L2 z&_OD+1&E^=KaSz{Gk%YJ?c(CFpW-Gk7!xkoFcYQ;tVb~d*E`USUC_IhlFbj|2~m3o zRSn#YLRjY+@Ru#P-Kt4$0JcHBf<{|tzry5 Xbk3C9O!f{e00000NkvXXu0mjfLg2Qp diff --git a/files/opencs/sound-generator.svg b/files/opencs/sound-generator.svg new file mode 100644 index 0000000000..4e27f19f41 --- /dev/null +++ b/files/opencs/sound-generator.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound.png b/files/opencs/sound.png deleted file mode 100644 index 86871611f5deae184cf6b986797020563c5c3ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;i2$ zi(`m|;M7Thd<_ab&YYdcn@< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/spell.png b/files/opencs/spell.png deleted file mode 100644 index 5890ea751b40483621bb334e57cd1bfabb8c155b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 319 zcmV-F0l@x=P)3$eLy(<*LW8GyUB_QiON@i7d;v&Ld(k^d!u2Gx@6y_deauH(Q;0>#iS z21qdF)xD~^oT%n66@X|Um%DN8`(B<3E!@aL?DWCL&uR;-l=q_S27NdeaHRKnBH6}; zbHf33(<&k^73jdwDDmq$D+X;b{lBa-o@t~WeJ*002ovPDHLkV1f|fiGTnA diff --git a/files/opencs/spell.svg b/files/opencs/spell.svg new file mode 100644 index 0000000000..e726d85a7e --- /dev/null +++ b/files/opencs/spell.svg @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/start-script.png b/files/opencs/start-script.png deleted file mode 100644 index 73ed157b9ed2c2366388723f8e5b25fff4bc3b97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359 zcmV-t0hs=YP)Q%iD#FbuV^VUy_zGW14pjAq*dWSMD~X{RfgUV_h! zasz4tfSa9O0ryxh=a?XB zf=o_JY^ti7sR{sWwoTKF2s#(|144HJfK!Jo%YqE1X-W|M{1Zd~{5ylcrur;`3~-I8 zuIr0ees$m;`MIkGG46U-GxZ99s|LS>;vs3-Lt1pt{7g}-u;fDPya%O?-LK1jXrETj zY5{PM3os7K82hm0O4G9UrzV_I}Un^?hcdO-#kvH8dF z#R=Wh@(Uh6k0j)d-_HyP1Xtj^h`ff5tb+0Uij%io@C{l + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/static.png b/files/opencs/static.png deleted file mode 100644 index 9f458e5d04a64ca5f077f3718b92f0fabbbbc857..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_W6< z#WBP}@a-f+u?7Vm=jeCr-|cx{$S>NnV)Blgt<$GWVL5T_R>w*s<6{<@22bxanl89j zwW_Q!xy_<|+l7kYlOk`X1TFe5x9Lrw1&d$x5>D3%+}k6 z&#_5$VsgdvYi>W{A66Z%dwhQ%)8(Hsf8Ot&`Y$-2`QiK?t)5lqGJ&pP@O1TaS?83{ F1OVZBQ%e8< diff --git a/files/opencs/static.svg b/files/opencs/static.svg new file mode 100644 index 0000000000..8e8ce541ce --- /dev/null +++ b/files/opencs/static.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/stop-openmw.png b/files/opencs/stop-openmw.png deleted file mode 100644 index d8f80967244e7a3fcb143d5617a0287666c7ca81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zW$)?Y7-Hd{{OA9FduAR3Bcnq%TO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-move.png b/files/opencs/transform-move.png deleted file mode 100644 index 1e5bd573d3bc6f57af8f81aea55e2d0ac8d18c78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmV-#1CacQP)e5n9XVwK@i8MXIBgo4N5K|*(VT0f+7g!0R;5{Ja{mA z@dNY{@BzH*NhA0SUc4$ILayQq=!yqXK_dnsyX~*$qqmZs&Q6@}6?F!Nt*NR0*S|id zyOJbnuQmg12HFh#vl&=;us$bu%mCAv;9x)Dei_q8v;fHbMbqgnnY4Qp`C6Y~~AnLIB#dksQ2* z92;Xs*h&mZUVM$5IM08V&<60Q>-GOtfL&yUdt%`74V$!+-*OVyxWL_8!|eA>aB!#h zXhWS)ek^S3u~rbh)eXzK8%i}%tnCEg=VWxr+M(OiC!+7t03VhUJF&yf*DPIR%iWv= zF);u@FVP2&-N~8{X3Pc^eIfh(aL~c=P{?4D`}gTJJ2|z*js(d%X+ci(n_%73X4_?N z01%Kqf5u3BhU0(xy6R_ozQ4RL!(4dy-MUf2r>{nF+M*h7P@E)?4fjF`JAI6Yu$$F0 znOZj;F;DMToE`8v?D}31@h9FbQi&Dnc{dNFC^IjrI!H=iUfCp3KKk^fF~2q&{J_sz ziG?VrytL4LWi}?3F2-JH1wqX7{FIWO0JYXf%o>Ap7;U|Lda>f&03RtIvHTf&f%Tua zu5S2*gO%Ruk1}E#Kq_C#d=u#kx(l%t_Be>% zauAIMGa)V~u^hD~HB4wn*eXASiYI6bLF^smFEao_SY}0LZkAz{}D~GShS2LiaHk>uCYe zO0T%1{8ze+AxlsB5aIVcm_c{C2pn6;HUn)2+6*+vz%SBJ5jawBdHVnW002ovPDHLk FV1fq_mMQ=M diff --git a/files/opencs/transform-move.svg b/files/opencs/transform-move.svg new file mode 100644 index 0000000000..1b9490f001 --- /dev/null +++ b/files/opencs/transform-move.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-rotate.png b/files/opencs/transform-rotate.png deleted file mode 100644 index b6c6bc58a78f2da3deedea488870a5c481cdb758..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1391 zcmV-#1(5oQP)e5m`jKpMHt7cW+p4XgBVakA|Zr5D2dT1DyY$eN)AR8 z6x3)`FbYO67d0y4E9(Pq@sI@rV!(qaXb=Q1iifzMlA!olA31MuX^<;)s{8_Z3Nl~v=R6}M?eqb{IXqttU*vo6t#F?^T&pvT7|IV~{7$8<_+l_lhfagmK&zp3O6zsh zUqgk0@jG;P(H!*zG*%cKqBE2Oq6a6oDic>nQIiZ~CTpU6hl%FMSLYGk=^n$07l*Jr z7wP4#1`U*o<6mJ!4<`mBF-(!6r^*A!EhhR)tpg&bt@_#Wye9p|8Ni4OQE;3kGG1l8 zD9898i2k7pLvA*4l(5MX37lfyL^coX6S8(_-N*qHoC5Ht5Fs{|Iwbm(?UjQ{U8*~& z%0&M9Y#!LRBl}OPBV&8(Mh<}XU2@ozaAh+m8ydgtl6t!|!*+@GU1Vn^o&C_m^na59 zjJ}Oi?R67d*>8mxTIA>pDkymwBe&Hon_B?6M0L6ksW{q&VfO`rG6~$ri2Sqp(vl3u z(bHf&8G;ey07_)@&Dza3hzy5p~j0qXa?lyaGVt} zvkyg=3CO60Ond~!o8awiisiir0Kbv6cH9g|#8^)EH&s?QAH%OKkD*tN>m!WKl*in@ z*8wJPa*Y>ZjQ<|s11`|0Z48!Y#e>kjY=l*IFGO>@>EH8T*dY#_QvgVw39l7+3)9(5 zW?$l%B|he7If*NaMSM7nxnEl}~1$!kY2PWUINOcJ+Mda;n;12U4NEe1SRghD`U) zCvqDc=1@&AMTLcgZ-&ZfQ<+Q4ZuF7s=Qm&mM#f=i&^7hrOSX x`4dZw{sg1nqHaqYfi?nd1lkC+5g0iF{{kJs-=Z!HNr(Ud002ovPDHLkV1lUTiDdu) diff --git a/files/opencs/transform-rotate.svg b/files/opencs/transform-rotate.svg new file mode 100644 index 0000000000..29bc3fdad5 --- /dev/null +++ b/files/opencs/transform-rotate.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-scale.png b/files/opencs/transform-scale.png deleted file mode 100644 index c641259bd731fe3203d2404fa98a4203793e2d00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 436 zcmV;l0ZaagP)e5SX&Z;Fbr(bUyk7Q^h{n)kKi}d1f>&ZfG)3sI&E4M{@O1hSw2yaHDd6n$M4k88P^9I{+1@$z9x{Ur(%KoHO( z=VqWg7?rIF3P6!&1K)`R`+yXPrx18DV6NA(f)r?u(66k>$*>MIY!*m?b`xj63xF7) z0J3gk0JJrMmJm0wrEKLoqyRVZ%BbSw>-_}m%j06+kCGaNe0LO e86X4yz`z^kUV6rk&j!T+0000 + + + + + + + + + + + + + + + + diff --git a/files/opencs/weapon.png b/files/opencs/weapon.png deleted file mode 100644 index e2c1d3dc164fe4da0dc6bb9ea6ae6bb6cc6d2411..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmV;50d@X~P)adGKj;`d+DAT@201E=Rt?)kr*leX6P{U2k@J$V@f z!JJwsJh}(ukQ@VR7jn}qz!iDJ(~qcgAQQ5L&J|W&82Xe~ARY8E`hl$GS;+xt7UYA{ zz%)gd(POff1z*K5~FCU6u!Fw7_|An$o5`3DfF2qalS#zpY- zUGxyp%5^w9Q-A6N3($&5>YP7$p4SY+aAPe%pe0DfTKlQ{Q55MLd(H&>ft;cr0UTn! z#@y3c>O0WEQtV-z#AY3JnQTWQFF^knjeP+`QZWOvnF*ljoEO%LcfO+@VE%RpW2Ol% ozL8phl-XIoP8VEnA>PQ_Uo!rSNh?8@vH$=807*qoM6N<$g2$kv7XSbN diff --git a/files/opencs/weapon.svg b/files/opencs/weapon.svg new file mode 100644 index 0000000000..6168812305 --- /dev/null +++ b/files/opencs/weapon.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c2f63259d8147db4927eee133812ef30a049a410 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Sun, 14 Apr 2024 10:47:05 +0200 Subject: [PATCH 1326/2167] Add missing developer tag to appdata --- files/openmw.appdata.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index 764082df00..d20d0740a8 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -21,6 +21,9 @@ Copyright 2020 Bret Curtis

org.openmw.launcher.desktop + + OpenMW Contributors + #dcccb8 From e0a016749c7c153d0bf123e3db9ee39d6f40a3d3 Mon Sep 17 00:00:00 2001 From: Martin Otto Date: Mon, 15 Apr 2024 06:47:48 +0000 Subject: [PATCH 1327/2167] Update/add German translation files --- files/data-mw/l10n/Calendar/de.yaml | 31 +++- files/data/l10n/Calendar/de.yaml | 49 ++++++ files/data/l10n/Interface/de.yaml | 54 +++--- files/data/l10n/OMWCamera/de.yaml | 74 ++++---- files/data/l10n/OMWControls/de.yaml | 86 ++++++++++ files/data/l10n/OMWEngine/de.yaml | 257 ++++++++++++++-------------- files/data/l10n/OMWShaders/de.yaml | 67 ++++++-- 7 files changed, 408 insertions(+), 210 deletions(-) create mode 100644 files/data/l10n/Calendar/de.yaml create mode 100644 files/data/l10n/OMWControls/de.yaml diff --git a/files/data-mw/l10n/Calendar/de.yaml b/files/data-mw/l10n/Calendar/de.yaml index b3ee621402..3c6b672dd2 100644 --- a/files/data-mw/l10n/Calendar/de.yaml +++ b/files/data-mw/l10n/Calendar/de.yaml @@ -1,4 +1,5 @@ -# source: https://en.uesp.net/wiki/Lore:Calendar +# Source for month and weekday names: German Morrowind GOTY version +# For reference: https://www.elderscrollsportal.de/almanach/Tamriel-Almanach:%C3%9Cbersetzungskompendium month1: "Morgenstern" month2: "Morgenröte" @@ -13,8 +14,26 @@ month10: "Eisherbst" month11: "Abenddämmerung" month12: "Abendstern" -# The variant of month names in the context "day X of month Y". -# In English it is the same, but some languages require a different form. +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +# Let me add the names for option (1) in case we want to switch in the future: +# +# monthInGenitive1: "des Morgensterns" +# monthInGenitive2: "der Morgenröte" +# monthInGenitive3: "der Erstsaat" +# monthInGenitive4: "der Regenhand" +# monthInGenitive5: "der Zweitsaat" +# monthInGenitive6: "des Mittjahres" +# monthInGenitive7: "der Sonnenhöhe" +# monthInGenitive8: "der Herbstsaat" +# monthInGenitive9: "des Herdfeuers" +# monthInGenitive10: "des Eisherbstes" +# monthInGenitive11: "der Abenddämmerung" +# monthInGenitive12: "des Abendsterns" monthInGenitive1: "Morgenstern" monthInGenitive2: "Morgenröte" monthInGenitive3: "Erstsaat" @@ -28,7 +47,9 @@ monthInGenitive10: "Eisherbst" monthInGenitive11: "Abenddämmerung" monthInGenitive12: "Abendstern" -dateFormat: "tag {day} im {monthInGenitive} {year, number, :: group-off}" +# Standard German date format: d. MMMM YYYY +# Modified example for TES lore: "16. Herbstsaat 3E 427" +dateFormat: "{day}. {month} {year, number, :: group-off}" weekday1: "Sundas" weekday2: "Morndas" @@ -36,4 +57,4 @@ weekday3: "Tirdas" weekday4: "Middas" weekday5: "Turdas" weekday6: "Fredas" -weekday7: "Loredas" +weekday7: "Loredas" \ No newline at end of file diff --git a/files/data/l10n/Calendar/de.yaml b/files/data/l10n/Calendar/de.yaml new file mode 100644 index 0000000000..c02765c5c2 --- /dev/null +++ b/files/data/l10n/Calendar/de.yaml @@ -0,0 +1,49 @@ +month1: "Januar" +month2: "Februar" +month3: "März" +month4: "April" +month5: "Mai" +month6: "Juni" +month7: "Juli" +month8: "August" +month9: "September" +month10: "Oktober" +month11: "November" +month12: "Dezember" + +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +monthInGenitive1: "Januar" +monthInGenitive2: "Februar" +monthInGenitive3: "März" +monthInGenitive4: "April" +monthInGenitive5: "Mai" +monthInGenitive6: "Juni" +monthInGenitive7: "Juli" +monthInGenitive8: "August" +monthInGenitive9: "September" +monthInGenitive10: "Oktober" +monthInGenitive11: "November" +monthInGenitive12: "Dezember" + +# Standard German date format: d. MMMM YYYY +# Example: "23. Februar 1337" +dateFormat: "{day}. {month} {year, number, :: group-off}" + +weekday1: "Sonntag" +weekday2: "Montag" +weekday3: "Dienstag" +weekday4: "Mittwoch" +weekday5: "Donnerstag" +weekday6: "Freitag" +weekday7: "Samstag" + +# In German, there are usually no "a.m."/"p.m." shenanigans going on. +# In case of ambiguity, "vormittags" ("mornings") and "nachmittags" ("in the afternoon") are used. +am: "vormittags" +pm: "nachmittags" +day: "Tag" diff --git a/files/data/l10n/Interface/de.yaml b/files/data/l10n/Interface/de.yaml index ac1a95a0ea..8457797422 100644 --- a/files/data/l10n/Interface/de.yaml +++ b/files/data/l10n/Interface/de.yaml @@ -1,28 +1,34 @@ +DurationDay: "{days} d " +DurationHour: "{hours} h " +DurationMinute: "{minutes} min " +# There is no abbreviation for "Monat" ("month") in German, so full terms are used instead. +DurationMonth: |- + {months, plural, + one{{months} Monat } + other{{months} Monate } + } +DurationSecond: "{seconds} s " +# In German, "J."/"Jr." exist as abbreviations for "Jahr" ("year") but are seldomly used. +# A plural version of these does not exist (at least, to my knowledge). +# +# To avoid confusion, use full terms instead. +DurationYear: |- + {years, plural, + one{{years} Jahr } + other{{years} Jahre } + } No: "Nein" NotAvailableShort: "N/A" Reset: "Zurücksetzen" Yes: "Ja" - -# To be translated: - -#DurationDay: "{days} d " -#DurationHour: "{hours} h " -#DurationMinute: "{minutes} min " -#DurationMonth: |- -# {months, plural, -# one{{months} mo } -# other{{months} mos } -# } -#DurationSecond: "{seconds} s " -#DurationYear: |- -# {years, plural, -# one{{years} yr } -# other{{years} yrs } -# } -#Cancel: "Cancel" -#Close: "Close" -#None: "None" -#OK: "OK" -#Off: "Off" -#On: "On" -#Copy: "Copy" +Cancel: "Abbrechen" +Close: "Schließen" +# This one is a bit tricky since it can be translated to +# "keiner"/"keine"/"keines", "nichts", or "leer" ("empty") depending on context. +# +# Using the "keine" option for now. +None: "Keine" +OK: "OK" +Off: "Aus" +On: "Ein" +Copy: "Kopieren" \ No newline at end of file diff --git a/files/data/l10n/OMWCamera/de.yaml b/files/data/l10n/OMWCamera/de.yaml index 62b50c5862..76ff7bece3 100644 --- a/files/data/l10n/OMWCamera/de.yaml +++ b/files/data/l10n/OMWCamera/de.yaml @@ -1,72 +1,70 @@ -Camera: "OpenMW Kamera" -settingsPageDescription: "OpenMW Kameraeinstellungen" +Camera: "OpenMW: Kamera" +settingsPageDescription: "OpenMW-Kameraeinstellungen" -thirdPersonSettings: "Dritte-Person-Modus" +thirdPersonSettings: "Verfolgerperspektive" -viewOverShoulder: "Blick über die Schulter" +viewOverShoulder: "Über-die-Schulter-Ansicht" viewOverShoulderDescription: | - Steuert den Dritte-Person-Ansichtsmodus. - Nein: Die Ansicht ist auf den Kopf vom Spielercharakter zentriert. Fadenkreuz ist ausgeblendet. - Ja: Während die Kamera mit ungezogener Waffe hinter der Schulter des Spielercharakter positioniert ist, ist das Fadenkreuz immer sichtbar. + Beeinflusst die Verfolgerperspektive. + Nein: Die Ansicht ist auf den Kopf des Spielercharakters zentriert; das Fadenkreuz ist ausgeblendet. + Ja: Die Kamera ist bei weggesteckter Waffe hinter der Schulter des Spielercharakters positioniert; das Fadenkreuz ist immer sichtbar. -shoulderOffsetX: "Schulteransicht horizontaler Versatz" +shoulderOffsetX: "Schulteransicht: horizontaler Offset" shoulderOffsetXDescription: > - Horizontaler Versatz der Über-die-Schulter-Ansicht. - Verwenden Sie für die linke Schulter einen negativen Wert. + Horizontaler Offset der „Über-die-Schulter-Ansicht“. + Größere Werte verschieben die Ansicht nach rechts; negative Werte verschieben die Ansicht über die linke Schulter. -shoulderOffsetY: "Schulteransicht vertikaler Versatz" +shoulderOffsetY: "Schulteransicht: vertikaler Offset" shoulderOffsetYDescription: > - Vertikaler Versatz der Über-die-Schulter-Ansicht. + Vertikaler Offset der „Über-die-Schulter-Ansicht“. -autoSwitchShoulder: "Automatischer Schulteransicht wechsel" +autoSwitchShoulder: "Schulter automatisch wechseln" autoSwitchShoulderDescription: > - Bei Hindernissen welche die Kamera in die Nähe des Spielercharakter bringen würde, - hat diese Einstellung den Effekt dass die Kamera automatisch auf Schulteransicht wechselt. + Falls aktiviert, wechselt die Ansicht temporär auf die andere Schulter, solange ein Hindernis die Kamera zu nah an den Spielercharakter bewegen würde. -zoomOutWhenMoveCoef: "Kamera-Zoom bei Bewegung" +zoomOutWhenMoveCoef: "Kamera-Zoomfaktor bei Bewegung" zoomOutWhenMoveCoefDescription: > - Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf sie zu (negativer Wert), während sich der Charakter bewegt. - Funktioniert nur, wenn "Blick über die Schulter" aktiviert ist. Der Wert 0 deaktiviert den Zoom (Standard: 20.0). + Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf ihn zu (negativer Wert), solange sich dieser bewegt. Der Wert 0 deaktiviert den Zoom (Standard: 20,0). + Funktioniert nur, wenn „Über-die-Schulter-Ansicht“ aktiviert ist. previewIfStandStill: "Vorschau bei Stillstand" previewIfStandStillDescription: > - Verhindert dass sich der Spielercharakter in die Kamerarichtung dreht, während er untätig ist und seine Waffe weggesteckt hat. + Falls aktiviert, dreht sich der Spielercharakter nicht in Blickrichtung der Kamera, solange er untätig ist und seine Waffe weggesteckt ist. -deferredPreviewRotation: "Verzögerte Drehung der Vorschau" +deferredPreviewRotation: "Sanfter Übergang aus der Vorschau" deferredPreviewRotationDescription: | - Wenn diese Einstellung aktiv ist, dreht sich die Figur nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung. - Wenn deaktiviert, dreht sich die Kamera und nicht der Spielercharakter. + Falls aktiviert, dreht sich der Spielercharakter nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung der Kamera. + Falls deaktiviert, dreht sich die Kamera ruckartig in Richtung des Spielercharakters. -ignoreNC: "Ignoriere „Keine Kollision“-Variable" +ignoreNC: "Ignoriere „No Collision“-Flag" ignoreNCDescription: > - Verhindert dass die Kamera sich durch Objekte bewegt bei denen das NC-Flag (No Collision) im NIF-Modell aktiviert ist. + Falls aktiviert, bewegt sich die Kamera nicht durch Objekte, deren NC-Flag („No Collision“-Flag) im NIF-Modell aktiviert ist. move360: "360°-Bewegung" move360Description: > - Macht die Bewegungsrichtung unabhängig von der Kamerarichtung, während die Waffe des Spielercharakters nicht gezogen ist. - Zum Beispiel schaut der Spielercharakter in die Kamera, während er rückwärts läuft. + Falls aktiviert, ist die Bewegungsrichtung des Spielercharakters bei weggesteckter Waffe unabhängig von der Kamerarichtung. + Beispiel: Läuft der Spielercharakter in diesem Modus rückwärts, dreht er sich stattdessen mit dem Gesicht zur Kamera. -move360TurnSpeed: "360°-Bewegungs-Drehgeschwindigkeit" -move360TurnSpeedDescription: "Drehgeschwindigkeitsmultiplikator (Standard: 5.0)." +move360TurnSpeed: "Multiplikator für 360°-Bewegung-Drehgeschwindigkeit" +move360TurnSpeedDescription: "Multiplikator für die Drehgeschwindigkeit bei aktivierter „360°-Bewegung“ (Standard: 5,0)." slowViewChange: "Sanfter Ansichtswechsel" -slowViewChangeDescription: "Macht den Übergang von der Ego-Perspektive zur Dritte-Person-Perspektive nicht augenblicklich." +slowViewChangeDescription: "Falls aktiviert, wechselt die Kamera nicht mehr ruckartig von der Ego- in die Verfolgerperspektive." -povAutoSwitch: "Automatischer Wechsel in Ego-Perspektive" -povAutoSwitchDescription: "Wechselt automatisch in die Ego-Perspektive wenn sich direkt hinter dem Spielercharakter ein Hindernis befindet." +povAutoSwitch: "Automatischer Wechsel zur Egoperspektive" +povAutoSwitchDescription: "Falls aktiviert, wechselt die Kamera automatisch in die Egoperspektive, sobald sich sich ein Hindernis direkt hinter dem Spielercharakter befindet." -headBobbingSettings: "Kopfbewegungen in der Ego-Perspektive" +headBobbingSettings: "Kopfwippen in der Egoperspektive" headBobbing_enabled: "Eingeschaltet" -headBobbing_enabledDescription: "" +headBobbing_enabledDescription: "Falls aktiviert, wippt die Kamera beim Laufen in der Egoperspektive auf und ab." -headBobbing_step: "Schrittweite" -headBobbing_stepDescription: "Die Länge jedes Schritts (Standard: 90.0)." +headBobbing_step: "Schrittlänge" +headBobbing_stepDescription: "Definiert die Länge eines Schritts, also die Frequenz des Kopfwippens (Standard: 90,0)." headBobbing_height: "Schritthöhe" -headBobbing_heightDescription: "Die Amplitude der Kopfbewegung (Standard: 3.0)." +headBobbing_heightDescription: "Definiert die Höhe eines Schritts, also die Amplitude des Kopfwippens (Standard: 3,0)." headBobbing_roll: "Maximaler Rollwinkel" -headBobbing_rollDescription: "Der maximale Rollwinkel in Grad (Standard: 0.2)." - +headBobbing_rollDescription: "Der maximale Rollwinkel der Kamera beim Kopfwippen in Grad (Standard: 0,2)." \ No newline at end of file diff --git a/files/data/l10n/OMWControls/de.yaml b/files/data/l10n/OMWControls/de.yaml new file mode 100644 index 0000000000..db757e9165 --- /dev/null +++ b/files/data/l10n/OMWControls/de.yaml @@ -0,0 +1,86 @@ +# Source for most of the setting names: German Morrowind GOTY version + +ControlsPage: "OpenMW-Steuerung" +ControlsPageDescription: "Zusätzliche Einstellungen für Spielereingaben" + +MovementSettings: "Fortbewegung" + +alwaysRun: "Immer rennen" +alwaysRunDescription: | + Falls aktiviert, rennt der Spielercharakter standardmäßig; falls deaktiviert, geht er standardmäßig. + Gedrückthalten der Umschalttaste („Shift“) invertiert das aktuelle Verhalten vorübergehend, Drücken der Feststelltaste („Caps Lock“) schaltet das aktuelle Verhalten um. + +toggleSneak: "Schleichen umschalten" +toggleSneakDescription: | + Falls aktiviert, schaltet die „Schleichen“-Taste den Schleichen-Modus dauerhaft ein bzw. aus; falls deaktiviert, muss die „Schleichen“-Taste gedrückt gehalten werden, um im Schleichen-Modus zu bleiben. + Diese Option ist vor allem für Spieler mit schleichlastigen Spielercharakteren zu empfehlen. + +smoothControllerMovement: "Sanfte Controller-Bewegung" +smoothControllerMovementDescription: | + Aktiviert die sanfte Controller-Steuerung mit den Analog-Sticks. Dies macht den Übergang zwischen gehen und rennen weniger abrupt. + +TogglePOV_name: "Perspektive umschalten" +TogglePOV_description: "Zwischen Ego- und Verfolgerperspektive umschalten. Gedrückt halten, um den Vorschau-Modus zu aktivieren." + +Zoom3rdPerson_name: "Hinein-/Herauszoomen" +Zoom3rdPerson_description: "Bewegt die Kamera in Verfolgerperspektive näher an den Spielercharakter oder weiter von ihm weg." + +MoveForward_name: "Vorwärts" +MoveForward_description: "Kann sich mit „Rückwärts“-Eingabe aufheben." + +MoveBackward_name: "Rückwärts" +MoveBackward_description: "Kann sich mit „Vorwärts“-Eingabe aufheben." + +MoveLeft_name: "Links" +MoveLeft_description: "Kann sich mit „Rechts“-Eingabe aufheben." + +MoveRight_name: "Rechts" +MoveRight_description: "Kann sich mit „Links“-Eingabe aufheben." + +Use_name: "Benutzen" +Use_description: "Abhängig von der Haltung des Spielercharakters mit einer Waffe angreifen, einen Zauber wirken oder ein Objekt verwenden bzw. aktivieren." + +Run_name: "Rennen" +Run_description: "Gedrückt halten, um den Spielercharakter abhängig vom „Immer rennen“-Status gehen bzw. rennen zu lassen." + +AlwaysRun_name: "Immer rennen" +AlwaysRun_description: "„Immer rennen“-Status umschalten." + +Jump_name: "Sprung" +Jump_description: "Springen, solange sich der Spielercharakter auf dem Boden befindet." + +AutoMove_name: "Automatische Vorwärts-Bewegung" +AutoMove_description: "„Automatische Bewegung“-Status umschalten. Falls aktiviert, bewegt sich der Spielercharakter kontinuierlich vorwärts." + +Sneak_name: "Schleichen" +Sneak_description: "Gedrückt halten, um bei ausgeschalteter „Schleichen umschalten“-Option zu schleichen." + +ToggleSneak_name: "Schleichen umschalten" +ToggleSneak_description: "Bei eingeschalteter „Schleichen umschalten“-Option den Schleichen-Modus umschalten." + +ToggleWeapon_name: "Waffe ziehen" +ToggleWeapon_description: "Kampfhaltung einnehmen bzw. verlassen." + +ToggleSpell_name: "Zauber vorbereiten" +ToggleSpell_description: "Zauberhaltung einnehmen bzw. verlassen." + +Inventory_name: "Menü-Modus" +Inventory_description: "Inventar-Modus öffnen bzw. schließen." + +Journal_name: "Tagebuch" +Journal_description: "Tagebuch öffnen bzw. schließen." + +QuickKeysMenu_name: "Kurzmenü" +QuickKeysMenu_description: "Kurzmenü für Schnellauswahl von Gegenständen und Zaubern öffnen bzw. schließen." + +SmoothMoveForward_name: "Sanfte Vorwärts-Bewegung" +SmoothMoveForward_description: "Angepasste Vorwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveBackward_name: "Sanfte Rückwärts-Bewegung" +SmoothMoveBackward_description: "Angepasste Rückwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveLeft_name: "Sanfte Bewegung nach links" +SmoothMoveLeft_description: "Angepasste Bewegung nach links für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveRight_name: "Sanfte Bewegung nach rechts" +SmoothMoveRight_description: "Angepasste Bewegung nach rechts für aktivierte „Sanfte Controller-Bewegung“-Option" \ No newline at end of file diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 142ab44994..3b583bfb21 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -1,114 +1,142 @@ # Console -#-- To be translated -#ConsoleWindow: "Console" +ConsoleWindow: "Kommandokonsole" # Debug window -DebugWindow: "Debug" -LogViewer: "Protokollansicht" +DebugWindow: "Debug-Fenster" +LogViewer: "Log-Ansicht" LuaProfiler: "Lua-Profiler" PhysicsProfiler: "Physik-Profiler" # Messages -BuildingNavigationMesh: "Baue Navigationsgitter" - -#-- To be translated -#AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -#InitializingData: "Initializing Data..." -#LoadingExterior: "Loading Area" -#LoadingFailed: "Failed to load saved game" -#LoadingInterior: "Loading Area" -#LoadingInProgress: "Loading Save Game" -#LoadingRequiresNewVersionError: |- -# This save file was created using a newer version of OpenMW and is thus not supported. -# Please upgrade to the newest OpenMW version to load this file. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. -#NewGameConfirmation: "Do you want to start a new game and lose the current one?" -#QuitGameConfirmation: "Quit the game?" -#SaveGameDenied: "The game cannot be saved right now." -#SavingInProgress: "Saving..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +AskLoadLastSave: "Der aktuellste Spielstand ist '%s'. Soll er geladen werden?" +BuildingNavigationMesh: "Erstelle Navigationsgitter..." +InitializingData: "Initialisiere Daten..." +LoadingExterior: "Lade Bereich..." +LoadingFailed: "Laden des Spielstandes fehlgeschlagen!" +LoadingInterior: "Lade Bereich..." +LoadingInProgress: "Lade Spielstand..." +LoadingRequiresNewVersionError: |- + Der Spielstand wurde mit einer neueren Version von OpenMW erstellt und wird daher nicht unterstützt. + Bitte aktualisieren Sie Ihre OpenMW-Installation, um den Spielstand laden zu können. +LoadingRequiresOldVersionError: |- + Der Spielstand wurde mit einer älteren Version von OpenMW erstellt, deren Speicherformat nicht mehr unterstützt wird. + Laden und speichern Sie den Spielstand mit Version {version}, um das Format anzupassen. +NewGameConfirmation: "Neues Spiel starten und aktuellen Fortschritt verwerfen?" +QuitGameConfirmation: "Spiel verlassen?" +SaveGameDenied: "Das Spiel kann gerade nicht gespeichert werden!" +SavingInProgress: "Speichere Spiel..." +ScreenshotFailed: "Speichern des Screenshots fehlgeschlagen!" +ScreenshotMade: "%s wurde gespeichert." # Save game menu -SelectCharacter: "Charakterauswahl..." +DeleteGame: "Spielstand löschen" +DeleteGameConfirmation: "Möchten Sie den Spielstand wirklich löschen?" +EmptySaveNameError: "Der Spielstand kann ohne Name nicht gespeichert werden!" +LoadGameConfirmation: "Spielstand laden und aktuellen Fortschritt verwerfen?" +MissingContentFilesConfirmation: |- + Die aktuell ausgewählten Spieledateien stimmen nicht mit den im Spielstand verwendeten überein, + was zu Fehlern beim Laden und während des Spielens führen kann. + Möchten Sie wirklich fortfahren? +MissingContentFilesList: |- + {files, plural, + one{\n\nEine fehlende Datei gefunden: } + few{\n\n{files} fehlende Dateien gefunden:\n} + other{\n\n{files} fehlende Dateien gefunden:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nDrücken Sie „Kopieren“, um den Namen in die Zwischenablage zu kopieren.} + few{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + other{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + } +OverwriteGameConfirmation: "Sind Sie sicher, dass Sie den Spielstand überschreiben wollen?" +SelectCharacter: "Charakter auswählen..." TimePlayed: "Spielzeit" -#-- To be translated -#DeleteGame: "Delete Game" -#DeleteGameConfirmation: "Are you sure you want to delete this saved game?" -#EmptySaveNameError: "Game can not be saved without a name!" -#LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -#MissingContentFilesConfirmation: |- -# The currently selected content files do not match the ones used by this save game. -# Errors may occur during load or game play. -# Do you wish to continue? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } -#OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" - # Settings menu ActorsProcessingRange: "Akteur-Verarbeitungsreichweite" Anisotropy: "Anisotropie" +Audio: "Audio" +AudioMaster: "Master-Lautstärke" +AudioVoice: "Stimmen" +AudioEffects: "Effekte" +AudioFootsteps: "Schritte" +AudioMusic: "Musik" CameraSensitivity: "Kameraempfindlichkeit" CameraZoomIn: "Kamera hineinzoomen" CameraZoomOut: "Kamera herauszoomen" -ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart um wirksam zu werden." +ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart, um wirksam zu werden." +ConfirmResetBindings: "Alle Tastenbelegungen zurücksetzen?" +ConfirmResolution: "Neue Auflösung wird sofort angewendet. Fortsetzen?" Controller: "Controller" +Controls: "Steuerung" +DelayLow: "Schnell" +DelayHigh: "Langsam" +DetailLevel: "Detailgrad" +Difficulty: "Schwierigkeitsgrad" +DifficultyEasy: "Leicht" +DifficultyHard: "Schwer" +DistanceHigh: "Weit" +DistanceLow: "Kurz" +EnableController: "Controller aktivieren" FieldOfView: "Sichtfeld" -FrameRateHint: "Hinweis: Drücken Sie F3, um die aktuelle Bildrate\nanzuzeigen." -InvertXAxis: "X-Achse umkehren" +FieldOfViewLow: "Niedrig" +FieldOfViewHigh: "Hoch" +FrameRateHint: "Hinweis: Drücken Sie F3,\num die aktuelle Bildrate anzuzeigen." +GammaCorrection: "Gamma-Korrektur" +GammaDark: "Dunkel" +GammaLight: "Hell" +GmstOverridesL10n: "Texte aus ESM-Dateien haben Vorrang" +InvertXAxis: "X-Achse invertieren" +InvertYAxis: "Y-Achse invertieren" Language: "Sprache" -LanguageNote: "Hinweis: Diese Einstellungen wirkt sich nicht auf Text von ESM-Dateien aus." +LanguageNote: "Hinweis: Diese Einstellungen wirken sich nicht auf Texte aus ESM-Dateien aus." LightingMethod: "Beleuchtungsmethode" LightingMethodLegacy: "Veraltet" LightingMethodShaders: "Shader" -LightingMethodShadersCompatibility: "Shader (Kompatibilität)" -LightingResetToDefaults: "Setzt auf Standardwerte zurück; möchten Sie fortfahren? Änderungen an der Beleuchtungsmethode erfordern einen Neustart." +LightingMethodShadersCompatibility: "Shader (Kompatibilitätsmodus)" +LightingResetToDefaults: "Einstellungen wirklich zurücksetzen? Das Ändern der „Beleuchtungsmethode“ erfordert einen Neustart." Lights: "Beleuchtung" -LightsBoundingSphereMultiplier: "Bounding-Sphere-Multiplikator" -LightsBoundingSphereMultiplierTooltip: "Standard: 1.65\nMultiplikator für Begrenzungskugel.\nHöhere Zahlen ermöglichen einen sanften Abfall, erfordern jedoch eine Erhöhung der Anzahl der maximalen Lichter.\n\nBeeinflusst nicht die Beleuchtung oder Lichtstärke." -LightsFadeStartMultiplier: "Licht Verblassungs-Start-Multiplikator" -LightsFadeStartMultiplierTooltip: "Standard: 0.85\nBruchteil der maximalen Entfernung, bei der die Lichter zu verblassen beginnen.\n\nStellen Sie hier einen niedrigen Wert für langsamere Übergänge oder einen hohen Wert für schnellere Übergänge ein." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsBoundingSphereMultiplier: "Multiplikator für Begrenzungssphäre" +LightsBoundingSphereMultiplierTooltip: "Standard: 1,65\nMultiplikator für Größe der Begrenzungssphäre.\nGrößere Werte ermöglichen einen sanfteren Abfall, erfordern jedoch eine Erhöhung der maximalen Anzahl von Lichtquellen.\n\nBeeinflusst nicht die Beleuchtung oder Lichtintensität." +LightsFadeStartMultiplier: "Multiplikator für Startwert der Lichtabblendung" +LightsFadeStartMultiplierTooltip: "Standard: 0,85\nBruchteil der maximalen Lichtreichweite, bei der Lichtquellen langsam zu verblassen beginnen.\n\nKleinere Werte führen zu einem sanfteren Übergang, der allerdings bereits bei geringerer Entfernung startet; größere Werte machen den Übergang abrupter, betreffen aber nur weiter entfernte Lichtquellen." +LightsLightingMethodTooltip: "Legt die interne Behandlung von Lichtquellen fest.\n\n + „Veraltet“ verwendet immer bis zu 8 Lichtquellen pro Objekt und führt zu Ergebnissen, die denen der Original-Engine am ähnlichsten sind.\n\n + „Shader (Kompatibilitätsmodus)“ entfernt das Maximum von 8 Lichtquellen pro Objekt; Bodenvegetation wird von Lichtquellen beleuchtet und das sanfte Abblenden von Lichtquellen wird aktiviert. Es wird empfohlen, diese Option für ältere Hardware und Maximalwerte für Lichtquellen nahe 8 zu verwenden.\n\n + „Shader“ bietet alle Vorteile von „Shader (Kompatibilitätsmodus)“, nutzt aber einen moderneren Ansatz, der größere Maximalwerte für Lichtquellen bei geringen bis keinen Leistungseinbußen ermöglicht. Funktioniert möglicherweise nicht auf älterer Hardware." LightsMaximumDistance: "Maximale Lichtreichweite" -LightsMaximumDistanceTooltip: "Standard: 8192\nMaximale Entfernung, bei der Lichter erscheinen (gemessen in Einheiten).\n\nSetzen Sie dies auf 0, um eine unbegrenzte Entfernung zu verwenden." -LightsMinimumInteriorBrightness: "Minimale Innenhelligkeit" -LightsMinimumInteriorBrightnessTooltip: "Standard: 0.08\nMinimale Umgebungshelligkeit im Innenraum.\n\nErhöhen Sie dies, wenn Sie das Gefühl haben, dass Innenräume zu dunkel sind." -MaxLights: "Maximale Anzahl an Lichtern" -MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtern pro Objekt.\n\nEine niedrige Zahl in der Nähe des Standardwerts führt zu Lichtsprüngen, ähnlich wie bei klassischer Beleuchtung." +LightsMaximumDistanceTooltip: "Standard: 8192 (1 Zelle)\nMaximale Entfernung, bis zu der Lichtquellen noch dargestellt werden (gemessen in In-Game-Einheiten).\n\nEin Wert von 0 entspricht einer unbegrenzten Reichweite." +LightsMinimumInteriorBrightness: "Minimale Helligkeit in Innenräumen" +LightsMinimumInteriorBrightnessTooltip: "Standard: 0,08\nMinimale Umgebungshelligkeit in Innenräumen.\n\nKann erhöht werden, falls Innenräume (v.a. bei Shader-Beleuchtungsmethoden) zu dunkel dargestellt werden." +MaxLights: "Maximale Anzahl von Lichtquellen pro Objekt" +MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtquellen, die ein Objekt beleuchten können.\n\nKleine Werte können gerade an Orten mit vielen Lichtquellen zum Aufploppen und zum schnellen Wechsel von Lichtern führen, wie es aus der Original-Engine und anderen The-Elder-Scrolls-Titeln bekannt ist." +MenuHelpDelay: "Verzögerung des Hilfe-Menüs" +MenuTransparency: "Menü-Transparenz" MouseAndKeyboard: "Maus/Tastatur" -PostProcessing: "Nachbearbeitung" -PostProcessingTooltip: "Optimiert über Nachbearbeitungs-HUD, siehe Eingabeeinstellungen." +PostProcessing: "Post-Processing" +PostProcessingIsNotEnabled: "Post-Processing ist nicht aktiviert!" +PostProcessingTooltip: "Wird über Post-Processing-HUD konfiguriert (siehe Tastenbelegung)." +Preferences: "Einstellungen" PrimaryLanguage: "Hauptsprache" PrimaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache haben die höchste Priorität." -RainRippleDetail: "Detail der Regenkräuselung" -RainRippleDetailDense: "Dicht" -RainRippleDetailSimple: "Einfach" -RainRippleDetailSparse: "Spärlich" -ReflectionShaderDetail: "Reflexions-Shader-Detail" +QualityHigh: "Hoch" +QualityLow: "Niedrig" +QualityMedium: "Mittel" +RainRippleDetail: "Detailgrad der Regentropfen-Wellen" +RainRippleDetailDense: "Dicht (mit Normal-Maps)" +RainRippleDetailSimple: "Einfach (keine Normal-Maps)" +RainRippleDetailSparse: "Spärlich (mit Normal-Maps)" +RebindAction: "Drücken Sie eine Taste oder einen Knopf, um diese Tastenbelegung zu ändern." +ReflectionShaderDetail: "Detailgrad des Wasserreflexions-Shaders" ReflectionShaderDetailActors: "Akteure" ReflectionShaderDetailGroundcover: "Bodenvegetation" ReflectionShaderDetailObjects: "Objekte" @@ -116,69 +144,40 @@ ReflectionShaderDetailSky: "Himmel" ReflectionShaderDetailTerrain: "Terrain" ReflectionShaderDetailWorld: "Welt" Refraction: "Lichtbrechung" -Screenshot: "Bildschirmfoto" +ResetControls: "Tastenbelegungen zurücksetzen" +Screenshot: "Screenshot" Scripts: "Skripte" +ScriptsDisabled: "Laden Sie einen Spielstand, um auf die Skripteinstellungen zugreifen zu können." SecondaryLanguage: "Sekundäre Sprache" -SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache können verwendet werden, wenn bei der primären Sprachdateien die erforderlichen Zeilen fehlen." -TextureFiltering: "Texturfilterung" +SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache werden als Rückfalloption verwendet, falls in den Dateien der Primärsprache die erforderlichen Zeilen fehlen." +SensitivityHigh: "Hoch" +SensitivityLow: "Niedrig" +SettingsWindow: "Optionen" +Subtitles: "Untertitel" +SunlightScattering: "Sonnenlicht-Streuung" +TestingExteriorCells: "Außenzellen testen" +TestingInteriorCells: "Innenzellen testen" +TextureFiltering: "Texturfilter" TextureFilteringBilinear: "Bilinear" -TextureFilteringDisabled: "Nichts" +TextureFilteringDisabled: "Keiner" TextureFilteringOther: "Sonstiges" TextureFilteringTrilinear: "Trilinear" ToggleHUD: "HUD umschalten" -TogglePostProcessorHUD: "Nachbearbeitungs-HUD umschalten" +TogglePostProcessorHUD: "Post-Processing-HUD umschalten" +TransparencyFull: "Vollständig" +TransparencyNone: "Keine" +Video: "Video" +ViewDistance: "Sichtweite" VSync: "VSync" +VSyncAdaptive: "Adaptiv" Water: "Wasser" -WaterShader: "Wasser Shader" +WaterShader: "Wasser-Shader" WaterShaderTextureQuality: "Texturqualität" -WindowBorder: "Fensterrand" +WindowBorder: "Fensterrahmen darstellen" WindowMode: "Fenstermodus" WindowModeFullscreen: "Vollbild" +WindowModeHint: "Hinweis: Die Option „Fenster in Vollbildgröße“\nverwendet automatisch die native Bildschirmauflösung." WindowModeWindowed: "Fenster" -WindowModeWindowedFullscreen: "Fenster Vollbild" - -#-- To be translated -#Audio: "Audio" -#AudioMaster: "Master" -#AudioVoice: "Voice" -#AudioEffects: "Effects" -#AudioFootsteps: "Footsteps" -#AudioMusic: "Music" -#ConfirmResetBindings: "Reset all controls to the default?" -#ConfirmResolution: "New resolution will be applied immediately. Do you want to continue?" -#Controls: "Controls" -#DelayLow: "Fast" -#DelayHigh: "Slow" -#DetailLevel: "Detail Level" -#Difficulty: "Difficulty" -#DifficultyEasy: "Easy" -#DifficultyHard: "Hard" -#DistanceHigh: "Far" -#DistanceLow: "Near" -#EnableController: "Enable Controller" -#FieldOfViewLow: "Low" -#FieldOfViewHigh: "High" -#GammaCorrection: "Gamma Correction" -#GammaDark: "Dark" -#GammaLight: "Light" -#GmstOverridesL10n: "Strings From ESM Files Have Priority" -#InvertYAxis: "Invert Y Axis" -#MenuHelpDelay: "Menu Help Delay" -#MenuTransparency: "Menu Transparency" -#Preferences: "Prefs" -#PostProcessingIsNotEnabled -#QualityHigh: "High" -#QualityLow: "Low" -#QualityMedium: "Medium" -#RebindAction: "Press a key or button to rebind this control." -#ResetControls: "Reset Controls" -#SensitivityHigh: "High" -#SensitivityLow: "Low" -#SettingsWindow: "Options" -#Subtitles: "Subtitles" -#TestingExteriorCells: "Testing exterior cells" -#TestingInteriorCells: "Testing interior cells" -#TransparencyFull: "Full" -#TransparencyNone: "None" -#Video: "Video" -#ViewDistance: "View Distance" +WindowModeWindowedFullscreen: "Fenster in Vollbildgröße" +# More fitting translations of "wobbly" are welcome +WobblyShores: "Wabbelige Uferlinien" \ No newline at end of file diff --git a/files/data/l10n/OMWShaders/de.yaml b/files/data/l10n/OMWShaders/de.yaml index 7d17199dd9..7ea5eb59e3 100644 --- a/files/data/l10n/OMWShaders/de.yaml +++ b/files/data/l10n/OMWShaders/de.yaml @@ -1,6 +1,6 @@ # Post-processing HUD -Abovewater: "Überwasser" +Abovewater: "Über Wasser" ActiveShaders: "Aktive Shader" Author: "Autor" Description: "Beschreibung" @@ -8,27 +8,66 @@ InactiveShaders: "Inaktive Shader" InExteriors: "Außenbereich" InInteriors: "Innenbereich" KeyboardControls: | - Tastatursteuerung:: + Tastatursteuerung: Shift+Pfeil-Rechts > Aktiviere Shader Shift+Pfeil-Links > Deaktiviere Shader - Shift+Pfeil-Hoch > Verschiebe Shader nach oben - Shift+Pfeil-Runter > Verschiebe Shader nach unten -PostProcessHUD: "Nachbearbeitungs HUD" -ResetShader: "Setze Shader auf Standardzustand zurück" -ShaderLocked: "Verschlossen" -ShaderLockedDescription: "Kann nicht umgeschaltet oder verschoben werden, gesteuert durch externes Lua-Skript" + Shift+Pfeil-Hoch > Verschiebe Shader in Liste nach oben + Shift+Pfeil-Runter > Verschiebe Shader in Liste nach unten +# Better avoid the German translation "Nachbearbeitung" as it might lead to confusion. +PostProcessHUD: "Post-Processing-HUD" +ResetShader: "Shader zurücksetzen" +ShaderLocked: "Gesperrt" +ShaderLockedDescription: "Shader kann nicht umgeschaltet oder verschoben werden, sondern wird durch externes Lua-Skript gesteuert." ShaderResetUniform: "r" -Underwater: "Unterwasser" +Underwater: "Unter Wasser" 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" +# Adjustments +# +AdjustmentsDescription: "Farbanpassungen" +# "Contrast Level" +ContrastLevelDescription: "Kontrast-Wert" ContrastLevelName: "Kontrast" -GammaLevelDescription: "Gamma-Level" +# "Gamma Level" GammaLevelName: "Gamma" +GammaLevelDescription: "Gamma-Wert" + +# Bloom +# +BloomDescription: "Bloom-Shader, der seine Berechnungen bei ungefähr linearem Licht durchführt." +# "Bloom Clamp Level" +BloomClampLevelName: "Oberer Grenzwert" +BloomClampLevelDescription: "Begrenze die Helligkeit einzelner Pixel auf diesen Wert, bevor Bloom in die Szene gemischt wird." +# "Bloom Threshold Level" +BloomThresholdLevelName: "Unterer Schwellenwert" +BloomThresholdLevelDescription: "Ignoriere Pixel, deren Helligkeit geringer als dieser Wert ist, bei der Berechnung der Bloom-Unschärfe. Dies wirkt sich nicht auf den Himmel aus." +# "Gamma Level" +# (see above) +# "Radius Level" +RadiusLevelName: "Radius" +RadiusLevelDescription: "Radius des Effektes" +# "Sky Factor Level" +SkyFactorLevelName: "Himmel-Faktor" +SkyFactorLevelDescription: "Multiplikator für Licht, das direkt vom Himmel kommt." +# "Strength Level" +StrengthLevelName: "Strength" +StrengthLevelDescription: "Stärke des Effektes" + + +# Debug +# +DebugDescription: "Debug-Shader" +# "Display Depth" +DebugHeaderDepth: "Tiefenpuffer (Z-Buffer)" +DisplayDepthName: "Visualisiere den Tiefenpuffer (Z-Puffer)" +# "Depth Factor" +DisplayDepthFactorName: "Faktor für Tiefenfärbung" +DisplayDepthFactorDescription: "Verknüpfungsfaktor zwischen dem Tiefenwert eines Pixels und der ausgegebenen Farbe. Größere Werte führen zu einem helleren Bild." +# "Display Normals" +DebugHeaderNormals: "Normalenvektoren" +DisplayNormalsName: "Visualisiere Normalenvektoren" +NormalsInWorldSpace: "Zeige Normalenvektoren in Spielwelt-Koordinaten" \ No newline at end of file From 58afe1ba23fe7c0fafd4e163fedf161ffd6bf9a1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 19:46:31 +0300 Subject: [PATCH 1328/2167] Support red-green normal maps --- apps/opencs/model/world/data.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + components/resource/imagemanager.cpp | 3 +-- components/shader/shadervisitor.cpp | 20 ++++++++++++++++++++ components/shader/shadervisitor.hpp | 1 + files/shaders/compatibility/bs/default.frag | 3 +++ files/shaders/compatibility/groundcover.frag | 6 +++++- files/shaders/compatibility/objects.frag | 6 +++++- files/shaders/compatibility/terrain.frag | 6 +++++- 9 files changed, 42 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 470ce04131..4198e1b980 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -152,6 +152,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); + // FIXME: this is severely out of date (see #7595) Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dc71e455b2..d5c5321f8e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -446,6 +446,7 @@ namespace MWRender globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; globalDefines["disableNormals"] = "1"; + globalDefines["reconstructNormalZ"] = "0"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index a7d2ef61a1..e7cc9f03e5 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -78,8 +78,7 @@ namespace Resource } break; } - // not bothering with checks for other compression formats right now, we are unlikely to ever use those - // anyway + // not bothering with checks for other compression formats right now default: return true; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7bce9de2a6..3867f1f43e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -184,6 +184,7 @@ namespace Shader , mAdditiveBlending(false) , mDiffuseHeight(false) , mNormalHeight(false) + , mReconstructNormalZ(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) @@ -429,6 +430,7 @@ namespace Shader normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); normalMapTex->setName("normalMap"); + normalMap = normalMapTex; int unit = texAttributes.size(); if (!writableStateSet) @@ -440,6 +442,23 @@ namespace Shader mRequirements.back().mNormalHeight = normalHeight; } } + + if (normalMap != nullptr && normalMap->getImage(0)) + { + // Special handling for red-green normal maps (e.g. BC5 or R8G8). + switch (normalMap->getImage(0)->getPixelFormat()) + { + case GL_RG: + case GL_RG_INTEGER: + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + { + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; + } + } + } + if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); @@ -629,6 +648,7 @@ namespace Shader defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + defineMap["reconstructNormalZ"] = reqs.mReconstructNormalZ ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index a8e79ec995..9ce0819bd3 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -110,6 +110,7 @@ namespace Shader bool mDiffuseHeight; // true if diffuse map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel + bool mReconstructNormalZ; // used for red-green normal maps (e.g. BC5) // -1 == no tangents required int mTexStageRequiringTangents; diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 77131c6a52..d2c8de0b22 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -77,6 +77,9 @@ void main() vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); specularColor *= normalTex.a; #else diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index dfdd6518c3..aab37d465d 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -59,7 +59,11 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 56c7abf27c..eb5b79a0c2 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -167,7 +167,11 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV + offset); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index abc7425eb0..f45f1f024e 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -63,7 +63,11 @@ void main() #endif #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, adjustedUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif From cf6b95ae7cf739c00fe00919bc1959b99deca515 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 23:10:29 +0300 Subject: [PATCH 1329/2167] Document some technical details regarding normal-mapping --- .../modding/texture-modding/texture-basics.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 78ae007704..8bbf018fba 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -25,6 +25,19 @@ Content creators need to know that OpenMW uses the DX format for normal maps, an See the section `Automatic use`_ further down below for detailed information. +The RGB channels of the normal map are used to store XYZ components of tangent space normals and the alpha channel of the normal map may be used to store a height map used for parallax. + +This is different from the setup used in Bethesda games that use the traditional pipeline, which may store specular information in the alpha channel. + +Special pixel formats that only store two color channels exist and are used by Bethesda games that employ a PBR-based pipeline. Compressed red-green formats are optimized for use with normal maps and suffer from far less quality degradation than S3TC-compressed normal maps of equivalent size. + +OpenMW supports the use of such pixel formats. When a red-green normal map is provided, the Z component of the normal will be reconstructed based on XY components it stores. +Naturally, since these formats cannot provide an alpha channel, they do not support parallax. + +Keep in mind, however, that while the necessary hardware support is widespread for compressed red-green formats, it is less ubiquitous than the support for S3TC family of compressed formats. +Should you run into the consequences of this, you might want to convert such textures into an uncompressed red-green format such as R8G8. +Be careful not to try and convert such textures into a full-color format as the previously non-existent blue channel would then be used. + Specular Mapping ################ From 3c0c1717a98951f6e0809911aaef12be3d29d37b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Mar 2024 07:43:33 +0300 Subject: [PATCH 1330/2167] Fix red-green normal map handling for terrain --- apps/opencs/model/world/data.cpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 1 - components/sceneutil/util.cpp | 14 ++++++++++++++ components/sceneutil/util.hpp | 2 ++ components/shader/shadervisitor.cpp | 15 +++++---------- components/terrain/material.cpp | 15 ++++++++++++++- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 4198e1b980..470ce04131 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -152,7 +152,6 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); - // FIXME: this is severely out of date (see #7595) Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d5c5321f8e..dc71e455b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -446,7 +446,6 @@ namespace MWRender globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; globalDefines["disableNormals"] = "1"; - globalDefines["reconstructNormalZ"] = "0"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index ab600de11d..a5629ee092 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -286,4 +286,18 @@ namespace SceneUtil mOperationQueue->add(operation); } + bool isRedGreenPixelFormat(GLenum format) + { + switch (format) + { + case GL_RG: + case GL_RG_INTEGER: + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + return true; + } + + return false; + } + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 29fee09176..0f4b82bbe0 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -112,6 +112,8 @@ namespace SceneUtil protected: osg::ref_ptr mOperationQueue; }; + + bool isRedGreenPixelFormat(GLenum format); } #endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 3867f1f43e..600e35a22a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -445,17 +446,11 @@ namespace Shader if (normalMap != nullptr && normalMap->getImage(0)) { - // Special handling for red-green normal maps (e.g. BC5 or R8G8). - switch (normalMap->getImage(0)->getPixelFormat()) + // Special handling for red-green normal maps (e.g. BC5 or R8G8) + if (SceneUtil::isRedGreenPixelFormat(normalMap->getImage(0)->getPixelFormat())) { - case GL_RG: - case GL_RG_INTEGER: - case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: - case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - { - mRequirements.back().mReconstructNormalZ = true; - mRequirements.back().mNormalHeight = false; - } + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fafe2dcb58..10dbeb9838 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -271,18 +272,30 @@ namespace Terrain stateset->addUniform(UniformCollection::value().mBlendMap); } + bool parallax = it->mNormalMap && it->mParallax; + bool reconstructNormalZ = false; + if (it->mNormalMap) { stateset->setTextureAttributeAndModes(2, it->mNormalMap); stateset->addUniform(UniformCollection::value().mNormalMap); + + // Special handling for red-green normal maps (e.g. BC5 or R8G8). + const osg::Image* image = it->mNormalMap->getImage(0); + if (image && SceneUtil::isRedGreenPixelFormat(image->getPixelFormat())) + { + reconstructNormalZ = true; + parallax = false; + } } Shader::ShaderManager::DefineMap defineMap; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; - defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + defineMap["parallax"] = parallax ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; + defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); From b016f414d5251e2ea7979bba6ad737487f94d920 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 15 Apr 2024 17:06:40 +0200 Subject: [PATCH 1331/2167] Add INFO record unit test --- apps/openmw_test_suite/esm3/testsaveload.cpp | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 2629442563..afffbbb3d6 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -603,6 +604,83 @@ namespace ESM EXPECT_EQ(result.mIcon, record.mIcon); } + TEST_P(Esm3SaveLoadRecordTest, infoShouldNotChange) + { + DialInfo record = { + .mData = { + .mType = ESM::Dialogue::Topic, + .mDisposition = 1, + .mRank = 2, + .mGender = ESM::DialInfo::NA, + .mPCrank = 3, + }, + .mSelects = { + ESM::DialogueCondition{ + .mVariable = {}, + .mValue = 42, + .mIndex = 0, + .mFunction = ESM::DialogueCondition::Function_Level, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + ESM::DialogueCondition{ + .mVariable = generateRandomString(32), + .mValue = 0, + .mIndex = 1, + .mFunction = ESM::DialogueCondition::Function_NotLocal, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + }, + .mId = generateRandomRefId(32), + .mPrev = generateRandomRefId(32), + .mNext = generateRandomRefId(32), + .mActor = generateRandomRefId(32), + .mRace = generateRandomRefId(32), + .mClass = generateRandomRefId(32), + .mFaction = generateRandomRefId(32), + .mPcFaction = generateRandomRefId(32), + .mCell = generateRandomRefId(32), + .mSound = generateRandomString(32), + .mResponse = generateRandomString(32), + .mResultScript = generateRandomString(32), + .mFactionLess = false, + .mQuestStatus = ESM::DialInfo::QS_None, + }; + + DialInfo result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mDisposition, record.mData.mDisposition); + EXPECT_EQ(result.mData.mRank, record.mData.mRank); + EXPECT_EQ(result.mData.mGender, record.mData.mGender); + EXPECT_EQ(result.mData.mPCrank, record.mData.mPCrank); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mPrev, record.mPrev); + EXPECT_EQ(result.mNext, record.mNext); + EXPECT_EQ(result.mActor, record.mActor); + EXPECT_EQ(result.mRace, record.mRace); + EXPECT_EQ(result.mClass, record.mClass); + EXPECT_EQ(result.mFaction, record.mFaction); + EXPECT_EQ(result.mPcFaction, record.mPcFaction); + EXPECT_EQ(result.mCell, record.mCell); + EXPECT_EQ(result.mSound, record.mSound); + EXPECT_EQ(result.mResponse, record.mResponse); + EXPECT_EQ(result.mResultScript, record.mResultScript); + EXPECT_EQ(result.mFactionLess, record.mFactionLess); + EXPECT_EQ(result.mQuestStatus, record.mQuestStatus); + EXPECT_EQ(result.mSelects.size(), record.mSelects.size()); + for (size_t i = 0; i < result.mSelects.size(); ++i) + { + const auto& resultS = result.mSelects[i]; + const auto& recordS = record.mSelects[i]; + EXPECT_EQ(resultS.mVariable, recordS.mVariable); + EXPECT_EQ(resultS.mValue, recordS.mValue); + EXPECT_EQ(resultS.mIndex, recordS.mIndex); + EXPECT_EQ(resultS.mFunction, recordS.mFunction); + EXPECT_EQ(resultS.mComparison, recordS.mComparison); + } + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } From 963035fe47c99d9ba33f0915c6271d40760d7620 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 15 Apr 2024 18:20:57 +0200 Subject: [PATCH 1332/2167] Change wander package column names to match reality --- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/columns.hpp | 16 ++++++++-------- apps/opencs/model/world/refidcollection.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index bdbb8f697c..d4c35c5cec 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -324,7 +324,6 @@ namespace CSMWorld { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, - { ColumnId_Idle1, "Idle 1" }, { ColumnId_Idle2, "Idle 2" }, { ColumnId_Idle3, "Idle 3" }, { ColumnId_Idle4, "Idle 4" }, @@ -332,6 +331,7 @@ namespace CSMWorld { ColumnId_Idle6, "Idle 6" }, { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, + { ColumnId_Idle9, "Idle 9" }, { ColumnId_RegionWeather, "Weather" }, { ColumnId_WeatherName, "Type" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 3c4bff07f6..469c1eee33 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -310,14 +310,14 @@ namespace CSMWorld ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, - ColumnId_Idle1 = 286, - ColumnId_Idle2 = 287, - ColumnId_Idle3 = 288, - ColumnId_Idle4 = 289, - ColumnId_Idle5 = 290, - ColumnId_Idle6 = 291, - ColumnId_Idle7 = 292, - ColumnId_Idle8 = 293, + ColumnId_Idle2 = 286, + ColumnId_Idle3 = 287, + ColumnId_Idle4 = 288, + ColumnId_Idle5 = 289, + ColumnId_Idle6 = 290, + ColumnId_Idle7 = 291, + ColumnId_Idle8 = 292, + ColumnId_Idle9 = 293, ColumnId_RegionWeather = 294, ColumnId_WeatherName = 295, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index c3af3d4673..e0d5799726 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -210,7 +210,6 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); @@ -218,6 +217,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle9, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( From 231ec03ef4bcc4a27820ddbe4cc617751c093b44 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 15 Apr 2024 17:30:11 +0000 Subject: [PATCH 1333/2167] swedish calendar translation --- files/data/l10n/Calendar/sv.yaml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 files/data/l10n/Calendar/sv.yaml diff --git a/files/data/l10n/Calendar/sv.yaml b/files/data/l10n/Calendar/sv.yaml new file mode 100644 index 0000000000..f3ac2b915b --- /dev/null +++ b/files/data/l10n/Calendar/sv.yaml @@ -0,0 +1,50 @@ +# Swedish does not actually upper-case first letter on months and weekday names, so I'm doing them lower case right now. + +month1: "januari" +month2: "februari" +month3: "mars" +month4: "april" +month5: "maj" +month6: "juni" +month7: "juli" +month8: "augusti" +month9: "september" +month10: "oktober" +month11: "november" +month12: "december" + +# There are no different grammatical forms of the months in Swedish + +monthInGenitive1: "januari" +monthInGenitive2: "februari" +monthInGenitive3: "mars" +monthInGenitive4: "april" +monthInGenitive5: "maj" +monthInGenitive6: "juni" +monthInGenitive7: "juli" +monthInGenitive8: "augusti" +monthInGenitive9: "september" +monthInGenitive10: "oktober" +monthInGenitive11: "november" +monthInGenitive12: "december" + +# Standard Swedish date format: d MMMM YYYY +# Source: http://www4.sprakochfolkminnen.se/cgi-bin/srfl/visasvar.py?sok=datum&svar=26089 +# Example: "23 Februari 1337" +dateFormat: "{day} {month} {year, number, :: group-off}" + +# The Swedish week starts with monday actually, but whatever. + +weekday1: "söndag" +weekday2: "måndag" +weekday3: "tisdag" +weekday4: "onsdag" +weekday5: "torsdag" +weekday6: "fredag" +weekday7: "lördag" + +# In Swedish, as with German, we don't use AM/PM but instead a 24h clock. +# But instead of that, we could use "förmiddag" and "eftermiddag", which is basically "morning" and "afternoon" +am: "förmiddag" +pm: "eftermiddag" +day: "dag" From 9448a30caffac31f5372c03accc17e2ea61adb18 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 15 Apr 2024 17:53:17 +0000 Subject: [PATCH 1334/2167] Accidental upper case in a comment --- files/data/l10n/Calendar/sv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/l10n/Calendar/sv.yaml b/files/data/l10n/Calendar/sv.yaml index f3ac2b915b..858e586412 100644 --- a/files/data/l10n/Calendar/sv.yaml +++ b/files/data/l10n/Calendar/sv.yaml @@ -30,7 +30,7 @@ monthInGenitive12: "december" # Standard Swedish date format: d MMMM YYYY # Source: http://www4.sprakochfolkminnen.se/cgi-bin/srfl/visasvar.py?sok=datum&svar=26089 -# Example: "23 Februari 1337" +# Example: "23 februari 1337" dateFormat: "{day} {month} {year, number, :: group-off}" # The Swedish week starts with monday actually, but whatever. From f184d8f390f084a99cd2276d9ea1c6bad5ea4848 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Apr 2024 00:02:12 +0200 Subject: [PATCH 1335/2167] Use RAII for AVIOContext, AVFormatContext, AVCodecContext and AVFrame pointers --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 200 +++++++++++-------------- apps/openmw/mwsound/ffmpeg_decoder.hpp | 35 ++++- 2 files changed, 122 insertions(+), 113 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index a6f3d0336f..9e7a2be3a8 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -1,15 +1,41 @@ #include "ffmpeg_decoder.hpp" -#include - #include +#include #include +#include #include #include namespace MWSound { + void AVIOContextDeleter::operator()(AVIOContext* ptr) const + { + if (ptr->buffer != nullptr) + av_freep(&ptr->buffer); + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&ptr); +#else + av_free(ptr); +#endif + } + + void AVFormatContextDeleter::operator()(AVFormatContext* ptr) const + { + avformat_close_input(&ptr); + } + + void AVCodecContextDeleter::operator()(AVCodecContext* ptr) const + { + avcodec_free_context(&ptr); + } + + void AVFrameDeleter::operator()(AVFrame* ptr) const + { + av_frame_free(&ptr); + } int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size) { @@ -75,7 +101,7 @@ namespace MWSound return false; std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams; - while (av_read_frame(mFormatCtx, &mPacket) >= 0) + while (av_read_frame(mFormatCtx.get(), &mPacket) >= 0) { /* Check if the packet belongs to this stream */ if (stream_idx == mPacket.stream_index) @@ -102,12 +128,12 @@ namespace MWSound do { /* Decode some data, and check for errors */ - int ret = avcodec_receive_frame(mCodecCtx, mFrame); + int ret = avcodec_receive_frame(mCodecCtx.get(), mFrame.get()); if (ret == AVERROR(EAGAIN)) { if (mPacket.size == 0 && !getNextPacket()) return false; - ret = avcodec_send_packet(mCodecCtx, &mPacket); + ret = avcodec_send_packet(mCodecCtx.get(), &mPacket); av_packet_unref(&mPacket); if (ret == 0) continue; @@ -187,137 +213,95 @@ namespace MWSound close(); mDataStream = mResourceMgr->get(fname); - if ((mFormatCtx = avformat_alloc_context()) == nullptr) + AVIOContextPtr ioCtx(avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek)); + if (ioCtx == nullptr) + throw std::runtime_error("Failed to allocate AVIO context"); + + AVFormatContext* formatCtx = avformat_alloc_context(); + if (formatCtx == nullptr) throw std::runtime_error("Failed to allocate context"); - try + formatCtx->pb = ioCtx.get(); + + // avformat_open_input frees user supplied AVFormatContext on failure + if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0) + throw std::runtime_error("Failed to open input"); + + AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr)); + + if (avformat_find_stream_info(formatCtxPtr.get(), nullptr) < 0) + throw std::runtime_error("Failed to find stream info"); + + AVStream** stream = nullptr; + for (size_t j = 0; j < formatCtxPtr->nb_streams; j++) { - mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); - if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) + if (formatCtxPtr->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; - } - avformat_free_context(mFormatCtx); - } - mFormatCtx = nullptr; - throw std::runtime_error("Failed to allocate input stream"); + stream = &formatCtxPtr->streams[j]; + break; } + } - if (avformat_find_stream_info(mFormatCtx, nullptr) < 0) - throw std::runtime_error("Failed to find stream info in " + fname); + if (stream == nullptr) + throw std::runtime_error("No audio streams"); - for (size_t j = 0; j < mFormatCtx->nb_streams; j++) - { - if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - { - mStream = &mFormatCtx->streams[j]; - break; - } - } - if (!mStream) - throw std::runtime_error("No audio streams in " + fname); + const AVCodec* codec = avcodec_find_decoder((*stream)->codecpar->codec_id); + if (codec == nullptr) + throw std::runtime_error("No codec found for id " + std::to_string((*stream)->codecpar->codec_id)); - const AVCodec* codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); - if (!codec) - { - std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id); - throw std::runtime_error(ss); - } + AVCodecContext* codecCtx = avcodec_alloc_context3(codec); + if (codecCtx == nullptr) + throw std::runtime_error("Failed to allocate codec context"); - AVCodecContext* avctx = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(avctx, (*mStream)->codecpar); + avcodec_parameters_to_context(codecCtx, (*stream)->codecpar); // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 - av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); + av_codec_set_pkt_timebase(avctx, (*stream)->time_base); #endif - mCodecCtx = avctx; + AVCodecContextPtr codecCtxPtr(std::exchange(codecCtx, nullptr)); - if (avcodec_open2(mCodecCtx, codec, nullptr) < 0) - throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); + if (avcodec_open2(codecCtxPtr.get(), codec, nullptr) < 0) + throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); - mFrame = av_frame_alloc(); + AVFramePtr frame(av_frame_alloc()); + if (frame == nullptr) + throw std::runtime_error("Failed to allocate frame"); - if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - // FIXME: Check for AL_EXT_FLOAT32 support - // else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) - // mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + // FIXME: Check for AL_EXT_FLOAT32 support + // else if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLT || codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLTP) + // mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; - mOutputChannelLayout = (*mStream)->codecpar->channel_layout; - if (mOutputChannelLayout == 0) - mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); + mOutputChannelLayout = (*stream)->codecpar->channel_layout; + if (mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout(codecCtxPtr->channels); - mCodecCtx->channel_layout = mOutputChannelLayout; - } - catch (...) - { - if (mStream) - avcodec_free_context(&mCodecCtx); - mStream = nullptr; + codecCtxPtr->channel_layout = mOutputChannelLayout; - if (mFormatCtx != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; - - avformat_close_input(&mFormatCtx); - } - } + mIoCtx = std::move(ioCtx); + mFrame = std::move(frame); + mFormatCtx = std::move(formatCtxPtr); + mCodecCtx = std::move(codecCtxPtr); + mStream = stream; } void FFmpeg_Decoder::close() { - if (mStream) - avcodec_free_context(&mCodecCtx); mStream = nullptr; + mCodecCtx.reset(); av_packet_unref(&mPacket); av_freep(&mDataBuf); - av_frame_free(&mFrame); + mFrame.reset(); swr_free(&mSwr); - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - // mFormatCtx->pb->buffer must be freed by hand, - // if not, valgrind will show memleak, see: - // - // https://trac.ffmpeg.org/ticket/1357 - // - if (mFormatCtx->pb->buffer != nullptr) - { - av_freep(&mFormatCtx->pb->buffer); - } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) - avio_context_free(&mFormatCtx->pb); -#else - av_freep(&mFormatCtx->pb); -#endif - } - avformat_close_input(&mFormatCtx); - } - + mFormatCtx.reset(); + mIoCtx.reset(); mDataStream.reset(); } @@ -436,10 +420,7 @@ namespace MWSound FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) : Sound_Decoder(vfs) - , mFormatCtx(nullptr) - , mCodecCtx(nullptr) , mStream(nullptr) - , mFrame(nullptr) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) @@ -470,5 +451,4 @@ namespace MWSound { close(); } - } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 9d15888fcf..ed3297403e 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -32,14 +32,43 @@ extern "C" namespace MWSound { + struct AVIOContextDeleter + { + void operator()(AVIOContext* ptr) const; + }; + + using AVIOContextPtr = std::unique_ptr; + + struct AVFormatContextDeleter + { + void operator()(AVFormatContext* ptr) const; + }; + + using AVFormatContextPtr = std::unique_ptr; + + struct AVCodecContextDeleter + { + void operator()(AVCodecContext* ptr) const; + }; + + using AVCodecContextPtr = std::unique_ptr; + + struct AVFrameDeleter + { + void operator()(AVFrame* ptr) const; + }; + + using AVFramePtr = std::unique_ptr; + class FFmpeg_Decoder final : public Sound_Decoder { - AVFormatContext* mFormatCtx; - AVCodecContext* mCodecCtx; + AVIOContextPtr mIoCtx; + AVFormatContextPtr mFormatCtx; + AVCodecContextPtr mCodecCtx; AVStream** mStream; AVPacket mPacket; - AVFrame* mFrame; + AVFramePtr mFrame; std::size_t mFrameSize; std::size_t mFramePos; From c622cfc2450a9b5fc02280aa9109d8c8951aaf6a Mon Sep 17 00:00:00 2001 From: trav5 Date: Mon, 15 Apr 2024 22:01:41 +0200 Subject: [PATCH 1336/2167] ESM::Dialogue Lua bindings 1 --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/corebindings.cpp | 3 +- apps/openmw/mwlua/dialoguebindings.cpp | 133 +++++++++++++++++++++++++ apps/openmw/mwlua/dialoguebindings.hpp | 12 +++ 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwlua/dialoguebindings.cpp create mode 100644 apps/openmw/mwlua/dialoguebindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index c9bfa87648..c71ad38665 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -62,7 +62,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings - mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings + mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index b212d4d01c..a0ce98eaaf 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -23,6 +23,7 @@ #include "luaevents.hpp" #include "magicbindings.hpp" #include "soundbindings.hpp" +#include "dialoguebindings.hpp" #include "stats.hpp" namespace MWLua @@ -98,7 +99,7 @@ namespace MWLua api["stats"] = initCoreStatsBindings(context); api["factions"] = initCoreFactionBindings(context); - + api["dialogue"] = initCoreDialogueBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp new file mode 100644 index 0000000000..4021b2ba54 --- /dev/null +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -0,0 +1,133 @@ +#include "dialoguebindings.hpp" +#include "context.hpp" +#include "recordstore.hpp" +#include "apps/openmw/mwworld/store.hpp" +#include +#include +#include +#include + +namespace +{ + sol::table prepareJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) + { + const auto dialogueRecordId = mwDialogue.mId.serializeText(); + sol::table result(lua, sol::create); + result["text"] = mwDialogue.mStringId; + + sol::table preparedInfos(lua, sol::create); + unsigned index = 1; + for (const auto& mwDialogueInfo : mwDialogue.mInfo) + { + if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name) + { + result["questName"] = mwDialogueInfo.mResponse; + continue; + } + sol::table infoElement(lua, sol::create); + infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); + infoElement["text"] = mwDialogueInfo.mResponse; + infoElement["questStage"] = mwDialogueInfo.mData.mJournalIndex; + infoElement["questFinished"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); + infoElement["questRestart"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); + preparedInfos[index++] = infoElement; + } + + result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); + return result; + } + + sol::table prepareNonJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) + { + const auto dialogueRecordId = mwDialogue.mId.serializeText(); + sol::table result(lua, sol::create); + result["text"] = mwDialogue.mStringId; + + sol::table preparedInfos(lua, sol::create); + unsigned index = 1; + for (const auto& mwDialogueInfo : mwDialogue.mInfo) + { + sol::table infoElement(lua, sol::create); + infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); + infoElement["text"] = mwDialogueInfo.mResponse; + infoElement["actorId"] = mwDialogueInfo.mActor.serializeText(); + infoElement["actorRace"] = mwDialogueInfo.mRace.serializeText(); + infoElement["actorClass"] = mwDialogueInfo.mClass.serializeText(); + infoElement["actorFaction"] = mwDialogueInfo.mFaction.serializeText(); + if (mwDialogueInfo.mData.mRank != -1) + { + infoElement["actorFactionRank"] = mwDialogueInfo.mData.mRank; + } + infoElement["actorCell"] = mwDialogueInfo.mClass.serializeText(); + infoElement["actorDisposition"] = mwDialogueInfo.mData.mDisposition; + if (mwDialogueInfo.mData.mGender != ESM::DialInfo::Gender::NA) + { + infoElement["actorGender"] = mwDialogueInfo.mData.mGender; + } + infoElement["playerFaction"] = mwDialogueInfo.mPcFaction.serializeText(); + if (mwDialogueInfo.mData.mPCrank != -1) + { + infoElement["playerFactionRank"] = mwDialogueInfo.mData.mPCrank; + } + if (not mwDialogueInfo.mSound.empty()) + { + infoElement["sound"] = Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(mwDialogueInfo.mSound)).value(); + } + //mResultScript TODO + //mSelects TODO + preparedInfos[index++] = infoElement; + } + + result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); + return result; + } +} + +namespace MWLua +{ + + sol::table initCoreDialogueBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table api(lua, sol::create); + + const MWWorld::Store& mwDialogueStore = MWBase::Environment::get().getESMStore()->get(); + + sol::table journalRecordsByQuestId(lua, sol::create); + sol::table topicRecordsByTopicId(lua, sol::create); + sol::table voiceRecordsById(lua, sol::create); + sol::table greetingRecordsById(lua, sol::create); + sol::table persuasionRecordsById(lua, sol::create); + for (const auto& mwDialogue : mwDialogueStore) + { + const auto dialogueRecordId = mwDialogue.mId.serializeText(); + if (mwDialogue.mType == ESM::Dialogue::Type::Journal) + { + journalRecordsByQuestId[dialogueRecordId] = prepareJournalRecord(lua, mwDialogue); + } + else if (mwDialogue.mType == ESM::Dialogue::Type::Topic) + { + topicRecordsByTopicId[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); + } + else if (mwDialogue.mType == ESM::Dialogue::Type::Voice) + { + voiceRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); + } + else if (mwDialogue.mType == ESM::Dialogue::Type::Greeting) + { + greetingRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); + } + else if (mwDialogue.mType == ESM::Dialogue::Type::Persuasion) + { + persuasionRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); + } + } + api["journalRecords"] = LuaUtil::makeStrictReadOnly(journalRecordsByQuestId); + api["topicRecords"] = LuaUtil::makeStrictReadOnly(topicRecordsByTopicId); + api["voiceRecords"] = LuaUtil::makeStrictReadOnly(voiceRecordsById); + api["greetingRecords"] = LuaUtil::makeStrictReadOnly(greetingRecordsById); + api["persuasionRecords"] = LuaUtil::makeStrictReadOnly(persuasionRecordsById); + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/dialoguebindings.hpp b/apps/openmw/mwlua/dialoguebindings.hpp new file mode 100644 index 0000000000..6909418e96 --- /dev/null +++ b/apps/openmw/mwlua/dialoguebindings.hpp @@ -0,0 +1,12 @@ +#ifndef MWLUA_DIALOGUEBINDINGS_H +#define MWLUA_DIALOGUEBINDINGS_H + +#include + +namespace MWLua +{ + struct Context; + sol::table initCoreDialogueBindings(const Context&); +} + +#endif // MWLUA_DIALOGUEBINDINGS_H From 34aec9caf9aee29bccd0243c5eaa488d74d59db2 Mon Sep 17 00:00:00 2001 From: trav5 Date: Mon, 15 Apr 2024 22:10:18 +0200 Subject: [PATCH 1337/2167] ESM::Dialogue Lua bindings 2 --- apps/openmw/mwlua/corebindings.cpp | 2 +- apps/openmw/mwlua/dialoguebindings.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index a0ce98eaaf..ebdb0738ad 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -19,11 +19,11 @@ #include "../mwworld/esmstore.hpp" #include "animationbindings.hpp" +#include "dialoguebindings.hpp" #include "factionbindings.hpp" #include "luaevents.hpp" #include "magicbindings.hpp" #include "soundbindings.hpp" -#include "dialoguebindings.hpp" #include "stats.hpp" namespace MWLua diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index 4021b2ba54..0457ac7bce 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -1,7 +1,7 @@ #include "dialoguebindings.hpp" +#include "apps/openmw/mwworld/store.hpp" #include "context.hpp" #include "recordstore.hpp" -#include "apps/openmw/mwworld/store.hpp" #include #include #include @@ -71,10 +71,11 @@ namespace } if (not mwDialogueInfo.mSound.empty()) { - infoElement["sound"] = Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(mwDialogueInfo.mSound)).value(); + infoElement["sound"] + = Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(mwDialogueInfo.mSound)).value(); } - //mResultScript TODO - //mSelects TODO + // mResultScript TODO + // mSelects TODO preparedInfos[index++] = infoElement; } @@ -91,7 +92,8 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table api(lua, sol::create); - const MWWorld::Store& mwDialogueStore = MWBase::Environment::get().getESMStore()->get(); + const MWWorld::Store& mwDialogueStore + = MWBase::Environment::get().getESMStore()->get(); sol::table journalRecordsByQuestId(lua, sol::create); sol::table topicRecordsByTopicId(lua, sol::create); From 443e341ae763cc906324e2445b52b894be87b610 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 16 Apr 2024 02:52:25 +0300 Subject: [PATCH 1338/2167] Generalize unsized pixel format computation --- components/sceneutil/util.cpp | 117 ++++++++++++++++++++++++++-- components/sceneutil/util.hpp | 4 +- components/shader/shadervisitor.cpp | 10 ++- components/terrain/material.cpp | 13 +++- 4 files changed, 132 insertions(+), 12 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index a5629ee092..21a753df12 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -286,18 +286,125 @@ namespace SceneUtil mOperationQueue->add(operation); } - bool isRedGreenPixelFormat(GLenum format) + GLenum computeUnsizedPixelFormat(GLenum format) { switch (format) { - case GL_RG: - case GL_RG_INTEGER: + // Try compressed formats first, they're more likely to be used + + // Generic + case GL_COMPRESSED_ALPHA_ARB: + return GL_ALPHA; + case GL_COMPRESSED_INTENSITY_ARB: + return GL_INTENSITY; + case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: + return GL_LUMINANCE_ALPHA; + case GL_COMPRESSED_LUMINANCE_ARB: + return GL_LUMINANCE; + case GL_COMPRESSED_RGB_ARB: + return GL_RGB; + case GL_COMPRESSED_RGBA_ARB: + return GL_RGBA; + + // S3TC + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + return GL_RGB; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + return GL_RGBA; + + // RGTC + case GL_COMPRESSED_RED_RGTC1_EXT: + case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: + return GL_RED; case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - return true; + return GL_RG; + + // PVRTC + case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return GL_RGB; + case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return GL_RGBA; + + // ETC + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + return GL_RED; + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + return GL_RG; + case GL_ETC1_RGB8_OES: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + return GL_RGB; + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + return GL_RGBA; + + // ASTC + case GL_COMPRESSED_RGBA_ASTC_4x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x12_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: + return GL_RGBA; + + // Plug in some holes computePixelFormat has, you never know when these could come in handy + case GL_INTENSITY4: + case GL_INTENSITY8: + case GL_INTENSITY12: + case GL_INTENSITY16: + return GL_INTENSITY; + + case GL_LUMINANCE4: + case GL_LUMINANCE8: + case GL_LUMINANCE12: + case GL_LUMINANCE16: + return GL_LUMINANCE; + + case GL_LUMINANCE4_ALPHA4: + case GL_LUMINANCE6_ALPHA2: + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE12_ALPHA4: + case GL_LUMINANCE12_ALPHA12: + case GL_LUMINANCE16_ALPHA16: + return GL_LUMINANCE_ALPHA; } - return false; + return osg::Image::computePixelFormat(format); } } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0f4b82bbe0..b76f46a688 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -113,7 +113,9 @@ namespace SceneUtil osg::ref_ptr mOperationQueue; }; - bool isRedGreenPixelFormat(GLenum format); + // Compute the unsized format equivalent to the given pixel format + // Unlike osg::Image::computePixelFormat, this also covers compressed formats + GLenum computeUnsizedPixelFormat(GLenum format); } #endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 600e35a22a..2676ea3168 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -447,10 +447,14 @@ namespace Shader if (normalMap != nullptr && normalMap->getImage(0)) { // Special handling for red-green normal maps (e.g. BC5 or R8G8) - if (SceneUtil::isRedGreenPixelFormat(normalMap->getImage(0)->getPixelFormat())) + switch (SceneUtil::computeUnsizedPixelFormat(normalMap->getImage(0)->getPixelFormat())) { - mRequirements.back().mReconstructNormalZ = true; - mRequirements.back().mNormalHeight = false; + case GL_RG: + case GL_RG_INTEGER: + { + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; + } } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 10dbeb9838..09d2680acd 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -282,10 +282,17 @@ namespace Terrain // Special handling for red-green normal maps (e.g. BC5 or R8G8). const osg::Image* image = it->mNormalMap->getImage(0); - if (image && SceneUtil::isRedGreenPixelFormat(image->getPixelFormat())) + if (image) { - reconstructNormalZ = true; - parallax = false; + switch (SceneUtil::computeUnsizedPixelFormat(image->getPixelFormat())) + { + case GL_RG: + case GL_RG_INTEGER: + { + reconstructNormalZ = true; + parallax = false; + } + } } } From a7021bf9cc6cfc9111505e95ebb83d2921d2f13e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 01:10:39 +0100 Subject: [PATCH 1339/2167] Clear std stream errors when reopening Prior errors are no longer relevant. Shouldn't make a difference unless you've tried printing something before the streams were set up. --- components/debug/debugging.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index bfde558c85..0699d0912b 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -73,16 +73,19 @@ namespace Debug { _wfreopen(L"CON", L"r", stdin); freopen("CON", "r", stdin); + std::cin.clear(); } if (!outRedirected) { _wfreopen(L"CON", L"w", stdout); freopen("CON", "w", stdout); + std::cout.clear(); } if (!errRedirected) { _wfreopen(L"CON", L"w", stderr); freopen("CON", "w", stderr); + std::cerr.clear(); } return true; From 61364c874f09f61870b66a6414496f741a2030ac Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 01:14:20 +0100 Subject: [PATCH 1340/2167] Warn future me off wasting their time again --- components/debug/debugging.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 0699d0912b..e6caf2b09d 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -61,6 +61,10 @@ namespace Debug bool outRedirected = isRedirected(STD_OUTPUT_HANDLE); bool errRedirected = isRedirected(STD_ERROR_HANDLE); + // Note: Do not spend three days reinvestigating this PowerShell bug thinking its our bug. + // https://gitlab.com/OpenMW/openmw/-/merge_requests/408#note_447467393 + // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them doesn't work. + if (AttachConsole(ATTACH_PARENT_PROCESS)) { fflush(stdout); From d8f19c6e7b8fac335cd42b599bb68cab82b75159 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 16 Apr 2024 03:29:47 +0300 Subject: [PATCH 1341/2167] Changelog (two-channel normal maps, #7932) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeb030bda5..8f2ee0eb63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,6 +225,7 @@ Feature #7875: Disable MyGUI windows snapping Feature #7914: Do not allow to move GUI windows out of screen Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks + Feature #7932: Support two-channel normal maps Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION From 83e3718bed550faefca66080eb66fb227fcd3f0a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 13:14:36 +0100 Subject: [PATCH 1342/2167] . c l a n g - f o r m a t --- components/debug/debugging.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e6caf2b09d..67e7ecaaf3 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -63,7 +63,8 @@ namespace Debug // Note: Do not spend three days reinvestigating this PowerShell bug thinking its our bug. // https://gitlab.com/OpenMW/openmw/-/merge_requests/408#note_447467393 - // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them doesn't work. + // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them + // doesn't work. if (AttachConsole(ATTACH_PARENT_PROCESS)) { From d06e8e2c24878dfecd1e74bca4b5ecc54c33b7ba Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 9 Apr 2024 12:24:19 +0400 Subject: [PATCH 1343/2167] Use Qt6 on Windows by default --- .gitlab-ci.yml | 4 ++-- CI/before_script.msvc.sh | 24 ++++++++++++++++++------ apps/opencs/CMakeLists.txt | 1 + files/windows/openmw-cs.exe.manifest | 9 +++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 files/windows/openmw-cs.exe.manifest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06c7e63cb0..feaccc2540 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -606,7 +606,7 @@ macOS14_Xcode15_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v8 + key: ninja-v9 paths: - ccache - deps @@ -722,7 +722,7 @@ macOS14_Xcode15_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v8 + key: msbuild-v9 paths: - deps - MSVC2019_64/deps/Qt diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 3919507bd1..b501c0162b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -563,7 +563,7 @@ ICU_VER="70_1" LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd" LZ4_VER="1.9.2" OPENAL_VER="1.23.0" -QT_VER="5.15.2" +QT_VER="6.6.2" OSG_ARCHIVE_NAME="OSGoS 3.6.5" OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" @@ -894,7 +894,7 @@ printf "Qt ${QT_VER}... " printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then pushd "$DEPS" > /dev/null - AQT_VERSION="v3.1.7" + AQT_VERSION="v3.1.12" if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then download "aqt ${AQT_VERSION}"\ "https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \ @@ -915,6 +915,9 @@ printf "Qt ${QT_VER}... " echo Done. fi + QT_MAJOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $1}') + QT_MINOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $2}') + cd $QT_SDK for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then @@ -922,13 +925,22 @@ printf "Qt ${QT_VER}... " else DLLSUFFIX="" fi - if [ "${QT_VER:0:1}" -eq "6" ]; then - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll + + if [ "${QT_MAJOR_VER}" -eq 6 ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll + + # Since Qt 6.7.0 plugin is called "qmodernwindowsstyle" + if [ "${QT_MINOR_VER}" -ge 7 ]; then + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll" + else + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + fi else - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" fi + add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" - add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" done echo Done. diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 3353a4586f..d6af64b33b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -257,6 +257,7 @@ endif() if (WIN32) target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY}) + target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest) endif() if (WIN32 AND BUILD_OPENCS) diff --git a/files/windows/openmw-cs.exe.manifest b/files/windows/openmw-cs.exe.manifest new file mode 100644 index 0000000000..3107f64706 --- /dev/null +++ b/files/windows/openmw-cs.exe.manifest @@ -0,0 +1,9 @@ + + + + + true + PerMonitorV2 + + + \ No newline at end of file From 118114c4988380ea7cef1c00878846ad8feb6d79 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 17 Apr 2024 09:04:17 +0400 Subject: [PATCH 1344/2167] Add misisng empty line --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b501c0162b..c2d91ccfd3 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1135,7 +1135,7 @@ fi echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}styles" done - + echo echo "- Qt Image Format DLLs..." mkdir -p ${DLL_PREFIX}imageformats for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do From 2653b76db9753fc4eabe68422e8a09182cedfc84 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:16:48 +0100 Subject: [PATCH 1345/2167] getTranslation/getTransformForNode refactor, unit tests --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../sceneutil/osgacontroller.cpp | 129 ++++++++++++++++++ components/sceneutil/osgacontroller.cpp | 49 +------ 3 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 apps/openmw_test_suite/sceneutil/osgacontroller.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index b91940cd77..db6ecb816a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -100,6 +100,8 @@ file(GLOB UNITTEST_SRC_FILES resource/testobjectcache.cpp vfs/testpathutil.cpp + + sceneutil/osgacontroller.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp new file mode 100644 index 0000000000..2ab6aeb37e --- /dev/null +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace SceneUtil; + + static const std::string ROOT_BONE_NAME = "bip01"; + + // Creates a merged anim track with a single root channel with two start/end matrix transforms + osg::ref_ptr createMergedAnimationTrack(std::string name, osg::Matrixf startTransform, + osg::Matrixf endTransform, float startTime = 0.0f, float endTime = 1.0f) + { + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + mergedAnimationTrack->setName(name); + + osgAnimation::MatrixKeyframeContainer* cbCntr = new osgAnimation::MatrixKeyframeContainer; + cbCntr->push_back(osgAnimation::MatrixKeyframe(startTime, startTransform)); + cbCntr->push_back(osgAnimation::MatrixKeyframe(endTime, endTransform)); + + osg::ref_ptr rootChannel = new osgAnimation::MatrixLinearChannel; + rootChannel->setName("transform"); + rootChannel->setTargetName(ROOT_BONE_NAME); + rootChannel->getOrCreateSampler()->setKeyframeContainer(cbCntr); + mergedAnimationTrack->addChannel(rootChannel); + return mergedAnimationTrack; + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnSampledChannelTranslationForBip01) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + // should be halfway between 0,0,0 and 1,1,1 + osg::Vec3f translation = controller.getTranslation(0.5f); + EXPECT_EQ(translation, osg::Vec3f(0.5f, 0.5f, 0.5f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(100.0f); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNoMergedTracks) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + // Has no merged tracks so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(0.5); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnIdentityIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(100.0f, ROOT_BONE_NAME), osg::Matrixf::identity()); + + // Has no bone animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(0.5f, "wrongbone"), osg::Matrixf::identity()); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnSampledAnimMatrixAtTime) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 + EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 + EXPECT_EQ(controller.getTransformForNode(1.1f, ROOT_BONE_NAME), endTransform); // start of test2 + EXPECT_EQ(controller.getTransformForNode(2.0f, ROOT_BONE_NAME), endTransform2); // end of test2 + } +} diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 5a3ae60293..d3268fef36 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -91,49 +91,6 @@ namespace SceneUtil } } - osg::Vec3f OsgAnimationController::getTranslation(float time) const - { - osg::Vec3f translationValue; - std::string animationName; - float newTime = time; - - // Find the correct animation based on time - for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) - { - if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) - { - newTime = time - emulatedAnimation.mStartTime; - animationName = emulatedAnimation.mName; - } - } - - // Find the root transform track in animation - for (const auto& mergedAnimationTrack : mMergedAnimationTracks) - { - if (mergedAnimationTrack->getName() != animationName) - continue; - - const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); - - for (const auto& channel : channels) - { - if (channel->getTargetName() != "bip01" || channel->getName() != "transform") - continue; - - if (osgAnimation::MatrixLinearSampler* templateSampler - = dynamic_cast(channel->getSampler())) - { - osg::Matrixf matrix; - templateSampler->getValueAt(newTime, matrix); - translationValue = matrix.getTrans(); - return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); - } - } - } - - return osg::Vec3f(); - } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; @@ -146,6 +103,7 @@ namespace SceneUtil { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; + break; } } @@ -175,6 +133,11 @@ namespace SceneUtil return osg::Matrixf::identity(); } + osg::Vec3f OsgAnimationController::getTranslation(float time) const + { + return getTransformForNode(time, "bip01").getTrans(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) From d09f32d9e4362a4d47c2fac4d32e9aef62ac0e15 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:19:49 +0100 Subject: [PATCH 1346/2167] Yes sir clang --- apps/openmw_test_suite/sceneutil/osgacontroller.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp index 2ab6aeb37e..309de4a878 100644 --- a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include #include @@ -36,7 +36,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -46,7 +46,8 @@ namespace endTransform.setTrans(1.0f, 1.0f, 1.0f); controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); // should be halfway between 0,0,0 and 1,1,1 osg::Vec3f translation = controller.getTranslation(0.5f); @@ -109,7 +110,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -119,7 +120,8 @@ namespace controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); osg::Matrixf endTransform2 = osg::Matrixf::identity(); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 From 537964f8d8c89bc5434fe2400699963018b12803 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 11:23:21 +0300 Subject: [PATCH 1347/2167] Reconstruct normal Z *properly* --- files/shaders/compatibility/bs/default.frag | 5 +++-- files/shaders/compatibility/groundcover.frag | 5 +++-- files/shaders/compatibility/objects.frag | 5 +++-- files/shaders/compatibility/terrain.frag | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index d2c8de0b22..3c665752d1 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -77,10 +77,11 @@ void main() vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); specularColor *= normalTex.a; #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index aab37d465d..96a79c8793 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -60,10 +60,11 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index eb5b79a0c2..7f163580cc 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -168,10 +168,11 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV + offset); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index f45f1f024e..9d89217f35 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -64,10 +64,11 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, adjustedUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif From 8cc512cbc9b2d4dd4441e469b9e685f9f599983c Mon Sep 17 00:00:00 2001 From: Benjamin Y Date: Thu, 18 Apr 2024 18:44:51 -0500 Subject: [PATCH 1348/2167] Convention fixes and remove confusing info --- apps/launcher/settingspage.cpp | 4 ++-- apps/launcher/ui/settingspage.ui | 4 ++-- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 2b02d6b0bb..5e617b1459 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -252,7 +252,7 @@ bool Launcher::SettingsPage::loadSettings() lightFadeMultiplierSpinBox->setValue(Settings::shaders().mLightFadeStart); lightsBoundingSphereMultiplierSpinBox->setValue(Settings::shaders().mLightBoundsMultiplier); lightsMinimumInteriorBrightnessSpinBox->setValue(Settings::shaders().mMinimumInteriorBrightness); - loadSettingBool(Settings::shaders().mForcePerPixelLighting, *forcePPLCheckBox); + loadSettingBool(Settings::shaders().mForcePerPixelLighting, *forcePplCheckBox); connect(lightingMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this, &SettingsPage::slotLightTypeCurrentIndexChanged); @@ -471,7 +471,7 @@ void Launcher::SettingsPage::saveSettings() Settings::shaders().mLightFadeStart.set(lightFadeMultiplierSpinBox->value()); Settings::shaders().mLightBoundsMultiplier.set(lightsBoundingSphereMultiplierSpinBox->value()); Settings::shaders().mMinimumInteriorBrightness.set(lightsMinimumInteriorBrightnessSpinBox->value()); - saveSettingBool(*forcePPLCheckBox, Settings::shaders().mForcePerPixelLighting); + saveSettingBool(*forcePplCheckBox, Settings::shaders().mForcePerPixelLighting); } // Audio diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 41fb4d8b7a..5edca39689 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1104,9 +1104,9 @@
- + - <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> Force per-pixel lighting diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index da9c50627c..297a2c944c 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index b80e3801d8..956d38b579 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1448,7 +1448,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 7cc80a287d..215aaec00e 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1463,7 +1463,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Минимальный уровень освещения в помещениях - <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> + <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> From b7aa3b9f47a1c28413bf30a67da7428701344d39 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 19 Apr 2024 07:48:26 +0100 Subject: [PATCH 1349/2167] Remove rename from RenameBonesVisitor, rename to RenameAnimCallbacksVisitor --- components/resource/scenemanager.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4bdd620819..ab3f92f10d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -383,18 +383,16 @@ namespace Resource } } - class RenameBonesVisitor : public osg::NodeVisitor + class RenameAnimCallbacksVisitor : public osg::NodeVisitor { public: - RenameBonesVisitor() + RenameAnimCallbacksVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { } void apply(osg::MatrixTransform& node) override { - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - // osgAnimation update callback name must match bone name/channel targets osg::Callback* cb = node.getUpdateCallback(); while (cb) @@ -687,7 +685,7 @@ namespace Resource { // Collada bones may have underscores in place of spaces due to a collada limitation // we should rename the bones and update callbacks here at load time - Resource::RenameBonesVisitor renameBoneVisitor; + Resource::RenameAnimCallbacksVisitor renameBoneVisitor; node->accept(renameBoneVisitor); if (osg::Group* group = dynamic_cast(node)) From 82931059fd82c3e396f757081466fcbb03c78051 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Apr 2024 22:34:40 +0200 Subject: [PATCH 1350/2167] Make NormalizedView constructor from const char* explicit --- apps/openmw/mwgui/mainmenu.cpp | 5 ++++- apps/openmw/mwgui/windowmanagerimp.cpp | 3 ++- apps/openmw_test_suite/misc/test_resourcehelpers.cpp | 9 ++++++--- apps/openmw_test_suite/vfs/testpathutil.cpp | 2 +- components/resource/scenemanager.cpp | 3 ++- components/vfs/pathutil.hpp | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index be3700342a..53f791fdac 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -37,7 +38,9 @@ namespace MWGui getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); - mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); + constexpr VFS::Path::NormalizedView menuBackgroundVideo("video/menu_background.bik"); + + mHasAnimatedMenu = mVFS->exists(menuBackgroundVideo); updateMenu(); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 61a8b3361c..3212e8f02b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -356,7 +356,8 @@ namespace MWGui mWindows.push_back(std::move(console)); trackWindow(mConsole, makeConsoleWindowSettingValues()); - bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); + constexpr VFS::Path::NormalizedView menubookOptionsOverTexture("textures/tx_menubook_options_over.dds"); + const bool questList = mResourceSystem->getVFS()->exists(menubookOptionsOverTexture); auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding); mGuiModeStates[GM_Journal] = GuiModeState(journal.get()); mWindows.push_back(std::move(journal)); diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp index 5290630394..48edb72578 100644 --- a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -8,19 +8,22 @@ namespace TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/bar.wav", *mVFS), "sound/bar.wav"); + constexpr VFS::Path::NormalizedView path("sound/bar.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } namespace diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 6eb84f97d5..3819f9905a 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -39,7 +39,7 @@ namespace VFS::Path TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) { - const NormalizedView view = "foo/bar/baz"; + const NormalizedView view("foo/bar/baz"); const Normalized value(view); EXPECT_EQ(value.view(), "foo/bar/baz"); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e4d0b4363d..6aeae80af6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -865,7 +865,8 @@ namespace Resource << ", using embedded marker_error instead"; } Files::IMemStream file(ErrorMarker::sValue.data(), ErrorMarker::sValue.size()); - return loadNonNif("error_marker.osgt", file, mImageManager); + constexpr VFS::Path::NormalizedView errorMarker("error_marker.osgt"); + return loadNonNif(errorMarker, file, mImageManager); } osg::ref_ptr SceneManager::cloneErrorMarker() diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 07e73acfa9..1696e656ad 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -79,7 +79,7 @@ namespace VFS::Path public: constexpr NormalizedView() noexcept = default; - constexpr NormalizedView(const char* value) + constexpr explicit NormalizedView(const char* value) : mValue(value) { if (!isNormalized(mValue)) From 38b005cda6a5d7de526a676777707348dc603aff Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Mar 2024 00:25:48 +0100 Subject: [PATCH 1351/2167] Use normalized path to store playlist music files --- apps/openmw/mwsound/soundmanagerimp.cpp | 17 +++++++++++------ apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 56224b4dcb..6ad1359c0d 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -300,13 +300,16 @@ namespace MWSound void SoundManager::startRandomTitle() { - const std::vector& filelist = mMusicFiles[mCurrentPlaylist]; - if (filelist.empty()) + const auto playlist = mMusicFiles.find(mCurrentPlaylist); + + if (playlist == mMusicFiles.end() || playlist->second.empty()) { advanceMusic(std::string()); return; } + const std::vector& filelist = playlist->second; + auto& tracklist = mMusicToPlay[mCurrentPlaylist]; // Do a Fisher-Yates shuffle @@ -359,18 +362,20 @@ namespace MWSound if (mCurrentPlaylist == playlist) return; - if (mMusicFiles.find(playlist) == mMusicFiles.end()) + auto it = mMusicFiles.find(playlist); + + if (it == mMusicFiles.end()) { - std::vector filelist; + std::vector filelist; auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) filelist.push_back(name); - mMusicFiles[playlist] = std::move(filelist); + it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist)); } // No Battle music? Use Explore playlist - if (playlist == "Battle" && mMusicFiles[playlist].empty()) + if (playlist == "Battle" && it->second.empty()) { playPlaylist("Explore"); return; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 75b1193118..bd9743c528 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -52,7 +52,7 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::unordered_map> mMusicFiles; + std::unordered_map> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played From e11a5a43526778468376e9a813c2ba86ca9a2363 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Mar 2024 00:40:03 +0100 Subject: [PATCH 1352/2167] Use normalized path for SoundManager::playPlaylist --- apps/niftest/niftest.cpp | 2 +- apps/opencs/model/world/resources.cpp | 2 +- apps/openmw/mwbase/soundmanager.hpp | 2 +- apps/openmw/mwgui/loadingscreen.cpp | 3 ++- apps/openmw/mwgui/settingswindow.cpp | 3 ++- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +++-- apps/openmw/mwsound/constants.hpp | 12 ++++++++++ apps/openmw/mwsound/soundmanagerimp.cpp | 23 ++++++++++--------- apps/openmw/mwsound/soundmanagerimp.hpp | 8 ++++--- apps/openmw/mwworld/worldimp.cpp | 4 +++- components/misc/resourcehelpers.cpp | 6 +++-- components/misc/resourcehelpers.hpp | 2 +- components/vfs/manager.cpp | 17 ++++++++++++++ components/vfs/manager.hpp | 4 ++++ components/vfs/pathutil.hpp | 22 ++++++++++++++++++ 15 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 apps/openmw/mwsound/constants.hpp diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b37d85d739..5dbe99c356 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -95,7 +95,7 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat vfs.addArchive(std::move(archive)); vfs.buildIndex(); - for (const auto& name : vfs.getRecursiveDirectoryIterator("")) + for (const auto& name : vfs.getRecursiveDirectoryIterator()) { if (isNIF(name.value())) { diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 9957af3d66..7082575c64 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -28,7 +28,7 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e size_t baseSize = mBaseDirectory.size(); - for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator()) { const std::string_view view = filepath.view(); if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 05b925f87d..a55d696224 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -126,7 +126,7 @@ namespace MWBase virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing - virtual void playPlaylist(const std::string& playlist) = 0; + virtual void playPlaylist(VFS::Path::NormalizedView playlist) = 0; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 8ba2bb8312..3a9aba7828 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -67,7 +67,8 @@ namespace MWGui != supported_extensions.end(); }; - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) + constexpr VFS::Path::NormalizedView splash("splash/"); + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(splash)) { if (isSupportedExtension(Misc::getFileExtension(name))) mSplashScreens.push_back(name); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 396d0b18a3..075a338592 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -402,7 +402,8 @@ namespace MWGui std::vector availableLanguages; const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/")) + constexpr VFS::Path::NormalizedView l10n("l10n/"); + for (const auto& path : vfs->getRecursiveDirectoryIterator(l10n)) { if (Misc::getFileExtension(path) == "yaml") { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 47fba46c75..048476c6ef 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -29,6 +29,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actors.hpp" #include "actorutil.hpp" @@ -1678,12 +1680,12 @@ namespace MWMechanics if (mMusicType != MWSound::MusicType::Explore && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && musicPlaying)) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); mMusicType = MWSound::MusicType::Explore; } else if (mMusicType != MWSound::MusicType::Battle && hasHostiles) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::battlePlaylist); mMusicType = MWSound::MusicType::Battle; } } diff --git a/apps/openmw/mwsound/constants.hpp b/apps/openmw/mwsound/constants.hpp new file mode 100644 index 0000000000..5022b142f9 --- /dev/null +++ b/apps/openmw/mwsound/constants.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H +#define OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H + +#include + +namespace MWSound +{ + constexpr VFS::Path::NormalizedView battlePlaylist("battle"); + constexpr VFS::Path::NormalizedView explorePlaylist("explore"); +} + +#endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 6ad1359c0d..4c3f227ecd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -26,14 +26,14 @@ #include "../mwmechanics/actorutil.hpp" +#include "constants.hpp" +#include "ffmpeg_decoder.hpp" +#include "openal_output.hpp" #include "sound.hpp" #include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound_output.hpp" -#include "ffmpeg_decoder.hpp" -#include "openal_output.hpp" - namespace MWSound { namespace @@ -352,12 +352,12 @@ namespace MWSound mechanicsManager->setMusicType(type); advanceMusic(normalizedName, fade); if (type == MWSound::MusicType::Battle) - mCurrentPlaylist = "Battle"; + mCurrentPlaylist = battlePlaylist; else if (type == MWSound::MusicType::Explore) - mCurrentPlaylist = "Explore"; + mCurrentPlaylist = explorePlaylist; } - void SoundManager::playPlaylist(const std::string& playlist) + void SoundManager::playPlaylist(VFS::Path::NormalizedView playlist) { if (mCurrentPlaylist == playlist) return; @@ -367,17 +367,18 @@ namespace MWSound if (it == mMusicFiles.end()) { std::vector filelist; - auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; - for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) + constexpr VFS::Path::NormalizedView music("music"); + const VFS::Path::Normalized playlistPath = music / playlist / VFS::Path::NormalizedView(); + for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath))) filelist.push_back(name); it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist)); } // No Battle music? Use Explore playlist - if (playlist == "Battle" && it->second.empty()) + if (playlist == battlePlaylist && it->second.empty()) { - playPlaylist("Explore"); + playPlaylist(explorePlaylist); return; } @@ -1024,7 +1025,7 @@ namespace MWSound mTimePassed = 0.0f; // Make sure music is still playing - if (!isMusicPlaying() && !mCurrentPlaylist.empty()) + if (!isMusicPlaying() && !mCurrentPlaylist.value().empty()) startRandomTitle(); Environment env = Env_Normal; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index bd9743c528..7fc7c143d5 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "../mwbase/soundmanager.hpp" @@ -52,7 +53,8 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::unordered_map> mMusicFiles; + std::unordered_map, VFS::Path::Hash, std::equal_to<>> + mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played @@ -90,7 +92,7 @@ namespace MWSound TrackList mActiveTracks; StreamPtr mMusic; - std::string mCurrentPlaylist; + VFS::Path::Normalized mCurrentPlaylist; bool mListenerUnderwater; osg::Vec3f mListenerPos; @@ -183,7 +185,7 @@ namespace MWSound bool isMusicPlaying() override; ///< Returns true if music is playing - void playPlaylist(const std::string& playlist) override; + void playPlaylist(VFS::Path::NormalizedView playlist) override; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 74335a1534..c72a9993d6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -90,6 +90,8 @@ #include "../mwphysics/object.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwsound/constants.hpp" + #include "actionteleport.hpp" #include "cellstore.hpp" #include "containerstore.hpp" @@ -394,7 +396,7 @@ namespace MWWorld { // Make sure that we do not continue to play a Title music after a new game video. MWBase::Environment::get().getSoundManager()->stopMusic(); - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); MWBase::Environment::get().getWindowManager()->playVideo(video, true); } } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1d5b57bfd9..1eb3800012 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -186,9 +186,11 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normali return prefix / resPath; } -std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) +std::string Misc::ResourceHelpers::correctMusicPath(std::string_view resPath) { - return "music\\" + resPath; + std::string result("music/"); + result += resPath; + return result; } std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index cda99d928d..4e747c54ee 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -42,7 +42,7 @@ namespace Misc VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); // Adds "music\\". - std::string correctMusicPath(const std::string& resPath); + std::string correctMusicPath(std::string_view resPath); // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 936dd64679..2d22a7a034 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -99,4 +99,21 @@ namespace VFS ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const + { + if (path.value().empty()) + return { mIndex.begin(), mIndex.end() }; + const auto it = mIndex.lower_bound(path); + if (it == mIndex.end() || !it->first.view().starts_with(path.value())) + return { it, it }; + std::string copy(path.value()); + ++copy.back(); + return { it, mIndex.lower_bound(copy) }; + } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator() const + { + return { mIndex.begin(), mIndex.end() }; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 59211602de..d64be1d1d1 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -65,6 +65,10 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; + RecursiveDirectoryRange getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const; + + RecursiveDirectoryRange getRecursiveDirectoryIterator() const; + /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 1696e656ad..50f16d1804 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -179,6 +179,12 @@ namespace VFS::Path return true; } + Normalized& operator=(NormalizedView value) + { + mValue = value.value(); + return *this; + } + Normalized& operator/=(NormalizedView value) { mValue.reserve(mValue.size() + value.value().size() + 1); @@ -258,6 +264,22 @@ namespace VFS::Path result /= rhs; return result; } + + struct Hash + { + using is_transparent = void; + + [[nodiscard]] std::size_t operator()(std::string_view sv) const { return std::hash{}(sv); } + + [[nodiscard]] std::size_t operator()(const std::string& s) const { return std::hash{}(s); } + + [[nodiscard]] std::size_t operator()(const Normalized& s) const { return std::hash{}(s.value()); } + + [[nodiscard]] std::size_t operator()(NormalizedView s) const + { + return std::hash{}(s.value()); + } + }; } #endif From 40cc16046b782df327a605a54e1857ee8fa52d1b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Apr 2024 19:51:10 +0200 Subject: [PATCH 1353/2167] Use normalized path for sound decoder --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 4 ++-- apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 +- apps/openmw/mwsound/movieaudiofactory.cpp | 12 ++++-------- apps/openmw/mwsound/sound_decoder.hpp | 4 +++- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 9e7a2be3a8..e9a5a1eb94 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -208,7 +208,7 @@ namespace MWSound return dec; } - void FFmpeg_Decoder::open(const std::string& fname) + void FFmpeg_Decoder::open(VFS::Path::NormalizedView fname) { close(); mDataStream = mResourceMgr->get(fname); @@ -224,7 +224,7 @@ namespace MWSound formatCtx->pb = ioCtx.get(); // avformat_open_input frees user supplied AVFormatContext on failure - if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0) + if (avformat_open_input(&formatCtx, fname.value().data(), nullptr, nullptr) != 0) throw std::runtime_error("Failed to open input"); AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr)); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index ed3297403e..264ff8fab7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -93,7 +93,7 @@ namespace MWSound bool getAVAudioData(); size_t readAVAudioData(void* data, size_t length); - void open(const std::string& fname) override; + void open(VFS::Path::NormalizedView fname) override; void close() override; std::string getName() override; diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 1bb5275c45..68ea221321 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -24,8 +24,10 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - void open(const std::string& fname) override; - void close() override; + void open(VFS::Path::NormalizedView fname) override { throw std::runtime_error("Method not implemented"); } + + void close() override {} + std::string getName() override; void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override; size_t read(char* buffer, size_t bytes) override; @@ -92,12 +94,6 @@ namespace MWSound std::shared_ptr mDecoderBridge; }; - void MWSoundDecoderBridge::open(const std::string& fname) - { - throw std::runtime_error("Method not implemented"); - } - void MWSoundDecoderBridge::close() {} - std::string MWSoundDecoderBridge::getName() { return mDecoder->getStreamName(); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index f6dcdb7032..17f9d28909 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -1,6 +1,8 @@ #ifndef GAME_SOUND_SOUND_DECODER_H #define GAME_SOUND_SOUND_DECODER_H +#include + #include #include @@ -36,7 +38,7 @@ namespace MWSound { const VFS::Manager* mResourceMgr; - virtual void open(const std::string& fname) = 0; + virtual void open(VFS::Path::NormalizedView fname) = 0; virtual void close() = 0; virtual std::string getName() = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 4c3f227ecd..039c283d7a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -267,7 +267,7 @@ namespace MWSound DecoderPtr decoder = getDecoder(); try { - decoder->open(filename); + decoder->open(VFS::Path::Normalized(filename)); } catch (std::exception& e) { From 4f6951b84fd2a8942e41ab0e23030ea3c5fbce29 Mon Sep 17 00:00:00 2001 From: Benjamin Y Date: Fri, 19 Apr 2024 16:49:19 -0500 Subject: [PATCH 1354/2167] added RU translation from Alexei --- files/lang/launcher_ru.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 215aaec00e..9cd6c03d06 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1464,11 +1464,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. Only affects objects drawn with shaders. Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. Note that groundcover shaders and particle effects ignore this setting.</p></body></html> - + Использовать попиксельное освещене принудительно. По умолчанию его используют только объекты, использующие карты бампа и карты нормалей. Влияет только на объекты, отрисовываемые шейдерами. Включение попиксельного освещения приводит к визуальным отличиям по сравнению с оригинальным движком Morrowind, так как некоторые источники света в Morrowind полагаются на повершинное освещение для того, чтобы выглядеть, как задумывалось. Обратите внимание, что шейдеры растительности и системы частиц игнорируют эту настройку. Force per-pixel lighting - + Попиксельное освещение From 5b0eb0b5b0c231dcdf7fa13287f194ccf4148465 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 20 Apr 2024 14:15:24 +0200 Subject: [PATCH 1355/2167] Log ptr for which agent bounds are not supported To make it easier to find what NPC or mod makes this happen. --- apps/openmw/mwworld/scene.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 64a258cff8..beb519b9e6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -190,7 +190,8 @@ namespace { const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(ptr); if (!navigator.addAgent(agentBounds)) - Log(Debug::Warning) << "Agent bounds are not supported by navigator: " << agentBounds; + Log(Debug::Warning) << "Agent bounds are not supported by navigator for " << ptr.toString() << ": " + << agentBounds; } } From d14fc1d28c4f09b296faaa69b019c67d02384710 Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 20 Apr 2024 14:28:20 +0200 Subject: [PATCH 1356/2167] ESM::Dialogue Lua bindings 3 --- apps/openmw/mwlua/dialoguebindings.cpp | 482 ++++++++++++++++++++----- 1 file changed, 385 insertions(+), 97 deletions(-) diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index 0457ac7bce..f9c60d8ba6 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -1,7 +1,10 @@ #include "dialoguebindings.hpp" +#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" #include "context.hpp" -#include "recordstore.hpp" +#include "object.hpp" #include #include #include @@ -9,79 +12,384 @@ namespace { - sol::table prepareJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) + template + class FilteredDialogueStore { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - sol::table result(lua, sol::create); - result["text"] = mwDialogue.mStringId; + const MWWorld::Store& mDialogueStore; - sol::table preparedInfos(lua, sol::create); - unsigned index = 1; - for (const auto& mwDialogueInfo : mwDialogue.mInfo) + const ESM::Dialogue* foundDialogueFilteredOut(const ESM::Dialogue* possibleResult) const { - if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name) + if (possibleResult and possibleResult->mType == filter) { - result["questName"] = mwDialogueInfo.mResponse; - continue; + return possibleResult; } - sol::table infoElement(lua, sol::create); - infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); - infoElement["text"] = mwDialogueInfo.mResponse; - infoElement["questStage"] = mwDialogueInfo.mData.mJournalIndex; - infoElement["questFinished"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); - infoElement["questRestart"] = (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); - preparedInfos[index++] = infoElement; + return nullptr; + } + public: + FilteredDialogueStore() + : mDialogueStore{MWBase::Environment::get().getESMStore()->get()} + {} + + class FilteredDialogueIterator + { + using DecoratedIterator = MWWorld::Store::iterator; + DecoratedIterator mIter; + public: + FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) + : mIter{decoratedIterator} + {} + + FilteredDialogueIterator& operator++() + { + do + { + std::advance(mIter, 1); + } while (mIter->mType != filter); + return *this; + } + + FilteredDialogueIterator operator++(int) + { + FilteredDialogueIterator iter = *this; + do + { + std::advance(mIter, 1); + } while (mIter->mType != filter); + return iter; + } + + bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; } + + bool operator!=(const FilteredDialogueIterator& x) const { return not (*this == x); } + + const ESM::Dialogue& operator*() const { return *mIter; } + + const ESM::Dialogue* operator->() const { return &(*mIter); } + }; + using iterator = FilteredDialogueIterator; + + const ESM::Dialogue* search(const ESM::RefId& id) const + { + return foundDialogueFilteredOut(mDialogueStore.search(id)); } - result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); - return result; - } - - sol::table prepareNonJournalRecord(sol::state_view& lua, const ESM::Dialogue& mwDialogue) - { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - sol::table result(lua, sol::create); - result["text"] = mwDialogue.mStringId; - - sol::table preparedInfos(lua, sol::create); - unsigned index = 1; - for (const auto& mwDialogueInfo : mwDialogue.mInfo) + const ESM::Dialogue* at(size_t index) const { - sol::table infoElement(lua, sol::create); - infoElement["id"] = (dialogueRecordId + '#' + mwDialogueInfo.mId.serializeText()); - infoElement["text"] = mwDialogueInfo.mResponse; - infoElement["actorId"] = mwDialogueInfo.mActor.serializeText(); - infoElement["actorRace"] = mwDialogueInfo.mRace.serializeText(); - infoElement["actorClass"] = mwDialogueInfo.mClass.serializeText(); - infoElement["actorFaction"] = mwDialogueInfo.mFaction.serializeText(); - if (mwDialogueInfo.mData.mRank != -1) + if (index >= getSize()) { - infoElement["actorFactionRank"] = mwDialogueInfo.mData.mRank; + return nullptr; } - infoElement["actorCell"] = mwDialogueInfo.mClass.serializeText(); - infoElement["actorDisposition"] = mwDialogueInfo.mData.mDisposition; - if (mwDialogueInfo.mData.mGender != ESM::DialInfo::Gender::NA) - { - infoElement["actorGender"] = mwDialogueInfo.mData.mGender; - } - infoElement["playerFaction"] = mwDialogueInfo.mPcFaction.serializeText(); - if (mwDialogueInfo.mData.mPCrank != -1) - { - infoElement["playerFactionRank"] = mwDialogueInfo.mData.mPCrank; - } - if (not mwDialogueInfo.mSound.empty()) - { - infoElement["sound"] - = Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(mwDialogueInfo.mSound)).value(); - } - // mResultScript TODO - // mSelects TODO - preparedInfos[index++] = infoElement; + + auto result = begin(); + std::advance(result, index); + + return &(*result); } - result["infos"] = LuaUtil::makeStrictReadOnly(preparedInfos); - return result; + size_t getSize() const + { + return std::count_if(mDialogueStore.begin(), mDialogueStore.end(), [this](const auto& d) { return d.mType == filter; }); + } + + iterator begin() const + { + iterator result{mDialogueStore.begin()}; + while (result != end() and result->mType != filter) + { + std::advance(result, 1); + } + return result; + } + + iterator end() const + { + return iterator{mDialogueStore.end()}; + } + }; + + template + void prepareBindingsForDialogueRecordStores(sol::table& table, const MWLua::Context& context) + { + using StoreT = FilteredDialogueStore; + + table["record"] = sol::overload( + [](const MWLua::Object& obj) -> const ESM::Dialogue* { return obj.ptr().get()->mBase; }, + [](std::string_view id) -> const ESM::Dialogue* + { + return StoreT{}.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); + }); + + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeBindingsClass = lua.new_usertype("ESM3_Dialogue_Type" + std::to_string(filter) + " Store"); + storeBindingsClass[sol::meta_function::to_string] = [](const StoreT& store) + { + return "{" + std::to_string(store.getSize()) + " ESM3_Dialogue_Type" + std::to_string(filter) + " records}"; + }; + storeBindingsClass[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; + storeBindingsClass[sol::meta_function::index] = sol::overload( + [](const StoreT& store, size_t index) -> const ESM::Dialogue* + { + if (index == 0 or index > store.getSize()) + { + return nullptr; + } + return store.at(index - 1); + }, + [](const StoreT& store, std::string_view id) -> const ESM::Dialogue* + { + return store.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); + }); + storeBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + storeBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + + table["records"] = StoreT{}; } + + struct DialogueInfos + { + ESM::RefId mDialogueRecordId; + + const ESM::Dialogue* getDialogueRecord() const + { + const auto& dialogueStore{MWBase::Environment::get().getESMStore()->get()}; + return dialogueStore.search(mDialogueRecordId); + } + }; + + void prepareBindingsForDialogueRecord(sol::state_view& lua) + { + auto recordBindingsClass = lua.new_usertype("ESM3_Dialogue"); + recordBindingsClass[sol::meta_function::to_string] = [lua](const ESM::Dialogue& rec) -> sol::object + { + return sol::make_object(lua, "ESM3_Dialogue[" + rec.mId.toDebugString() + "]"); + }; + recordBindingsClass["id"] = sol::readonly_property([lua](const ESM::Dialogue& rec) + { + return sol::make_object(lua, rec.mId.serializeText()); + }); + recordBindingsClass["name"] = sol::readonly_property([lua](const ESM::Dialogue& rec) + { + return sol::make_object(lua, rec.mStringId); + }); + recordBindingsClass["questName"] = sol::readonly_property([lua](const ESM::Dialogue& rec) -> sol::object + { + if (rec.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + for (const auto& mwDialogueInfo : rec.mInfo) + { + if (mwDialogueInfo.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Name) + { + return sol::make_object(lua, mwDialogueInfo.mResponse); + } + } + return sol::nil; + }); + recordBindingsClass["infos"] = sol::readonly_property([lua](const ESM::Dialogue& rec) + { + return DialogueInfos{rec.mId}; + }); + } + + void prepareBindingsForDialogueRecordInfoList(sol::state_view& lua) + { + auto recordInfosBindingsClass = lua.new_usertype("ESM3_Dialogue_Infos"); + recordInfosBindingsClass[sol::meta_function::to_string] = [lua](const DialogueInfos& store) -> sol::object + { + if (const ESM::Dialogue* dialogueRecord = store.getDialogueRecord()) + { + return sol::make_object(lua, "{" + std::to_string(dialogueRecord->mInfo.size()) + " ESM3_Dialogue[" + dialogueRecord->mId.toDebugString() + "] info elements}"); + } + return sol::nil; + }; + recordInfosBindingsClass[sol::meta_function::length] = [](const DialogueInfos& store) + { + const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); + return dialogueRecord ? dialogueRecord->mInfo.size() : 0; + }; + recordInfosBindingsClass[sol::meta_function::index] = [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* + { + const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); + if (not dialogueRecord or index == 0 or index > dialogueRecord->mInfo.size()) + { + return nullptr; + } + ESM::Dialogue::InfoContainer::const_iterator iter{dialogueRecord->mInfo.cbegin()}; + std::advance(iter, index - 1); + return &(*iter); + }; + recordInfosBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + recordInfosBindingsClass[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + } + + void prepareBindingsForDialogueRecordInfoListElement(sol::state_view& lua) + { + auto recordInfoBindingsClass = lua.new_usertype("ESM3_Dialogue_Info"); + + recordInfoBindingsClass[sol::meta_function::to_string] = [lua](const ESM::DialInfo& rec) + { + return sol::make_object(lua, "ESM3_Dialogue_Info[" + rec.mId.toDebugString() + "]"); + }; + recordInfoBindingsClass["id"] = sol::readonly_property([lua](const ESM::DialInfo& rec) + { + return sol::make_object(lua, rec.mId.serializeText()); + }); + recordInfoBindingsClass["text"] = sol::readonly_property([lua](const ESM::DialInfo& rec) + { + return sol::make_object(lua, rec.mResponse); + }); + recordInfoBindingsClass["questStage"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mData.mJournalIndex); + }); + recordInfoBindingsClass["questFinished"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); + }); + recordInfoBindingsClass["questRestart"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); + }); + recordInfoBindingsClass["filterActorId"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mActor.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorRace"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mRace.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorClass"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mClass.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorFaction"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mFaction.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorFactionRank"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mRank; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorCell"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mCell.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorDisposition"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mData.mDisposition); + }); + recordInfoBindingsClass["filterActorGender"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mGender; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterPlayerFaction"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mPcFaction.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterPlayerFactionRank"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mPCrank; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["sound"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object + { + if (rec.mData.mType == ESM::Dialogue::Type::Journal or rec.mSound == "") + { + return sol::nil; + } + return sol::make_object(lua, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value()); + }); + // // mResultScript TODO + // // mSelects TODO + } + + void prepareBindingsForDialogueRecords(sol::state_view& lua) + { + prepareBindingsForDialogueRecord(lua); + prepareBindingsForDialogueRecordInfoList(lua); + prepareBindingsForDialogueRecordInfoListElement(lua); + } +} + +namespace sol +{ + template + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -92,43 +400,23 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table api(lua, sol::create); - const MWWorld::Store& mwDialogueStore - = MWBase::Environment::get().getESMStore()->get(); + sol::table journalTable(lua, sol::create); + sol::table topicTable(lua, sol::create); + sol::table greetingTable(lua, sol::create); + sol::table persuasionTable(lua, sol::create); + sol::table voiceTable(lua, sol::create); + prepareBindingsForDialogueRecordStores(journalTable, context); + prepareBindingsForDialogueRecordStores(topicTable, context); + prepareBindingsForDialogueRecordStores(greetingTable, context); + prepareBindingsForDialogueRecordStores(persuasionTable, context); + prepareBindingsForDialogueRecordStores(voiceTable, context); + api["journal"] = journalTable; + api["topic"] = topicTable; + api["greeting"] = greetingTable; + api["persuasion"] = persuasionTable; + api["voice"] = voiceTable; - sol::table journalRecordsByQuestId(lua, sol::create); - sol::table topicRecordsByTopicId(lua, sol::create); - sol::table voiceRecordsById(lua, sol::create); - sol::table greetingRecordsById(lua, sol::create); - sol::table persuasionRecordsById(lua, sol::create); - for (const auto& mwDialogue : mwDialogueStore) - { - const auto dialogueRecordId = mwDialogue.mId.serializeText(); - if (mwDialogue.mType == ESM::Dialogue::Type::Journal) - { - journalRecordsByQuestId[dialogueRecordId] = prepareJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Topic) - { - topicRecordsByTopicId[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Voice) - { - voiceRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Greeting) - { - greetingRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - else if (mwDialogue.mType == ESM::Dialogue::Type::Persuasion) - { - persuasionRecordsById[dialogueRecordId] = prepareNonJournalRecord(lua, mwDialogue); - } - } - api["journalRecords"] = LuaUtil::makeStrictReadOnly(journalRecordsByQuestId); - api["topicRecords"] = LuaUtil::makeStrictReadOnly(topicRecordsByTopicId); - api["voiceRecords"] = LuaUtil::makeStrictReadOnly(voiceRecordsById); - api["greetingRecords"] = LuaUtil::makeStrictReadOnly(greetingRecordsById); - api["persuasionRecords"] = LuaUtil::makeStrictReadOnly(persuasionRecordsById); + prepareBindingsForDialogueRecords(lua); return LuaUtil::makeReadOnly(api); } From 3bb7bf1a4a1f8f2c533d1b05037fafd7c3fb48e0 Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 20 Apr 2024 15:47:34 +0200 Subject: [PATCH 1357/2167] ESM::Dialogue Lua bindings 4 --- apps/openmw/mwlua/dialoguebindings.cpp | 323 ++++++++++++------------- 1 file changed, 155 insertions(+), 168 deletions(-) diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index f9c60d8ba6..c1d26f1831 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -1,10 +1,10 @@ #include "dialoguebindings.hpp" -#include #include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" #include "context.hpp" #include "object.hpp" +#include #include #include #include @@ -25,19 +25,23 @@ namespace } return nullptr; } + public: FilteredDialogueStore() - : mDialogueStore{MWBase::Environment::get().getESMStore()->get()} - {} + : mDialogueStore{ MWBase::Environment::get().getESMStore()->get() } + { + } class FilteredDialogueIterator { using DecoratedIterator = MWWorld::Store::iterator; DecoratedIterator mIter; + public: FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) - : mIter{decoratedIterator} - {} + : mIter{ decoratedIterator } + { + } FilteredDialogueIterator& operator++() { @@ -60,7 +64,7 @@ namespace bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; } - bool operator!=(const FilteredDialogueIterator& x) const { return not (*this == x); } + bool operator!=(const FilteredDialogueIterator& x) const { return not(*this == x); } const ESM::Dialogue& operator*() const { return *mIter; } @@ -88,12 +92,13 @@ namespace size_t getSize() const { - return std::count_if(mDialogueStore.begin(), mDialogueStore.end(), [this](const auto& d) { return d.mType == filter; }); + return std::count_if( + mDialogueStore.begin(), mDialogueStore.end(), [this](const auto& d) { return d.mType == filter; }); } iterator begin() const { - iterator result{mDialogueStore.begin()}; + iterator result{ mDialogueStore.begin() }; while (result != end() and result->mType != filter) { std::advance(result, 1); @@ -101,10 +106,7 @@ namespace return result; } - iterator end() const - { - return iterator{mDialogueStore.end()}; - } + iterator end() const { return iterator{ mDialogueStore.end() }; } }; template @@ -114,29 +116,26 @@ namespace table["record"] = sol::overload( [](const MWLua::Object& obj) -> const ESM::Dialogue* { return obj.ptr().get()->mBase; }, - [](std::string_view id) -> const ESM::Dialogue* - { + [](std::string_view id) -> const ESM::Dialogue* { return StoreT{}.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); }); sol::state_view& lua = context.mLua->sol(); - sol::usertype storeBindingsClass = lua.new_usertype("ESM3_Dialogue_Type" + std::to_string(filter) + " Store"); - storeBindingsClass[sol::meta_function::to_string] = [](const StoreT& store) - { + sol::usertype storeBindingsClass + = lua.new_usertype("ESM3_Dialogue_Type" + std::to_string(filter) + " Store"); + storeBindingsClass[sol::meta_function::to_string] = [](const StoreT& store) { return "{" + std::to_string(store.getSize()) + " ESM3_Dialogue_Type" + std::to_string(filter) + " records}"; }; storeBindingsClass[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; storeBindingsClass[sol::meta_function::index] = sol::overload( - [](const StoreT& store, size_t index) -> const ESM::Dialogue* - { + [](const StoreT& store, size_t index) -> const ESM::Dialogue* { if (index == 0 or index > store.getSize()) { return nullptr; } return store.at(index - 1); }, - [](const StoreT& store, std::string_view id) -> const ESM::Dialogue* - { + [](const StoreT& store, std::string_view id) -> const ESM::Dialogue* { return store.search(ESM::RefId::deserializeText(Misc::StringUtils::lowerCase(id))); }); storeBindingsClass[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); @@ -151,7 +150,7 @@ namespace const ESM::Dialogue* getDialogueRecord() const { - const auto& dialogueStore{MWBase::Environment::get().getESMStore()->get()}; + const auto& dialogueStore{ MWBase::Environment::get().getESMStore()->get() }; return dialogueStore.search(mDialogueRecordId); } }; @@ -159,20 +158,14 @@ namespace void prepareBindingsForDialogueRecord(sol::state_view& lua) { auto recordBindingsClass = lua.new_usertype("ESM3_Dialogue"); - recordBindingsClass[sol::meta_function::to_string] = [lua](const ESM::Dialogue& rec) -> sol::object - { + recordBindingsClass[sol::meta_function::to_string] = [lua](const ESM::Dialogue& rec) -> sol::object { return sol::make_object(lua, "ESM3_Dialogue[" + rec.mId.toDebugString() + "]"); }; - recordBindingsClass["id"] = sol::readonly_property([lua](const ESM::Dialogue& rec) - { - return sol::make_object(lua, rec.mId.serializeText()); - }); - recordBindingsClass["name"] = sol::readonly_property([lua](const ESM::Dialogue& rec) - { - return sol::make_object(lua, rec.mStringId); - }); - recordBindingsClass["questName"] = sol::readonly_property([lua](const ESM::Dialogue& rec) -> sol::object - { + recordBindingsClass["id"] = sol::readonly_property( + [lua](const ESM::Dialogue& rec) { return sol::make_object(lua, rec.mId.serializeText()); }); + recordBindingsClass["name"] + = sol::readonly_property([lua](const ESM::Dialogue& rec) { return sol::make_object(lua, rec.mStringId); }); + recordBindingsClass["questName"] = sol::readonly_property([lua](const ESM::Dialogue& rec) -> sol::object { if (rec.mType != ESM::Dialogue::Type::Journal) { return sol::nil; @@ -186,36 +179,34 @@ namespace } return sol::nil; }); - recordBindingsClass["infos"] = sol::readonly_property([lua](const ESM::Dialogue& rec) - { - return DialogueInfos{rec.mId}; - }); + recordBindingsClass["infos"] + = sol::readonly_property([lua](const ESM::Dialogue& rec) { return DialogueInfos{ rec.mId }; }); } void prepareBindingsForDialogueRecordInfoList(sol::state_view& lua) { auto recordInfosBindingsClass = lua.new_usertype("ESM3_Dialogue_Infos"); - recordInfosBindingsClass[sol::meta_function::to_string] = [lua](const DialogueInfos& store) -> sol::object - { + recordInfosBindingsClass[sol::meta_function::to_string] = [lua](const DialogueInfos& store) -> sol::object { if (const ESM::Dialogue* dialogueRecord = store.getDialogueRecord()) { - return sol::make_object(lua, "{" + std::to_string(dialogueRecord->mInfo.size()) + " ESM3_Dialogue[" + dialogueRecord->mId.toDebugString() + "] info elements}"); + return sol::make_object(lua, + "{" + std::to_string(dialogueRecord->mInfo.size()) + " ESM3_Dialogue[" + + dialogueRecord->mId.toDebugString() + "] info elements}"); } return sol::nil; }; - recordInfosBindingsClass[sol::meta_function::length] = [](const DialogueInfos& store) - { + recordInfosBindingsClass[sol::meta_function::length] = [](const DialogueInfos& store) { const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); return dialogueRecord ? dialogueRecord->mInfo.size() : 0; }; - recordInfosBindingsClass[sol::meta_function::index] = [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* - { + recordInfosBindingsClass[sol::meta_function::index] + = [](const DialogueInfos& store, size_t index) -> const ESM::DialInfo* { const ESM::Dialogue* dialogueRecord = store.getDialogueRecord(); if (not dialogueRecord or index == 0 or index > dialogueRecord->mInfo.size()) { return nullptr; } - ESM::Dialogue::InfoContainer::const_iterator iter{dialogueRecord->mInfo.cbegin()}; + ESM::Dialogue::InfoContainer::const_iterator iter{ dialogueRecord->mInfo.cbegin() }; std::advance(iter, index - 1); return &(*iter); }; @@ -227,141 +218,137 @@ namespace { auto recordInfoBindingsClass = lua.new_usertype("ESM3_Dialogue_Info"); - recordInfoBindingsClass[sol::meta_function::to_string] = [lua](const ESM::DialInfo& rec) - { + recordInfoBindingsClass[sol::meta_function::to_string] = [lua](const ESM::DialInfo& rec) { return sol::make_object(lua, "ESM3_Dialogue_Info[" + rec.mId.toDebugString() + "]"); }; - recordInfoBindingsClass["id"] = sol::readonly_property([lua](const ESM::DialInfo& rec) - { - return sol::make_object(lua, rec.mId.serializeText()); - }); - recordInfoBindingsClass["text"] = sol::readonly_property([lua](const ESM::DialInfo& rec) - { - return sol::make_object(lua, rec.mResponse); - }); - recordInfoBindingsClass["questStage"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { + recordInfoBindingsClass["id"] = sol::readonly_property( + [lua](const ESM::DialInfo& rec) { return sol::make_object(lua, rec.mId.serializeText()); }); + recordInfoBindingsClass["text"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) { return sol::make_object(lua, rec.mResponse); }); + recordInfoBindingsClass["questStage"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { if (rec.mData.mType != ESM::Dialogue::Type::Journal) { return sol::nil; } return sol::make_object(lua, rec.mData.mJournalIndex); }); - recordInfoBindingsClass["questFinished"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType != ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); - }); - recordInfoBindingsClass["questRestart"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType != ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); - }); - recordInfoBindingsClass["filterActorId"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mActor.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorRace"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mRace.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorClass"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mClass.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorFaction"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mFaction.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorFactionRank"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mData.mRank; - return result == -1 ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorCell"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mCell.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterActorDisposition"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - return sol::make_object(lua, rec.mData.mDisposition); - }); - recordInfoBindingsClass["filterActorGender"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mData.mGender; - return result == -1 ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterPlayerFaction"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mPcFaction.serializeText(); - return result.empty() ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["filterPlayerFactionRank"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { - if (rec.mData.mType == ESM::Dialogue::Type::Journal) - { - return sol::nil; - } - const auto result = rec.mData.mPCrank; - return result == -1 ? sol::nil : sol::make_object(lua, result); - }); - recordInfoBindingsClass["sound"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object - { + recordInfoBindingsClass["questFinished"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Finished); + }); + recordInfoBindingsClass["questRestart"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType != ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mQuestStatus == ESM::DialInfo::QuestStatus::QS_Restart); + }); + recordInfoBindingsClass["filterActorId"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mActor.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorRace"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mRace.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorClass"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mClass.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorFaction"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mFaction.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorFactionRank"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mRank; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorCell"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mCell.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterActorDisposition"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + return sol::make_object(lua, rec.mData.mDisposition); + }); + recordInfoBindingsClass["filterActorGender"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mGender; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterPlayerFaction"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mPcFaction.serializeText(); + return result.empty() ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["filterPlayerFactionRank"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + if (rec.mData.mType == ESM::Dialogue::Type::Journal) + { + return sol::nil; + } + const auto result = rec.mData.mPCrank; + return result == -1 ? sol::nil : sol::make_object(lua, result); + }); + recordInfoBindingsClass["sound"] = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { if (rec.mData.mType == ESM::Dialogue::Type::Journal or rec.mSound == "") { return sol::nil; } - return sol::make_object(lua, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value()); + return sol::make_object( + lua, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value()); }); - // // mResultScript TODO - // // mSelects TODO + recordInfoBindingsClass["resultScript"] + = sol::readonly_property([lua](const ESM::DialInfo& rec) -> sol::object { + return rec.mResultScript.empty() ? sol::nil : sol::make_object(lua, rec.mResultScript); + }); } void prepareBindingsForDialogueRecords(sol::state_view& lua) From 1e36b176792034cf3763f4e18be5815919bbc11b Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 20 Apr 2024 17:44:36 +0200 Subject: [PATCH 1358/2167] ESM::Dialogue Lua bindings 5 --- apps/openmw/mwlua/dialoguebindings.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index c1d26f1831..6dbe331d6e 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -38,6 +38,8 @@ namespace DecoratedIterator mIter; public: + using difference_type = DecoratedIterator::difference_type; + FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) : mIter{ decoratedIterator } { @@ -93,7 +95,7 @@ namespace size_t getSize() const { return std::count_if( - mDialogueStore.begin(), mDialogueStore.end(), [this](const auto& d) { return d.mType == filter; }); + mDialogueStore.begin(), mDialogueStore.end(), [](const auto& d) { return d.mType == filter; }); } iterator begin() const @@ -381,7 +383,6 @@ namespace sol namespace MWLua { - sol::table initCoreDialogueBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); From f9eefd9ac44cf954de099ea7486b66f71f44bf28 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Sat, 20 Apr 2024 16:35:11 +0000 Subject: [PATCH 1359/2167] Swedish translation of the launcher --- files/lang/components_sv.ts | 95 +++ files/lang/launcher_sv.ts | 1494 +++++++++++++++++++++++++++++++++++ files/lang/wizard_sv.ts | 678 ++++++++++++++++ 3 files changed, 2267 insertions(+) create mode 100644 files/lang/components_sv.ts create mode 100644 files/lang/launcher_sv.ts create mode 100644 files/lang/wizard_sv.ts diff --git a/files/lang/components_sv.ts b/files/lang/components_sv.ts new file mode 100644 index 0000000000..8682a569bd --- /dev/null +++ b/files/lang/components_sv.ts @@ -0,0 +1,95 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + Välj språk som används av ESM/ESP-innehållsfiler så att OpenMW kan hitta deras kodning. + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + Kunde inte hitta beroende fil: %1 + + + Dependent file needs to be active: %1 + Beroende fil måste vara aktiv: %1 + + + This file needs to load after %1 + Denna fil måste laddas efter %1 + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + <b>Skapare:</b> %1<br/><b>Formatversion:</b> %2<br/><b>Ändrad:</b> %3<br/><b>Sökväg:</b><br/>%4<br/><br/><b>Beskrivning:</b><br/>%5<br/><br/><b>Beroenden: </b>%6<br/> + + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en del av OpenMW.</b><br/> + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.</b><br/> + + + + ContentSelectorView::ContentSelector + + &Check Selected + &Bocka i markerade + + + &Uncheck Selected + &Bocka ur markerade + + + &Copy Path(s) to Clipboard + &Kopiera sökväg(ar) till klippbordet + + + <No game file> + <Ingen spelfil> + + + + Process::ProcessInvoker + + Error starting executable + Kunde inte starta körbara filen + + + Error running executable + Fel när körbara filen skulle köras + + + +Arguments: + + +Argument: + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte hitta %1</b></p><p>Applikationen hittas inte.</p><p>Se till att OpenMW är korrekt installerat och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Applikationen är inte körbar.</p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Ett fel uppstod när %1 skulle startas.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Körbara filen %1 gav ett felmeddelande</b></p><p>Ett fel uppstod när %1 kördes.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts new file mode 100644 index 0000000000..4cd75487b2 --- /dev/null +++ b/files/lang/launcher_sv.ts @@ -0,0 +1,1494 @@ + + + + + DataFilesPage + + Content Files + Innehållsfiler + + + Data Directories + Datakataloger + + + Scan directories for likely data directories and append them at the end of the list. + Skanna kataloger för troliga datakataloger och lägg till dem i slutet på listan. + + + Append + Lägg till + + + Scan directories for likely data directories and insert them above the selected position + Skanna kataloger för troliga datakataloger och lägg till dem ovanför den markerade positionen + + + Insert Above + Lägg till ovanför + + + Move selected directory one position up + Flytta markerad katalog en position upp + + + Move Up + Flytta upp + + + Move selected directory one position down + Flytta markerad katalog en position ner + + + Move Down + Flytta ner + + + Remove selected directory + Ta bort markerad katalog + + + Remove + Ta bort + + + Archive Files + Arkivfiler + + + Move selected archive one position up + Flytta markerat arkiv en position upp + + + Move selected archive one position down + Flytta markerat arkiv en position ner + + + Navigation Mesh Cache + Cache för navigeringsmesh + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + Generera navigeringsmesh för allt innehåll. Kommer användas av motorn för att ladda celler snabbare. + + + Update + Uppdatera + + + Cancel navigation mesh generation. Already processed data will be saved. + Avbryt generering av navigationsmesh. Redan processad data kommer sparas. + + + Cancel + Avbryt + + + MiB + MiB + + + Content List + Innehållslista + + + Select a content list + Välj en innehållslista + + + New Content List + Ny innehållslista + + + &New Content List + &Ny innehållslista + + + Clone Content List + Klona innehållslista + + + Delete Content List + Radera innehållslista + + + Ctrl+N + Ctrl+N + + + Ctrl+G + Ctrl+G + + + Ctrl+D + Ctrl+D + + + Check Selection + Kryssa markering + + + Uncheck Selection + Avkryssa markering + + + Refresh Data Files + Uppdatera datafiler + + + Ctrl+R + Ctrl+R + + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: innehållsfiler som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: kataloger som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: arkiv som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + Remove Unused Tiles + Ta bort oanvända celler + + + Max Size + Maximal storlek + + + + GraphicsPage + + 0 + 0 + + + 2 + 2 + + + 4 + 4 + + + 8 + 8 + + + 16 + 16 + + + Custom: + Egen: + + + Standard: + Standard: + + + Fullscreen + Helskärm + + + Windowed Fullscreen + Helskärm i fönsterläge + + + Windowed + Fönster + + + Disabled + Inaktiverad + + + Enabled + Aktiverad + + + Adaptive + Adaptiv + + + FPS + FPS + + + × + × + + + Screen + Skärm + + + Resolution + Upplösning + + + Window Mode + Fönsterläge + + + Framerate Limit + Gränsvärde bilduppdateringsfrekvens + + + Window Border + Fönster, ram + + + Anti-Aliasing + Kantutjämning + + + Vertical Synchronization + Vertikal synkronisering + + + + ImportPage + + Form + Can be translated in different ways depending on context. Will return to later. + + + + Morrowind Installation Wizard + Installationsguide för Morrowind + + + Run &Installation Wizard + Kör &Installationsguide + + + Morrowind Settings Importer + Inställningsimporterare för Morrowind + + + Browse... + Bläddra... + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + Run &Settings Importer + Kör &Inställningsimporterare + + + File to Import Settings From: + Fil att importera inställningar från: + + + Import Bitmap Fonts + Importera bitmapfonter + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkeringar + + + + Launcher::DataFilesPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + New Content List + Ny innehållslista + + + Content List name: + Namn på innehållslista: + + + Clone Content List + Klona innehållslista + + + Select Directory + Välj katalog + + + Delete Content List + Radera innehållslista + + + Are you sure you want to delete <b>%1</b>? + Är du säker på att du vill radera <b>%1</b>? + + + Delete + Radera + + + Contains content file(s) + Innehåller innehållsfil(er) + + + Will be added to the current profile + Kommer läggas till nuvarande profil + + + &Check Selected + &Kryssa markerade + + + &Uncheck Selected + &Avkryssa markerade + + + Resolved as %1 + Löst som %1 + + + This is the data-local directory and cannot be disabled + Det här är den data-lokala katalogen och kan inte inaktiveras + + + This directory is part of OpenMW and cannot be disabled + Denna katalog är en del av OpenMW och kan inte inaktiveras + + + This directory is enabled in an openmw.cfg other than the user one + Denna katalog är aktiverad i en annan openmw.cfg än användarens + + + This archive is enabled in an openmw.cfg other than the user one + Detta arkiv är aktiverat i en annan openmw.cfg än användarens + + + + Launcher::GraphicsPage + + Error receiving number of screens + Det gick inte att ta emot antalet skärmar + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + <br><b>SDL_GetNumVideoDisplays misslyckades:</b><br><br> + + + Screen + Skärm + + + Error receiving resolutions + Det gick inte att ta emot upplösningar + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + <br><b>SDL_GetNumDisplayModes misslyckades:</b><br><br> + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + <br><b>SDL_GetDisplayMode misslyckades:</b><br><br> + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + Det gick inte att skriva en OpenMW-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Importer finished + Importeraren klar + + + Failed to import settings from INI file. + Misslyckades att importera inställningar från INI-fil. + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna eller skapa %1 för att skriva </b></p><p>Kontrollera att du har rätt behörigheter och försök igen.</p></body></html> + + + + Launcher::MainDialog + + Close + Stäng + + + Launch OpenMW + Starta OpenMW + + + Help + Hjälp + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + First run + Första körning + + + Run &Installation Wizard + Kör &Installationsguide + + + Skip + Hoppa över + + + OpenMW %1 release + OpenMW version %1 + + + OpenMW development (%1) + OpenMW utvecklarversion (%1) + + + Compiled on %1 %2 + Kompilerad den %1 %2 + + + Error detecting Morrowind installation + Kunde inte hitta Morrowindinstallation + + + Run &Installation Wizard... + Kör &Installationsguide... + + + Error reading OpenMW configuration files + Fel när OpenMW-konfigurationsfil skulle läsas + + + Error writing OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle skrivas> + + + Error writing user settings file + Fel när användarinställningsfil skulle skrivas + + + Error writing Launcher configuration file + Fel när Startarens-konfigurationsfil skulle skrivas + + + No game file selected + Ingen spelfil vald + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + <html><head/><body><p><b>Välkommen till OpenMW!</b></p><p>Det är rekommenderat att du kör Installationsguiden.</p><p>Installationsguiden låter dig välja en befintlig Morrowindinstallation eller installera Morrowind för OpenMW.</p></body></html> + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning:</b><br><br>%1<br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + <br><b>Kunde inte hitta Data Files-platsen</b><br><br>Katalogen som innehåller datafilerna hittades inte. + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + <br>Problemet kan bero på en ofullständig installation av OpenMW.<br>Ominstallation av OpenMW kan lösa problemet.<br> + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna eller skapa %0 för att skriva</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + <br><b>Du har ingen spelfil markerad.</b><br><br>OpenMW kan inte starta utan en spelfil markerad.<br> + + + Error creating OpenMW configuration directory: code %0 + Kunde inte skapa konfigurationskatalog för OpenMW: kod %0 + + + <br><b>Could not create directory %0</b><br><br>%1<br> + <br><b>Kunde inte skapa katalog %0</b><br><br>%1<br> + + + + Launcher::SettingsPage + + Text file (*.txt) + Textfil (*.txt) + + + + MainWindow + + OpenMW Launcher + OpenMW Startare + + + OpenMW version + OpenMW version + + + toolBar + toolBar + + + Data Files + Datafiler + + + Allows to setup data files and directories + Låter dig ställa in datafiler och kataloger + + + Settings + Inställningar + + + Allows to tweak engine settings + Låter dig justera spelmotorinställningar + + + Import + Importera + + + Allows to import data from original engine + Låter dig importera data från originalmotorn + + + Display + Bild + + + Allows to change display settings + Låter dig ändra bildinställningar + + + + QObject + + Select configuration file + Välj konfigurationsfil + + + Select script file + Välj skriptfil + + + + SelectSubdirs + + Select directories you wish to add + Välj kataloger du vill lägga till + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + <html><head/><body><p>Gör dispositionsändring av handlare orsakad av handel permanent.</p></body></html> + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Gör att följare och eskorter till spelaren själva påbörjar strid med fiender som påbörjat strid med dem eller spelaren. Annars kommer de vänta tills fienden eller spelaren har attackerat dem först.</p></body></html> + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Gör att "Damage Fatigue"-magieffekten blir obegränsad såsom "Drain Fatigue"-effekten.</p><p>Det innebär att du, till skillnad från i Morrowind, kommer kunna slå ner figurer till marken med denna effekt.</p></body></html> + + + Uncapped Damage Fatigue + Obegränsad "Damage Fatigue" + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Avbryter strid med icke-spelbara figurer påverkade av "Calm"-besvjärjelser varje bildruta – såsom i Morrowind utan MCP.</p></body></html> + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p>Gör värderingen av fyllda "Soulgems" endast baserad på själens magnitud.</p></body></html> + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Får spelaren att simma något uppåt från siktlinjen. Endast applicerat på tredjepersonsperspektivet. Avsett att göra simning enklare.</p></body></html> + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + <html><head/><body><p>Gör det möjligt att stjäla föremål från icke-spelbara figurer som är avsvimmade.</p></body></html> + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Om aktiv kommer en en navigeringsmesh av världsgeometrin byggas i bakgrunden som används för pathfinding. Om inaktiv kommer endast pathgrid användas för att bygga vägar. Enkelkärnade processorer kan få kraftigt försämrad prestanda. Kan påverka prestandan på flerkärniga processorer något. Flerkärniga processorer kan ha olika fördröjning för att uppdatera navigeringsmesh. Förflyttning mellan externa världar och att gå in eller ut ur en plats producerar en uppdatering av navigeringsmesh. Icke-spelbara figurer kan inte hitta vägar innan navigeringsmesh har skapats runt om dem. Testa att inaktivera denna funktion om du vill ha en mer gammaldags AI som inte vet var den ska gå när du står bakom den där stenen och skjuter ett eldklot.</p></body></html> + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>Om aktiverat kommer icke-spelbara figurer göra undanmanövrar för att undvika kollisioner med andra.</p></body></html> + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Ta inte med varelsers vikt i beräkningen av hastighet.</p></body></html> + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Om denna inställning är aktiv tillåts spelaren plundra figurer (exempelvis tillkallade varelser) under deras dödsanimation, om de inte är i strid.</p><p>Om inställningen är inaktiv måste spelaren vänta tills dödsanimationen är slut. Detta gör det mycket svårare att exploatera tillkallade varelser (exempelvis plundra Draemoror eller Golden Saints för att få dyrbara vapen). Inställningen är i konflikt med skyltdocks-moddar som använder SkipAnim för att förhindra avslutning av dödsanimationer.</p></body></html> + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Gör att obeväpnade varelseattacker kan reducera rustningars skick, precis som attacker från icke-spelbara figurer och beväpnade varelser.</p></body></html> + + + Off + Av + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>Antalet exekveringstrådar som kommer användas för att beräkna fysikuppdateringar i bakgrunden.</p><p>Ett värde högre än 1 kräver att Bullet-biblioteket är kompilerat med multithreading-stöd.</p></body></html> + + + Cylinder + Cylinder + + + Visuals + Visuellt + + + Animations + Animationer + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>Använd animationer för magiska föremål, precis som för besvärjelser.</p></body></html> + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>Gör spelarens och icke-spelbara figurers rörelser mjukare. Rekommenderas att användas tillsammans med "vänd mot rörelseriktningen" aktiverad.</p></body></html> + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Ladda per-grupp KF-filer och skelettfiler från Animations-katalogen</p></body></html> + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + <html><head/><body><p>Påverkar sido- och diagonalförflyttningar. Förflyttningar blir mer realistiska om aktiv.</p><p>Om aktiv kommer spelarrollfiguren vända underkroppen i rikting mot förflyttningen. Överkroppen vänds delvis. Huvudet pekar alltid dit kameran ser. I stidsläge fungerar det bara för diagonal förflyttning. Utanför strid ändrar inställningen förflyttningar rakt höger och vänster också. Inställningen vänder också hela kroppen upp eller ner vid simning enligt förflyttningsriktningen.</p></body></html> + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade vapen (med koger och vapenslidor), kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade sköldar, kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>I tredjepersonsperspektiv kommer kameran svänga efter spelarens förflyttningsanimationer. Detta var det förvalda beteendet i OpenMW 0.48.0 och tidigare.</p></body></html> + + + Shaders + Shader + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + <html><head/><body><p>Om funktionen är aktiv kommer normalkartor (normal maps) att hittas och användas automatiskt om de har korrekt namn + (se 'normal map pattern', t.ex. för en bastextur foo.dds ska normalkartan heta foo_n.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + <html><head/><body><p>Se 'autoanvänd normalkartor (normal maps) på objekt'. Påverkar terräng.</p></body></html> + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + <html><head/><body><p>Om den här funktionen är aktiverad kommer spekularitetskartor (specular maps) att hittas och användas + (see 'specular map pattern', t.ex. för en bastextur foo.dds, + ska spekularitetskartan heta foo_spec.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen + (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + <html><head/><body><p>Om en fil med mönstret 'terrain specular map pattern' finns, använd den filen som en 'diffuse specular'-karta. Texturen måste innehålla färglagren i RGB-kanalerna (som vanligt) och spekularitet i alfakanalen.</p></body></html> + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + <html><head/><body><p>Normalt sett påverkas omgivningskartors reflektioner inte av ljus, vilket gör att omgivningskartor (och således så kallade bump mappade objekt) glöder i mörkret. + Morrowind Code Patch inkluderar en inställning att kringå detta genom att lägga omgivningskartor före ljussättningen. Det här är motsvarigheten till den inställningen. + Påverkade objekt kommer använda shaders. + </p></body></html> + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Gör att MSAA fungerar med alfa-testade modeller, vilket ger snyggare kanter utan synliga pixlar. Kan påverka prestandan negativt.</p></body></html> + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + <html><head/><body><p>Aktiverar mjuka partiklar på partikeleffekter. Denna teknik mjukar upp övergången mellan individuella partiklar och annan ogenomskinlig geometri genom att smälta samman dem.</p></body></html> + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Simulerar mip maps med coverage-preserving för att förhindra att alfa-testade modeller krymper när de kommer längre bort. Kommer göra att dessa modeller växer istället; se instruktioner i modinstallationen för hur detta ska ställas in.</p></body></html> + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>EXPERIMENTELLT: Förhindra att regn och snö faller genom tak och överhäng.</p></body></html> + + + Fog + Dimma + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Som standard blir dimman tätare proportionellt med ditt avstånd till avklippningsdistansen, vilket ger distortion vid skärmens kanter. + Denna inställning gör att dimman använder det faktiska ögonpunktsavståndet (så kallat euklidiskt avstånd) för att beräkna dimman, vilket får dimman att se mindre artificiell ut, särskilt vid hög FoV.</p></body></html> + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Använd exponentiell formel för dimma. Som standard används linjär dimma.</p></body></html> + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + <html><head/><body><p>Reducera synligheten av avklippsplanet genom att smälta samman objekt med himmelen.</p></body></html> + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>Fraktionen av det maximala avståndet där utsmetning mellan himmel och horisont påbörjas.</p></body></html> + + + Terrain + Terräng + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + <html><head/><body><p>Bestämmer hur stort ett objekt måste vara för att vara synligt på skärmen. Objektets storlek divideras med sitt avstånd till kameran. Resultatet av denna division jämförs med detta värde. Ju mindre värdet är, desto fler objekt kommer du se i scenen.</p></body></html> + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Om aktiverat används paging och LOD-algoritmer för att rita upp all terräng. Om inaktiverat visas endast terrängen i den laddade cellen.</p></body></html> + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + <html><head/><body><p>Använd objekt-paging för aktiva cellers rutnät.</p></body></html> + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>Om den här inställningen är aktiv kommer modeller med stöd för det att använda dag- och natt-bytesnoder.</p></body></html> + + + Post Processing + Efterbehandling + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer efterbehandling (post processing) att vara aktiverat.</p></body></html> + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>Återrendera transparenta objekt med forcerad alpha clipping.</p></body></html> + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Bestämmer hur mycket ögonanpassningen kan förändras från bildruta till bildruta. Lägre värden ger långsammare övergångar.</p></body></html> + + + Audio + Ljud + + + Select your preferred audio device. + Välj din föredragna ljudenhet. + + + Default + Förvalt + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + Denna inställning kontrollerar HRTF, vilket simulerar 3D-ljud på stereosystem. + + + HRTF + HRTF + + + Automatic + Automatisk + + + On + + + + Select your preferred HRTF profile. + Välj din föredragna HRTF-profil. + + + Interface + Gränssnitt + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> + + + Size of characters in game texts. + Storlek på tecken i speltext. + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer behållare som stödjer grafisk örtplockning (graphic herbalism) använda den funktionen istället för att öppna menyn.</p></body></html> + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + <html><head/><body><p>Förhindrar att handlare tar på sig föremål som säljs till dem.</p></body></html> + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + <html><head/><body><p>Tränare väljer nu endast de färdigheter som kan tränas genom att använda deras basfärdighetsvärde, vilket tillåter förbättring av merkantil utan att göra merkantil till en erbjuden färdighet.</p></body></html> + + + Miscellaneous + Diverse + + + Saves + Sparfiler + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + <html><head/><body><p>Denna inställning avgör huruvida mängden tid spelaren har spenderat i spelet kommer visas för varje sparat spel i Ladda spel-menyn.</p></body></html> + + + JPG + JPG + + + PNG + PNG + + + TGA + TGA + + + Testing + Testning + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> + + + Browse… + Bläddra… + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Kollision används för både fysiksimulering och navigationsmeshgenerering för pathfinding. Cylinder ger bäst förenlighet mellan tillgängliga navigeringsvägar och möjlighet att förflytta förbi dem. Ändring av detta värde påverkar navigeringsmeshgenereringen – därför kommer inte navigeringsmesh disk cache för ett värde vara användbart för ett annat. + + + Gameplay + Spelmekanik + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + <html><head/><body><p>Om aktiverad kommer magisk ammunition krävas för att förbigå normal vapenmotståndskraft eller -svaghet. Om inaktiverad krävs ett magiskt avståndsvapen eller en magisk ammunition.</p></body></html> + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Få förtrollade vapen utan Magisk-flagga att förbigå normal vapenmotståndskraft, såsom i Morrowind.</p></body></html> + + + cells + celler + + + Shadows + Skuggor + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + <html><head/><body><p>Typ av "compute scene bounds" beräkningsmetod att använda. Bounds (förvalt) för en bra balans mellan prestanda och skuggkvalitet, primitives för snyggare skuggor eller none för ingen beräkning alls.</p></body></html> + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><head/><body><p>64 spelenheter är 1 yard eller ungefär 0,9 meter i verkligheten</p></body></html> + + + unit(s) + enhet(er) + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + <html><head/><body><p>Aktiverar skuggor för icke-spelbara figurer och varelser bortsett från spelarrollfiguren. Kan ha en liten negativ prestandapåverkan.</p></body></html> + + + 512 + 512 + + + 1024 + 1024 + + + 2048 + 2048 + + + 4096 + 4096 + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><head/><body><p>Den fraktion av gränsen ovan vid vilken skuggor gradvis börjar blekna bort.</p></body></html> + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor exklusivt för spelarrollfiguren. Kan ha en mycket liten negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + <html><head/><body><p>Upplösningen för varje individuell skuggkarta. Ökning av den ökar skuggkvalitén signifikant, men kan ha en lättare negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><head/><body><p>Avståndet från kameran vid vilken skuggor helt försvinner.</p></body></html> + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för huvudsakligen icke-rörliga objekt. Kan ha en signifikant negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + <html><head/><body><p>På grund av begränsningar med Morrowinds data kan endast figurer ge ifrån sig skuggor inomhus, vilket vissa kan tycka vara distraherande.</p><p>Har ingen effekt om figur/spelarskuggor inte är aktiverat.</p></body></html> + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för terräng, inklusive avlägsen terräng. Kan ha en signifikant negativ påverkan på prestanda och skuggkvalité.</p></body></html> + + + Lighting + Ljussättning + + + Tooltip + Inforuta + + + Crosshair + Hårkors + + + Screenshots + Skärmdumpar + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + <html><head/><body><p>Ge figurer en möjlighet att simma över vattenytan när de följer andra figurer, oberoende av deras förmåga att simma. Har endast effekt när navigeringsmesh är aktiverat.</p></body></html> + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + <html><head/><body><p>Effekter av reflekterade "Absorb"-besvärjelser speglas inte – såsom i Morrowind.</p></body></html> + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + <html><head/><body><p>Maximala avståndet där ljuskällor syns (mätt i enheter).</p><p>Värdet 0 ger oändligt avstånd.</p></body></html> + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + <html><head/><body><p>Maximalt antal ljuskällor per objekt.</p><p>Ett lågt tal nära det förvalda kommer orsaka att ljuskällor poppar upp som vid ljussättningsmetoden Gammaldags.</p></body></html> + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + <html><head/><body><p>Fraktion av det maximala avståndet från vilket ljuskällor börjar blekna.</p><p>Välj ett lågt värde för långsammare övergång eller högre värde för snabbare övergång.</p></body></html> + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + <html><head/><body><p>Välj intern hantering av ljuskällor.</p> +<p> "Gammaldags" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.</p> +<p>"Shader (kompatibilitet)" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.</p> +<p> "Shader" har alla fördelar som "Shader (kompatibilitet)" har, men med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara.</p></body></html> + + + Legacy + Gammaldags + + + Shaders (compatibility) + Shader (kompatibilitet) + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + <html><head/><body><p>Multiplikator för ljusens gränssfär.</p><p>Högre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.</p><p>Påverkar inte ljusstyrkan.</p></body></html> + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + <html><head/><body><p>Minsta omgivande ljusstyrka i interiörer.</p><p>Öka värdet om du anser att interiörer är för mörka.</p></body></html> + + + In third-person view, use the camera as the sound listener instead of the player character. + Använd kameran som ljudlyssnare istället för spelarrollfiguren i tredjepersonsperspektivet. + + + Permanent Barter Disposition Changes + Permanenta handelsförändringar + + + Classic Calm Spells Behavior + Klassiskt beteende för "Calm"-besvärjelser + + + NPCs Avoid Collisions + Icke-spelbara figurer undviker kollisioner + + + Soulgem Values Rebalance + Ombalansering av "Soulgems" värdering + + + Day Night Switch Nodes + Dag/natt-bytesnoder + + + Followers Defend Immediately + Följare försvarar omedelbart + + + Only Magical Ammo Bypass Resistance + Bara magisk ammunition förbigår motstånd + + + Graphic Herbalism + Grafisk örtplockning + + + Swim Upward Correction + Simma uppåt korrigering + + + Enchanted Weapons Are Magical + Förtrollade vapen är magiska + + + Merchant Equipping Fix + Handlare klär inte på sig + + + Can Loot During Death Animation + Kan plundra under dödsanimation + + + Classic Reflected Absorb Spells Behavior + Klassiskt beteende för reflekterade "Absorb"-besvärjelser + + + Unarmed Creature Attacks Damage Armor + Obeväpnad attack från varelser skadar rustning + + + Affect Werewolves + Påverka varulvar + + + Do Not Affect Werewolves + Påverka inte varulvar + + + Background Physics Threads + Fysiktrådar i bakgrunden + + + Actor Collision Shape Type + Figurers kollisionsformtyp + + + Axis-Aligned Bounding Box + Axeljusterad begränsningslåda + + + Rotating Box + Roterande låda + + + Smooth Movement + Mjuka rörelser + + + Use Additional Animation Sources + Använd extra animationskällor + + + Weapon Sheathing + Vapenhölstring + + + Shield Sheathing + Sköldhölstring + + + Player Movement Ignores Animation + Spelarförflyttningar ignorerar animation + + + Use Magic Item Animation + Animationer för magiska föremål + + + Auto Use Object Normal Maps + Använd automatiskt normalkartor på objekt + + + Soft Particles + Mjuka partiklar + + + Auto Use Object Specular Maps + Använd automatiskt spekularitetskartor på objekt + + + Auto Use Terrain Normal Maps + Använd automatiskt normalkartor på terräng + + + Auto Use Terrain Specular Maps + Använd automatiskt spekularitetskartor på terräng + + + Use Anti-Aliased Alpha Testing + Använd kantutjämnad alfa-testning + + + Bump/Reflect Map Local Lighting + Bump/Reflektionskartor lokalt ljus + + + Weather Particle Occlusion + Regn/snö blockeras av tak + + + Exponential Fog + Exponentiell dimma + + + Radial Fog + Radiell dimma + + + Sky Blending Start + Smeta ut horisont/himmel, start + + + Sky Blending + Smeta ut horisont/himmel + + + Object Paging Min Size + Object paging needs a better translation + Minsta storlek för objektpaging + + + Viewing Distance + Siktavstånd + + + Distant Land + Avlägsen terräng + + + Active Grid Object Paging + Object paging needs a better translation + Objektpaging i aktivt rutnät + + + Transparent Postpass + Will return to later + + + + Auto Exposure Speed + Autoexponeringshastighet + + + Enable Post Processing + Aktivera efterbehandling (post processing) + + + Shadow Planes Computation Method + Skuggplaner beräkningsmetod + + + Enable Actor Shadows + Aktivera rollfigurskuggor + + + Fade Start Multiplier + Blekningsstartmultiplikator + + + Enable Player Shadows + Aktivera spelarskuggor + + + Shadow Map Resolution + Skuggkartsupplösning + + + Shadow Distance Limit: + Skuggavståndsgräns: + + + Enable Object Shadows + Aktivera objektskuggor + + + Enable Indoor Shadows + Aktivera interiöra skuggor + + + Enable Terrain Shadows + Aktivera terrängskuggor + + + Maximum Light Distance + Maximalt ljusavstånd + + + Max Lights + Max antal ljuskällor + + + Lighting Method + Ljussättningsmetod + + + Bounding Sphere Multiplier + Gränssfärsmultiplikator + + + Minimum Interior Brightness + Minsta ljusstyrka i interiörer + + + Audio Device + Ljudenhet + + + HRTF Profile + HRTF-profil + + + Tooltip and Crosshair + Inforuta och hårkors + + + GUI Scaling Factor + Skalningsfaktor för gränssnitt + + + Show Effect Duration + Visa effektvaraktighet + + + Change Dialogue Topic Color + Ändra färg på dialogämnen + + + Font Size + Fontstorlek + + + Show Projectile Damage + Visa projektilskada + + + Show Melee Info + Visa närstridsinfo + + + Stretch Menu Background + Sträck ut menybakgrund + + + Show Owned Objects + Visa ägda objekt + + + Show Enchant Chance + Visa chans för "Enchant" + + + Maximum Quicksaves + Max snabbsparfiler + + + Screenshot Format + Skärmdumpformat + + + Grab Cursor + Ta över muspekaren + + + Default Cell + Förinställd cell + + + Bounds + Bounds + + + Primitives + Primitives + + + None + None + + + Always Allow Actors to Follow over Water + Tillåt alltid figurer att följa över vatten + + + Racial Variation in Speed Fix + Fix för varelsers hastighetsvariation + + + Use Navigation Mesh for Pathfinding + Använd navigeringsmesh för pathfinding + + + Trainers Choose Offered Skills by Base Value + Tränare väljer erbjudna färdigheter efter grundvärde + + + Steal from Knocked out Actors in Combat + Stjäl från avsvimmade rollfigurer i strid + + + Factor Strength into Hand-to-Hand Combat + Räkna in styrka i obeväpnad strid + + + Turn to Movement Direction + Vänd mot rörelseriktningen + + + Adjust Coverage for Alpha Test + Justera täckning för alfa-testning + + + Use the Camera as the Sound Listener + Använd kameran som ljudlyssnaren + + + Can Zoom on Maps + Kan zooma på kartor + + + Add "Time Played" to Saves + Lägg till spelad tid i sparfiler + + + Notify on Saved Screenshot + Ge notis vid sparad skärmdump + + + Skip Menu and Generate Default Character + Hoppa över meny och generera förinställd rollfigur + + + Start Default Character at + Starta förinställd rollfigur vid + + + Run Script After Startup: + Kör skript efter uppstart: + + + diff --git a/files/lang/wizard_sv.ts b/files/lang/wizard_sv.ts new file mode 100644 index 0000000000..7f2f4f67fc --- /dev/null +++ b/files/lang/wizard_sv.ts @@ -0,0 +1,678 @@ + + + + + ComponentSelectionPage + + WizardPage + WizardPage + + + Select Components + Välj komponenter + + + Which components should be installed? + Vilka komponenter ska installeras? + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + <html><head/><body><p>Välj vilka officiella Morrowindexpansioner som ska installeras. För bäst resultat rekommenderas att båda expansionerna installeras.</p><p><span style=" font-weight:bold;">Note:</span> Det är möjligt att installera expansioner senare genom att köra denna guide igen.<br/></p></body></html> + + + Selected components: + Valda komponenter: + + + + ConclusionPage + + WizardPage + WizardPage + + + Completing the OpenMW Wizard + Färdigställer OpenMWs installationsguide + + + Placeholder + Placeholder + + + + ExistingInstallationPage + + WizardPage + WizardPage + + + Select Existing Installation + Välj befintlig installation + + + Select an existing installation for OpenMW to use or modify. + Välj en befintlig installation som OpenMW kan använda eller modifiera. + + + Detected installations: + Hittade installationer: + + + Browse... + Bläddra... + + + + ImportPage + + WizardPage + WizardPage + + + Import Settings + Importera inställningar + + + Import settings from the Morrowind installation. + Importera inställningar från Morrowindinstallationen. + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + <html><head/><body><p>OpenMW behöver importera inställningar från Morrowinds konfigurationsfil för att fungera korrekt.</p><p><span style=" font-weight:bold;">Notera:</span> Det är möjligt att importera inställningarna senare genom att köra denna guide igen.</p><p/></body></html> + + + Import Settings From Morrowind.ini + Importera inställningar från Morrowind.ini + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkering + + + Import Bitmap Fonts Setup From Morrowind.ini + Importera bitmapfonter från Morrowind.ini + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller därför andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + + InstallationPage + + WizardPage + WizardPage + + + Installing + Installerar + + + Please wait while Morrowind is installed on your computer. + Vänta medan Morrowind installeras på din dator. + + + + InstallationTargetPage + + WizardPage + WizardPage + + + Select Installation Destination + Välj installationsplats + + + Where should Morrowind be installed? + Var ska Morrowind installeras? + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Morrowind will be installed to the following location. + Morrowind kommer installeras på följande plats. + + + Browse... + Bläddra... + + + + IntroPage + + WizardPage + WizardPage + + + Welcome to the OpenMW Wizard + Välkommen till OpenMWs installationsguide + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + Denna guide hjälper dig att installera Morrowind och expansionerna för OpenMW. + + + + LanguageSelectionPage + + WizardPage + WizardPage + + + Select Morrowind Language + Välj Morrowinds språk + + + What is the language of the Morrowind installation? + Vad är språket på Morrowindinstallationen? + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + Select the language of the Morrowind installation. + Välj språket på Morrowindinstallationen. + + + + MethodSelectionPage + + WizardPage + WizardPage + + + Select Installation Method + Välj installationsmetod + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + <html><head/><body><p>Välj hur du vill installera <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + Retail CD/DVD + Köpt CD/DVD + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + Install from a retail disc to a new location. + Installera från en köpt skiva till en ny plats. + + + Existing Installation + Befintlig installation + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Select an existing installation. + Välj en befintlig installation. + + + Don't have a copy? + Äger du inte spelet? + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + Buy the game + Köp spelet + + + + QObject + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + <br><b>Kunde inte hitta Morrowind.ini</b><br><br>Guiden behöver uppdatera inställningarna i denna fil.<br><br>Tryck på "Bläddra..." för att specificera filens plats manuellt.<br> + + + B&rowse... + B&läddra... + + + Select configuration file + Välj konfigurationsfil + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + <b>Morrowind.bsa</b> saknas!<br>Se till att din Morrowindinstallation är komplett. + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + <br><b>Det kan finnas nyare version av Morrowind tillgänglig.</b><br><br>Vill du fortsätta ändå?<br> + + + Most recent Morrowind not detected + Senaste versionen av Morrowind hittades inte + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + Välj ett giltigt %1 installationsmedium.<br><b>Tips</b>: säkerställ att det finns åtminstone en <b>.cab</b>-fil. + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + Det kan finnas en mer uppdaterad version av Morrowind tillgänglig.<br><br>Vill du fortsätta ändå? + + + + Wizard::ComponentSelectionPage + + &Install + &Installera + + + &Skip + &Hoppa över + + + Morrowind (installed) + Morrowind (installerat) + + + Morrowind + Morrowind + + + Tribunal (installed) + Tribunal (installerat) + + + Tribunal + Tribunal + + + Bloodmoon (installed) + Bloodmoon (installerat) + + + Bloodmoon + Bloodmoon + + + About to install Tribunal after Bloodmoon + På väg att installera Tribunal efter Bloodmoon + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + <html><head/><body><p><b>Du håller på att installera Tribunal</b></p><p>Bloodmoon finns redan installerat på din dator.</p><p>Det är dock rekommenderat att du installerar Tribunal före Bloodmoon.</p><p>Vill du ominstallera Bloodmoon?</p></body></html> + + + Re-install &Bloodmoon + Ominstallera &Bloodmoon + + + + Wizard::ConclusionPage + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + <html><head/><body><p>OpenMWs Installationsguide har installerat Morrowind på din dator.</p></body></html> + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + <html><head/><body><p>OpenMWs installationsguide har justerat din befintliga Morrowindinstallation.</body></html> + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + <html><head/><body><p>OpenMWs installationsguide misslyckades med att installera Morrowind på din dator.</p><p>Vänligen rapportera eventuella buggar på vår <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Se till att du inkluderar installationsloggen.</p><br/></body></html> + + + + Wizard::ExistingInstallationPage + + No existing installations detected + Inga befintliga installationer hittades + + + Error detecting Morrowind configuration + Kunde inte hitta Morrowind-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Select Morrowind.esm (located in Data Files) + Markera Morrowind.esm (hittas i Data Files) + + + Morrowind master file (Morrowind.esm) + Morrowind masterfil (Morrowind.esm) + + + Error detecting Morrowind files + Kunde inte hitta Morrowindfiler + + + + Wizard::InstallationPage + + <p>Attempting to install component %1.</p> + <p>Försöker installera komponent %1.</p> + + + Attempting to install component %1. + Försöker installera komponent %1. + + + %1 Installation + %1 Installation + + + Select %1 installation media + Välj %1 installationsmedia + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + <p><br/><span style="color:red;"><b>Fel: Installationen avbröts av användaren</b></span></p> + + + <p>Detected old version of component Morrowind.</p> + <p>Hittade gammal version av Morrowind.</p> + + + Detected old version of component Morrowind. + Hittade gammal version av komponenten Morrowind. + + + Morrowind Installation + Installation av Morrowind + + + Installation finished + Installationen klar + + + Installation completed successfully! + Installationen slutfördes! + + + Installation failed! + Installationen misslyckades! + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + <p><br/><span style="color:red;"><b>Fel: %1</b></p> + + + <p><span style="color:red;"><b>%1</b></p> + <p><span style="color:red;"><b>%1</b></p> + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + <html><head/><body><p><b>Guiden har stött på ett fel</b></p><p>Felet som rapporterades var:</p><p>%1</p><p>Tryck på &quot;Visa detaljer...&quot; för mer information.</p></body></html> + + + An error occurred + Ett fel uppstod + + + + Wizard::InstallationTargetPage + + Error creating destination + Fel när målkatalogen skulle skapas + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skriva till målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + <html><head/><body><p><b>Målkatalogen är inte tom</b></p><p>Det finns en befintlig Morrowindinstallation i den specificerade katalogen.</p><p>Välj en annan katalog eller gå tillbaka och välj katalogen som en befintlig installation.</p></body></html> + + + Insufficient permissions + Otillräckliga behörigheter + + + Destination not empty + Platsen är inte tom + + + Select where to install Morrowind + Välj där Morrowind ska installeras + + + + Wizard::LanguageSelectionPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + + Wizard::MainWizard + + OpenMW Wizard + OpenMW installationsguide + + + Error opening Wizard log file + Kunde inte öppna guidens loggfil + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för att skriva</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för läsning</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + Quit Wizard + Avsluta guiden + + + Are you sure you want to exit the Wizard? + Är du säker på att du vill avsluta guiden? + + + Error creating OpenMW configuration directory + Fel vid skapande av OpenMW-konfigurationskatalog + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa %1</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error writing OpenMW configuration file + Kunde inte skriva OpenMW-konfigurationsfil + + + + Wizard::UnshieldWorker + + Failed to open Morrowind configuration file! + Kunde inte öppna en Morrowind-konfigurationsfil! + + + Opening %1 failed: %2. + Öppnar %1 misslyckad: %2. + + + Failed to write Morrowind configuration file! + Kunde inte skriva en Morrowind-konfigurationsfil! + + + Writing to %1 failed: %2. + Skriver till %1 misslyckad: %2. + + + Installing: %1 + Installerar: %1 + + + Installing: %1 directory + Installerar: %1 katalog + + + Installation finished! + Installationen slutförd! + + + Component parameter is invalid! + Komponentparametern är ogiltig! + + + An invalid component parameter was supplied. + En ogiltig komponentparameter angavs. + + + Failed to find a valid archive containing %1.bsa! Retrying. + Misslyckades att hitta ett giltigt arkiv som innehåller %1.bsa! Försöker igen. + + + Installing %1 + Installerar %1 + + + Installation media path not set! + Sökväg till installationsmedia inte inställd! + + + The source path for %1 was not set. + Källsökvägen för %1 ställdes inte in. + + + Cannot create temporary directory! + Kan inte skapa temporär katalog! + + + Failed to create %1. + Kunde inte skapa %1. + + + Cannot move into temporary directory! + Kan inte flytta till temporär katalog! + + + Failed to move into %1. + Misslyckades att flytta till %1. + + + Moving installation files + Flyttar installationsfiler + + + Could not install directory! + Kunde inte installera katalog! + + + Installing %1 to %2 failed. + Installation %1 till %2 misslyckades. + + + Could not install translation file! + Kunde inte installera översättningsfil! + + + Failed to install *%1 files. + Kunde inte installera *%1 filer. + + + Could not install Morrowind data file! + Kunde inte installera Morrowind-datafil! + + + Failed to install %1. + Misslyckades att installera %1. + + + Could not install Morrowind configuration file! + Kunde inte installera Morrowind-konfigurationsfil! + + + Installing: Sound directory + Installerar: Ljudkatalog + + + Could not find Tribunal data file! + Tribunal-datafil hittades inte! + + + Failed to find %1. + Misslyckades att hitta %1. + + + Could not find Tribunal patch file! + Tribunal-patchfil hittades inte! + + + Could not find Bloodmoon data file! + Bloodmoon-datafil hittades inte! + + + Updating Morrowind configuration file + Uppdaterar Morrowind-konfigurationsfil + + + %1 installation finished! + %1 installation klar! + + + Extracting: %1 + Extraherar: %1 + + + Failed to open InstallShield Cabinet File. + Misslyckades att öppna en InstallShield Cabinet-fil. + + + Opening %1 failed. + Misslyckades att öppna %1. + + + Failed to extract %1. + Misslyckades att extrahera %1. + + + Complete path: %1 + Färdigställ sökväg: %1 + + + From aea7b10986343bca77ae74c9f2892c9cf7161dc6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 17 Apr 2024 22:53:55 +0300 Subject: [PATCH 1360/2167] Add dummy BGSM/BGEM file reader --- components/CMakeLists.txt | 4 + components/bgsm/file.cpp | 20 +++++ components/bgsm/file.hpp | 164 +++++++++++++++++++++++++++++++++++++ components/bgsm/reader.cpp | 35 ++++++++ components/bgsm/reader.hpp | 25 ++++++ components/bgsm/stream.cpp | 110 +++++++++++++++++++++++++ components/bgsm/stream.hpp | 143 ++++++++++++++++++++++++++++++++ 7 files changed, 501 insertions(+) create mode 100644 components/bgsm/file.cpp create mode 100644 components/bgsm/file.hpp create mode 100644 components/bgsm/reader.cpp create mode 100644 components/bgsm/reader.hpp create mode 100644 components/bgsm/stream.cpp create mode 100644 components/bgsm/stream.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 68411be2fc..084deaea58 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -107,6 +107,10 @@ add_component_dir (settings windowmode ) +add_component_dir (bgsm + reader stream file + ) + add_component_dir (bsa bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream ) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp new file mode 100644 index 0000000000..870d9e4067 --- /dev/null +++ b/components/bgsm/file.cpp @@ -0,0 +1,20 @@ +#include "file.hpp" + +#include "stream.hpp" + +namespace Bgsm +{ + void MaterialFile::read(BGSMStream& stream) + { + } + + void BGSMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + } + + void BGEMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + } +} diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp new file mode 100644 index 0000000000..117b135e4f --- /dev/null +++ b/components/bgsm/file.hpp @@ -0,0 +1,164 @@ +#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP +#define OPENMW_COMPONENTS_BGSM_FILE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace Bgsm +{ + class BGSMStream; + + enum class ShaderType + { + Lighting, + Effect, + }; + + struct MaterialFile + { + ShaderType mShaderType; + std::uint32_t mVersion; + std::uint32_t mClamp; + osg::Vec2f mUVOffset, mUVScale; + float mTransparency; + std::uint8_t mSourceBlendMode; + std::uint32_t mDestinationBlendMode; + std::uint32_t mAlphaTestMode; + std::uint8_t mAlphaTestRef; + bool mAlphaTest; + bool mDepthWrite, mDepthTest; + bool mSSR; + bool mWetnessControlSSR; + bool mDecal; + bool mTwoSided; + bool mDecalNoFade; + bool mNonOccluder; + bool mRefraction; + bool mRefractionFalloff; + float mRefractionPower; + bool mEnvMap; + float mEnvMapMaskScale; + bool mDepthBias; + bool mGrayscaleToPaletteColor; + std::uint8_t mMaskWrites; + + MaterialFile() = default; + virtual void read(BGSMStream& stream); + virtual ~MaterialFile() {} + }; + + struct BGSMFile : MaterialFile + { + std::string mDiffuseMap; + std::string mNormalMap; + std::string mSmoothSpecMap; + std::string mGreyscaleMap; + std::string mGlowMap; + std::string mWrinkleMap; + std::string mSpecularMap; + std::string mLightingMap; + std::string mFlowMap; + std::string mDistanceFieldAlphaMap; + std::string mEnvMap; + std::string mInnerLayerMap; + std::string mDisplacementMap; + bool mEnableEditorAlphaRef; + bool mTranslucency; + bool mTranslucencyThickObject; + bool mTranslucencyMixAlbedoWithSubsurfaceColor; + osg::Vec4f mTranslucencySubsurfaceColor; + float mTranslucencyTransmissiveScale; + float mTranslucencyTurbulence; + bool mRimLighting; + float mRimPower; + float mBackLightPower; + bool mSursurfaceLighting; + float mSubsurfaceLightingRolloff; + bool mSpecularEnabled; + osg::Vec4f mSpecularColor; + float mSpecularMult; + float mSmoothness; + float mFresnelPower; + float mWetnessControlSpecScale; + float mWetnessControlSpecPowerScale; + float mWetnessControlSpecMinvar; + float mWetnessControlEnvMapScale; + float mWetnessControlFresnelPower; + float mWetnessControlMetalness; + bool mPBR; + bool mCustomPorosity; + float mPorosityValue; + std::string mRootMaterialPath; + bool mAnisoLighting; + bool mEmitEnabled; + osg::Vec4f mEmittanceColor; + float mEmittanceMult; + bool mModelSpaceNormals; + bool mExternalEmittance; + float mLumEmittance; + bool mUseAdaptiveEmissive; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mBackLighting; + bool mReceiveShadows; + bool mHideSecret; + bool mCastShadows; + bool mDissolveFade; + bool mAssumeShadowmask; + bool mHasGlowMap; + bool mEnvMapWindow; + bool mEnvMapEye; + bool mHair; + osg::Vec4f mHairTintColor; + bool mTree; + bool mFacegen; + bool mSkinTint; + bool mTessellate; + osg::Vec2f mDisplacementMapParams; + osg::Vec3f mTesselationParams; + float mGrayscaleToPaletteScale; + bool mSkewSpecularAlpha; + bool mTerrain; + osg::Vec3f mTerrainParams; + + void read(BGSMStream& stream) override; + }; + + struct BGEMFile : MaterialFile + { + std::string mBaseMap; + std::string mGrayscaleMap; + std::string mEnvMap; + std::string mNormalMap; + std::string mEnvMapMask; + std::string mSpecularMap; + std::string mLightingMap; + std::string mGlowMap; + bool mBlood; + bool mEffectLighting; + bool mFalloff; + bool mFalloffColor; + bool mGrayscaleToPaletteAlpha; + bool mSoft; + osg::Vec4f mBaseColor; + float mBaseColorScale; + osg::Vec4f mFalloffParams; + float mLightingInfluence; + std::uint8_t mEnvmapMinLOD; + float mSoftDepth; + osg::Vec4f mEmittanceColor; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mHasGlowMap; + bool mEffectPbrSpecular; + + void read(BGSMStream& stream) override; + }; + + using MaterialFilePtr = std::shared_ptr; +} +#endif diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp new file mode 100644 index 0000000000..c89d872bd7 --- /dev/null +++ b/components/bgsm/reader.cpp @@ -0,0 +1,35 @@ +#include "reader.hpp" + +#include +#include +#include + +#include "file.hpp" +#include "stream.hpp" + +namespace Bgsm +{ + void Reader::parse(Files::IStreamPtr&& inputStream) + { + BGSMStream stream(*this, std::move(inputStream)); + + std::array signature; + stream.readArray(signature); + std::string shaderType(signature.data(), 4); + if (shaderType == "BGEM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Effect; + } + else if (shaderType == "BGSM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Lighting; + } + else + throw std::runtime_error("Invalid material file"); + + mFile->read(stream); + } + +} diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp new file mode 100644 index 0000000000..5ac67c0467 --- /dev/null +++ b/components/bgsm/reader.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP +#define OPENMW_COMPONENTS_BGSM_READER_HPP + +#include +#include +#include +#include + +#include + +#include "file.hpp" + +namespace Bgsm +{ + class Reader + { + std::unique_ptr mFile; + + public: + void parse(Files::IStreamPtr&& stream); + + std::uint32_t getVersion() const { return mFile->mVersion; } + }; +} +#endif diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp new file mode 100644 index 0000000000..d8434fcb8a --- /dev/null +++ b/components/bgsm/stream.cpp @@ -0,0 +1,110 @@ +#include "stream.hpp" + +#include + +#include "reader.hpp" + +namespace +{ + + // Read a range of elements into a dynamic buffer per-element + // This one should be used if the type cannot be read contiguously + // (e.g. quaternions) + template + void readRange(Bgsm::BGSMStream& stream, T* dest, size_t size) + { + for (T& value : std::span(dest, size)) + stream.read(value); + } + + // Read a range of elements into a dynamic buffer + // This one should be used if the type can be read contiguously as an array of a different type + // (e.g. osg::VecXf can be read as a float array of X elements) + template + void readAlignedRange(Files::IStreamPtr& stream, T* dest, size_t size) + { + static_assert(std::is_standard_layout_v); + static_assert(std::alignment_of_v == std::alignment_of_v); + static_assert(sizeof(T) == sizeof(elementType) * numElements); + Bgsm::readDynamicBufferOfType(stream, reinterpret_cast(dest), size * numElements); + } + +} + +namespace Bgsm +{ + + std::uint32_t BGSMStream::getVersion() const + { + return mReader.getVersion(); + } + + std::string BGSMStream::getSizedString(size_t length) + { + std::string str(length, '\0'); + mStream->read(str.data(), length); + if (mStream->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); + return str; + } + + void BGSMStream::getSizedStrings(std::vector& vec, size_t size) + { + vec.resize(size); + for (size_t i = 0; i < vec.size(); i++) + vec[i] = getSizedString(); + } + + template <> + void BGSMStream::read(osg::Vec2f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec3f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec4f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(std::string& str) + { + str = getSizedString(); + } + + template <> + void BGSMStream::read(osg::Vec2f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(osg::Vec3f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(osg::Vec4f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(std::string* dest, size_t size) + { + for (std::string& value : std::span(dest, size)) + value = getSizedString(); + } + +} diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp new file mode 100644 index 0000000000..8b0e1efdbe --- /dev/null +++ b/components/bgsm/stream.hpp @@ -0,0 +1,143 @@ +#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP +#define OPENMW_COMPONENTS_BGSM_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Bgsm +{ + class Reader; + + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of " + + std::to_string(numInstances) + " instances"); + if constexpr (Misc::IS_BIG_ENDIAN) + for (std::size_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); + } + + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances]) + { + readBufferOfType(pIStream, static_cast(dest)); + } + + template + inline void readDynamicBufferOfType(Files::IStreamPtr& pIStream, T* dest, std::size_t numInstances) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") dynamic buffer of " + + std::to_string(numInstances) + " instances"); + if constexpr (Misc::IS_BIG_ENDIAN) + for (std::size_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); + } + + class BGSMStream + { + const Reader& mReader; + Files::IStreamPtr mStream; + + public: + explicit BGSMStream( + const Reader& reader, Files::IStreamPtr&& stream) + : mReader(reader) + , mStream(std::move(stream)) + { + } + + const Reader& getFile() const { return mReader; } + + std::uint32_t getVersion() const; + + void skip(size_t size) { mStream->ignore(size); } + + /// Read into a single instance of type + template + void read(T& data) + { + readBufferOfType<1>(mStream, &data); + } + + /// Read multiple instances of type into an array + template + void readArray(std::array& arr) + { + readBufferOfType(mStream, arr.data()); + } + + /// Read instances of type into a dynamic buffer + template + void read(T* dest, size_t size) + { + readDynamicBufferOfType(mStream, dest, size); + } + + /// Read multiple instances of type into a vector + template + void readVector(std::vector& vec, size_t size) + { + if (size == 0) + return; + vec.resize(size); + read(vec.data(), size); + } + + /// Extract an instance of type + template + T get() + { + T data; + read(data); + return data; + } + + /// Read a string of the given length + std::string getSizedString(size_t length); + + /// Read a string of the length specified in the file + std::string getSizedString() { return getSizedString(get()); } + + /// Read a list of strings + void getSizedStrings(std::vector& vec, size_t size); + }; + + template <> + void BGSMStream::read(osg::Vec2f& vec); + template <> + void BGSMStream::read(osg::Vec3f& vec); + template <> + void BGSMStream::read(osg::Vec4f& vec); + template <> + void BGSMStream::read(std::string& str); + + template <> + void BGSMStream::read(osg::Vec2f* dest, size_t size); + template <> + void BGSMStream::read(osg::Vec3f* dest, size_t size); + template <> + void BGSMStream::read(osg::Vec4f* dest, size_t size); + template <> + void BGSMStream::read(std::string* dest, size_t size); +} + +#endif From 4a03555d53d6794d077470e33a65367ca8f61dba Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 01:00:49 +0300 Subject: [PATCH 1361/2167] Add BGEM/BGSM file support to niftest --- apps/niftest/niftest.cpp | 50 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b37d85d739..3cd4382121 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,13 @@ bool isNIF(const std::filesystem::path& filename) { return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); } + +/// Check if the file is a material file. +bool isMaterial(const std::filesystem::path& filename) +{ + return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm"); +} + /// See if the file has the "bsa" extension. bool isBSA(const std::filesystem::path& filename) { @@ -81,6 +89,36 @@ void readNIF( } } + +void readMaterial( + const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) +{ + const std::string pathStr = Files::pathToUnicodeString(path); + if (!quiet) + { + if (hasExtension(path, ".bgem")) + std::cout << "Reading BGEM file '" << pathStr << "'"; + else + std::cout << "Reading BGSM file '" << pathStr << "'"; + if (!source.empty()) + std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; + std::cout << std::endl; + } + const std::filesystem::path fullPath = !source.empty() ? source / path : path; + try + { + Bgsm::Reader reader; + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } + catch (std::exception& e) + { + std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; + } +} + /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. void readVFS(std::unique_ptr&& archive, const std::filesystem::path& archivePath, bool quiet) @@ -101,6 +139,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat { readNIF(archivePath, name.value(), &vfs, quiet); } + else if (isMaterial(name.value())) + { + readMaterial(archivePath, name.value(), &vfs, quiet); + } } if (!archivePath.empty() && !isBSA(archivePath)) @@ -129,10 +171,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool& writeDebugLog, bool& quiet) { - bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files + bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files Usages: - niftest + niftest Scan the file or directories for NIF errors. Allowed options)"); @@ -225,6 +267,10 @@ int main(int argc, char** argv) { readNIF({}, path, vfs.get(), quiet); } + else if (isMaterial(path)) + { + readMaterial({}, path, vfs.get(), quiet); + } else if (auto archive = makeArchive(path)) { readVFS(std::move(archive), path, quiet); From 124df1be6115a651308c6f310e913aa53c552302 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 01:51:31 +0300 Subject: [PATCH 1362/2167] Parse shared part of material files --- components/bgsm/file.cpp | 34 ++++++++++++++++++++++++++++++++++ components/bgsm/file.hpp | 6 +++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 870d9e4067..1ad1918ccf 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -6,6 +6,40 @@ namespace Bgsm { void MaterialFile::read(BGSMStream& stream) { + stream.read(mVersion); + stream.read(mClamp); + stream.read(mUVOffset); + stream.read(mUVScale); + stream.read(mTransparency); + stream.read(mAlphaBlend); + stream.read(mSourceBlendMode); + stream.read(mDestinationBlendMode); + stream.read(mAlphaTestThreshold); + stream.read(mAlphaTest); + stream.read(mDepthWrite); + stream.read(mSSR); + stream.read(mWetnessControlSSR); + stream.read(mDecal); + stream.read(mTwoSided); + stream.read(mDecalNoFade); + stream.read(mNonOccluder); + stream.read(mRefraction); + stream.read(mRefractionFalloff); + stream.read(mRefractionPower); + if (mVersion < 10) + { + stream.read(mEnvMap); + stream.read(mEnvMapMaskScale); + } + else + { + stream.read(mDepthBias); + } + stream.read(mGrayscaleToPaletteColor); + if (mVersion >= 6) + { + stream.read(mMaskWrites); + } } void BGSMFile::read(BGSMStream& stream) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 117b135e4f..db0059cd29 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -27,10 +27,10 @@ namespace Bgsm std::uint32_t mClamp; osg::Vec2f mUVOffset, mUVScale; float mTransparency; - std::uint8_t mSourceBlendMode; + bool mAlphaBlend; + std::uint32_t mSourceBlendMode; std::uint32_t mDestinationBlendMode; - std::uint32_t mAlphaTestMode; - std::uint8_t mAlphaTestRef; + std::uint8_t mAlphaTestThreshold; bool mAlphaTest; bool mDepthWrite, mDepthTest; bool mSSR; From 015aca2cfd986bc048282828e246e042506ce585 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:28:48 +0300 Subject: [PATCH 1363/2167] Initial BGSM file parsing --- components/bgsm/file.cpp | 124 +++++++++++++++++++++++++++++++++++++++ components/bgsm/file.hpp | 6 +- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 1ad1918ccf..bb627ae34e 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -45,6 +45,130 @@ namespace Bgsm void BGSMFile::read(BGSMStream& stream) { MaterialFile::read(stream); + + stream.read(mDiffuseMap); + stream.read(mNormalMap); + stream.read(mSmoothSpecMap); + stream.read(mGreyscaleMap); + if (mVersion >= 3) + { + stream.read(mGlowMap); + stream.read(mWrinkleMap); + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mFlowMap); + if (mVersion >= 17) + { + stream.read(mDistanceFieldAlphaMap); + } + } + else + { + stream.read(mEnvMap); + stream.read(mGlowMap); + stream.read(mInnerLayerMap); + stream.read(mWrinkleMap); + stream.read(mDisplacementMap); + } + stream.read(mEnableEditorAlphaThreshold); + if (mVersion >= 8) + { + stream.read(mTranslucency); + stream.read(mTranslucencyThickObject); + stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor); + stream.read(mTranslucencySubsurfaceColor); + stream.read(mTranslucencyTransmissiveScale); + stream.read(mTranslucencyTurbulence); + } + else + { + stream.read(mRimLighting); + stream.read(mRimPower); + stream.read(mBackLightPower); + stream.read(mSubsurfaceLighting); + stream.read(mSubsurfaceLightingRolloff); + } + stream.read(mSpecularEnabled); + stream.read(mSpecularColor); + stream.read(mSpecularMult); + stream.read(mSmoothness); + stream.read(mFresnelPower); + stream.read(mWetnessControlSpecScale); + stream.read(mWetnessControlSpecPowerScale); + stream.read(mWetnessControlSpecMinvar); + if (mVersion < 10) + { + stream.read(mWetnessControlEnvMapScale); + } + stream.read(mWetnessControlFresnelPower); + stream.read(mWetnessControlMetalness); + if (mVersion >= 3) + { + stream.read(mPBR); + if (mVersion >= 9) + { + stream.read(mCustomPorosity); + stream.read(mPorosityValue); + } + } + stream.read(mRootMaterialPath); + stream.read(mAnisoLighting); + stream.read(mEmitEnabled); + if (mEmitEnabled) + { + stream.read(mEmittanceColor); + } + stream.read(mEmittanceMult); + stream.read(mModelSpaceNormals); + stream.read(mExternalEmittance); + if (mVersion >= 12) + { + stream.read(mLumEmittance); + if (mVersion >= 13) + { + stream.read(mUseAdaptiveEmissive); + stream.read(mAdaptiveEmissiveExposureParams); + } + } + else if (mVersion < 8) + { + stream.read(mBackLighting); + } + stream.read(mReceiveShadows); + stream.read(mHideSecret); + stream.read(mCastShadows); + stream.read(mDissolveFade); + stream.read(mAssumeShadowmask); + stream.read(mHasGlowMap); + if (mVersion < 7) + { + stream.read(mEnvMapWindow); + stream.read(mEnvMapEye); + } + stream.read(mHair); + stream.read(mHairTintColor); + stream.read(mTree); + stream.read(mFacegen); + stream.read(mSkinTint); + stream.read(mTessellate); + if (mVersion < 3) + { + stream.read(mDisplacementMapParams); + stream.read(mTessellationParams); + } + stream.read(mGrayscaleToPaletteScale); + if (mVersion >= 1) + { + stream.read(mSkewSpecularAlpha); + stream.read(mTerrain); + if (mTerrain) + { + if (mVersion == 3) + stream.skip(4); // Unknown + + stream.read(mTerrainParams); + } + } } void BGEMFile::read(BGSMStream& stream) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index db0059cd29..57b08f68dc 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -68,7 +68,7 @@ namespace Bgsm std::string mEnvMap; std::string mInnerLayerMap; std::string mDisplacementMap; - bool mEnableEditorAlphaRef; + bool mEnableEditorAlphaThreshold; bool mTranslucency; bool mTranslucencyThickObject; bool mTranslucencyMixAlbedoWithSubsurfaceColor; @@ -78,7 +78,7 @@ namespace Bgsm bool mRimLighting; float mRimPower; float mBackLightPower; - bool mSursurfaceLighting; + bool mSubsurfaceLighting; float mSubsurfaceLightingRolloff; bool mSpecularEnabled; osg::Vec4f mSpecularColor; @@ -120,7 +120,7 @@ namespace Bgsm bool mSkinTint; bool mTessellate; osg::Vec2f mDisplacementMapParams; - osg::Vec3f mTesselationParams; + osg::Vec3f mTessellationParams; float mGrayscaleToPaletteScale; bool mSkewSpecularAlpha; bool mTerrain; From 8ef6304dd9ea205cedfff2cc837e9a92efa5f3b6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:33:30 +0300 Subject: [PATCH 1364/2167] BGSM colors are Vec3 --- components/bgsm/file.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 57b08f68dc..00e913917d 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -72,7 +72,7 @@ namespace Bgsm bool mTranslucency; bool mTranslucencyThickObject; bool mTranslucencyMixAlbedoWithSubsurfaceColor; - osg::Vec4f mTranslucencySubsurfaceColor; + osg::Vec3f mTranslucencySubsurfaceColor; float mTranslucencyTransmissiveScale; float mTranslucencyTurbulence; bool mRimLighting; @@ -81,7 +81,7 @@ namespace Bgsm bool mSubsurfaceLighting; float mSubsurfaceLightingRolloff; bool mSpecularEnabled; - osg::Vec4f mSpecularColor; + osg::Vec3f mSpecularColor; float mSpecularMult; float mSmoothness; float mFresnelPower; @@ -97,7 +97,7 @@ namespace Bgsm std::string mRootMaterialPath; bool mAnisoLighting; bool mEmitEnabled; - osg::Vec4f mEmittanceColor; + osg::Vec3f mEmittanceColor; float mEmittanceMult; bool mModelSpaceNormals; bool mExternalEmittance; @@ -114,7 +114,7 @@ namespace Bgsm bool mEnvMapWindow; bool mEnvMapEye; bool mHair; - osg::Vec4f mHairTintColor; + osg::Vec3f mHairTintColor; bool mTree; bool mFacegen; bool mSkinTint; @@ -145,13 +145,13 @@ namespace Bgsm bool mFalloffColor; bool mGrayscaleToPaletteAlpha; bool mSoft; - osg::Vec4f mBaseColor; + osg::Vec3f mBaseColor; float mBaseColorScale; osg::Vec4f mFalloffParams; float mLightingInfluence; std::uint8_t mEnvmapMinLOD; float mSoftDepth; - osg::Vec4f mEmittanceColor; + osg::Vec3f mEmittanceColor; osg::Vec3f mAdaptiveEmissiveExposureParams; bool mHasGlowMap; bool mEffectPbrSpecular; From f9f8c1e5912bdd3461e267f12cf4fa3a033d3094 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:57:32 +0300 Subject: [PATCH 1365/2167] Fix depth test reading in BGSM --- components/bgsm/file.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index bb627ae34e..63b2df1d12 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -17,6 +17,7 @@ namespace Bgsm stream.read(mAlphaTestThreshold); stream.read(mAlphaTest); stream.read(mDepthWrite); + stream.read(mDepthTest); stream.read(mSSR); stream.read(mWetnessControlSSR); stream.read(mDecal); From 484a360792f42b696baaacd4d6711c204b1ace50 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:58:19 +0300 Subject: [PATCH 1366/2167] Add a safety measure for string loading in BGSM --- components/bgsm/stream.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index d8434fcb8a..00cc382d3f 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -41,6 +41,9 @@ namespace Bgsm std::string BGSMStream::getSizedString(size_t length) { + // Prevent potential memory allocation freezes; strings this long are not expected in BGSM + if (length > 1024) + throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); std::string str(length, '\0'); mStream->read(str.data(), length); if (mStream->bad()) From cb77bcc4c8797a76e33770b53046f8035b2c9d97 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 03:27:42 +0300 Subject: [PATCH 1367/2167] Initial BGEM file parsing --- components/bgsm/file.cpp | 51 +++++++++++++++++++++++++++++++++++++--- components/bgsm/file.hpp | 8 +++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 63b2df1d12..f330d8a84e 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -29,7 +29,7 @@ namespace Bgsm stream.read(mRefractionPower); if (mVersion < 10) { - stream.read(mEnvMap); + stream.read(mEnvMapEnabled); stream.read(mEnvMapMaskScale); } else @@ -50,7 +50,7 @@ namespace Bgsm stream.read(mDiffuseMap); stream.read(mNormalMap); stream.read(mSmoothSpecMap); - stream.read(mGreyscaleMap); + stream.read(mGrayscaleMap); if (mVersion >= 3) { stream.read(mGlowMap); @@ -140,7 +140,7 @@ namespace Bgsm stream.read(mCastShadows); stream.read(mDissolveFade); stream.read(mAssumeShadowmask); - stream.read(mHasGlowMap); + stream.read(mGlowMapEnabled); if (mVersion < 7) { stream.read(mEnvMapWindow); @@ -175,5 +175,50 @@ namespace Bgsm void BGEMFile::read(BGSMStream& stream) { MaterialFile::read(stream); + + stream.read(mBaseMap); + stream.read(mGrayscaleMap); + stream.read(mEnvMap); + stream.read(mNormalMap); + stream.read(mEnvMapMask); + if (mVersion >= 10) + { + if (mVersion >= 11) + { + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mGlowMap); + } + stream.read(mEnvMapEnabled); + stream.read(mEnvMapMaskScale); + } + stream.read(mBlood); + stream.read(mEffectLighting); + stream.read(mFalloff); + stream.read(mFalloffColor); + stream.read(mGrayscaleToPaletteAlpha); + stream.read(mSoft); + stream.read(mBaseColor); + stream.read(mBaseColorScale); + stream.read(mFalloffParams); + stream.read(mLightingInfluence); + stream.read(mEnvmapMinLOD); + stream.read(mSoftDepth); + if (mVersion >= 11) + { + stream.read(mEmittanceColor); + if (mVersion >= 15) + { + stream.read(mAdaptiveEmissiveExposureParams); + if (mVersion >= 16) + { + stream.read(mGlowMapEnabled); + if (mVersion >= 20) + { + stream.read(mEffectPbrSpecular); + } + } + } + } } } diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 00e913917d..3524297e2d 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -42,7 +42,7 @@ namespace Bgsm bool mRefraction; bool mRefractionFalloff; float mRefractionPower; - bool mEnvMap; + bool mEnvMapEnabled; float mEnvMapMaskScale; bool mDepthBias; bool mGrayscaleToPaletteColor; @@ -58,7 +58,7 @@ namespace Bgsm std::string mDiffuseMap; std::string mNormalMap; std::string mSmoothSpecMap; - std::string mGreyscaleMap; + std::string mGrayscaleMap; std::string mGlowMap; std::string mWrinkleMap; std::string mSpecularMap; @@ -110,7 +110,7 @@ namespace Bgsm bool mCastShadows; bool mDissolveFade; bool mAssumeShadowmask; - bool mHasGlowMap; + bool mGlowMapEnabled; bool mEnvMapWindow; bool mEnvMapEye; bool mHair; @@ -153,7 +153,7 @@ namespace Bgsm float mSoftDepth; osg::Vec3f mEmittanceColor; osg::Vec3f mAdaptiveEmissiveExposureParams; - bool mHasGlowMap; + bool mGlowMapEnabled; bool mEffectPbrSpecular; void read(BGSMStream& stream) override; From fe1cb3a5aeb3da82c5fb11478f859d2014a211e7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 04:00:20 +0300 Subject: [PATCH 1368/2167] Add a resource manager for BGSM files --- components/CMakeLists.txt | 2 +- components/bgsm/reader.hpp | 2 + components/resource/bgsmfilemanager.cpp | 62 +++++++++++++++++++++++++ components/resource/bgsmfilemanager.hpp | 27 +++++++++++ components/resource/stats.cpp | 1 + 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 components/resource/bgsmfilemanager.cpp create mode 100644 components/resource/bgsmfilemanager.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 084deaea58..df23121c5c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -129,7 +129,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation foreachbulletobject errormarker cachestats + resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager ) add_component_dir (shader diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 5ac67c0467..2d669900ad 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -20,6 +20,8 @@ namespace Bgsm void parse(Files::IStreamPtr&& stream); std::uint32_t getVersion() const { return mFile->mVersion; } + + std::unique_ptr& getFile() { return mFile; } }; } #endif diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp new file mode 100644 index 0000000000..5155db17ce --- /dev/null +++ b/components/resource/bgsmfilemanager.cpp @@ -0,0 +1,62 @@ +#include "bgsmfilemanager.hpp" + +#include + +#include + +#include +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + class BgsmFileHolder : public osg::Object + { + public: + BgsmFileHolder(const Bgsm::MaterialFilePtr& file) + : mBgsmFile(file) + { + } + BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop) + : mBgsmFile(copy.mBgsmFile) + { + } + + BgsmFileHolder() = default; + + META_Object(Resource, BgsmFileHolder) + + Bgsm::MaterialFilePtr mBgsmFile; + }; + + BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay) + : ResourceManager(vfs, expiryDelay) + { + } + + BgsmFileManager::~BgsmFileManager() = default; + + Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get())->mBgsmFile; + else + { + Bgsm::Reader reader; + reader.parse(mVFS->get(name)); + Bgsm::MaterialFilePtr file = std::move(reader.getFile()); + obj = new BgsmFileHolder(file); + mCache->addEntryToObjectCache(name.value(), obj); + return file; + } + } + + void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const + { + Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats); + } + +} diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp new file mode 100644 index 0000000000..b7c0d07c5a --- /dev/null +++ b/components/resource/bgsmfilemanager.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H + +#include + +#include "resourcemanager.hpp" + +namespace Resource +{ + + /// @brief Handles caching of material files. + /// @note May be used from any thread. + class BgsmFileManager : public ResourceManager + { + public: + BgsmFileManager(const VFS::Manager* vfs, double expiryDelay); + ~BgsmFileManager(); + + /// Retrieve a material file from the cache or load it from the VFS if not cached yet. + Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + }; + +} + +#endif diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 9bb90635d1..6730ddb303 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -87,6 +87,7 @@ namespace Resource "Image", "Nif", "Keyframe", + "BSShader Material", "Groundcover Chunk", "Object Chunk", "Terrain Chunk", From 1a961f30210a6947f8ab39cbacfad042e6259ecd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 05:01:12 +0300 Subject: [PATCH 1369/2167] Extremely early handling for BGSM/BGEM files --- apps/bulletobjecttool/main.cpp | 4 +- apps/navmeshtool/main.cpp | 4 +- .../nifosg/testnifloader.cpp | 6 +- components/misc/resourcehelpers.cpp | 5 + components/misc/resourcehelpers.hpp | 1 + components/nifosg/nifloader.cpp | 116 +++++++++++++++++- components/nifosg/nifloader.hpp | 3 +- components/resource/resourcesystem.cpp | 10 +- components/resource/resourcesystem.hpp | 3 + components/resource/scenemanager.cpp | 12 +- components/resource/scenemanager.hpp | 4 +- 11 files changed, 151 insertions(+), 17 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index b27c8135d6..4dbdb56350 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -173,7 +174,8 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::forEachBulletObject( diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 94ab7ef082..d75a1af5e2 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -220,7 +221,8 @@ namespace NavMeshTool Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index f05d651301..cdab51e6c2 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -70,7 +70,7 @@ namespace init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), R"( osg::Group { UniqueID 1 @@ -259,7 +259,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } @@ -289,7 +289,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1d5b57bfd9..5c3f87b3e7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -173,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } +std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs) +{ + return correctResourcePath({ { "materials" } }, resPath, vfs); +} + std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) { std::string res = "meshes\\"; diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index cda99d928d..a2e05610a6 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -34,6 +34,7 @@ namespace Misc /// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available /// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :( std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); + std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs); // Adds "meshes\\". std::string correctMeshPath(std::string_view resPath); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8d46b0f751..0150c2dc90 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include // particle @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -238,15 +240,17 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, Resource::BgsmFileManager* materialMgr) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) , mBethVersion(bethver) + , mMaterialMgr(materialMgr) { } std::filesystem::path mFilename; unsigned int mVersion, mUserVersion, mBethVersion; + Resource::BgsmFileManager* mMaterialMgr; size_t mFirstRootTextureIndex{ ~0u }; bool mFoundFirstRootTexturingProperty = false; @@ -2155,6 +2159,98 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } + void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, Resource::ImageManager* imageManager, + std::vector& boundTextures) + { + if (!mMaterialMgr) + return; + + Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(path)); + if (!material) + return; + + if (material->mShaderType == Bgsm::ShaderType::Lighting) + { + const Bgsm::BGSMFile* bgsm = static_cast(material.get()); + + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + const unsigned int uvSet = 0; + if (!bgsm->mDiffuseMap.empty()) + { + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName("diffuseMap"); + boundTextures.emplace_back(uvSet); + } + + if (!bgsm->mNormalMap.empty()) + { + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName("normalMap"); + boundTextures.emplace_back(uvSet); + } + + if (bgsm->mTwoSided) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + if (bgsm->mTree) + stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + + handleDepthFlags(stateset, bgsm->mDepthTest, bgsm->mDepthWrite); + } + else + { + const Bgsm::BGEMFile* bgem = static_cast(material.get()); + if (!bgem->mBaseMap.empty()) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + std::string filename = Misc::ResourceHelpers::correctTexturePath( + bgem->mBaseMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + texture2d->setName("diffuseMap"); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgem->mClamp >> 1) & 0x1, bgem->mClamp & 0x1); + const unsigned int texUnit = 0; + const unsigned int uvSet = 0; + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + boundTextures.push_back(uvSet); + } + + bool useFalloff = bgem->mFalloff; + stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); + if (useFalloff) + stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams)); + handleDepthFlags(stateset, bgem->mDepthTest, bgem->mDepthWrite); + } + } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector& boundTextures) @@ -2421,6 +2517,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); + if (normalizedName.ends_with(".bgsm")) + { + handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + break; + } if (!texprop->mTextureSet.empty()) handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); @@ -2442,6 +2544,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); + if (normalizedName.ends_with(".bgem")) + { + handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + break; + } if (!texprop->mSourceTexture.empty()) { if (!boundTextures.empty()) @@ -2860,15 +2968,15 @@ namespace NifOsg } }; - osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager) + osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) { - LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); + LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); return impl.load(file, imageManager); } void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) { - LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion()); + LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion(), nullptr); impl.loadKf(kf, target); } diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 21e0ae097c..b016248f07 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -18,6 +18,7 @@ namespace osg namespace Resource { class ImageManager; + class BgsmFileManager; } namespace NifOsg @@ -30,7 +31,7 @@ namespace NifOsg public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// if so. - static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager); + static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 65a83a60ab..33bba791a8 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,7 @@ #include +#include "bgsmfilemanager.hpp" #include "imagemanager.hpp" #include "keyframemanager.hpp" #include "niffilemanager.hpp" @@ -15,11 +16,13 @@ namespace Resource : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); + mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); + mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); + addResourceManager(mBgsmFileManager.get()); addResourceManager(mKeyframeManager.get()); // note, scene references images so add images afterwards for correct implementation of updateCache() addResourceManager(mSceneManager.get()); @@ -43,6 +46,11 @@ namespace Resource return mImageManager.get(); } + BgsmFileManager* ResourceSystem::getBgsmFileManager() + { + return mBgsmFileManager.get(); + } + NifFileManager* ResourceSystem::getNifFileManager() { return mNifFileManager.get(); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index f7f09b9277..5609176a89 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -25,6 +25,7 @@ namespace Resource class SceneManager; class ImageManager; + class BgsmFileManager; class NifFileManager; class KeyframeManager; class BaseResourceManager; @@ -41,6 +42,7 @@ namespace Resource SceneManager* getSceneManager(); ImageManager* getImageManager(); + BgsmFileManager* getBgsmFileManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); @@ -74,6 +76,7 @@ namespace Resource private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; + std::unique_ptr mBgsmFileManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ab3f92f10d..daeafeaf1d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -55,6 +55,7 @@ #include #include +#include "bgsmfilemanager.hpp" #include "errormarker.hpp" #include "imagemanager.hpp" #include "niffilemanager.hpp" @@ -409,7 +410,7 @@ namespace Resource }; SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay) + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) @@ -424,6 +425,7 @@ namespace Resource , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) + , mBgsmFileManager(bgsmFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMagFilter(osg::Texture::LINEAR) , mMaxAnisotropy(1) @@ -795,11 +797,11 @@ namespace Resource } osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, - Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* materialMgr) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") - return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); + return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr); else if (ext == "spt") { Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename; @@ -921,7 +923,7 @@ namespace Resource { path.changeExtension(meshType); if (mVFS->exists(path)) - return load(path, mVFS, mImageManager, mNifFileManager); + return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); } } catch (const std::exception& e) @@ -953,7 +955,7 @@ namespace Resource osg::ref_ptr loaded; try { - loaded = load(normalized, mVFS, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); loaded->accept(extraDataVisitor); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 3ad8a24892..31ad51694c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -32,6 +32,7 @@ namespace Resource { class ImageManager; class NifFileManager; + class BgsmFileManager; class SharedStateManager; } @@ -90,7 +91,7 @@ namespace Resource { public: explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay); + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay); ~SceneManager(); Shader::ShaderManager& getShaderManager(); @@ -259,6 +260,7 @@ namespace Resource Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; + Resource::BgsmFileManager* mBgsmFileManager; osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; From 96f5ae5a8dae3afe522e9fd2ea5d30c9abb8a5b9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 07:19:17 +0300 Subject: [PATCH 1370/2167] Handle BGSM decal flag, hide visibility editor markers --- components/nifosg/nifloader.cpp | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0150c2dc90..08a4fd5d89 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -773,7 +773,7 @@ namespace NifOsg if (isGeometry && !args.mSkipMeshes) { - bool skip; + bool skip = false; if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) { skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker")) @@ -781,7 +781,10 @@ namespace NifOsg || Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow"); } else - skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); + { + if (args.mHasMarkers) + skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); + } if (!skip) { if (isNiGeometry) @@ -2165,7 +2168,8 @@ namespace NifOsg if (!mMaterialMgr) return; - Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(path)); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); + Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); if (!material) return; @@ -2211,12 +2215,8 @@ namespace NifOsg boundTextures.emplace_back(uvSet); } - if (bgsm->mTwoSided) - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); - - handleDepthFlags(stateset, bgsm->mDepthTest, bgsm->mDepthWrite); } else { @@ -2247,8 +2247,18 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); if (useFalloff) stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams)); - handleDepthFlags(stateset, bgem->mDepthTest, bgem->mDepthWrite); } + + if (material->mTwoSided) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + if (material->mDecal) + { + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, @@ -2517,10 +2527,9 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); - if (normalizedName.ends_with(".bgsm")) + if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgsm")) { - handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); break; } if (!texprop->mTextureSet.empty()) @@ -2544,10 +2553,9 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); - if (normalizedName.ends_with(".bgem")) + if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgem")) { - handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); break; } if (!texprop->mSourceTexture.empty()) From 1d65aaee714346bcff899f45a64abed2f48dda77 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 07:43:33 +0300 Subject: [PATCH 1371/2167] Formatting --- apps/niftest/niftest.cpp | 1 - components/bgsm/stream.hpp | 5 ++--- components/nifosg/nifloader.cpp | 20 ++++++++++++-------- components/nifosg/nifloader.hpp | 3 ++- components/resource/resourcesystem.cpp | 3 ++- components/resource/scenemanager.cpp | 3 ++- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 3cd4382121..c364303376 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -89,7 +89,6 @@ void readNIF( } } - void readMaterial( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp index 8b0e1efdbe..2e03a52dd4 100644 --- a/components/bgsm/stream.hpp +++ b/components/bgsm/stream.hpp @@ -3,9 +3,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -58,8 +58,7 @@ namespace Bgsm Files::IStreamPtr mStream; public: - explicit BGSMStream( - const Reader& reader, Files::IStreamPtr&& stream) + explicit BGSMStream(const Reader& reader, Files::IStreamPtr&& stream) : mReader(reader) , mStream(std::move(stream)) { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 08a4fd5d89..657d053706 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -240,7 +240,8 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, Resource::BgsmFileManager* materialMgr) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, + Resource::BgsmFileManager* materialMgr) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) @@ -783,7 +784,8 @@ namespace NifOsg else { if (args.mHasMarkers) - skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); + skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") + || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); } if (!skip) { @@ -2162,8 +2164,8 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } - void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, Resource::ImageManager* imageManager, - std::vector& boundTextures) + void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, + Resource::ImageManager* imageManager, std::vector& boundTextures) { if (!mMaterialMgr) return; @@ -2229,8 +2231,8 @@ namespace NifOsg stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); boundTextures.clear(); } - std::string filename = Misc::ResourceHelpers::correctTexturePath( - bgem->mBaseMap, imageManager->getVFS()); + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); @@ -2976,9 +2978,11 @@ namespace NifOsg } }; - osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) + osg::ref_ptr Loader::load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) { - LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); + LoaderImpl impl( + file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); return impl.load(file, imageManager); } diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index b016248f07..14f16088cc 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -31,7 +31,8 @@ namespace NifOsg public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// if so. - static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); + static osg::ref_ptr load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 33bba791a8..f012627efb 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -18,7 +18,8 @@ namespace Resource mNifFileManager = std::make_unique(vfs, encoder); mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); + mSceneManager = std::make_unique( + vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index daeafeaf1d..690e38bbf8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -797,7 +797,8 @@ namespace Resource } osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, - Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* materialMgr) + Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, + Resource::BgsmFileManager* materialMgr) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") From 8997bd68544d6a7e2e62593f49d6df63591a52f7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 10:10:03 +0300 Subject: [PATCH 1372/2167] Apply shader material transparency parameters, get rid of unwanted shiny --- components/nifosg/nifloader.cpp | 122 +++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 17 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 657d053706..81aeb7a2fc 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2164,17 +2164,21 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } - void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, - Resource::ImageManager* imageManager, std::vector& boundTextures) + Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) { if (!mMaterialMgr) - return; + return nullptr; + + if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) + return nullptr; std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); - Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); - if (!material) - return; + return mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); + } + void handleShaderMaterial(Bgsm::MaterialFilePtr material, osg::StateSet* stateset, + Resource::ImageManager* imageManager, std::vector& boundTextures) + { if (material->mShaderType == Bgsm::ShaderType::Lighting) { const Bgsm::BGSMFile* bgsm = static_cast(material.get()); @@ -2253,13 +2257,6 @@ namespace NifOsg if (material->mTwoSided) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - if (material->mDecal) - { - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } @@ -2529,9 +2526,10 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgsm")) + Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); + if (material) { - handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, imageManager, boundTextures); break; } if (!texprop->mTextureSet.empty()) @@ -2555,9 +2553,10 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgem")) + Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); + if (material) { - handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, imageManager, boundTextures); break; } if (!texprop->mSourceTexture.empty()) @@ -2832,6 +2831,52 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); + Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); + if (shaderMat) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr alphaFunc(new osg::AlphaFunc( + osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), + getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + if (!mPushedSorter) + setBin_Transparent(stateset); + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(shaderMat.get()); + specEnabled + = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly + specStrength = bgsm->mSpecularMult; + emissiveMult = bgsm->mEmittanceMult; + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); + } + break; + } mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); @@ -2855,6 +2900,49 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); + Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); + if (shaderMat) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr alphaFunc(new osg::AlphaFunc( + osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), + getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + if (!mPushedSorter) + setBin_Transparent(stateset); + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) + { + auto bgem = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); + if (bgem->mSoft) + SceneUtil::setupSoftEffect(*node, bgem->mSoftDepth, true, bgem->mSoftDepth); + } + break; + } if (shaderprop->decal()) { osg::StateSet* stateset = node->getOrCreateStateSet(); From e680123482190419db2ffa54315ea2a8479ae7d1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 13:35:29 +0300 Subject: [PATCH 1373/2167] NifLoader: Make the image manager a member --- components/nifosg/nifloader.cpp | 125 +++++++++++++++----------------- 1 file changed, 57 insertions(+), 68 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 81aeb7a2fc..6f630b4dd5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -240,18 +240,17 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, - Resource::BgsmFileManager* materialMgr) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) , mBethVersion(bethver) - , mMaterialMgr(materialMgr) { } std::filesystem::path mFilename; unsigned int mVersion, mUserVersion, mBethVersion; - Resource::BgsmFileManager* mMaterialMgr; + Resource::BgsmFileManager* mMaterialManager{ nullptr }; + Resource::ImageManager* mImageManager{ nullptr }; size_t mFirstRootTextureIndex{ ~0u }; bool mFoundFirstRootTexturingProperty = false; @@ -344,7 +343,6 @@ namespace NifOsg struct HandleNodeArgs { unsigned int mNifVersion; - Resource::ImageManager* mImageManager; SceneUtil::TextKeyMap* mTextKeys; std::vector mBoundTextures = {}; int mAnimFlags = 0; @@ -354,7 +352,7 @@ namespace NifOsg osg::Node* mRootNode = nullptr; }; - osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) + osg::ref_ptr load(Nif::FileView nif) { const size_t numRoots = nif.numRoots(); std::vector roots; @@ -376,10 +374,8 @@ namespace NifOsg created->setDataVariance(osg::Object::STATIC); for (const Nif::NiAVObject* root : roots) { - auto node = handleNode(root, nullptr, nullptr, - { .mNifVersion = nif.getVersion(), - .mImageManager = imageManager, - .mTextKeys = &textkeys->mTextKeys }); + auto node = handleNode( + root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys }); created->addChild(node); } if (mHasNightDayLabel) @@ -410,8 +406,7 @@ namespace NifOsg } void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags) { bool hasStencilProperty = false; @@ -449,8 +444,7 @@ namespace NifOsg if (property.getPtr()->recIndex == mFirstRootTextureIndex) applyTo->setUserValue("overrideFx", 1); } - handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } } @@ -462,8 +456,7 @@ namespace NifOsg shaderprop = static_cast(nifNode)->mShaderProperty; if (!shaderprop.empty()) - handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -527,8 +520,7 @@ namespace NifOsg sequenceNode->setMode(osg::Sequence::START); } - osg::ref_ptr handleSourceTexture( - const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) { if (!st) return nullptr; @@ -536,8 +528,8 @@ namespace NifOsg osg::ref_ptr image; if (st->mExternal) { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); - image = imageManager->getImage(filename); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, mImageManager->getVFS()); + image = mImageManager->getImage(filename); } else if (!st->mData.empty()) { @@ -552,7 +544,7 @@ namespace NifOsg texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); } - bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) + bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset) { if (nifNode->recType != Nif::RC_NiTextureEffect) { @@ -595,7 +587,7 @@ namespace NifOsg return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr())); osg::ref_ptr texture2d(new osg::Texture2D(image)); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -766,7 +758,7 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); + applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); @@ -868,7 +860,7 @@ namespace NifOsg if (!effect.empty()) { osg::ref_ptr effectStateSet = new osg::StateSet; - if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager)) + if (handleEffect(effect.getPtr(), effectStateSet)) for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i) currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet); } @@ -1035,8 +1027,7 @@ namespace NifOsg } void handleTextureControllers(const Nif::NiProperty* texProperty, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - osg::StateSet* stateset, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags) { for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { @@ -1070,7 +1061,7 @@ namespace NifOsg if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(source.getPtr())); osg::ref_ptr texture(new osg::Texture2D(image)); if (image) texture->setTextureSize(image->s(), image->t()); @@ -1986,7 +1977,7 @@ namespace NifOsg void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, - Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) { @@ -2039,7 +2030,7 @@ namespace NifOsg if (!tex.mSourceTexture.empty()) { const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); - osg::ref_ptr image = handleSourceTexture(st, imageManager); + osg::ref_ptr image = handleSourceTexture(st); texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2161,23 +2152,23 @@ namespace NifOsg boundTextures.push_back(uvSet); } } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); } Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) { - if (!mMaterialMgr) + if (!mMaterialManager) return nullptr; if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) return nullptr; - std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); - return mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); + return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); } - void handleShaderMaterial(Bgsm::MaterialFilePtr material, osg::StateSet* stateset, - Resource::ImageManager* imageManager, std::vector& boundTextures) + void handleShaderMaterial( + Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector& boundTextures) { if (material->mShaderType == Bgsm::ShaderType::Lighting) { @@ -2194,8 +2185,8 @@ namespace NifOsg if (!bgsm->mDiffuseMap.empty()) { std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2209,8 +2200,8 @@ namespace NifOsg if (!bgsm->mNormalMap.empty()) { std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2236,8 +2227,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2261,8 +2252,7 @@ namespace NifOsg } void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, - const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, - std::vector& boundTextures) + const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) { if (!boundTextures.empty()) { @@ -2291,8 +2281,8 @@ namespace NifOsg } } std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2374,8 +2364,8 @@ namespace NifOsg } void handleProperty(const Nif::NiProperty* property, osg::Node* node, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags, bool hasStencilProperty) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags, + bool hasStencilProperty) { switch (property->recType) { @@ -2457,8 +2447,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - handleTextureProperty( - texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); + handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags); node->setUserValue("applyMode", static_cast(texprop->mApplyMode)); break; } @@ -2472,10 +2461,9 @@ namespace NifOsg if (!texprop->mTextureSet.empty()) { auto textureSet = texprop->mTextureSet.getPtr(); - handleTextureSet( - textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); + handleTextureSet(textureSet, texprop->mClamp, node->getName(), stateset, boundTextures); } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->refraction()) SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; @@ -2497,8 +2485,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2515,7 +2503,7 @@ namespace NifOsg } } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } @@ -2529,13 +2517,13 @@ namespace NifOsg Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); if (material) { - handleShaderMaterial(material, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, boundTextures); break; } if (!texprop->mTextureSet.empty()) - handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, - imageManager, boundTextures); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureSet( + texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, boundTextures); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) @@ -2556,7 +2544,7 @@ namespace NifOsg Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); if (material) { - handleShaderMaterial(material, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, boundTextures); break; } if (!texprop->mSourceTexture.empty()) @@ -2568,8 +2556,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename = Misc::ResourceHelpers::correctTexturePath( - texprop->mSourceTexture, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + texprop->mSourceTexture, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2601,7 +2589,7 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); if (useFalloff) stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); @@ -3067,16 +3055,17 @@ namespace NifOsg }; osg::ref_ptr Loader::load( - Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager) { - LoaderImpl impl( - file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); - return impl.load(file, imageManager); + LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); + impl.mMaterialManager = materialManager; + impl.mImageManager = imageManager; + return impl.load(file); } void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) { - LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion(), nullptr); + LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion()); impl.loadKf(kf, target); } From 4ccf9c1917c0ebc0d1b7219469a41d438c1475c4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 16:07:21 +0300 Subject: [PATCH 1374/2167] Deduplicate NifLoader texture attachment Handle non-existent shader materials more gracefully Deduplicate shader material drawable property handling --- components/nifosg/nifloader.cpp | 521 +++++++++++++------------------- 1 file changed, 205 insertions(+), 316 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6f630b4dd5..7177377771 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -520,28 +520,18 @@ namespace NifOsg sequenceNode->setMode(osg::Sequence::START); } - osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) const { - if (!st) - return nullptr; - - osg::ref_ptr image; - if (st->mExternal) + if (st) { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, mImageManager->getVFS()); - image = mImageManager->getImage(filename); - } - else if (!st->mData.empty()) - { - image = handleInternalTexture(st->mData.getPtr()); - } - return image; - } + if (st->mExternal) + return getTextureImage(st->mFile); - void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT) - { - texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + if (!st->mData.empty()) + return handleInternalTexture(st->mData.getPtr()); + } + + return nullptr; } bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset) @@ -587,16 +577,12 @@ namespace NifOsg return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr())); - osg::ref_ptr texture2d(new osg::Texture2D(image)); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - texture2d->setName("envMap"); - handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT()); - - int texUnit = 3; // FIXME - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + const unsigned int uvSet = 0; + const unsigned int texUnit = 3; // FIXME + std::vector boundTextures; + boundTextures.resize(3); // Dummy vector for attachNiSourceTexture + attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(), + textureEffect->wrapT(), uvSet, stateset, boundTextures); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -1026,6 +1012,54 @@ namespace NifOsg } } + osg::ref_ptr getTextureImage(std::string_view path) const + { + if (!mImageManager) + return nullptr; + + std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS()); + return mImageManager->getImage(filename); + } + + osg::ref_ptr attachTexture(const std::string& name, osg::ref_ptr image, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + unsigned int texUnit = boundTextures.size(); + if (stateset) + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName(name); + boundTextures.emplace_back(uvSet); + return texture2d; + } + + osg::ref_ptr attachExternalTexture(const std::string& name, const std::string& path, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + osg::ref_ptr attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st, + bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset, + std::vector& boundTextures) const + { + return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + static void clearBoundTextures(osg::StateSet* stateset, std::vector& boundTextures) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + } + void handleTextureControllers(const Nif::NiProperty* texProperty, SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags) { @@ -1056,17 +1090,16 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } + const unsigned int uvSet = 0; + std::vector boundTextures; // Dummy list for attachTexture for (const auto& source : flipctrl->mSources) { if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr())); - osg::ref_ptr texture(new osg::Texture2D(image)); - if (image) - texture->setTextureSize(image->s(), image->t()); - texture->setWrap(osg::Texture::WRAP_S, wrapS); - texture->setWrap(osg::Texture::WRAP_T, wrapT); + // NB: not changing the stateset + osg::ref_ptr texture + = attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -1811,7 +1844,7 @@ namespace NifOsg } } - osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) const { if (pixelData->mMipmaps.empty()) return nullptr; @@ -1946,7 +1979,7 @@ namespace NifOsg return image; } - osg::ref_ptr createEmissiveTexEnv() + static osg::ref_ptr createEmissiveTexEnv() { osg::ref_ptr texEnv(new osg::TexEnvCombine); // Sum the previous colour and the emissive colour. @@ -1979,31 +2012,40 @@ namespace NifOsg osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags) { - if (!boundTextures.empty()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } + // overriding a parent NiTexturingProperty, so remove what was previously bound + clearBoundTextures(stateset, boundTextures); // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the // shadow casting shader will need to be updated accordingly. for (size_t i = 0; i < texprop->mTextures.size(); ++i) { - if (texprop->mTextures[i].mEnabled - || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) + const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; + if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) { + std::string textureName; switch (i) { // These are handled later on case Nif::NiTexturingProperty::BaseTexture: + textureName = "diffuseMap"; + break; case Nif::NiTexturingProperty::GlowTexture: + textureName = "glowMap"; + break; case Nif::NiTexturingProperty::DarkTexture: + textureName = "darkMap"; + break; case Nif::NiTexturingProperty::BumpTexture: + textureName = "bumpMap"; + break; case Nif::NiTexturingProperty::DetailTexture: + textureName = "detailMap"; + break; case Nif::NiTexturingProperty::DecalTexture: + textureName = "decalMap"; + break; case Nif::NiTexturingProperty::GlossTexture: + textureName = "glossMap"; break; default: { @@ -2013,12 +2055,9 @@ namespace NifOsg } } - unsigned int uvSet = 0; - // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d; - if (texprop->mTextures[i].mEnabled) + const unsigned int texUnit = boundTextures.size(); + if (tex.mEnabled) { - const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; if (tex.mSourceTexture.empty() && texprop->mController.empty()) { if (i == 0) @@ -2028,32 +2067,18 @@ namespace NifOsg } if (!tex.mSourceTexture.empty()) - { - const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); - osg::ref_ptr image = handleSourceTexture(st); - texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - } + attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(), + tex.mUVSet, stateset, boundTextures); else - texture2d = new osg::Texture2D; - - handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT()); - - uvSet = tex.mUVSet; + attachTexture( + textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures); } else { // Texture only comes from NiFlipController, so tex is ignored, set defaults - texture2d = new osg::Texture2D; - handleTextureWrapping(texture2d, true, true); - uvSet = 0; + attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures); } - unsigned int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - if (i == Nif::NiTexturingProperty::GlowTexture) { stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -2121,41 +2146,12 @@ namespace NifOsg texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } - - switch (i) - { - case Nif::NiTexturingProperty::BaseTexture: - texture2d->setName("diffuseMap"); - break; - case Nif::NiTexturingProperty::BumpTexture: - texture2d->setName("bumpMap"); - break; - case Nif::NiTexturingProperty::GlowTexture: - texture2d->setName("emissiveMap"); - break; - case Nif::NiTexturingProperty::DarkTexture: - texture2d->setName("darkMap"); - break; - case Nif::NiTexturingProperty::DetailTexture: - texture2d->setName("detailMap"); - break; - case Nif::NiTexturingProperty::DecalTexture: - texture2d->setName("decalMap"); - break; - case Nif::NiTexturingProperty::GlossTexture: - texture2d->setName("glossMap"); - break; - default: - break; - } - - boundTextures.push_back(uvSet); } } handleTextureControllers(texprop, composite, stateset, animflags); } - Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) + Bgsm::MaterialFilePtr getShaderMaterial(std::string_view path) const { if (!mMaterialManager) return nullptr; @@ -2164,81 +2160,43 @@ namespace NifOsg return nullptr; std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); - return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + try + { + return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to load shader material: " << e.what(); + return nullptr; + } } - void handleShaderMaterial( + void handleShaderMaterialNodeProperties( Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector& boundTextures) { + const unsigned int uvSet = 0; + const bool wrapS = (material->mClamp >> 1) & 0x1; + const bool wrapT = material->mClamp & 0x1; if (material->mShaderType == Bgsm::ShaderType::Lighting) { const Bgsm::BGSMFile* bgsm = static_cast(material.get()); - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - - const unsigned int uvSet = 0; if (!bgsm->mDiffuseMap.empty()) - { - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - texture2d->setName("diffuseMap"); - boundTextures.emplace_back(uvSet); - } + attachExternalTexture( + "diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures); if (!bgsm->mNormalMap.empty()) - { - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - texture2d->setName("normalMap"); - boundTextures.emplace_back(uvSet); - } + attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures); if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); } - else + else if (material->mShaderType == Bgsm::ShaderType::Effect) { const Bgsm::BGEMFile* bgem = static_cast(material.get()); + if (!bgem->mBaseMap.empty()) - { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgem->mClamp >> 1) & 0x1, bgem->mClamp & 0x1); - const unsigned int texUnit = 0; - const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - } + attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures); bool useFalloff = bgem->mFalloff; stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); @@ -2251,16 +2209,55 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } - void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, + void handleShaderMaterialDrawableProperties( + Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) + { + osg::StateSet* stateset = node.getOrCreateStateSet(); + osg::ref_ptr alphaFunc( + new osg::AlphaFunc(osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node.getOrCreateStateSet(); + osg::ref_ptr blendFunc(new osg::BlendFunc( + getBlendMode(shaderMat->mSourceBlendMode), getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node.getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); + } + else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) + { + auto bgem = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); + if (bgem->mSoft) + SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth); + } + } + + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT, const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - const unsigned int uvSet = 0; for (size_t i = 0; i < textureSet->mTextures.size(); ++i) @@ -2270,8 +2267,16 @@ namespace NifOsg switch (static_cast(i)) { case Nif::BSShaderTextureSet::TextureType::Base: + attachExternalTexture( + "diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Normal: + attachExternalTexture( + "normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Glow: + attachExternalTexture( + "emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); break; default: { @@ -2280,31 +2285,6 @@ namespace NifOsg continue; } } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - // BSShaderTextureSet presence means there's no need for FFP support for the affected node - switch (static_cast(i)) - { - case Nif::BSShaderTextureSet::TextureType::Base: - texture2d->setName("diffuseMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Normal: - texture2d->setName("normalMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Glow: - texture2d->setName("emissiveMap"); - break; - default: - break; - } - boundTextures.emplace_back(uvSet); } } @@ -2458,11 +2438,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) - { - auto textureSet = texprop->mTextureSet.getPtr(); - handleTextureSet(textureSet, texprop->mClamp, node->getName(), stateset, boundTextures); - } + handleTextureSet( + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->refraction()) SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); @@ -2476,31 +2457,17 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); if (!texprop->mFilename.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT()); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - if (mBethVersion >= 27) - { - useFalloff = true; - stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - } + attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(), + uvSet, stateset, boundTextures); + } + if (mBethVersion >= 27) + { + useFalloff = true; + stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, stateset, animflags); @@ -2514,15 +2481,17 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); - if (material) + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) { - handleShaderMaterial(material, stateset, boundTextures); + handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; } + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) handleTextureSet( - texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, boundTextures); + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -2541,33 +2510,20 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); - if (material) + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) { - handleShaderMaterial(material, stateset, boundTextures); + handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; } if (!texprop->mSourceTexture.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename = Misc::ResourceHelpers::correctTexturePath( - texprop->mSourceTexture, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; + unsigned int texUnit = boundTextures.size(); + attachExternalTexture( + "diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures); { osg::ref_ptr texMat(new osg::TexMat); // This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController) @@ -2819,40 +2775,11 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); - Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); - if (shaderMat) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { - mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - if (shaderMat->mAlphaBlend) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), - getBlendMode(shaderMat->mDestinationBlendMode))); - blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(stateset); - } - if (shaderMat->mDecal) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mAlphaBlend && !mPushedSorter) + setBin_Transparent(node->getOrCreateStateSet()); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2860,8 +2787,6 @@ namespace NifOsg = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly specStrength = bgsm->mSpecularMult; emissiveMult = bgsm->mEmittanceMult; - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); } break; } @@ -2888,47 +2813,11 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); - Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); - if (shaderMat) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { - mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - if (shaderMat->mAlphaBlend) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), - getBlendMode(shaderMat->mDestinationBlendMode))); - blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(stateset); - } - if (shaderMat->mDecal) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } - if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) - { - auto bgem = static_cast(shaderMat.get()); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); - if (bgem->mSoft) - SceneUtil::setupSoftEffect(*node, bgem->mSoftDepth, true, bgem->mSoftDepth); - } + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mAlphaBlend && !mPushedSorter) + setBin_Transparent(node->getOrCreateStateSet()); break; } if (shaderprop->decal()) From 5789eb73b104bc93b433528bd0df476b7b843682 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 16:47:00 +0300 Subject: [PATCH 1375/2167] Deduplicate decal and alpha handling in NifLoader --- components/nifosg/nifloader.cpp | 168 ++++++++++++++------------------ 1 file changed, 73 insertions(+), 95 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7177377771..5af4e57c53 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2209,36 +2209,79 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } + void handleDecal(bool hasSortAlpha, osg::ref_ptr stateset) + { + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + polygonOffset = shareAttribute(polygonOffset); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + } + + void handleAlphaTesting( + bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node) + { + if (enabled) + { + osg::ref_ptr alphaFunc(new osg::AlphaFunc(function, threshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + else if (osg::StateSet* stateset = node.getStateSet()) + { + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + } + } + + void handleAlphaBlending( + bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node) + { + if (enabled) + { + osg::ref_ptr stateset = node.getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + + if (sort) + { + hasSortAlpha = true; + if (!mPushedSorter) + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + else if (!mPushedSorter) + { + stateset->setRenderBinToInherit(); + } + } + else if (osg::ref_ptr stateset = node.getStateSet()) + { + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + if (!mPushedSorter) + stateset->setRenderBinToInherit(); + } + } + void handleShaderMaterialDrawableProperties( Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) { mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) - { - osg::StateSet* stateset = node.getOrCreateStateSet(); - osg::ref_ptr alphaFunc( - new osg::AlphaFunc(osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - if (shaderMat->mAlphaBlend) - { - osg::StateSet* stateset = node.getOrCreateStateSet(); - osg::ref_ptr blendFunc(new osg::BlendFunc( - getBlendMode(shaderMat->mSourceBlendMode), getBlendMode(shaderMat->mDestinationBlendMode))); - blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; - } + handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node); + handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode, + true, hasSortAlpha, node); if (shaderMat->mDecal) { - osg::StateSet* stateset = node.getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + handleDecal(hasSortAlpha, node.getOrCreateStateSet()); } if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { @@ -2627,12 +2670,9 @@ namespace NifOsg bool hasMatCtrl = false; bool hasSortAlpha = false; - osg::StateSet* blendFuncStateSet = nullptr; - auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); }; auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; - auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); }; auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif; float emissiveMult = 1.f; @@ -2718,52 +2758,10 @@ namespace NifOsg case Nif::RC_NiAlphaProperty: { const Nif::NiAlphaProperty* alphaprop = static_cast(property); - if (alphaprop->useAlphaBlending()) - { - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()), - getBlendMode(alphaprop->destinationBlendMode()))); - // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. - // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. - // Either way, D3D8.1 doesn't do that, so adapt the destination factor. - if (blendFunc->getDestination() == GL_DST_ALPHA) - blendFunc->setDestination(GL_ONE); - blendFunc = shareAttribute(blendFunc); - node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - if (!alphaprop->noSorter()) - { - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(node->getStateSet()); - } - else - { - if (!mPushedSorter) - setBin_Inherit(node->getStateSet()); - } - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - blendFuncStateSet = stateset; - if (!mPushedSorter) - blendFuncStateSet->setRenderBinToInherit(); - } - - if (alphaprop->useAlphaTesting()) - { - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - } + handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(), + alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node); + handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()), + alphaprop->mThreshold, *node); break; } case Nif::RC_BSShaderPPLightingProperty: @@ -2778,8 +2776,6 @@ namespace NifOsg if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); - if (shaderMat->mAlphaBlend && !mPushedSorter) - setBin_Transparent(node->getOrCreateStateSet()); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2799,15 +2795,7 @@ namespace NifOsg specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); if (shaderprop->decal()) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleDecal(hasSortAlpha, node->getOrCreateStateSet()); break; } case Nif::RC_BSEffectShaderProperty: @@ -2816,20 +2804,10 @@ namespace NifOsg if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); - if (shaderMat->mAlphaBlend && !mPushedSorter) - setBin_Transparent(node->getOrCreateStateSet()); break; } if (shaderprop->decal()) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleDecal(hasSortAlpha, node->getOrCreateStateSet()); if (shaderprop->softEffect()) SceneUtil::setupSoftEffect( *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); From 4e3d45db1baf2f656ac1f26cee1d8d31e7c74678 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 07:46:41 +0300 Subject: [PATCH 1376/2167] Deduplicate file handling in niftest --- apps/niftest/niftest.cpp | 79 ++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index c364303376..8f8c408a87 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -26,7 +26,7 @@ namespace bpo = boost::program_options; /// See if the file has the named extension -bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) +bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind) { const auto extension = Files::pathToUnicodeString(filename.extension()); return Misc::StringUtils::ciEqual(extension, extensionToFind); @@ -59,16 +59,17 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) return nullptr; } -void readNIF( +void readFile( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { const std::string pathStr = Files::pathToUnicodeString(path); + const bool isNif = isNIF(path); if (!quiet) { - if (hasExtension(path, ".kf")) - std::cout << "Reading KF file '" << pathStr << "'"; + if (isNif) + std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'"; else - std::cout << "Reading NIF file '" << pathStr << "'"; + std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; @@ -76,41 +77,23 @@ void readNIF( const std::filesystem::path fullPath = !source.empty() ? source / path : path; try { - Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); - Nif::Reader reader(file, nullptr); - if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); + if (isNif) + { + Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); + Nif::Reader reader(file, nullptr); + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } else - reader.parse(Files::openConstrainedFileStream(fullPath)); - } - catch (std::exception& e) - { - std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; - } -} - -void readMaterial( - const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) -{ - const std::string pathStr = Files::pathToUnicodeString(path); - if (!quiet) - { - if (hasExtension(path, ".bgem")) - std::cout << "Reading BGEM file '" << pathStr << "'"; - else - std::cout << "Reading BGSM file '" << pathStr << "'"; - if (!source.empty()) - std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; - std::cout << std::endl; - } - const std::filesystem::path fullPath = !source.empty() ? source / path : path; - try - { - Bgsm::Reader reader; - if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); - else - reader.parse(Files::openConstrainedFileStream(fullPath)); + { + Bgsm::Reader reader; + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } } catch (std::exception& e) { @@ -134,13 +117,9 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - if (isNIF(name.value())) + if (isNIF(name.value()) || isMaterial(name.value())) { - readNIF(archivePath, name.value(), &vfs, quiet); - } - else if (isMaterial(name.value())) - { - readMaterial(archivePath, name.value(), &vfs, quiet); + readFile(archivePath, name.value(), &vfs, quiet); } } @@ -262,13 +241,9 @@ int main(int argc, char** argv) const std::string pathStr = Files::pathToUnicodeString(path); try { - if (isNIF(path)) + if (isNIF(path) || isMaterial(path)) { - readNIF({}, path, vfs.get(), quiet); - } - else if (isMaterial(path)) - { - readMaterial({}, path, vfs.get(), quiet); + readFile({}, path, vfs.get(), quiet); } else if (auto archive = makeArchive(path)) { @@ -276,7 +251,7 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory" + std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory" << std::endl; } } From 6f9206428d882a176952c6fa8bc3014a9c614bdc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 07:53:16 +0300 Subject: [PATCH 1377/2167] Don't ignore material files in NifLoader tests --- apps/openmw_test_suite/nifosg/testnifloader.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index cdab51e6c2..fa023fff0d 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ namespace { VFS::Manager mVfs; Resource::ImageManager mImageManager{ &mVfs, 0 }; + Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 }; const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); osg::ref_ptr mOptions = new osgDB::Options; @@ -70,7 +72,7 @@ namespace init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), R"( osg::Group { UniqueID 1 @@ -259,7 +261,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } @@ -289,7 +291,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } From 32d24e73fedd482f531d285b3daa211ebcce7131 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:00:27 +0300 Subject: [PATCH 1378/2167] Update changelog (#7777, Bethesda material files) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cf145f82..caa3299c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -219,6 +219,7 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7777: Support external Bethesda material files (BGSM/BGEM) Feature #7792: Support Timescale Clouds Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context From 8325e100df61854a29494daf815b9be14a65820e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:12:53 +0300 Subject: [PATCH 1379/2167] More decal deduplication --- components/nifosg/nifloader.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5af4e57c53..4616ac440f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2209,8 +2209,11 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } - void handleDecal(bool hasSortAlpha, osg::ref_ptr stateset) + void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node) { + if (!enabled) + return; + osg::ref_ptr stateset = node.getOrCreateStateSet(); osg::ref_ptr polygonOffset(new osg::PolygonOffset); polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); @@ -2279,10 +2282,7 @@ namespace NifOsg handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node); handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode, true, hasSortAlpha, node); - if (shaderMat->mDecal) - { - handleDecal(hasSortAlpha, node.getOrCreateStateSet()); - } + handleDecal(shaderMat->mDecal, hasSortAlpha, node); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2794,8 +2794,7 @@ namespace NifOsg emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); - if (shaderprop->decal()) - handleDecal(hasSortAlpha, node->getOrCreateStateSet()); + handleDecal(shaderprop->decal(), hasSortAlpha, *node); break; } case Nif::RC_BSEffectShaderProperty: @@ -2806,8 +2805,7 @@ namespace NifOsg handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); break; } - if (shaderprop->decal()) - handleDecal(hasSortAlpha, node->getOrCreateStateSet()); + handleDecal(shaderprop->decal(), hasSortAlpha, *node); if (shaderprop->softEffect()) SceneUtil::setupSoftEffect( *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); From ea5e101821debafb63f47d263c507ce0b4bc4248 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:24:50 +0300 Subject: [PATCH 1380/2167] Handle glow maps for BGSM files --- components/nifosg/nifloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4616ac440f..f661e1e8c6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2188,6 +2188,9 @@ namespace NifOsg if (!bgsm->mNormalMap.empty()) attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures); + if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty()) + attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures); + if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); } From 6be2bb70c3cbf478b21d40405e031bfada9e57e4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 18:11:56 +0300 Subject: [PATCH 1381/2167] Remove unused remnants of NIFStream from BGSMStream --- components/bgsm/reader.cpp | 2 +- components/bgsm/reader.hpp | 2 - components/bgsm/stream.cpp | 96 +++++--------------------------------- components/bgsm/stream.hpp | 73 ++--------------------------- 4 files changed, 17 insertions(+), 156 deletions(-) diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index c89d872bd7..47c052fb81 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -11,7 +11,7 @@ namespace Bgsm { void Reader::parse(Files::IStreamPtr&& inputStream) { - BGSMStream stream(*this, std::move(inputStream)); + BGSMStream stream(std::move(inputStream)); std::array signature; stream.readArray(signature); diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 2d669900ad..2d8a0ed481 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -19,8 +19,6 @@ namespace Bgsm public: void parse(Files::IStreamPtr&& stream); - std::uint32_t getVersion() const { return mFile->mVersion; } - std::unique_ptr& getFile() { return mFile; } }; } diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index 00cc382d3f..2c11066709 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -1,66 +1,8 @@ #include "stream.hpp" -#include - -#include "reader.hpp" - -namespace -{ - - // Read a range of elements into a dynamic buffer per-element - // This one should be used if the type cannot be read contiguously - // (e.g. quaternions) - template - void readRange(Bgsm::BGSMStream& stream, T* dest, size_t size) - { - for (T& value : std::span(dest, size)) - stream.read(value); - } - - // Read a range of elements into a dynamic buffer - // This one should be used if the type can be read contiguously as an array of a different type - // (e.g. osg::VecXf can be read as a float array of X elements) - template - void readAlignedRange(Files::IStreamPtr& stream, T* dest, size_t size) - { - static_assert(std::is_standard_layout_v); - static_assert(std::alignment_of_v == std::alignment_of_v); - static_assert(sizeof(T) == sizeof(elementType) * numElements); - Bgsm::readDynamicBufferOfType(stream, reinterpret_cast(dest), size * numElements); - } - -} - namespace Bgsm { - std::uint32_t BGSMStream::getVersion() const - { - return mReader.getVersion(); - } - - std::string BGSMStream::getSizedString(size_t length) - { - // Prevent potential memory allocation freezes; strings this long are not expected in BGSM - if (length > 1024) - throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); - std::string str(length, '\0'); - mStream->read(str.data(), length); - if (mStream->bad()) - throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); - size_t end = str.find('\0'); - if (end != std::string::npos) - str.erase(end); - return str; - } - - void BGSMStream::getSizedStrings(std::vector& vec, size_t size) - { - vec.resize(size); - for (size_t i = 0; i < vec.size(); i++) - vec[i] = getSizedString(); - } - template <> void BGSMStream::read(osg::Vec2f& vec) { @@ -82,32 +24,18 @@ namespace Bgsm template <> void BGSMStream::read(std::string& str) { - str = getSizedString(); - } - - template <> - void BGSMStream::read(osg::Vec2f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(osg::Vec3f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(osg::Vec4f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(std::string* dest, size_t size) - { - for (std::string& value : std::span(dest, size)) - value = getSizedString(); + std::uint32_t length; + read(length); + // Prevent potential memory allocation freezes; strings this long are not expected in BGSM + if (length > 1024) + throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); + str = std::string(length, '\0'); + mStream->read(str.data(), length); + if (mStream->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + std::size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); } } diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp index 2e03a52dd4..a355523367 100644 --- a/components/bgsm/stream.hpp +++ b/components/bgsm/stream.hpp @@ -8,23 +8,21 @@ #include #include #include -#include #include #include +#include #include #include namespace Bgsm { - class Reader; - template inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) { static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); - pIStream->read((char*)dest, numInstances * sizeof(T)); + pIStream->read(reinterpret_cast(dest), numInstances * sizeof(T)); if (pIStream->bad()) throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of " + std::to_string(numInstances) + " instances"); @@ -39,35 +37,16 @@ namespace Bgsm readBufferOfType(pIStream, static_cast(dest)); } - template - inline void readDynamicBufferOfType(Files::IStreamPtr& pIStream, T* dest, std::size_t numInstances) - { - static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); - pIStream->read((char*)dest, numInstances * sizeof(T)); - if (pIStream->bad()) - throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") dynamic buffer of " - + std::to_string(numInstances) + " instances"); - if constexpr (Misc::IS_BIG_ENDIAN) - for (std::size_t i = 0; i < numInstances; i++) - Misc::swapEndiannessInplace(dest[i]); - } - class BGSMStream { - const Reader& mReader; Files::IStreamPtr mStream; public: - explicit BGSMStream(const Reader& reader, Files::IStreamPtr&& stream) - : mReader(reader) - , mStream(std::move(stream)) + explicit BGSMStream(Files::IStreamPtr&& stream) + : mStream(std::move(stream)) { } - const Reader& getFile() const { return mReader; } - - std::uint32_t getVersion() const; - void skip(size_t size) { mStream->ignore(size); } /// Read into a single instance of type @@ -83,41 +62,6 @@ namespace Bgsm { readBufferOfType(mStream, arr.data()); } - - /// Read instances of type into a dynamic buffer - template - void read(T* dest, size_t size) - { - readDynamicBufferOfType(mStream, dest, size); - } - - /// Read multiple instances of type into a vector - template - void readVector(std::vector& vec, size_t size) - { - if (size == 0) - return; - vec.resize(size); - read(vec.data(), size); - } - - /// Extract an instance of type - template - T get() - { - T data; - read(data); - return data; - } - - /// Read a string of the given length - std::string getSizedString(size_t length); - - /// Read a string of the length specified in the file - std::string getSizedString() { return getSizedString(get()); } - - /// Read a list of strings - void getSizedStrings(std::vector& vec, size_t size); }; template <> @@ -128,15 +72,6 @@ namespace Bgsm void BGSMStream::read(osg::Vec4f& vec); template <> void BGSMStream::read(std::string& str); - - template <> - void BGSMStream::read(osg::Vec2f* dest, size_t size); - template <> - void BGSMStream::read(osg::Vec3f* dest, size_t size); - template <> - void BGSMStream::read(osg::Vec4f* dest, size_t size); - template <> - void BGSMStream::read(std::string* dest, size_t size); } #endif From 77c3cd44674eb9e875e3a87f6f672bdf1e16bf7d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 18:48:12 +0300 Subject: [PATCH 1382/2167] More BGSM cleanup --- components/bgsm/file.cpp | 40 ++++++++----------------- components/bgsm/file.hpp | 2 +- components/bgsm/reader.cpp | 1 - components/bgsm/stream.cpp | 2 -- components/nifosg/nifloader.cpp | 22 +++++++------- components/resource/bgsmfilemanager.cpp | 4 --- components/resource/bgsmfilemanager.hpp | 2 +- 7 files changed, 25 insertions(+), 48 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index f330d8a84e..6b763321be 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -38,9 +38,7 @@ namespace Bgsm } stream.read(mGrayscaleToPaletteColor); if (mVersion >= 6) - { stream.read(mMaskWrites); - } } void BGSMFile::read(BGSMStream& stream) @@ -59,9 +57,7 @@ namespace Bgsm stream.read(mLightingMap); stream.read(mFlowMap); if (mVersion >= 17) - { stream.read(mDistanceFieldAlphaMap); - } } else { @@ -98,9 +94,7 @@ namespace Bgsm stream.read(mWetnessControlSpecPowerScale); stream.read(mWetnessControlSpecMinvar); if (mVersion < 10) - { stream.read(mWetnessControlEnvMapScale); - } stream.read(mWetnessControlFresnelPower); stream.read(mWetnessControlMetalness); if (mVersion >= 3) @@ -116,9 +110,7 @@ namespace Bgsm stream.read(mAnisoLighting); stream.read(mEmitEnabled); if (mEmitEnabled) - { stream.read(mEmittanceColor); - } stream.read(mEmittanceMult); stream.read(mModelSpaceNormals); stream.read(mExternalEmittance); @@ -181,14 +173,14 @@ namespace Bgsm stream.read(mEnvMap); stream.read(mNormalMap); stream.read(mEnvMapMask); + if (mVersion >= 11) + { + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mGlowMap); + } if (mVersion >= 10) { - if (mVersion >= 11) - { - stream.read(mSpecularMap); - stream.read(mLightingMap); - stream.read(mGlowMap); - } stream.read(mEnvMapEnabled); stream.read(mEnvMapMaskScale); } @@ -205,20 +197,12 @@ namespace Bgsm stream.read(mEnvmapMinLOD); stream.read(mSoftDepth); if (mVersion >= 11) - { stream.read(mEmittanceColor); - if (mVersion >= 15) - { - stream.read(mAdaptiveEmissiveExposureParams); - if (mVersion >= 16) - { - stream.read(mGlowMapEnabled); - if (mVersion >= 20) - { - stream.read(mEffectPbrSpecular); - } - } - } - } + if (mVersion >= 15) + stream.read(mAdaptiveEmissiveExposureParams); + if (mVersion >= 16) + stream.read(mGlowMapEnabled); + if (mVersion >= 20) + stream.read(mEffectPbrSpecular); } } diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 3524297e2d..d3fb189256 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -50,7 +50,7 @@ namespace Bgsm MaterialFile() = default; virtual void read(BGSMStream& stream); - virtual ~MaterialFile() {} + virtual ~MaterialFile() = default; }; struct BGSMFile : MaterialFile diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index 47c052fb81..facdee9fb2 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -31,5 +31,4 @@ namespace Bgsm mFile->read(stream); } - } diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index 2c11066709..c4fa9c1d8c 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -2,7 +2,6 @@ namespace Bgsm { - template <> void BGSMStream::read(osg::Vec2f& vec) { @@ -37,5 +36,4 @@ namespace Bgsm if (end != std::string::npos) str.erase(end); } - } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f661e1e8c6..05a8378c11 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2151,18 +2151,19 @@ namespace NifOsg handleTextureControllers(texprop, composite, stateset, animflags); } - Bgsm::MaterialFilePtr getShaderMaterial(std::string_view path) const + static Bgsm::MaterialFilePtr getShaderMaterial( + std::string_view path, Resource::BgsmFileManager* materialManager) { - if (!mMaterialManager) + if (!materialManager) return nullptr; if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) return nullptr; - std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS()); try { - return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + return materialManager->get(VFS::Path::Normalized(normalizedPath)); } catch (std::exception& e) { @@ -2528,7 +2529,7 @@ namespace NifOsg node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); clearBoundTextures(stateset, boundTextures); - if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) { handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; @@ -2557,7 +2558,7 @@ namespace NifOsg node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); clearBoundTextures(stateset, boundTextures); - if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) { handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; @@ -2776,15 +2777,14 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); - if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); - specEnabled - = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly - specStrength = bgsm->mSpecularMult; + specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting + specStrength = 1.f; // bgsm->mSpecularMult; emissiveMult = bgsm->mEmittanceMult; } break; @@ -2803,7 +2803,7 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); - if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); break; diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp index 5155db17ce..7f749e9453 100644 --- a/components/resource/bgsmfilemanager.cpp +++ b/components/resource/bgsmfilemanager.cpp @@ -1,7 +1,5 @@ #include "bgsmfilemanager.hpp" -#include - #include #include @@ -36,8 +34,6 @@ namespace Resource { } - BgsmFileManager::~BgsmFileManager() = default; - Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp index b7c0d07c5a..3c77c2c665 100644 --- a/components/resource/bgsmfilemanager.hpp +++ b/components/resource/bgsmfilemanager.hpp @@ -14,7 +14,7 @@ namespace Resource { public: BgsmFileManager(const VFS::Manager* vfs, double expiryDelay); - ~BgsmFileManager(); + ~BgsmFileManager() = default; /// Retrieve a material file from the cache or load it from the VFS if not cached yet. Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name); From c1639e54b120c8e429b2117b29f3fdf55e54a8a0 Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 20 Apr 2024 19:53:56 +0200 Subject: [PATCH 1383/2167] ESM::Dialogue Lua bindings 6 --- apps/openmw/mwlua/dialoguebindings.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index 6dbe331d6e..29c292491a 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -38,7 +38,11 @@ namespace DecoratedIterator mIter; public: + using iterator_category = DecoratedIterator::iterator_category; + using value_type = DecoratedIterator::value_type; using difference_type = DecoratedIterator::difference_type; + using pointer = DecoratedIterator::pointer; + using reference = DecoratedIterator::reference; FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) : mIter{ decoratedIterator } @@ -64,14 +68,28 @@ namespace return iter; } + FilteredDialogueIterator& operator+=(difference_type advance) + { + while (advance > 0) + { + std::advance(mIter, 1); + if (mIter->mType != filter) + { + --advance; + } + } + return *this; + } + bool operator==(const FilteredDialogueIterator& x) const { return mIter == x.mIter; } bool operator!=(const FilteredDialogueIterator& x) const { return not(*this == x); } - const ESM::Dialogue& operator*() const { return *mIter; } + const value_type& operator*() const { return *mIter; } - const ESM::Dialogue* operator->() const { return &(*mIter); } + const value_type* operator->() const { return &(*mIter); } }; + using iterator = FilteredDialogueIterator; const ESM::Dialogue* search(const ESM::RefId& id) const From 277d8305304bc94332ee313032444be44c2f7d2d Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 20 Apr 2024 20:23:37 +0200 Subject: [PATCH 1384/2167] ESM::Dialogue Lua bindings 6 --- apps/openmw/mwlua/dialoguebindings.cpp | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwlua/dialoguebindings.cpp b/apps/openmw/mwlua/dialoguebindings.cpp index 29c292491a..c1708816bc 100644 --- a/apps/openmw/mwlua/dialoguebindings.cpp +++ b/apps/openmw/mwlua/dialoguebindings.cpp @@ -36,6 +36,7 @@ namespace { using DecoratedIterator = MWWorld::Store::iterator; DecoratedIterator mIter; + DecoratedIterator mEndIter; public: using iterator_category = DecoratedIterator::iterator_category; @@ -44,8 +45,9 @@ namespace using pointer = DecoratedIterator::pointer; using reference = DecoratedIterator::reference; - FilteredDialogueIterator(const DecoratedIterator& decoratedIterator) - : mIter{ decoratedIterator } + FilteredDialogueIterator(const DecoratedIterator& pointingIterator, const DecoratedIterator& end) + : mIter{ pointingIterator } + , mEndIter{ end } { } @@ -53,8 +55,8 @@ namespace { do { - std::advance(mIter, 1); - } while (mIter->mType != filter); + ++mIter; + } while (mIter->mType != filter and mIter != mEndIter); return *this; } @@ -63,17 +65,17 @@ namespace FilteredDialogueIterator iter = *this; do { - std::advance(mIter, 1); - } while (mIter->mType != filter); + ++mIter; + } while (mIter->mType != filter and mIter != mEndIter); return iter; } FilteredDialogueIterator& operator+=(difference_type advance) { - while (advance > 0) + while (advance > 0 and mIter != mEndIter) { - std::advance(mIter, 1); - if (mIter->mType != filter) + ++mIter; + if (mIter->mType == filter) { --advance; } @@ -105,7 +107,7 @@ namespace } auto result = begin(); - std::advance(result, index); + result += index; return &(*result); } @@ -118,15 +120,15 @@ namespace iterator begin() const { - iterator result{ mDialogueStore.begin() }; + iterator result{ mDialogueStore.begin(), mDialogueStore.end() }; while (result != end() and result->mType != filter) { - std::advance(result, 1); + ++result; } return result; } - iterator end() const { return iterator{ mDialogueStore.end() }; } + iterator end() const { return iterator{ mDialogueStore.end(), mDialogueStore.end() }; } }; template From a863899eb1a158fce32cc3193e8899f17c4d4c24 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Apr 2024 19:51:48 +0200 Subject: [PATCH 1385/2167] Use normalized path for SoundManager::streamMusic --- apps/openmw/engine.cpp | 6 ++--- apps/openmw/mwbase/soundmanager.hpp | 2 +- apps/openmw/mwgui/levelupdialog.cpp | 5 ++-- apps/openmw/mwlua/soundbindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 4 ++- apps/openmw/mwscript/soundextensions.cpp | 2 +- apps/openmw/mwsound/constants.hpp | 3 +++ apps/openmw/mwsound/soundmanagerimp.cpp | 31 +++++++++++------------- apps/openmw/mwsound/soundmanagerimp.hpp | 10 ++++---- components/misc/resourcehelpers.cpp | 7 +++--- components/misc/resourcehelpers.hpp | 6 ++--- 11 files changed, 40 insertions(+), 38 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 63473fe67d..179dbcdc32 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -64,6 +64,7 @@ #include "mwscript/interpretercontext.hpp" #include "mwscript/scriptmanagerimp.hpp" +#include "mwsound/constants.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" @@ -987,9 +988,8 @@ void OMW::Engine::go() // start in main menu mWindowManager->pushGuiMode(MWGui::GM_MainMenu); - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); else Log(Debug::Warning) << "Title music not found"; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index a55d696224..ab3f9c5605 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -117,7 +117,7 @@ namespace MWBase virtual void stopMusic() = 0; ///< Stops music if it's playing - virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0; + virtual void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) = 0; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 4950e3edf4..f1a40a3f16 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -22,6 +22,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwsound/constants.hpp" + #include "class.hpp" namespace @@ -216,8 +218,7 @@ namespace MWGui center(); // Play LevelUp Music - MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special); + MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Special); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index 57d1e606c1..a5ec69b4fc 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -141,7 +141,7 @@ namespace MWLua api["streamMusic"] = [](std::string_view fileName, const sol::optional& options) { auto args = getStreamMusicArgs(options); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); + sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Scripted, args.mFade); }; api["say"] diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index addf62df34..32f81c398e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,6 +39,8 @@ #include "../mwrender/vismask.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actorutil.hpp" #include "aicombataction.hpp" @@ -1798,7 +1800,7 @@ namespace MWMechanics MWBase::Environment::get().getStateManager()->askLoadRecent(); // Play Death Music if it was the player dying MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Death.mp3", MWSound::MusicType::Special); + MWSound::deathMusic, MWSound::MusicType::Special); } else { diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index ee39860584..79bdf20160 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -63,7 +63,7 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - std::string music{ runtime.getStringLiteral(runtime[0].mInteger) }; + const VFS::Path::Normalized music(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWBase::Environment::get().getSoundManager()->streamMusic( diff --git a/apps/openmw/mwsound/constants.hpp b/apps/openmw/mwsound/constants.hpp index 5022b142f9..217dd1935e 100644 --- a/apps/openmw/mwsound/constants.hpp +++ b/apps/openmw/mwsound/constants.hpp @@ -7,6 +7,9 @@ namespace MWSound { constexpr VFS::Path::NormalizedView battlePlaylist("battle"); constexpr VFS::Path::NormalizedView explorePlaylist("explore"); + constexpr VFS::Path::NormalizedView titleMusic("music/special/morrowind title.mp3"); + constexpr VFS::Path::NormalizedView triumphMusic("music/special/mw_triumph.mp3"); + constexpr VFS::Path::NormalizedView deathMusic("music/special/mw_death.mp3"); } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 039c283d7a..bcaec8ddfd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -252,13 +252,13 @@ namespace MWSound } } - void SoundManager::streamMusicFull(const std::string& filename) + void SoundManager::streamMusicFull(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; stopMusic(); - if (filename.empty()) + if (filename.value().empty()) return; Log(Debug::Info) << "Playing \"" << filename << "\""; @@ -267,9 +267,9 @@ namespace MWSound DecoderPtr decoder = getDecoder(); try { - decoder->open(VFS::Path::Normalized(filename)); + decoder->open(filename); } - catch (std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what(); return; @@ -285,7 +285,7 @@ namespace MWSound mOutput->streamSound(std::move(decoder), mMusic.get()); } - void SoundManager::advanceMusic(const std::string& filename, float fadeOut) + void SoundManager::advanceMusic(VFS::Path::NormalizedView filename, float fadeOut) { if (!isMusicPlaying()) { @@ -304,7 +304,7 @@ namespace MWSound if (playlist == mMusicFiles.end() || playlist->second.empty()) { - advanceMusic(std::string()); + advanceMusic(VFS::Path::NormalizedView()); return; } @@ -338,7 +338,7 @@ namespace MWSound return mMusic && mOutput->isStreamPlaying(mMusic.get()); } - void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade) + void SoundManager::streamMusic(VFS::Path::NormalizedView filename, MusicType type, float fade) { const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); @@ -347,10 +347,8 @@ namespace MWSound && type != MusicType::Special) return; - std::string normalizedName = VFS::Path::normalizeFilename(filename); - mechanicsManager->setMusicType(type); - advanceMusic(normalizedName, fade); + advanceMusic(filename, fade); if (type == MWSound::MusicType::Battle) mCurrentPlaylist = battlePlaylist; else if (type == MWSound::MusicType::Explore) @@ -367,8 +365,8 @@ namespace MWSound if (it == mMusicFiles.end()) { std::vector filelist; - constexpr VFS::Path::NormalizedView music("music"); - const VFS::Path::Normalized playlistPath = music / playlist / VFS::Path::NormalizedView(); + const VFS::Path::Normalized playlistPath + = Misc::ResourceHelpers::correctMusicPath(playlist) / VFS::Path::NormalizedView(); for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath))) filelist.push_back(name); @@ -1143,10 +1141,10 @@ namespace MWSound if (!mMusic || !mMusic->updateFade(duration) || !mOutput->isStreamPlaying(mMusic.get())) { stopMusic(); - if (!mNextMusic.empty()) + if (!mNextMusic.value().empty()) { streamMusicFull(mNextMusic); - mNextMusic.clear(); + mNextMusic = VFS::Path::Normalized(); } } else @@ -1166,9 +1164,8 @@ namespace MWSound if (isMainMenu && !isMusicPlaying()) { - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); } updateSounds(duration); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 7fc7c143d5..1ba80f1d73 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -56,7 +56,7 @@ namespace MWSound std::unordered_map, VFS::Path::Hash, std::equal_to<>> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played - std::string mLastPlayedMusic; // The music file that was last played + VFS::Path::Normalized mLastPlayedMusic; // The music file that was last played WaterSoundUpdater mWaterSoundUpdater; @@ -104,7 +104,7 @@ namespace MWSound Sound* mUnderwaterSound; Sound* mNearWaterSound; - std::string mNextMusic; + VFS::Path::Normalized mNextMusic; bool mPlaybackPaused; RegionSoundSelector mRegionSoundSelector; @@ -125,8 +125,8 @@ namespace MWSound StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); - void streamMusicFull(const std::string& filename); - void advanceMusic(const std::string& filename, float fadeOut = 1.f); + void streamMusicFull(VFS::Path::NormalizedView filename); + void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f); void startRandomTitle(); void cull3DSound(SoundBase* sound); @@ -176,7 +176,7 @@ namespace MWSound void stopMusic() override; ///< Stops music if it's playing - void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override; + void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) override; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1eb3800012..d3d75a8176 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -186,11 +186,10 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normali return prefix / resPath; } -std::string Misc::ResourceHelpers::correctMusicPath(std::string_view resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctMusicPath(VFS::Path::NormalizedView resPath) { - std::string result("music/"); - result += resPath; - return result; + static constexpr VFS::Path::NormalizedView prefix("music"); + return prefix / resPath; } std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 4e747c54ee..c0728dc442 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -38,11 +38,11 @@ namespace Misc // Adds "meshes\\". std::string correctMeshPath(std::string_view resPath); - // Adds "sound\\". + // Prepends "sound/". VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); - // Adds "music\\". - std::string correctMusicPath(std::string_view resPath); + // Prepends "music/". + VFS::Path::Normalized correctMusicPath(VFS::Path::NormalizedView resPath); // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); From 884668927f73d7c19a9c84793c236620f4f21ab5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 21:20:30 +0300 Subject: [PATCH 1386/2167] BGSM Reader: include cleanup, adjust getFile return result --- components/bgsm/reader.cpp | 1 - components/bgsm/reader.hpp | 5 +---- components/resource/bgsmfilemanager.cpp | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index facdee9fb2..eefc8b48b5 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -4,7 +4,6 @@ #include #include -#include "file.hpp" #include "stream.hpp" namespace Bgsm diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 2d8a0ed481..48508c9143 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -1,10 +1,7 @@ #ifndef OPENMW_COMPONENTS_BGSM_READER_HPP #define OPENMW_COMPONENTS_BGSM_READER_HPP -#include -#include #include -#include #include @@ -19,7 +16,7 @@ namespace Bgsm public: void parse(Files::IStreamPtr&& stream); - std::unique_ptr& getFile() { return mFile; } + std::unique_ptr getFile() { return std::move(mFile); } }; } #endif diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp index 7f749e9453..2d439ccc8a 100644 --- a/components/resource/bgsmfilemanager.cpp +++ b/components/resource/bgsmfilemanager.cpp @@ -43,7 +43,7 @@ namespace Resource { Bgsm::Reader reader; reader.parse(mVFS->get(name)); - Bgsm::MaterialFilePtr file = std::move(reader.getFile()); + Bgsm::MaterialFilePtr file = reader.getFile(); obj = new BgsmFileHolder(file); mCache->addEntryToObjectCache(name.value(), obj); return file; From 2264d067fce43b6b2001cc83e367d063001e0955 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Apr 2024 16:57:33 +0400 Subject: [PATCH 1387/2167] Clamp widgets coordinates to avoid crashes --- apps/opencs/view/doc/view.cpp | 11 ++++++++++- apps/opencs/view/world/tablesubview.cpp | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index f5cdb1b8fc..88a33108c0 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -1110,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) { QRect rect; if (isGrowLimit) - rect = QApplication::screenAt(pos())->geometry(); + { + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + + rect = QApplication::screenAt(position)->geometry(); + } else rect = desktopRect(); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 891d954ad4..b48eaec31d 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -78,8 +78,16 @@ CSVWorld::TableSubView::TableSubView( widget->setLayout(layout); setWidget(widget); + + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + // prefer height of the screen and full width of the table - const QRect rect = QApplication::screenAt(pos())->geometry(); + const QRect rect = QApplication::screenAt(position)->geometry(); int frameHeight = 40; // set a reasonable default QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) From 1909ec121b7f478b35780d8acedcb57667ca3bc3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 8 Apr 2024 22:02:05 +0400 Subject: [PATCH 1388/2167] Take in account faction reaction changes --- apps/openmw/mwbase/dialoguemanager.hpp | 5 +++++ apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 11 +++++++++++ apps/openmw/mwdialogue/dialoguemanagerimp.hpp | 2 ++ apps/openmw/mwlua/factionbindings.cpp | 13 +++++++++++++ 4 files changed, 31 insertions(+) diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 70887c69c1..ccb4b9f43f 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -2,6 +2,7 @@ #define GAME_MWBASE_DIALOGUEMANAGER_H #include +#include #include #include #include @@ -108,11 +109,15 @@ namespace MWBase /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int diff) = 0; + /// Set faction1's opinion of faction2. virtual void setFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2, int absolute) = 0; /// @return faction1's opinion of faction2 virtual int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const = 0; + /// @return all faction's opinion overrides + virtual const std::map* getFactionReactionOverrides(const ESM::RefId& faction) const = 0; + /// Removes the last added topic response for the given actor from the journal virtual void clearInfoActor(const MWWorld::Ptr& actor) const = 0; }; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 556b5b53d7..27178606be 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -738,6 +738,17 @@ namespace MWDialogue return 0; } + const std::map* DialogueManager::getFactionReactionOverrides(const ESM::RefId& faction) const + { + // Make sure the faction exists + MWBase::Environment::get().getESMStore()->get().find(faction); + + const auto found = mChangedFactionReaction.find(faction); + if (found != mChangedFactionReaction.end()) + return &found->second; + return nullptr; + } + void DialogueManager::clearInfoActor(const MWWorld::Ptr& actor) const { if (actor == mActor && !mLastTopic.empty()) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index c214106fdc..af8adfb876 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -126,6 +126,8 @@ namespace MWDialogue /// @return faction1's opinion of faction2 int getFactionReaction(const ESM::RefId& faction1, const ESM::RefId& faction2) const override; + const std::map* getFactionReactionOverrides(const ESM::RefId& faction) const override; + /// Removes the last added topic response for the given actor from the journal void clearInfoActor(const MWWorld::Ptr& actor) const override; }; diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index 83b9cfc5e8..8f85671ff5 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -4,6 +4,9 @@ #include #include +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/environment.hpp" + #include "../mwworld/store.hpp" #include "idcollectionbindings.hpp" @@ -70,6 +73,16 @@ namespace MWLua sol::table res(lua, sol::create); for (const auto& [factionId, reaction] : rec.mReactions) res[factionId.serializeText()] = reaction; + + const auto* overrides + = MWBase::Environment::get().getDialogueManager()->getFactionReactionOverrides(rec.mId); + + if (overrides != nullptr) + { + for (const auto& [factionId, reaction] : *overrides) + res[factionId.serializeText()] = reaction; + } + return res; }); factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { From 440dc30ffad981babb1b8f9a9d28e8b82f62d358 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Apr 2024 15:37:02 +0100 Subject: [PATCH 1389/2167] Run Windows jobs on new Windows Server 2022 images If no other software changed, then the same cache keys will still work. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index feaccc2540..02f31cea4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -533,7 +533,7 @@ macOS14_Xcode15_arm64: .Windows_Ninja_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: @@ -656,7 +656,7 @@ macOS14_Xcode15_arm64: .Windows_MSBuild_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: From da56e1073ef6881518761d3ddbf942b0324ce4ad Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Apr 2024 16:10:36 +0100 Subject: [PATCH 1390/2167] Try MSVC 2022 Looks like they *did* upgrade MSVC on the new Windows images. --- .gitlab-ci.yml | 60 ++++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02f31cea4a..406d6411d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -570,10 +570,10 @@ macOS14_Xcode15_arm64: - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E + - New-Item -Type File -Force -Path MSVC2022_64_Ninja\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E - Get-Volume - - cd MSVC2019_64_Ninja + - cd MSVC2022_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - ccache --show-stats -v @@ -583,47 +583,41 @@ macOS14_Xcode15_arm64: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v9 + key: ninja-2022-v9 paths: - ccache - deps - - MSVC2019_64_Ninja/deps/Qt + - MSVC2022_64_Ninja/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64_Ninja/*.log - - MSVC2019_64_Ninja/*/*.log - - MSVC2019_64_Ninja/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log + - MSVC2022_64_Ninja/*.log + - MSVC2022_64_Ninja/**/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h @@ -688,9 +682,9 @@ macOS14_Xcode15_arm64: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - - cd MSVC2019_64 + - New-Item -Type File -Force -Path MSVC2022_64\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E + - cd MSVC2022_64 - Get-Volume - cmake --build . --config $config - cd $config @@ -699,46 +693,40 @@ macOS14_Xcode15_arm64: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v9 + key: msbuild-2022-v9 paths: - deps - - MSVC2019_64/deps/Qt + - MSVC2022_64/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64/*.log - - MSVC2019_64/*/*.log - - MSVC2019_64/*/*/*.log - - MSVC2019_64/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*/*.log + - MSVC2022_64/*.log + - MSVC2022_64/**/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h From 1bb48bcef74d2b5123812e1b4e1e180fc622696c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Apr 2024 20:56:06 +0400 Subject: [PATCH 1391/2167] Treat editor icons as scalable --- CI/before_script.msvc.sh | 18 + CMakeLists.txt | 20 +- apps/launcher/CMakeLists.txt | 2 +- apps/opencs/view/doc/startup.cpp | 8 +- apps/opencs/view/doc/startup.hpp | 2 +- apps/opencs/view/render/instancemovemode.cpp | 2 +- apps/opencs/view/widget/scenetoolmode.cpp | 2 +- .../view/widget/scenetoolshapebrush.cpp | 16 +- .../view/widget/scenetooltexturebrush.cpp | 16 +- apps/opencs/view/widget/scenetooltoggle2.cpp | 2 +- apps/opencs/view/world/dragrecordtable.cpp | 2 +- apps/wizard/CMakeLists.txt | 2 +- .../contentselector/view/contentselector.cpp | 2 +- files/opencs/configure.svg | 501 ++++++++++++++++++ files/opencs/{scalable => }/editor-icons.svg | 0 files/opencs/menu-reload.svg | 85 ++- files/opencs/object.svg | 8 +- files/opencs/raster/startup/big/configure.png | Bin 17972 -> 0 bytes .../opencs/raster/startup/small/configure.png | Bin 1450 -> 0 bytes .../raster/startup/small/create-addon.png | Bin 1714 -> 0 bytes .../raster/startup/small/edit-content.png | Bin 2471 -> 0 bytes .../opencs/raster/startup/small/new-game.png | Bin 2122 -> 0 bytes files/opencs/record-edit.svg | 2 +- files/opencs/record-touch.svg | 31 +- files/opencs/resources.qrc | 2 +- files/opencs/run-log.svg | 7 +- files/opencs/scalable/startup/configure.svgz | Bin 5808 -> 0 bytes files/opencs/sound.svg | 14 +- 28 files changed, 669 insertions(+), 75 deletions(-) create mode 100644 files/opencs/configure.svg rename files/opencs/{scalable => }/editor-icons.svg (100%) delete mode 100644 files/opencs/raster/startup/big/configure.png delete mode 100644 files/opencs/raster/startup/small/configure.png delete mode 100644 files/opencs/raster/startup/small/create-addon.png delete mode 100644 files/opencs/raster/startup/small/edit-content.png delete mode 100644 files/opencs/raster/startup/small/new-game.png delete mode 100644 files/opencs/scalable/startup/configure.svgz diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index c2d91ccfd3..2423f0c54f 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -357,6 +357,16 @@ add_qt_image_dlls() { QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@" } +declare -A QT_ICONENGINES +QT_ICONENGINES["Release"]="" +QT_ICONENGINES["Debug"]="" +QT_ICONENGINES["RelWithDebInfo"]="" +add_qt_icon_dlls() { + local CONFIG=$1 + shift + QT_ICONENGINES[$CONFIG]="${QT_ICONENGINES[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -942,6 +952,7 @@ printf "Qt ${QT_VER}... " add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" + add_qt_icon_dlls $CONFIGURATION "$(pwd)/plugins/iconengines/qsvgicon${DLLSUFFIX}.dll" done echo Done. } @@ -1143,6 +1154,13 @@ fi cp "$DLL" "${DLL_PREFIX}imageformats" done echo + echo "- Qt Icon Engine DLLs..." + mkdir -p ${DLL_PREFIX}iconengines + for DLL in ${QT_ICONENGINES[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}iconengines" + done + echo done #fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 074c91344b..b478a799eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -825,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + + get_property(QT_QSVG_ICON_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgIconPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_ICON_PLUGIN_DIR "${QT_QSVG_ICON_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_ICON_PLUGIN_GROUP "${QT_QSVG_ICON_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_ICON_PLUGIN_NAME "${QT_QSVG_ICON_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY) if (BUILD_OPENCS) @@ -832,13 +844,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) - configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) - - get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) - get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) - get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) - get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bd6a7062fd..91b985948d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -83,7 +83,7 @@ target_link_libraries(openmw-launcher components_qt ) -target_link_libraries(openmw-launcher Qt::Widgets Qt::Core) +target_link_libraries(openmw-launcher Qt::Widgets Qt::Core Qt::Svg) if (BUILD_WITH_CODE_COVERAGE) target_compile_options(openmw-launcher PRIVATE --coverage) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 27463b0456..e6323bf1a1 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -10,7 +10,7 @@ #include #include -QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon) +QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QString& icon) { int column = mColumn--; @@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons() mLayout = new QGridLayout(widget); /// \todo add icons - QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content")); + QPushButton* loadDocument = addButton("Edit A Content File", ":startup/edit-content"); connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument); - QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon")); + QPushButton* createAddon = addButton("Create A New Addon", ":startup/create-addon"); connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon); - QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game")); + QPushButton* createGame = addButton("Create A New Game", ":startup/create-game"); connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame); for (int i = 0; i < 3; ++i) diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp index 061b91b2d1..f2cccfcd38 100644 --- a/apps/opencs/view/doc/startup.hpp +++ b/apps/opencs/view/doc/startup.hpp @@ -20,7 +20,7 @@ namespace CSVDoc int mColumn; QGridLayout* mLayout; - QPushButton* addButton(const QString& label, const QIcon& icon); + QPushButton* addButton(const QString& label, const QString& icon); QWidget* createButtons(); diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index e4004a1537..bc999eb633 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -8,7 +8,7 @@ class QWidget; CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) - : ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")), + : ModeButton(QIcon(":scenetoolbar/transform-move"), "Move selected instances" "