diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index 341e613c2..96a0a2dda 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -151,6 +151,12 @@ function core.setting_get_pos(name) end +-- See l_env.cpp for the other functions +function core.get_artificial_light(param1) + return math.floor(param1 / 16) +end + + -- To be overriden by protection mods function core.is_protected(pos, name) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 9e9af20da..c4eb56374 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -4824,6 +4824,22 @@ Environment access * `pos`: The position where to measure the light. * `timeofday`: `nil` for current time, `0` for night, `0.5` for day * Returns a number between `0` and `15` or `nil` + * `nil` is returned e.g. when the map isn't loaded at `pos` +* `minetest.get_natural_light(pos[, timeofday])` + * Figures out the sunlight (or moonlight) value at pos at the given time of + day. + * `pos`: The position of the node + * `timeofday`: `nil` for current time, `0` for night, `0.5` for day + * Returns a number between `0` and `15` or `nil` + * This function tests 203 nodes in the worst case, which happens very + unlikely +* `minetest.get_artificial_light(param1)` + * Calculates the artificial light (light from e.g. torches) value from the + `param1` value. + * `param1`: The param1 value of a `paramtype = "light"` node. + * Returns a number between `0` and `15` + * Currently it's the same as `math.floor(param1 / 16)`, except that it + ensures compatibility. * `minetest.place_node(pos, node)` * Place node with the same effects that a player would cause * `minetest.dig_node(pos)` diff --git a/games/devtest/mods/testtools/init.lua b/games/devtest/mods/testtools/init.lua index df5bc8e55..d578b264a 100644 --- a/games/devtest/mods/testtools/init.lua +++ b/games/devtest/mods/testtools/init.lua @@ -1,6 +1,8 @@ local S = minetest.get_translator("testtools") local F = minetest.formspec_escape +dofile(minetest.get_modpath("testtools") .. "/light.lua") + -- TODO: Add a Node Metadata tool minetest.register_tool("testtools:param2tool", { diff --git a/games/devtest/mods/testtools/light.lua b/games/devtest/mods/testtools/light.lua new file mode 100644 index 000000000..a9458ca6b --- /dev/null +++ b/games/devtest/mods/testtools/light.lua @@ -0,0 +1,22 @@ + +local S = minetest.get_translator("testtools") + +minetest.register_tool("testtools:lighttool", { + description = S("Light tool"), + inventory_image = "testtools_lighttool.png", + groups = { testtool = 1, disable_repair = 1 }, + on_use = function(itemstack, user, pointed_thing) + local pos = pointed_thing.above + if pointed_thing.type ~= "node" or not pos then + return + end + + local node = minetest.get_node(pos) + local time = minetest.get_timeofday() + local sunlight = minetest.get_natural_light(pos) + local artificial = minetest.get_artificial_light(node.param1) + local message = ("param1 0x%02x | time %.5f | sunlight %d | artificial %d") + :format(node.param1, time, sunlight, artificial) + minetest.chat_send_player(user:get_player_name(), message) + end +}) diff --git a/games/devtest/mods/testtools/textures/testtools_lighttool.png b/games/devtest/mods/testtools/textures/testtools_lighttool.png new file mode 100644 index 000000000..6f744b7fa Binary files /dev/null and b/games/devtest/mods/testtools/textures/testtools_lighttool.png differ diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 138e88ae8..8d50d664d 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -407,6 +407,46 @@ int ModApiEnvMod::l_get_node_light(lua_State *L) return 1; } + +// get_natural_light(pos, timeofday) +// pos = {x=num, y=num, z=num} +// timeofday: nil = current time, 0 = night, 0.5 = day +int ModApiEnvMod::l_get_natural_light(lua_State *L) +{ + GET_ENV_PTR; + + v3s16 pos = read_v3s16(L, 1); + + bool is_position_ok; + MapNode n = env->getMap().getNode(pos, &is_position_ok); + if (!is_position_ok) + return 0; + + // If the daylight is 0, nothing needs to be calculated + u8 daylight = n.param1 & 0x0f; + if (daylight == 0) { + lua_pushinteger(L, 0); + return 1; + } + + u32 time_of_day; + if (lua_isnumber(L, 2)) { + time_of_day = 24000.0 * lua_tonumber(L, 2); + time_of_day %= 24000; + } else { + time_of_day = env->getTimeOfDay(); + } + u32 dnr = time_to_daynight_ratio(time_of_day, true); + + // If it's the same as the artificial light, the sunlight needs to be + // searched for because the value may not emanate from the sun + if (daylight == n.param1 >> 4) + daylight = env->findSunlight(pos); + + lua_pushinteger(L, dnr * daylight / 1000); + return 1; +} + // place_node(pos, node) // pos = {x=num, y=num, z=num} int ModApiEnvMod::l_place_node(lua_State *L) @@ -1358,6 +1398,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(get_node); API_FCT(get_node_or_nil); API_FCT(get_node_light); + API_FCT(get_natural_light); API_FCT(place_node); API_FCT(dig_node); API_FCT(punch_node); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 07d4d2438..7f212b5fc 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -56,6 +56,11 @@ private: // timeofday: nil = current time, 0 = night, 0.5 = day static int l_get_node_light(lua_State *L); + // get_natural_light(pos, timeofday) + // pos = {x=num, y=num, z=num} + // timeofday: nil = current time, 0 = night, 0.5 = day + static int l_get_natural_light(lua_State *L); + // place_node(pos, node) // pos = {x=num, y=num, z=num} static int l_place_node(lua_State *L); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 320042e19..6ef56efc8 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1066,6 +1066,91 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) return true; } +u8 ServerEnvironment::findSunlight(v3s16 pos) const +{ + // Directions for neighbouring nodes with specified order + static const v3s16 dirs[] = { + v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1), + v3s16(0, -1, 0), v3s16(0, 1, 0) + }; + + const NodeDefManager *ndef = m_server->ndef(); + + // found_light remembers the highest known sunlight value at pos + u8 found_light = 0; + + struct stack_entry { + v3s16 pos; + s16 dist; + }; + std::stack stack; + stack.push({pos, 0}); + + std::unordered_map dists; + dists[MapDatabase::getBlockAsInteger(pos)] = 0; + + while (!stack.empty()) { + struct stack_entry e = stack.top(); + stack.pop(); + + v3s16 currentPos = e.pos; + s8 dist = e.dist + 1; + + for (const v3s16& off : dirs) { + v3s16 neighborPos = currentPos + off; + s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos); + + // Do not walk neighborPos multiple times unless the distance to the start + // position is shorter + auto it = dists.find(neighborHash); + if (it != dists.end() && dist >= it->second) + continue; + + // Position to walk + bool is_position_ok; + MapNode node = m_map->getNode(neighborPos, &is_position_ok); + if (!is_position_ok) { + // This happens very rarely because the map at currentPos is loaded + m_map->emergeBlock(neighborPos, false); + node = m_map->getNode(neighborPos, &is_position_ok); + if (!is_position_ok) + continue; // not generated + } + + const ContentFeatures &def = ndef->get(node); + if (!def.sunlight_propagates) { + // Do not test propagation here again + dists[neighborHash] = -1; + continue; + } + + // Sunlight could have come from here + dists[neighborHash] = dist; + u8 daylight = node.param1 & 0x0f; + + // In the special case where sunlight shines from above and thus + // does not decrease with upwards distance, daylight is always + // bigger than nightlight, which never reaches 15 + int possible_finlight = daylight - dist; + if (possible_finlight <= found_light) { + // Light from here cannot make a brighter light at currentPos than + // found_light + continue; + } + + u8 nightlight = node.param1 >> 4; + if (daylight > nightlight) { + // Found a valid daylight + found_light = possible_finlight; + } else { + // Sunlight may be darker, so walk the neighbours + stack.push({neighborPos, dist}); + } + } + } + return found_light; +} + void ServerEnvironment::clearObjects(ClearObjectsMode mode) { infostream << "ServerEnvironment::clearObjects(): " diff --git a/src/serverenvironment.h b/src/serverenvironment.h index af742e290..cfd5b8f3e 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -322,6 +322,9 @@ public: bool removeNode(v3s16 p); bool swapNode(v3s16 p, const MapNode &n); + // Find the daylight value at pos with a Depth First Search + u8 findSunlight(v3s16 pos) const; + // Find all active objects inside a radius around a point void getObjectsInsideRadius(std::vector &objects, const v3f &pos, float radius, std::function include_obj_cb)