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:
commit
ef8e7d97cb
32 changed files with 468 additions and 245 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 "")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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*)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -730,6 +730,7 @@ namespace Nif
|
|||
std::vector<HavokFilter> mHavokFilters;
|
||||
|
||||
void read(NIFStream* nif) override;
|
||||
void post(Reader& nif) override;
|
||||
};
|
||||
|
||||
struct bhkCompressedMeshShape : public bhkShape
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue