From c1520c9e11995c44a7d9ff7b1a90899dc2461f17 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:30:47 +0200 Subject: [PATCH 01/14] lua: apply patch for "Stack overflow in vararg functions with many fixed parameters called with few arguments" known as CVE-2014-5461 --- lib/lua/src/ldo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lua/src/ldo.c b/lib/lua/src/ldo.c index 7eeea636c..37a7660fd 100644 --- a/lib/lua/src/ldo.c +++ b/lib/lua/src/ldo.c @@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) { CallInfo *ci; StkId st, base; Proto *p = cl->p; - luaD_checkstack(L, p->maxstacksize); + luaD_checkstack(L, p->maxstacksize + p->numparams); func = restorestack(L, funcr); if (!p->is_vararg) { /* no varargs? */ base = func + 1; From 88ffe75b58d5b4bc2e95aa61892750693fab30dd Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:32:42 +0200 Subject: [PATCH 02/14] lua: apply patch for "Chunk with too many lines may crash Lua" --- lib/lua/src/llex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lua/src/llex.c b/lib/lua/src/llex.c index d1b179670..5affe7c16 100644 --- a/lib/lua/src/llex.c +++ b/lib/lua/src/llex.c @@ -133,7 +133,7 @@ static void inclinenumber (LexState *ls) { if (currIsNewline(ls) && ls->current != old) next(ls); /* skip `\n\r' or `\r\n' */ if (++ls->linenumber >= MAX_INT) - luaX_syntaxerror(ls, "chunk has too many lines"); + luaX_lexerror(ls, "chunk has too many lines", 0); } From 7362ecb3b41051cffedf0af6aaab024a55546e91 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:34:52 +0200 Subject: [PATCH 03/14] lua: apply patch for "Compiler can optimize away overflow check in table.unpack" --- lib/lua/src/lbaselib.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/lua/src/lbaselib.c b/lib/lua/src/lbaselib.c index 2ab550bd4..ed293ebdc 100644 --- a/lib/lua/src/lbaselib.c +++ b/lib/lua/src/lbaselib.c @@ -340,13 +340,14 @@ static int luaB_assert (lua_State *L) { static int luaB_unpack (lua_State *L) { - int i, e, n; + int i, e; + unsigned int n; luaL_checktype(L, 1, LUA_TTABLE); i = luaL_optint(L, 2, 1); e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1)); if (i > e) return 0; /* empty range */ - n = e - i + 1; /* number of elements */ - if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ + n = (unsigned int)e - (unsigned int)i; /* number of elements minus 1 */ + if (n > (INT_MAX - 10) || !lua_checkstack(L, ++n)) return luaL_error(L, "too many results to unpack"); lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ while (i++ < e) /* push arg[i + 1...e] */ From 77ac20a66b91c03bd8fcafef79f0b0d7a4e4c6c5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:39:09 +0200 Subject: [PATCH 04/14] lua: apply patch for "Parameter 'what' of 'debug.getinfo' cannot start with '>'" --- lib/lua/src/ldblib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/lua/src/ldblib.c b/lib/lua/src/ldblib.c index 628a6c458..2c55edcda 100644 --- a/lib/lua/src/ldblib.c +++ b/lib/lua/src/ldblib.c @@ -101,6 +101,7 @@ static int db_getinfo (lua_State *L) { int arg; lua_State *L1 = getthread(L, &arg); const char *options = luaL_optstring(L, arg+2, "flnSu"); + luaL_argcheck(L, options[0] != '>', arg + 2, "invalid option '>'"); if (lua_isnumber(L, arg+1)) { if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { lua_pushnil(L); /* level out of range */ From 837aab0e98cc2d38455fbdab67d4bd90bf1fc16e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:46:26 +0200 Subject: [PATCH 05/14] lua: apply patch for "read overflow in 'l_strcmp'" (minimal port of changes) --- lib/lua/src/lvm.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/lua/src/lvm.c b/lib/lua/src/lvm.c index a438d7805..60a715a6d 100644 --- a/lib/lua/src/lvm.c +++ b/lib/lua/src/lvm.c @@ -209,14 +209,15 @@ static int l_strcmp (const TString *ls, const TString *rs) { int temp = strcoll(l, r); if (temp != 0) return temp; else { /* strings are equal up to a `\0' */ - size_t len = strlen(l); /* index of first `\0' in both strings */ - if (len == lr) /* r is finished? */ - return (len == ll) ? 0 : 1; - else if (len == ll) /* l is finished? */ - return -1; /* l is smaller than r (because r is not finished) */ - /* both strings longer than `len'; go on comparing (after the `\0') */ - len++; - l += len; ll -= len; r += len; lr -= len; + size_t zl1 = strlen(l); /* index of first '\0' in 'l' */ + size_t zl2 = strlen(r); /* index of first '\0' in 'r' */ + if (zl2 == lr) /* 'r' is finished? */ + return (zl1 == ll) ? 0 : 1; /* check 'l' */ + else if (zl1 == ll) /* 'l' is finished? */ + return -1; /* 'l' is less than 'r' ('r' is not finished) */ + /* both strings longer than 'zl'; go on comparing after the '\0' */ + zl1++; zl2++; + l += zl1; ll -= zl1; r += zl2; lr -= zl2; } } } From 868b548dd06f6a60ccfffd24bd04813d8fdf28c1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Jun 2024 17:48:26 +0200 Subject: [PATCH 06/14] lua: add short readme --- lib/lua/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 lib/lua/README.md diff --git a/lib/lua/README.md b/lib/lua/README.md new file mode 100644 index 000000000..b82e4a652 --- /dev/null +++ b/lib/lua/README.md @@ -0,0 +1,6 @@ +## Lua 5.1.5 source + +This copy has been patched with Minetest-specific changes (`lua_atccall`) +so it is *not* interchangeable with upstream PUC Lua. + +A patch for CVE-2014-5461 has been applied. From 7709d9228904241b171f33a0cc645d4755237e5c Mon Sep 17 00:00:00 2001 From: grorp Date: Sun, 30 Jun 2024 20:39:28 +0200 Subject: [PATCH 07/14] Restore old inconsistent minimum digging time behavior (#14777) and restore default of 0.16 for repeat_place_time since it was only changed to be in line with repeat_dig_time. --- builtin/settingtypes.txt | 4 +-- doc/lua_api.md | 5 +++- games/devtest/mods/basetools/init.lua | 24 +++++++++++++++++- .../textures/basetools_mesepick_no_delay.png | Bin 0 -> 169 bytes src/client/game.cpp | 12 ++++++--- src/defaultsettings.cpp | 2 +- 6 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 games/devtest/mods/basetools/textures/basetools_mesepick_no_delay.png diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index bb7ba7f7f..955c7e599 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -114,11 +114,11 @@ always_fly_fast (Always fly fast) bool true # the place button. # # Requires: keyboard_mouse -repeat_place_time (Place repetition interval) float 0.25 0.15 2.0 +repeat_place_time (Place repetition interval) float 0.25 0.16 2.0 # The minimum time in seconds it takes between digging nodes when holding # the dig button. -repeat_dig_time (Dig repetition interval) float 0.15 0.15 2.0 +repeat_dig_time (Minimum dig repetition interval) float 0.0 0.0 2.0 # Automatically jump up single-node obstacles. autojump (Automatic jumping) bool false diff --git a/doc/lua_api.md b/doc/lua_api.md index ee1f4060b..a749b337e 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2352,9 +2352,12 @@ for this group, and unable to dig the rating `1`, which is the toughest. Unless there is a matching group that enables digging otherwise. If the result digging time is 0, a delay of 0.15 seconds is added between -digging nodes; If the player releases LMB after digging, this delay is set to 0, +digging nodes. If the player releases LMB after digging, this delay is set to 0, i.e. players can more quickly click the nodes away instead of holding LMB. +This extra delay is not applied in case of a digging time between 0 and 0.15, +so a digging time of 0.01 is actually faster than a digging time of 0. + ### Damage groups List of damage for groups of entities. See [Entity damage mechanism]. diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index aa91d5e92..17e53c1fe 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -71,7 +71,7 @@ end -- Mese Pickaxe: special tool that digs "everything" instantly minetest.register_tool("basetools:pick_mese", { description = "Mese Pickaxe".."\n".. - "Digs diggable nodes instantly", + "Digs diggable nodes instantly.", inventory_image = "basetools_mesepick.png", tool_capabilities = { full_punch_interval = 1.0, @@ -88,6 +88,28 @@ minetest.register_tool("basetools:pick_mese", { }) +-- A variant of the mese pickaxe that is not affected by the 0.15s digging delay +minetest.register_tool("basetools:pick_mese_no_delay", { + description = "Mese Pickaxe (no delay)".."\n".. + "Digs diggable nodes instantly.".."\n".. + "There is no delay between digging each node,\n".. + 'but the "repeat_dig_time" setting is still respected.', + inventory_image = "basetools_mesepick_no_delay.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=3, + groupcaps={ + cracky={times={[1]=0.001, [2]=0.001, [3]=0.001}, maxlevel=255}, + crumbly={times={[1]=0.001, [2]=0.001, [3]=0.001}, maxlevel=255}, + snappy={times={[1]=0.001, [2]=0.001, [3]=0.001}, maxlevel=255}, + choppy={times={[1]=0.001, [2]=0.001, [3]=0.001}, maxlevel=255}, + dig_immediate={times={[1]=0.001, [2]=0.001, [3]=0.001}, maxlevel=255}, + }, + damage_groups = {fleshy=100}, + }, +}) + + -- -- Pickaxes: Dig cracky -- diff --git a/games/devtest/mods/basetools/textures/basetools_mesepick_no_delay.png b/games/devtest/mods/basetools/textures/basetools_mesepick_no_delay.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7db7e0a2e72eef50b28da4952e27014eb68d94 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`1)eUBAr`0CPCUqYz<`JO>szU% z)?cpre}6G6^8C@324Ss5OAKGEd_18sG@Y$`Cfib$&wtuP16p($d70 0.3f) + runData.nodig_delay_timer = 0.3f; + else if (runData.dig_instantly) + runData.nodig_delay_timer = 0.15f; // Ensure that the delay between breaking nodes // (dig_time_complete + nodig_delay_timer) is at least the @@ -4392,8 +4396,8 @@ void Game::readSettings() m_cache_enable_fog = g_settings->getBool("enable_fog"); m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f); m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f); - m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.15f, 2.0f); - m_repeat_dig_time = g_settings->getFloat("repeat_dig_time", 0.15f, 2.0f); + m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.16f, 2.0f); + m_repeat_dig_time = g_settings->getFloat("repeat_dig_time", 0.0f, 2.0f); m_cache_enable_noclip = g_settings->getBool("noclip"); m_cache_enable_free_move = g_settings->getBool("free_move"); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 13b323960..ca89b642d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -359,7 +359,7 @@ void set_default_settings() settings->setDefault("invert_hotbar_mouse_wheel", "false"); settings->setDefault("mouse_sensitivity", "0.2"); settings->setDefault("repeat_place_time", "0.25"); - settings->setDefault("repeat_dig_time", "0.15"); + settings->setDefault("repeat_dig_time", "0.0"); settings->setDefault("safe_dig_and_place", "false"); settings->setDefault("random_input", "false"); settings->setDefault("aux1_descends", "false"); From 3958c19f839696d7789295fa5b364c2090744d72 Mon Sep 17 00:00:00 2001 From: grorp Date: Sun, 30 Jun 2024 20:39:36 +0200 Subject: [PATCH 08/14] Remove enable_touch special case for C++ menu scaling (#14800) --- src/gui/modalMenu.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 00bc143bb..bd8358b62 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -59,14 +59,8 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, m_menumgr(menumgr), m_remap_click_outside(remap_click_outside) { - m_gui_scale = std::max(g_settings->getFloat("gui_scaling"), 0.5f); - const float screen_dpi_scale = RenderingEngine::getDisplayDensity(); - - if (g_settings->getBool("enable_touch")) { - m_gui_scale *= 1.1f - 0.3f * screen_dpi_scale + 0.2f * screen_dpi_scale * screen_dpi_scale; - } else { - m_gui_scale *= screen_dpi_scale; - } + m_gui_scale = g_settings->getFloat("gui_scaling", 0.5f, 20.0f) * + RenderingEngine::getDisplayDensity(); setVisible(true); m_menumgr->createdMenu(this); From 321b217feb9243d3689edcca26ededf3f8d87fc6 Mon Sep 17 00:00:00 2001 From: 1F616EMO~nya Date: Mon, 1 Jul 2024 23:26:15 +0800 Subject: [PATCH 09/14] Log node/ore name when detecting deprecated fields (#14794) --- src/script/common/c_content.cpp | 17 ++++++++++------- src/script/common/c_converter.h | 3 ++- src/script/lua_api/l_mapgen.cpp | 6 +++--- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 988094b9f..6f7d86447 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -760,7 +760,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) f.setDefaultAlphaMode(); - warn_if_field_exists(L, index, "alpha", + warn_if_field_exists(L, index, "alpha", "node " + f.name, "Obsolete, only limited compatibility provided; " "replaced by \"use_texture_alpha\""); if (getintfield_default(L, index, "alpha", 255) != 255) @@ -768,7 +768,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) lua_getfield(L, index, "use_texture_alpha"); if (lua_isboolean(L, -1)) { - warn_if_field_exists(L, index, "use_texture_alpha", + warn_if_field_exists(L, index, "use_texture_alpha", "node " + f.name, "Boolean values are deprecated; use the new choices"); if (lua_toboolean(L, -1)) f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND; @@ -1315,13 +1315,16 @@ void pushnode(lua_State *L, const MapNode &n) } /******************************************************************************/ -void warn_if_field_exists(lua_State *L, int table, - const char *name, const std::string &message) +void warn_if_field_exists(lua_State *L, int table, const char *fieldname, + std::string_view name, std::string_view message) { - lua_getfield(L, table, name); + lua_getfield(L, table, fieldname); if (!lua_isnil(L, -1)) { - warningstream << "Field \"" << name << "\": " - << message << std::endl; + warningstream << "Field \"" << fieldname << "\""; + if (!name.empty()) { + warningstream << " on " << name; + } + warningstream << ": " << message << std::endl; infostream << script_get_backtrace(L) << std::endl; } lua_pop(L, 1); diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 05d43953e..04f1ba0f9 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -120,7 +120,8 @@ void push_aabb3f_vector (lua_State *L, const std::vector void warn_if_field_exists(lua_State *L, int table, const char *fieldname, - const std::string &message); + std::string_view name, + std::string_view message); size_t write_array_slice_float(lua_State *L, int table_index, float *data, v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 2b61f0e77..8e1ef97d6 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -1354,7 +1354,7 @@ int ModApiMapgen::l_register_ore(lua_State *L) ore->flags = 0; //// Get noise_threshold - warn_if_field_exists(L, index, "noise_threshhold", + warn_if_field_exists(L, index, "noise_threshhold", "ore " + ore->name, "Deprecated: new name is \"noise_threshold\"."); float nthresh; @@ -1364,9 +1364,9 @@ int ModApiMapgen::l_register_ore(lua_State *L) ore->nthresh = nthresh; //// Get y_min/y_max - warn_if_field_exists(L, index, "height_min", + warn_if_field_exists(L, index, "height_min", "ore " + ore->name, "Deprecated: new name is \"y_min\"."); - warn_if_field_exists(L, index, "height_max", + warn_if_field_exists(L, index, "height_max", "ore " + ore->name, "Deprecated: new name is \"y_max\"."); int ymin, ymax; From 95e77bd7cb31dc8c7cbee71cec15cea355dc9862 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 1 Jul 2024 18:29:53 +0200 Subject: [PATCH 10/14] Translation updater: Fix error when no translation strings were found --- util/mod_translation_updater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/mod_translation_updater.py b/util/mod_translation_updater.py index d23b96e59..1cc59825d 100755 --- a/util/mod_translation_updater.py +++ b/util/mod_translation_updater.py @@ -458,7 +458,7 @@ def generate_template(folder, mod_name): dOut[s] = sources if len(dOut) == 0: - return None + return (None, None) # Convert source file set to list, sort it and add comment symbols. # Needed because a set is unsorted and might result in unpredictable. From ea827e4c5d5f67b2dc38b4d2ef754c7c70701781 Mon Sep 17 00:00:00 2001 From: grorp Date: Mon, 1 Jul 2024 20:41:54 +0200 Subject: [PATCH 11/14] Fix new texture properties not being sent for minetest.add_particle (#14760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lars Müller --- doc/lua_api.md | 5 ++++- src/particles.cpp | 16 ++++++++++++---- src/particles.h | 6 ++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index a749b337e..126431bf9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -10657,7 +10657,10 @@ Used by `minetest.add_particle`. texture = "image.png", -- The texture of the particle -- v5.6.0 and later: also supports the table format described in the - -- following section + -- following section, but due to a bug this did not take effect + -- (beyond the texture name). + -- v5.9.0 and later: fixes the bug. + -- Note: "texture.animation" is ignored here. Use "animation" below instead. playername = "singleplayer", -- Optional, if specified spawns particle only on the player's client diff --git a/src/particles.cpp b/src/particles.cpp index c67d72711..fdc041c3e 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -197,7 +197,8 @@ enum class ParticleTextureFlags : u8 { * decltype everywhere */ using FlagT = std::underlying_type_t; -void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly) const +void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, + bool newPropertiesOnly, bool skipAnimation) const { /* newPropertiesOnly is used to de/serialize parameters of the legacy texture * field, which are encoded separately from the texspec string */ @@ -213,14 +214,19 @@ void ServerParticleTexture::serialize(std::ostream &os, u16 protocol_ver, bool n if (!newPropertiesOnly) os << serializeString32(string); - if (animated) + if (!skipAnimation && animated) animation.serialize(os, protocol_ver); } -void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly) +void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, + bool newPropertiesOnly, bool skipAnimation) { FlagT flags = 0; deSerializeParameterValue(is, flags); + // new texture properties were missing in ParticleParameters::serialize + // before Minetest 5.9.0 + if (is.eof()) + return; animated = !!(flags & FlagT(ParticleTextureFlags::animated)); blendmode = BlendMode((flags & FlagT(ParticleTextureFlags::blend)) >> 1); @@ -230,7 +236,7 @@ void ServerParticleTexture::deSerialize(std::istream &is, u16 protocol_ver, bool if (!newPropertiesOnly) string = deSerializeString32(is); - if (animated) + if (!skipAnimation && animated) animation.deSerialize(is, protocol_ver); } @@ -254,6 +260,7 @@ void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const writeV3F32(os, drag); jitter.serialize(os); bounce.serialize(os); + texture.serialize(os, protocol_ver, true, true); } template @@ -291,4 +298,5 @@ void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver) return; jitter.deSerialize(is); bounce.deSerialize(is); + texture.deSerialize(is, protocol_ver, true, true); } diff --git a/src/particles.h b/src/particles.h index 4c178f4c4..a8d30390b 100644 --- a/src/particles.h +++ b/src/particles.h @@ -276,8 +276,10 @@ struct ParticleTexture struct ServerParticleTexture : public ParticleTexture { std::string string; - void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false) const; - void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false); + void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false, + bool skipAnimation = false) const; + void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false, + bool skipAnimation = false); }; struct CommonParticleParams From d5444e117288037ba70b9b891c5a5b47d0f25957 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Mon, 1 Jul 2024 20:49:00 +0200 Subject: [PATCH 12/14] Fix more missing newlines in `lua_api.md` (#14791) Poor rendering in online documentation: https://api.minetest.net/minetest-namespace-reference/#list-of-apis-available-in-the-mapgen-env https://api.minetest.net/minetest-namespace-reference/#list-of-apis-available-in-an-async-environment --- doc/lua_api.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index 126431bf9..37cf00b56 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6730,6 +6730,7 @@ This allows you easy interoperability for delegating work to jobs. ### List of APIs available in an async environment Classes: + * `AreaStore` * `ItemStack` * `PerlinNoise` @@ -6743,17 +6744,20 @@ Classes: * `Settings` Class instances that can be transferred between environments: + * `ItemStack` * `PerlinNoise` * `PerlinNoiseMap` * `VoxelManip` Functions: + * Standalone helpers such as logging, filesystem, encoding, hashing or compression APIs * `minetest.register_async_metatable` (see above) Variables: + * `minetest.settings` * `minetest.registered_items`, `registered_nodes`, `registered_tools`, `registered_craftitems` and `registered_aliases` @@ -6805,6 +6809,7 @@ does not have a global step or timer. ### List of APIs available in the mapgen env Classes: + * `AreaStore` * `ItemStack` * `PerlinNoise` @@ -6818,6 +6823,7 @@ Classes: * `Settings` Functions: + * Standalone helpers such as logging, filesystem, encoding, hashing or compression APIs * `minetest.get_biome_id`, `get_biome_name`, `get_heat`, `get_humidity`, @@ -6828,6 +6834,7 @@ Functions: * these only operate on the current chunk (if inside a callback) Variables: + * `minetest.settings` * `minetest.registered_items`, `registered_nodes`, `registered_tools`, `registered_craftitems` and `registered_aliases` From 8ed55b3afff09e47c3b4f6448b5fbc47eafcea63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:50:38 +0200 Subject: [PATCH 13/14] Help modders deal with object invalidation (#14769) * Skip invalid objects in raycasts * Add `ObjectRef:is_valid` method * Add object inside radius / area iterators which skip invalid objects * Update docs to clarify object invalidation and how to deal with it --------- Co-authored-by: sfan5 --- builtin/game/misc.lua | 26 +++++++++++ doc/lua_api.md | 37 ++++++++++++---- games/devtest/mods/unittests/entity.lua | 58 ++++++++++++++++++++++++- src/script/lua_api/l_env.cpp | 13 +++++- src/script/lua_api/l_object.cpp | 9 ++++ src/script/lua_api/l_object.h | 3 ++ 6 files changed, 134 insertions(+), 12 deletions(-) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index dc394a00c..a8a6700f9 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -272,3 +272,29 @@ function core.get_globals_to_transfer() } return all end + +do + local function valid_object_iterator(objects) + local i = 0 + local function next_valid_object() + i = i + 1 + local obj = objects[i] + if obj == nil then + return + end + if obj:is_valid() then + return obj + end + return next_valid_object() + end + return next_valid_object + end + + function core.objects_inside_radius(center, radius) + return valid_object_iterator(core.get_objects_inside_radius(center, radius)) + end + + function core.objects_in_area(min_pos, max_pos) + return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos)) + end +end diff --git a/doc/lua_api.md b/doc/lua_api.md index 37cf00b56..f18f90921 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6133,12 +6133,24 @@ Environment access * Items can be added also to unloaded and non-generated blocks. * `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player * Returns nothing in case of error (player offline, doesn't exist, ...). -* `minetest.get_objects_inside_radius(pos, radius)` - * returns a list of ObjectRefs. +* `minetest.get_objects_inside_radius(center, radius)` + * returns a list of ObjectRefs * `radius`: using a Euclidean metric -* `minetest.get_objects_in_area(pos1, pos2)` - * returns a list of ObjectRefs. - * `pos1` and `pos2` are the min and max positions of the area to search. + * **Warning**: Any kind of interaction with the environment or other APIs + can cause later objects in the list to become invalid while you're iterating it. + (e.g. punching an entity removes its children) + It is recommended to use `minetest.objects_inside_radius` instead, which + transparently takes care of this possibility. +* `minetest.objects_inside_radius(center, radius)` + * returns an iterator of valid objects + * example: `for obj in minetest.objects_inside_radius(center, radius) do obj:punch(...) end` +* `minetest.get_objects_in_area(min_pos, max_pos)` + * returns a list of ObjectRefs + * `min_pos` and `max_pos` are the min and max positions of the area to search + * **Warning**: The same warning as for `minetest.get_objects_inside_radius` applies. + Use `minetest.objects_in_area` instead to iterate only valid objects. +* `minetest.objects_in_area(min_pos, max_pos)` + * returns an iterator of valid objects * `minetest.set_timeofday(val)`: set time of day * `val` is between `0` and `1`; `0` for midnight, `0.5` for midday * `minetest.get_timeofday()`: get time of day @@ -7807,13 +7819,18 @@ When you receive an `ObjectRef` as a callback argument or from another API function, it is possible to store the reference somewhere and keep it around. It will keep functioning until the object is unloaded or removed. -However, doing this is **NOT** recommended as there is (intentionally) no method -to test if a previously acquired `ObjectRef` is still valid. -Instead, `ObjectRefs` should be "let go" of as soon as control is returned from -Lua back to the engine. +However, doing this is **NOT** recommended - `ObjectRefs` should be "let go" +of as soon as control is returned from Lua back to the engine. + Doing so is much less error-prone and you will never need to wonder if the object you are working with still exists. +If this is not feasible, you can test whether an `ObjectRef` is still valid +via `object:is_valid()`. + +Getters may be called for invalid objects and will return nothing then. +All other methods should not be called on invalid objects. + ### Attachments It is possible to attach objects to other objects (`set_attach` method). @@ -7832,6 +7849,8 @@ child will follow movement and rotation of that bone. ### Methods +* `is_valid()`: returns whether the object is valid. + * See "Advice on handling `ObjectRefs`" above. * `get_pos()`: returns position as vector `{x=num, y=num, z=num}` * `set_pos(pos)`: * Sets the position of the object. diff --git a/games/devtest/mods/unittests/entity.lua b/games/devtest/mods/unittests/entity.lua index 565dd6adf..38d026663 100644 --- a/games/devtest/mods/unittests/entity.lua +++ b/games/devtest/mods/unittests/entity.lua @@ -71,13 +71,13 @@ local function test_entity_lifecycle(_, pos) -- with binary in staticdata local obj = core.add_entity(pos, "unittests:callbacks", "abc\000def") + assert(obj and obj:is_valid()) check_log({"on_activate(7)"}) obj:set_hp(0) check_log({"on_death(nil)", "on_deactivate(true)"}) - -- objectref must be invalid now - assert(obj:get_velocity() == nil) + assert(not obj:is_valid()) end unittests.register("test_entity_lifecycle", test_entity_lifecycle, {map=true}) @@ -130,3 +130,57 @@ local function test_entity_attach(player, pos) obj:remove() end unittests.register("test_entity_attach", test_entity_attach, {player=true, map=true}) + +core.register_entity("unittests:dummy", { + initial_properties = { + hp_max = 1, + visual = "upright_sprite", + textures = { "no_texture.png" }, + static_save = false, + }, +}) + +local function test_entity_raycast(_, pos) + local obj1 = core.add_entity(pos, "unittests:dummy") + local obj2 = core.add_entity(pos:offset(1, 0, 0), "unittests:dummy") + local raycast = core.raycast(pos:offset(-1, 0, 0), pos:offset(2, 0, 0), true, false) + for pt in raycast do + if pt.type == "object" then + assert(pt.ref == obj1) + obj1:remove() + obj2:remove() + obj1 = nil -- object should be hit exactly one + end + end + assert(obj1 == nil) +end +unittests.register("test_entity_raycast", test_entity_raycast, {map=true}) + +local function test_object_iterator(pos, make_iterator) + local obj1 = core.add_entity(pos, "unittests:dummy") + local obj2 = core.add_entity(pos, "unittests:dummy") + assert(obj1 and obj2) + local found = false + -- As soon as we find one of the objects, we remove both, invalidating the other. + for obj in make_iterator() do + assert(obj:is_valid()) + if obj == obj1 or obj == obj2 then + obj1:remove() + obj2:remove() + found = true + end + end + assert(found) +end + +unittests.register("test_objects_inside_radius", function(_, pos) + test_object_iterator(pos, function() + return core.objects_inside_radius(pos, 1) + end) +end, {map=true}) + +unittests.register("test_objects_in_area", function(_, pos) + test_object_iterator(pos, function() + return core.objects_in_area(pos:offset(-1, -1, -1), pos:offset(1, 1, 1)) + end) +end, {map=true}) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 06dc27b2d..2b3d15bea 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -155,6 +155,7 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, int LuaRaycast::l_next(lua_State *L) { GET_PLAIN_ENV_PTR; + ServerEnvironment *senv = dynamic_cast(env); bool csm = false; #ifndef SERVER @@ -163,7 +164,17 @@ int LuaRaycast::l_next(lua_State *L) LuaRaycast *o = checkObject(L, 1); PointedThing pointed; - env->continueRaycast(&o->state, &pointed); + for (;;) { + env->continueRaycast(&o->state, &pointed); + if (pointed.type != POINTEDTHING_OBJECT) + break; + if (!senv) + break; + const auto *obj = senv->getActiveObject(pointed.object_id); + if (obj && !obj->isGone()) + break; + // skip gone object + } if (pointed.type == POINTEDTHING_NOTHING) lua_pushnil(L); else diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index bc5ddba5c..6baf146e5 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_object.h" #include +#include #include "lua_api/l_internal.h" #include "lua_api/l_inventory.h" #include "lua_api/l_item.h" @@ -106,6 +107,13 @@ int ObjectRef::l_remove(lua_State *L) return 0; } +// is_valid(self) +int ObjectRef::l_is_valid(lua_State *L) +{ + lua_pushboolean(L, getobject(checkObject(L, 1)) != nullptr); + return 1; +} + // get_pos(self) int ObjectRef::l_get_pos(lua_State *L) { @@ -2646,6 +2654,7 @@ const char ObjectRef::className[] = "ObjectRef"; luaL_Reg ObjectRef::methods[] = { // ServerActiveObject luamethod(ObjectRef, remove), + luamethod(ObjectRef, is_valid), luamethod_aliased(ObjectRef, get_pos, getpos), luamethod_aliased(ObjectRef, set_pos, setpos), luamethod(ObjectRef, add_pos), diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 73264db10..3e6d01201 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -67,6 +67,9 @@ private: // remove(self) static int l_remove(lua_State *L); + // is_valid(self) + static int l_is_valid(lua_State *L); + // get_pos(self) static int l_get_pos(lua_State *L); From fcb4f258f57cbc0bf3d0e724c7fb3748d220ec5b Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 2 Jul 2024 20:57:43 +0200 Subject: [PATCH 14/14] GUITable: Scale images with display density / row height (#14709) --- doc/lua_api.md | 4 +- games/devtest/mods/testformspec/formspec.lua | 41 +++++++++++++++- src/gui/guiTable.cpp | 49 +++++++++++++++----- src/gui/guiTable.h | 1 + 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index f18f90921..5709109ab 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3248,7 +3248,7 @@ Elements * Types: `text`, `image`, `color`, `indent`, `tree` * `text`: show cell contents as text * `image`: cell contents are an image index, use column options to define - images. + images. images are scaled down to fit the row height if necessary. * `color`: cell contents are a ColorString and define color of following cell. * `indent`: cell contents are a number and define indentation of following @@ -3269,7 +3269,7 @@ Elements * `0=` sets image for image index 0 * `1=` sets image for image index 1 * `2=` sets image for image index 2 - * and so on; defined indices need not be contiguous empty or + * and so on; defined indices need not be contiguous. empty or non-numeric cells are treated as `0`. * `color` column options: * `span=`: number of following columns to affect diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index c2074e6e0..99ee691f1 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -64,6 +64,41 @@ local inv_style_fs = [[ list[current_player;main;.5,7;8,4] ]] +-- Some textures from textures/base/pack and Devtest, with many different sizes +-- and aspect ratios. +local image_column = "image,0=logo.png,1=rare_controls.png,2=checkbox_16.png," .. + "3=checkbox_32.png,4=checkbox_64.png,5=default_lava.png," .. + "6=progress_bar.png,7=progress_bar_bg.png" +local words = { + "esciunt", "repudiandae", "repellat", "voluptatem", "autem", "vitae", "et", + "minima", "quasi", "facere", "nihil", "ea", "nemo", "rem", "non", "eos", + "laudantium", "eveniet", "veritatis", +} + +local reseed = math.random(2^31-1) +math.randomseed(1337) + +local table_content = {} +for i = 1, 100 do + table.insert(table_content, words[math.random(#words)]) + table.insert(table_content, words[math.random(#words)]) + table.insert(table_content, words[math.random(#words)]) + table.insert(table_content, math.random(0, 7)) + table.insert(table_content, math.random(0, 7)) + table.insert(table_content, math.random(0, 7)) + table.insert(table_content, words[math.random(#words)]) +end + +math.randomseed(reseed) + +local table_fs = table.concat({ + "tablecolumns[text,align=left;text,align=right;text,align=center;", + image_column, ",align=left;", + image_column, ",align=right;", + image_column, ",align=center;text,align=right]", + "table[0,0;17,12;the_table;", table.concat(table_content, ","), ";1]" +}) + local hypertext_basic = [[A hypertext element Normal test This is a normal text. @@ -350,6 +385,10 @@ local pages = { "label[11,0.5;Noclip]" .. "container[11.5,1]" .. clip_fs:gsub("%%c", "true") .. "container_end[]", + -- Table + "size[18,13]real_coordinates[true]" .. + "container[0.5,0.5]" .. table_fs.. "container_end[]", + -- Hypertext "size[12,13]real_coordinates[true]" .. "container[0.5,0.5]" .. hypertext_fs .. "container_end[]", @@ -477,7 +516,7 @@ local function show_test_formspec(pname) page = page() end - local fs = page .. "tabheader[0,0;11,0.65;maintabs;Real Coord,Styles,Noclip,Hypertext,Tabs,Invs,Window,Anim,Model,ScrollC,Sound,Background,Unsized;" .. page_id .. ";false;false]" + local fs = page .. "tabheader[0,0;11,0.65;maintabs;Real Coord,Styles,Noclip,Table,Hypertext,Tabs,Invs,Window,Anim,Model,ScrollC,Sound,Background,Unsized;" .. page_id .. ";false;false]" minetest.show_formspec(pname, "testformspec:formspec", fs) end diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index 93a56ed3c..a58020300 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "client/renderingengine.h" #include "debug.h" +#include "irrlicht_changes/CGUITTFont.h" #include "log.h" #include "client/texturesource.h" #include "gettime.h" @@ -227,6 +228,8 @@ void GUITable::setTable(const TableOptions &options, s32 content_index; // Next cell: Width in pixels s32 content_width; + // Next cell: Image scale (only for "image" column type) + f32 image_scale; // Vector of completed cells in this row std::vector cells; // Stores colors and how long they last (maximum column index) @@ -236,6 +239,17 @@ void GUITable::setTable(const TableOptions &options, }; TempRow *rows = new TempRow[rowcount]; + CGUITTFont *ttfont = dynamic_cast(m_font); + f32 desired_image_scale = 1.0f; + if (ttfont) { + // This gives us the effective font size, which is chosen taking display + // density and gui_scaling into account. + // Since row height scales with font size, this gives better results than + // just using display density and gui_scaling when a non-standard font + // size is used (e.g. Android default of 14). + desired_image_scale = std::max(1.0f, ttfont->getFontSize() / 16.0f); + } + // Get em width. Pedantically speaking, the width of "M" is not // necessarily the same as the em width, but whatever, close enough. s32 em = 6; @@ -373,8 +387,18 @@ void GUITable::setTable(const TableOptions &options, if (row->content_index >= 0) image = m_images[row->content_index]; + row->image_scale = 1.0f; + row->content_width = 0; + if (image) { + f32 max_image_scale = (f32)m_rowheight / (f32)image->getOriginalSize().Height; + // Scale with display density and make sure it fits into the row + row->image_scale = std::min(desired_image_scale, max_image_scale); + // When upscaling, fractional factors would cause artifacts + if (row->image_scale > 1.0f) + row->image_scale = std::floor(row->image_scale); + row->content_width = image->getOriginalSize().Width * row->image_scale; + } // Get content width and update xmax - row->content_width = image ? image->getOriginalSize().Width : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); @@ -384,6 +408,7 @@ void GUITable::setTable(const TableOptions &options, newcell.xmin = rows[i].x + padding; alignContent(&newcell, xmax, rows[i].content_width, align); newcell.content_index = rows[i].content_index; + newcell.image_scale = rows[i].image_scale; rows[i].cells.push_back(newcell); rows[i].x = newcell.xmax; } @@ -740,23 +765,23 @@ void GUITable::drawCell(const Cell *cell, video::SColor color, video::ITexture *image = m_images[cell->content_index]; if (image) { - core::position2d dest_pos = - row_rect.UpperLeftCorner; - dest_pos.X += cell->xpos; core::rect source_rect( core::position2d(0, 0), image->getOriginalSize()); - s32 imgh = source_rect.LowerRightCorner.Y; + core::rect dest_rect( + 0, 0, + image->getOriginalSize().Width * cell->image_scale, + image->getOriginalSize().Height * cell->image_scale); + dest_rect += row_rect.UpperLeftCorner + v2s32(cell->xpos, 0); + + s32 imgh = dest_rect.getHeight(); s32 rowh = row_rect.getHeight(); + // Center vertically if needed if (imgh < rowh) - dest_pos.Y += (rowh - imgh) / 2; - else - source_rect.LowerRightCorner.Y = rowh; + dest_rect += v2s32(0, (rowh - imgh) / 2); - video::SColor color(255, 255, 255, 255); - - driver->draw2DImage(image, dest_pos, source_rect, - &client_clip, color, true); + driver->draw2DImage(image, dest_rect, source_rect, + &client_clip, nullptr, true); } } } diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 76a0e94d0..271649ff2 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -166,6 +166,7 @@ protected: video::SColor color; bool color_defined; s32 reported_column; + f32 image_scale; // only for "image" type columns }; struct Row {