Compare commits

...

11 Commits

Author SHA1 Message Date
Lars Müller
484b4e3b8e
Merge d74ec349c7c94b8cec144c364963c99f35b96581 into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1 2024-06-28 11:51:07 +02:00
grorp
9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00
Erich Schubert
514e106414
Fix missing newline before Markdown list (#14783)
Renders incorrectly e.g. on https://api.minetest.net/spatial-vectors/
2024-06-26 22:21:18 +02:00
Lars Mueller
d74ec349c7 Minor doc adjustments 2024-06-22 17:00:15 +02:00
Lars Mueller
dc116f0a44 Fix tests 2024-06-22 15:26:20 +02:00
Lars Müller
e00f1d0763
Apply suggestions from code review
Co-authored-by: sfan5 <sfan5@live.de>
2024-06-22 14:21:14 +02:00
Lars Mueller
89079a6619 Fix up documentation 2024-06-21 19:56:25 +02:00
Lars Mueller
be14c86320 Add object inside radius / area iterators 2024-06-21 19:08:13 +02:00
Lars Mueller
0810275640 Add ObjectRef:is_valid method 2024-06-21 18:42:55 +02:00
Lars Mueller
0ad415c18d Skip invalid objects in raycasts 2024-06-21 18:33:28 +02:00
Lars Mueller
d4e8da1041 Add (failing) unit test for raycasts w.r.t. entities 2024-06-21 18:33:24 +02:00
8 changed files with 191 additions and 23 deletions

@ -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

@ -3902,6 +3902,7 @@ Operators
---------
Operators can be used if all of the involved vectors have metatables:
* `v1 == v2`:
* Returns whether `v1` and `v2` are identical.
* `-v`:
@ -6129,12 +6130,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
@ -7796,13 +7809,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).
@ -7821,6 +7839,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.

@ -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})

@ -129,9 +129,9 @@ EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEv
}
#endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{
switch (key) {
switch (irrlichtKey) {
// keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!)
case KEY_RETURN:
@ -189,24 +189,68 @@ bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
}
}
int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode
// looks
switch (key) {
case KEY_RETURN:
case KEY_ESCAPE:
return (int)key;
return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default:
break;
}
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
if (assumedChar & (1 << 30))
// This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0;
switch (key) {
switch (irrlichtKey) {
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
@ -218,7 +262,7 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
case KEY_NUMLOCK:
return 0;
default:
return assumedChar;
return sdlKey;
}
}
@ -825,7 +869,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key);
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent);
} break;

@ -273,10 +273,10 @@ class CIrrDeviceSDL : public CIrrDeviceStub
#endif
// Check if a key is a known special character with no side effects on text boxes.
static bool keyIsKnownSpecial(EKEY_CODE key);
static bool keyIsKnownSpecial(EKEY_CODE irrlichtKey);
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key);
static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents();

@ -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<ServerEnvironment*>(env);
bool csm = false;
#ifndef SERVER
@ -163,7 +164,17 @@ int LuaRaycast::l_next(lua_State *L)
LuaRaycast *o = checkObject<LuaRaycast>(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

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_object.h"
#include <cmath>
#include <lua.h>
#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<ObjectRef>(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),

@ -67,6 +67,9 @@ class ObjectRef : public ModApiBase {
// 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);