1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-30 23:34:34 +00:00

Resolve merge conflicts from !4971

This commit is contained in:
AnyOldName3 2025-11-10 16:01:47 +00:00
commit ef8e7d97cb
32 changed files with 468 additions and 245 deletions

View file

@ -429,7 +429,11 @@ Ubuntu_Clang:
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.linux.sh
- cp extern/.clang-tidy build/.clang-tidy
- mkdir -p build/extern build/apps/launcher build/apps/opencs build/apps/wizard
- cp extern/.clang-tidy build/extern/
- cp extern/.clang-tidy build/apps/launcher/
- cp extern/.clang-tidy build/apps/opencs/
- cp extern/.clang-tidy build/apps/wizard/
- cd build
- find . -name *.o -exec touch {} \;
- cmake --build . -- -j $(nproc) ${BUILD_TARGETS}

View file

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 51)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 99)
set(OPENMW_LUA_API_REVISION 101)
set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "")

View file

@ -158,6 +158,13 @@ namespace
EXPECT_EQ(get<std::string>(lua, "darkRed:asHex()"), "a01112");
EXPECT_TRUE(get<bool>(lua, "green:asRgba() == util.vector4(0, 1, 0, 1)"));
EXPECT_TRUE(get<bool>(lua, "red:asRgb() == util.vector3(1, 0, 0)"));
lua.safe_script("green = util.color.commaString('0,255,0')");
EXPECT_EQ(get<std::string>(lua, "tostring(green)"), "(0, 1, 0, 1)");
lua.safe_script("red = util.color.commaString('255, 0, 0, 255')");
EXPECT_EQ(get<std::string>(lua, "tostring(red)"), "(1, 0, 0, 1)");
lua.safe_script("blue = util.color.commaString('0, 0, 1000, 255')");
EXPECT_EQ(get<std::string>(lua, "tostring(blue)"), "(0, 0, 1, 1)");
EXPECT_ERROR(lua.safe_script("white = util.color.commaString('aaa')"), "Invalid comma-separated color");
}
TEST_F(LuaUtilPackageTest, Transform)

View file

@ -82,9 +82,10 @@ namespace MWGui
if (Settings::gui().mControllerMenus)
{
mControllerFocus = -1;
if (mItemCount > 0)
mControllerFocus = std::clamp(mControllerFocus, 0, mItemCount - 1);
else
mControllerFocus = -1;
updateControllerFocus(-1, mControllerFocus);
}

View file

@ -222,21 +222,23 @@ namespace MWLua
// Run event handlers for events that were sent before `finalizeEventBatch`.
mLuaEvents.callEventHandlers();
// Run queued callbacks
for (CallbackWithData& c : mQueuedCallbacks)
c.mCallback.tryCall(c.mArg);
mQueuedCallbacks.clear();
mLua.protectedCall([&](LuaUtil::LuaView& lua) {
// Run queued callbacks
for (CallbackWithData& c : mQueuedCallbacks)
c.mCallback.tryCall(c.mArg);
mQueuedCallbacks.clear();
// Run engine handlers
mEngineEvents.callEngineHandlers();
bool isPaused = timeManager.isPaused();
// Run engine handlers
mEngineEvents.callEngineHandlers();
bool isPaused = timeManager.isPaused();
float frameDuration = MWBase::Environment::get().getFrameDuration();
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->update(isPaused ? 0 : frameDuration);
mGlobalScripts.update(isPaused ? 0 : frameDuration);
float frameDuration = MWBase::Environment::get().getFrameDuration();
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->update(isPaused ? 0 : frameDuration);
mGlobalScripts.update(isPaused ? 0 : frameDuration);
mLua.protectedCall([&](LuaUtil::LuaView& lua) { mScriptTracker.unloadInactiveScripts(lua); });
mScriptTracker.unloadInactiveScripts(lua);
});
}
void LuaManager::objectTeleported(const MWWorld::Ptr& ptr)

View file

@ -26,18 +26,27 @@ namespace
{
std::vector<T> ignore;
if (const auto& ignoreObj = options.get<sol::optional<MWLua::LObject>>("ignore"))
if (const auto& ignoreObj = options.get<sol::optional<sol::object>>("ignore"))
{
ignore.push_back(ignoreObj->ptr());
}
else if (const auto& ignoreTable = options.get<sol::optional<sol::table>>("ignore"))
{
ignoreTable->for_each([&](const auto& _, const sol::object& value) {
if (value.is<MWLua::LObject>())
if (ignoreObj->is<MWLua::LObject>())
ignore.push_back(ignoreObj->as<MWLua::LObject>().ptr());
else if (ignoreObj->is<MWLua::LObjectList>())
{
for (const MWLua::ObjectId& id : *ignoreObj->as<MWLua::LObjectList>().mIds)
{
ignore.push_back(value.as<MWLua::LObject>().ptr());
ignore.push_back(MWLua::LObject(id).ptr());
}
});
}
else
{
// ignoreObj->as throws if the type doesn't match, but an unchecked value.as crashes...
ignoreObj->as<sol::lua_table>().for_each([&](sol::object _, sol::object value) {
if (value.is<MWLua::LObject>())
ignore.push_back(value.as<MWLua::LObject>().ptr());
else
throw std::runtime_error("Table value is not a GameObject");
});
}
}
return ignore;

View file

@ -77,6 +77,7 @@ namespace MWLua
luaManager->addAction([state] { MWBase::Environment::get().getWindowManager()->setHudVisibility(state); });
};
api["_isHudVisible"] = []() -> bool { return MWBase::Environment::get().getWindowManager()->isHudVisible(); };
api["_getDefaultFontSize"] = []() -> int { return Settings::gui().mFontSize; };
api["showMessage"]
= [luaManager = context.mLuaManager](std::string_view message, const sol::optional<sol::table>& options) {
MWGui::ShowInDialogueMode mode = MWGui::ShowInDialogueMode_IfPossible;

View file

@ -1,6 +1,8 @@
#include "combat.hpp"
#include <array>
#include <components/misc/rng.hpp>
#include <components/settings/values.hpp>
@ -504,15 +506,19 @@ namespace MWMechanics
}
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (isWerewolf)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Sound* sound = store.get<ESM::Sound>().searchRandom("WolfHit", prng);
if (sound)
sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f);
}
else if (!healthdmg)
sndMgr->playSound3D(victim, ESM::RefId::stringRefId("Hand To Hand Hit"), 1.0f, 1.0f);
{
static const std::array<ESM::RefId, 2> sounds
= { ESM::RefId::stringRefId("Hand To Hand Hit"), ESM::RefId::stringRefId("Hand To Hand Hit 2") };
sndMgr->playSound3D(victim, sounds[Misc::Rng::rollDice(sounds.size(), prng)], 1.0f, 1.0f);
}
}
void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float attackStrength)

View file

@ -21,11 +21,11 @@ namespace MWMechanics
{
/// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him)
/// @param actor The actor that will potentially catch diseases. Currently only the player can catch diseases.
/// @param actor The actor that will potentially catch diseases. Actors cannot catch diseases from the player.
/// @param carrier The disease carrier.
inline void diseaseContact(const MWWorld::Ptr& actor, const MWWorld::Ptr& carrier)
{
if (!carrier.getClass().isActor() || actor != getPlayer())
if (!carrier.getClass().isActor() || carrier == getPlayer())
return;
float fDiseaseXferChance = MWBase::Environment::get()
@ -71,13 +71,16 @@ namespace MWMechanics
creatureStats.getActiveSpells().addSpell(spell, actor, false);
MWBase::Environment::get().getWorld()->applyLoopingParticles(actor);
std::string msg = MWBase::Environment::get()
.getESMStore()
->get<ESM::GameSetting>()
.find("sMagicContractDisease")
->mValue.getString();
msg = Misc::StringUtils::format(msg, spell->mName);
MWBase::Environment::get().getWindowManager()->messageBox(msg);
if (actor == getPlayer())
{
std::string msg = MWBase::Environment::get()
.getESMStore()
->get<ESM::GameSetting>()
.find("sMagicContractDisease")
->mValue.getString();
msg = Misc::StringUtils::format(msg, spell->mName);
MWBase::Environment::get().getWindowManager()->messageBox(msg);
}
}
}
}

View file

@ -1,4 +1,6 @@
#include <gtest/gtest.h>
#include <array>
#include <sstream>
#include "testutils.hpp"
@ -126,6 +128,34 @@ player -> addSpell "fire_bite", 645
PositionCell "Rabenfels, Taverne" 4480.000 3968.000 15820.000 0
End)mwscript";
const std::string sScript5 = R"mwscript(Begin messagebox_format_script
float fVal
set fVal to 12.34
MessageBox "hello world"
MessageBox "%.0f" fVal
MessageBox "%.f" fVal
MessageBox "a %03.0f b" fVal
MessageBox "%+04.0f" fVal
MessageBox "%+4.0f" fVal
MessageBox "%+ 4.0f" fVal
MessageBox "%0+ 4.0f" fVal
MessageBox "%0+ #4.0f" fVal
MessageBox "%- 5.0f" fVal
MessageBox "%g" fVal
MessageBox "%.3g" fVal
MessageBox "%.5g" fVal
MessageBox "%#.5g" fVal
MessageBox "%-5g" fVal
MessageBox "%- 5g" fVal
MessageBox "%.1b" fVal
End)mwscript";
const std::string sIssue587 = R"mwscript(Begin stalresetScript
@ -579,6 +609,47 @@ End)mwscript";
EXPECT_FALSE(!compile(sScript4));
}
TEST_F(MWScriptTest, mwscript_test_messagebox_format)
{
if (const auto script = compile(sScript5))
{
TestInterpreterContext context;
run(*script, context);
using std::string_view_literals::operator""sv;
constexpr std::array expected{
"hello world"sv,
"12"sv,
"12"sv,
"a 012 b"sv,
"+012"sv,
" +12"sv,
" +12"sv,
"+012"sv,
"+12."sv,
" 12 "sv,
"12.34"sv,
"12.3"sv,
"12.34"sv,
"12.340"sv,
"12.34"sv,
" 12.34"sv,
"b"sv,
};
const std::vector<std::string>& output = context.getMessages();
EXPECT_EQ(expected.size(), output.size());
for (std::size_t i = 0; i < output.size(); i++)
{
EXPECT_EQ(expected[i], output[i]);
}
}
else
{
FAIL();
}
}
TEST_F(MWScriptTest, mwscript_test_587)
{
EXPECT_FALSE(!compile(sIssue587));

View file

@ -145,8 +145,11 @@ namespace
{
LocalVariables mLocals;
std::map<ESM::RefId, GlobalVariables> mMembers;
std::vector<std::string> mMessages;
public:
const std::vector<std::string>& getMessages() { return mMessages; }
ESM::RefId getTarget() const override { return ESM::RefId(); }
int getLocalShort(int index) const override { return mLocals.getShort(index); }
@ -161,7 +164,10 @@ namespace
void setLocalFloat(int index, float value) override { mLocals.setFloat(index, value); }
void messageBox(std::string_view message, const std::vector<std::string>& buttons) override {}
void messageBox(std::string_view message, const std::vector<std::string>& buttons) override
{
mMessages.emplace_back(message);
}
void report(const std::string& message) override {}

View file

@ -481,7 +481,7 @@ namespace Compiler
}
void GetArgumentsFromMessageFormat::visitedPlaceholder(
Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/, Notation /*notation*/)
Placeholder placeholder, int /*flags*/, int /*width*/, int /*precision*/, Notation /*notation*/)
{
switch (placeholder)
{

View file

@ -88,7 +88,7 @@ namespace Compiler
protected:
void visitedPlaceholder(
Placeholder placeholder, char padding, int width, int precision, Notation notation) override;
Placeholder placeholder, int flags, int width, int precision, Notation notation) override;
void visitedCharacter(char c) override {}
public:

View file

@ -2,8 +2,7 @@
#define INTERPRETER_MISCOPCODES_H_INCLUDED
#include <algorithm>
#include <sstream>
#include <stdexcept>
#include <format>
#include <string>
#include <vector>
@ -23,78 +22,84 @@ namespace Interpreter
protected:
void visitedPlaceholder(
Placeholder placeholder, char padding, int width, int precision, Notation notation) override
Placeholder placeholder, int flags, int width, int precision, Notation notation) override
{
std::ostringstream out;
out.fill(padding);
if (width != -1)
out.width(width);
if (precision != -1)
out.precision(precision);
std::string formatString;
switch (placeholder)
if (placeholder == StringPlaceholder)
{
case StringPlaceholder:
{
int index = mRuntime[0].mInteger;
mRuntime.pop();
int index = mRuntime[0].mInteger;
mRuntime.pop();
out << mRuntime.getStringLiteral(index);
mFormattedMessage += out.str();
}
break;
case IntegerPlaceholder:
std::string_view value = mRuntime.getStringLiteral(index);
if (precision >= 0)
value = value.substr(0, static_cast<std::size_t>(precision));
if (width < 0)
mFormattedMessage += value;
else
{
Type_Integer value = mRuntime[0].mInteger;
mRuntime.pop();
out << value;
mFormattedMessage += out.str();
formatString = "{:";
if (flags & PrependZero)
formatString += '0';
if (flags & AlignLeft)
formatString += '<';
else
formatString += '>';
formatString += "{}}";
std::vformat_to(
std::back_inserter(mFormattedMessage), formatString, std::make_format_args(value, width));
}
break;
case FloatPlaceholder:
}
else
{
formatString = "{:";
if (flags & AlignLeft)
formatString += '<';
if (flags & PositiveSign)
formatString += '+';
else if (flags & PositiveSpace)
formatString += ' ';
if (flags & AlternateForm)
formatString += '#';
if (flags & PrependZero)
formatString += '0';
if (width >= 0)
formatString += "{}";
if (placeholder == FloatPlaceholder)
{
if (precision >= 0)
formatString += ".{}";
formatString += static_cast<char>(notation);
}
else
precision = -1;
formatString += '}';
const auto appendMessage = [&](auto value) {
if (width >= 0 && precision >= 0)
std::vformat_to(std::back_inserter(mFormattedMessage), formatString,
std::make_format_args(value, width, precision));
else if (width >= 0)
std::vformat_to(
std::back_inserter(mFormattedMessage), formatString, std::make_format_args(value, width));
else if (precision >= 0)
std::vformat_to(std::back_inserter(mFormattedMessage), formatString,
std::make_format_args(value, precision));
else
std::vformat_to(
std::back_inserter(mFormattedMessage), formatString, std::make_format_args(value));
};
if (placeholder == FloatPlaceholder)
{
float value = mRuntime[0].mFloat;
mRuntime.pop();
if (notation == Notation::Fixed)
{
out << std::fixed << value;
mFormattedMessage += out.str();
}
else if (notation == Notation::Shortest)
{
out << value;
std::string standard = out.str();
out.str(std::string());
out.clear();
out << std::scientific << value;
std::string scientific = out.str();
mFormattedMessage += standard.length() < scientific.length() ? standard : scientific;
}
// TODO switch to std::format so the precision argument applies to these two
else if (notation == Notation::HexLower)
{
out << std::hexfloat << value;
mFormattedMessage += out.str();
}
else if (notation == Notation::HexUpper)
{
out << std::uppercase << std::hexfloat << value;
mFormattedMessage += out.str();
}
else
{
out << std::scientific << value;
mFormattedMessage += out.str();
}
appendMessage(value);
}
else
{
Type_Integer value = mRuntime[0].mInteger;
mRuntime.pop();
appendMessage(value);
}
break;
default:
break;
}
}

View file

@ -50,7 +50,8 @@ namespace LuaUtil
}
public:
friend class LuaState;
template <class Function>
friend int invokeProtectedCall(lua_State*, Function&&);
// Returns underlying sol::state.
sol::state_view& sol() { return mSol; }
@ -67,6 +68,45 @@ namespace LuaUtil
return res;
}
// Pushing to the stack from outside a Lua context crashes the engine if no memory can be allocated to grow the
// stack
template <class Function>
[[nodiscard]] int invokeProtectedCall(lua_State* luaState, Function&& function)
{
if (!lua_checkstack(luaState, 2))
return LUA_ERRMEM;
lua_pushcfunction(luaState, [](lua_State* state) {
void* f = lua_touserdata(state, 1);
LuaView view(state);
(*static_cast<Function*>(f))(view);
return 0;
});
lua_pushlightuserdata(luaState, &function);
return lua_pcall(luaState, 1, 0, 0);
}
template <class Lambda>
void protectedCall(lua_State* luaState, Lambda&& f)
{
int result = invokeProtectedCall(luaState, std::forward<Lambda>(f));
switch (result)
{
case LUA_OK:
break;
case LUA_ERRMEM:
throw std::runtime_error("Lua error: out of memory");
case LUA_ERRRUN:
{
sol::optional<std::string> error = sol::stack::check_get<std::string>(luaState);
if (error)
throw std::runtime_error(*error);
}
[[fallthrough]];
default:
throw std::runtime_error("Lua error: " + std::to_string(result));
}
}
// Holds Lua state.
// Provides additional features:
// - Load scripts from the virtual filesystem;
@ -87,43 +127,10 @@ namespace LuaUtil
LuaState(const LuaState&) = delete;
LuaState(LuaState&&) = delete;
// Pushing to the stack from outside a Lua context crashes the engine if no memory can be allocated to grow the
// stack
template <class Function>
[[nodiscard]] int invokeProtectedCall(Function&& function) const
{
if (!lua_checkstack(mSol.lua_state(), 2))
return LUA_ERRMEM;
lua_pushcfunction(mSol.lua_state(), [](lua_State* state) {
void* f = lua_touserdata(state, 1);
LuaView view(state);
(*static_cast<Function*>(f))(view);
return 0;
});
lua_pushlightuserdata(mSol.lua_state(), &function);
return lua_pcall(mSol.lua_state(), 1, 0, 0);
}
template <class Lambda>
void protectedCall(Lambda&& f) const
{
int result = invokeProtectedCall(std::forward<Lambda>(f));
switch (result)
{
case LUA_OK:
break;
case LUA_ERRMEM:
throw std::runtime_error("Lua error: out of memory");
case LUA_ERRRUN:
{
sol::optional<std::string> error = sol::stack::check_get<std::string>(mSol.lua_state());
if (error)
throw std::runtime_error(*error);
}
[[fallthrough]];
default:
throw std::runtime_error("Lua error: " + std::to_string(result));
}
LuaUtil::protectedCall(mSol.lua_state(), std::forward<Lambda>(f));
}
// Note that constructing a sol::state_view is only safe from a Lua context. Use protectedCall to get one

View file

@ -407,6 +407,16 @@ namespace LuaUtil
}
void ScriptsContainer::save(ESM::LuaScripts& data)
{
if (const UnloadedData* unloadedData = std::get_if<UnloadedData>(&mData))
{
data.mScripts = unloadedData->mScripts;
return;
}
mLua.protectedCall([&](LuaView& view) { save(view, data); });
}
void ScriptsContainer::save(LuaView&, ESM::LuaScripts& data)
{
if (const UnloadedData* unloadedData = std::get_if<UnloadedData>(&mData))
{
@ -614,12 +624,12 @@ namespace LuaUtil
return data;
}
ScriptsContainer::UnloadedData& ScriptsContainer::ensureUnloaded(LuaView&)
ScriptsContainer::UnloadedData& ScriptsContainer::ensureUnloaded(LuaView& view)
{
if (UnloadedData* data = std::get_if<UnloadedData>(&mData))
return *data;
UnloadedData data;
save(data);
save(view, data);
mAPI.erase("openmw.interfaces");
UnloadedData& out = mData.emplace<UnloadedData>(std::move(data));
for (auto& [_, handlers] : mEngineHandlers)
@ -751,9 +761,11 @@ namespace LuaUtil
void ScriptsContainer::processTimers(double simulationTime, double gameTime)
{
LoadedData& data = ensureLoaded();
updateTimerQueue(data.mSimulationTimersQueue, simulationTime);
updateTimerQueue(data.mGameTimersQueue, gameTime);
mLua.protectedCall([&](LuaView& view) {
LoadedData& data = ensureLoaded();
updateTimerQueue(data.mSimulationTimersQueue, simulationTime);
updateTimerQueue(data.mGameTimersQueue, gameTime);
});
}
static constexpr float instructionCountAvgCoef = 1.0f / 30; // averaging over approximately 30 frames

View file

@ -286,6 +286,7 @@ namespace LuaUtil
static void removeHandler(std::vector<Handler>& list, int scriptId);
void insertInterface(int scriptId, const Script& script);
void removeInterface(int scriptId, const Script& script);
void save(LuaView&, ESM::LuaScripts&);
ScriptIdsWithInitializationData mAutoStartScripts;
const UserdataSerializer* mSerializer = nullptr;

View file

@ -2,7 +2,6 @@
#define COMPONENTS_LUA_UTIL_H
#include <cstdint>
#include <string>
#include <sol/sol.hpp>
@ -24,11 +23,11 @@ namespace LuaUtil
// ADL-based customization point for sol2 to automatically convert ESM::RefId
// Empty RefIds are converted to nil, non-empty ones are serialized to strings
inline int sol_lua_push(sol::types<ESM::RefId>, lua_State* L, const ESM::RefId& id)
inline int sol_lua_push(sol::types<ESM::RefId>, lua_State* state, const ESM::RefId& id)
{
if (id.empty())
return sol::stack::push(L, sol::lua_nil);
return sol::stack::push(L, id.serializeText());
return sol::stack::push(state, sol::lua_nil);
return sol::stack::push(state, id.serializeText());
}
#endif
#endif

View file

@ -8,6 +8,9 @@
#include <components/misc/color.hpp>
#include <components/misc/mathutil.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <MyGUI_StringUtility.h>
#include "luastate.hpp"
#include "util.hpp"
@ -243,6 +246,28 @@ namespace LuaUtil
color["rgba"] = [](float r, float g, float b, float a) { return Misc::Color(r, g, b, a); };
color["rgb"] = [](float r, float g, float b) { return Misc::Color(r, g, b, 1); };
color["hex"] = [](std::string_view hex) { return Misc::Color::fromHex(hex); };
color["commaString"] = [](std::string_view str) {
int wrongChars = std::count_if(
str.begin(), str.end(), [](unsigned char c) { return !std::isdigit(c) && c != ' ' && c != ','; });
if (wrongChars != 0)
{
throw std::runtime_error("Invalid comma-separated color: " + std::string(str));
}
std::vector<std::string> rgba;
Misc::StringUtils::split(str, rgba, ",");
if (rgba.size() != 3 && rgba.size() != 4)
{
throw std::runtime_error("Invalid comma-separated color: " + std::string(str));
}
if (rgba.size() == 3)
rgba.push_back("255");
return Misc::Color(MyGUI::utility::parseInt(rgba[0]) / 255.f, MyGUI::utility::parseInt(rgba[1]) / 255.f,
MyGUI::utility::parseInt(rgba[2]) / 255.f, MyGUI::utility::parseInt(rgba[3]) / 255.f);
};
util["color"] = LuaUtil::makeReadOnly(color);
// Lua bindings for Transform

View file

@ -46,7 +46,9 @@ namespace LuaUi
void LuaTextEdit::textChange(MyGUI::EditBox*)
{
triggerEvent("textChanged", sol::make_object(lua(), mEditBox->getCaption().asUTF8()));
protectedCall([this](LuaUtil::LuaView& view) {
triggerEvent("textChanged", sol::make_object(view.sol(), mEditBox->getCaption().asUTF8()));
});
}
void LuaTextEdit::updateCoord()

View file

@ -169,28 +169,23 @@ namespace LuaUi
return result;
}
sol::table WidgetExtension::makeTable() const
{
return sol::table(lua(), sol::create);
}
sol::object WidgetExtension::keyEvent(MyGUI::KeyCode code) const
sol::object WidgetExtension::keyEvent(LuaUtil::LuaView& view, MyGUI::KeyCode code) const
{
auto keySym = SDL_Keysym();
keySym.sym = SDLUtil::myGuiKeyToSdl(code);
keySym.scancode = SDL_GetScancodeFromKey(keySym.sym);
keySym.mod = static_cast<Uint16>(SDL_GetModState());
return sol::make_object(lua(), keySym);
return sol::make_object(view.sol(), keySym);
}
sol::object WidgetExtension::mouseEvent(
int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const
LuaUtil::LuaView& view, int left, int top, MyGUI::MouseButton button = MyGUI::MouseButton::None) const
{
osg::Vec2f position(static_cast<float>(left), static_cast<float>(top));
MyGUI::IntPoint absolutePosition = mWidget->getAbsolutePosition();
osg::Vec2f offset = position
- osg::Vec2f(static_cast<float>(absolutePosition.left), static_cast<float>(absolutePosition.top));
sol::table table = makeTable();
sol::table table = view.newTable();
int sdlButton = SDLUtil::myGuiMouseButtonToSdl(button);
table["position"] = position;
table["offset"] = offset;
@ -373,31 +368,39 @@ namespace LuaUi
void WidgetExtension::keyPress(MyGUI::Widget*, MyGUI::KeyCode code, MyGUI::Char ch)
{
if (code == MyGUI::KeyCode::None)
{
propagateEvent("textInput", [ch](auto w) {
MyGUI::UString uString;
uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch));
return sol::make_object(w->lua(), uString.asUTF8());
});
}
else
propagateEvent("keyPress", [code](auto w) { return w->keyEvent(code); });
protectedCall([=, this](LuaUtil::LuaView& view) {
if (code == MyGUI::KeyCode::None)
{
propagateEvent("textInput", [&](auto w) {
MyGUI::UString uString;
uString.push_back(static_cast<MyGUI::UString::unicode_char>(ch));
return sol::make_object(view.sol(), uString.asUTF8());
});
}
else
propagateEvent("keyPress", [&](auto w) { return w->keyEvent(view, code); });
});
}
void WidgetExtension::keyRelease(MyGUI::Widget*, MyGUI::KeyCode code)
{
propagateEvent("keyRelease", [code](auto w) { return w->keyEvent(code); });
protectedCall([=, this](LuaUtil::LuaView& view) {
propagateEvent("keyRelease", [&](auto w) { return w->keyEvent(view, code); });
});
}
void WidgetExtension::mouseMove(MyGUI::Widget*, int left, int top)
{
propagateEvent("mouseMove", [left, top](auto w) { return w->mouseEvent(left, top); });
protectedCall([=, this](LuaUtil::LuaView& view) {
propagateEvent("mouseMove", [&](auto w) { return w->mouseEvent(view, left, top); });
});
}
void WidgetExtension::mouseDrag(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
propagateEvent("mouseMove", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
protectedCall([=, this](LuaUtil::LuaView& view) {
propagateEvent("mouseMove", [&](auto w) { return w->mouseEvent(view, left, top, button); });
});
}
void WidgetExtension::mouseClick(MyGUI::Widget* /*widget*/)
@ -412,12 +415,16 @@ namespace LuaUi
void WidgetExtension::mousePress(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
propagateEvent("mousePress", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
protectedCall([=, this](LuaUtil::LuaView& view) {
propagateEvent("mousePress", [&](auto w) { return w->mouseEvent(view, left, top, button); });
});
}
void WidgetExtension::mouseRelease(MyGUI::Widget*, int left, int top, MyGUI::MouseButton button)
{
propagateEvent("mouseRelease", [left, top, button](auto w) { return w->mouseEvent(left, top, button); });
protectedCall([=, this](LuaUtil::LuaView& view) {
propagateEvent("mouseRelease", [&](auto w) { return w->mouseEvent(view, left, top, button); });
});
}
void WidgetExtension::focusGain(MyGUI::Widget*, MyGUI::Widget*)

View file

@ -83,9 +83,8 @@ namespace LuaUi
void registerEvents(MyGUI::Widget* w);
void clearEvents(MyGUI::Widget* w);
sol::table makeTable() const;
sol::object keyEvent(MyGUI::KeyCode) const;
sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const;
sol::object keyEvent(LuaUtil::LuaView& view, MyGUI::KeyCode) const;
sol::object mouseEvent(LuaUtil::LuaView& view, int left, int top, MyGUI::MouseButton button) const;
MyGUI::IntSize parentSize() const;
virtual MyGUI::IntSize childScalingSize() const;
@ -104,7 +103,11 @@ namespace LuaUi
virtual void updateProperties();
virtual void updateChildren() {}
lua_State* lua() const { return mLua; }
template <class Lambda>
void protectedCall(Lambda&& f) const
{
LuaUtil::protectedCall(mLua, std::forward<Lambda>(f));
}
void triggerEvent(std::string_view name, sol::object argument) const;
template <class ArgFactory>

View file

@ -78,9 +78,11 @@ namespace LuaUi
mPreviousMouse.left = left;
mPreviousMouse.top = top;
sol::table table = makeTable();
table["position"] = osg::Vec2f(static_cast<float>(mCoord.left), static_cast<float>(mCoord.top));
table["size"] = osg::Vec2f(static_cast<float>(mCoord.width), static_cast<float>(mCoord.height));
triggerEvent("windowDrag", table);
protectedCall([this](LuaUtil::LuaView& view) {
sol::table table = view.newTable();
table["position"] = osg::Vec2f(static_cast<float>(mCoord.left), static_cast<float>(mCoord.top));
table["size"] = osg::Vec2f(static_cast<float>(mCoord.width), static_cast<float>(mCoord.height));
triggerEvent("windowDrag", table);
});
}
}

View file

@ -27,58 +27,71 @@ namespace Misc
{
for (std::size_t i = 0; i < m.size(); ++i)
{
if (m[i] == '%')
{
if (++i < m.size())
{
if (m[i] == '%')
visitedCharacter('%');
else
{
char pad = ' ';
if (m[i] == '0' || m[i] == ' ')
{
pad = m[i];
++i;
}
int width = parseNumber(i, m, -1);
if (i < m.size())
{
int precision = -1;
if (m[i] == '.')
{
++i;
precision = parseNumber(i, m, 0);
}
if (i < m.size())
{
if (m[i] == 'S' || m[i] == 's')
visitedPlaceholder(StringPlaceholder, pad, width, precision, Notation::Fixed);
else if (m[i] == 'd' || m[i] == 'i')
visitedPlaceholder(IntegerPlaceholder, pad, width, precision, Notation::Fixed);
else if (m[i] == 'f' || m[i] == 'F')
visitedPlaceholder(FloatPlaceholder, pad, width, precision, Notation::Fixed);
else if (m[i] == 'e' || m[i] == 'E')
visitedPlaceholder(FloatPlaceholder, pad, width, precision, Notation::Scientific);
else if (m[i] == 'g' || m[i] == 'G')
visitedPlaceholder(FloatPlaceholder, pad, width, precision, Notation::Shortest);
else if (m[i] == 'a')
visitedPlaceholder(FloatPlaceholder, pad, width, precision, Notation::HexLower);
else if (m[i] == 'A')
visitedPlaceholder(FloatPlaceholder, pad, width, precision, Notation::HexUpper);
else
visitedCharacter(m[i]);
}
}
}
}
}
else
if (m[i] != '%')
{
visitedCharacter(m[i]);
continue;
}
if (++i == m.size())
break;
if (m[i] == '%')
{
visitedCharacter('%');
continue;
}
int flags = None;
while (i < m.size())
{
if (m[i] == '-')
flags |= AlignLeft;
else if (m[i] == '+')
flags |= PositiveSign;
else if (m[i] == ' ')
flags |= PositiveSpace;
else if (m[i] == '0')
flags |= PrependZero;
else if (m[i] == '#')
flags |= AlternateForm;
else
break;
++i;
}
int width = parseNumber(i, m, -1);
if (i < m.size())
{
int precision = -1;
if (m[i] == '.')
{
++i;
precision = parseNumber(i, m, 0);
}
if (i < m.size())
{
if (m[i] == 'S' || m[i] == 's')
visitedPlaceholder(StringPlaceholder, flags, width, precision, Notation::Fixed);
else if (m[i] == 'd' || m[i] == 'i')
visitedPlaceholder(IntegerPlaceholder, flags, width, precision, Notation::Fixed);
else if (m[i] == 'f' || m[i] == 'F')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::Fixed);
else if (m[i] == 'e')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::ScientificLower);
else if (m[i] == 'E')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::ScientificUpper);
else if (m[i] == 'g')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::ShortestLower);
else if (m[i] == 'G')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::ShortestUpper);
else if (m[i] == 'a')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::HexLower);
else if (m[i] == 'A')
visitedPlaceholder(FloatPlaceholder, flags, width, precision, Notation::HexUpper);
else
visitedCharacter(m[i]);
}
}
}
}

View file

@ -15,17 +15,28 @@ namespace Misc
FloatPlaceholder
};
enum class Notation
enum Flags
{
Fixed,
Scientific,
Shortest,
HexUpper,
HexLower
None = 0,
PositiveSpace = 1,
PositiveSign = 2,
AlignLeft = 4,
PrependZero = 8,
AlternateForm = 16
};
virtual void visitedPlaceholder(
Placeholder placeholder, char padding, int width, int precision, Notation notation)
enum class Notation : char
{
Fixed = 'f',
ScientificUpper = 'E',
ScientificLower = 'e',
ShortestUpper = 'G',
ShortestLower = 'g',
HexUpper = 'A',
HexLower = 'a'
};
virtual void visitedPlaceholder(Placeholder placeholder, int flags, int width, int precision, Notation notation)
= 0;
virtual void visitedCharacter(char c) = 0;

View file

@ -770,6 +770,11 @@ namespace Nif
filter.read(nif);
}
void bhkListShape::post(Reader& nif)
{
postRecordList(nif, mSubshapes);
}
void bhkCompressedMeshShape::read(NIFStream* nif)
{
mTarget.read(nif);

View file

@ -730,6 +730,7 @@ namespace Nif
std::vector<HavokFilter> mHavokFilters;
void read(NIFStream* nif) override;
void post(Reader& nif) override;
};
struct bhkCompressedMeshShape : public bhkShape

View file

@ -218,7 +218,7 @@ Lock a container or door
.. code-block:: Lua
core.sendGlobalEvent('Lock', {taret = selected, magnitude = 50})
core.sendGlobalEvent('Lock', {target = selected, magnitude = 50})
**Unlock**
@ -226,4 +226,4 @@ Unlock a container or door
.. code-block:: Lua
core.sendGlobalEvent('Unlock', {taret = selected})
core.sendGlobalEvent('Unlock', {target = selected})

View file

@ -132,6 +132,12 @@ local function skillUsedHandler(skillid, params)
end
local skillStat = NPC.stats.skills[skillid](self)
if (skillStat.base >= 100 and params.skillGain > 0) or
(skillStat.base <= 0 and params.skillGain < 0) then
return false
end
skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid)
if skillStat.progress >= 1 then

View file

@ -1,11 +1,12 @@
local core = require('openmw.core')
local ui = require('openmw.ui')
local util = require('openmw.util')
return {
textNormalSize = 16,
textHeaderSize = 16,
headerColor = util.color.rgb(223 / 255, 201 / 255, 159 / 255),
normalColor = util.color.rgb(202 / 255, 165 / 255, 96 / 255),
textNormalSize = ui._getDefaultFontSize(),
textHeaderSize = ui._getDefaultFontSize(),
headerColor = util.color.commaString(core.getGMST("FontColor_color_header")),
normalColor = util.color.commaString(core.getGMST("FontColor_color_normal")),
border = 2,
thickBorder = 4,
padding = 2,

View file

@ -57,10 +57,13 @@
-- @return #number
---
-- Get a GMST setting from content files.
-- Get a game setting with given name (from GMST ESM records or from openmw.cfg).
-- @function [parent=#core] getGMST
-- @param #string setting Setting name
-- @return #any
-- @usage local skillBonus = core.getGMST('fMinorSkillBonus') -- get a numeric GMST from ESM data
-- @usage local jailFormatString = core.getGMST('sNotifyMessage42') -- get a string GMST from ESM data
-- @usage local bloodTextureName = core.getGMST('Blood_Texture_1') -- get a "fallback" parameter value from openmw.cfg (always a string)
---
-- The game's difficulty setting.

View file

@ -477,6 +477,16 @@
-- @param #number a
-- @return #Color
---
-- Creates a Color from comma-separated string (in RGB or RGBA order, spaces are ignored)
-- @function [parent=#COLOR] commaString
-- @param #string str
-- @return #Color
-- @usage local color = util.color.commaString('255,0,0') -- red color
-- @usage local color = util.color.commaString('10000,0,0') -- red color (values are still capped at 255)
-- @usage local color = util.color.commaString('0, 0, 255, 255') -- blue color, with explicit alpha
-- @usage local color = util.color.commaString('0,255,0,128') -- green color, semi-transparent
---
-- Creates a Color from RGB format. Equivalent to calling util.rgba with a = 1.
-- @function [parent=#COLOR] rgb