Many changes

Change the search algorithm
Change default values and messages
Use minetest.after instead of minetest.globalstep
This commit is contained in:
HybridDog 2020-07-28 20:28:44 +02:00
parent f2293b66ab
commit 2537583096
4 changed files with 212 additions and 133 deletions

View File

@ -1 +1,2 @@
MIT
fill_3d.lua: LGPL 2.1; copied and modified from Minetest builtin
init.lua: MIT

View File

@ -1,10 +1,10 @@
[Mod] cave lighting chatcommand [cave_lighting]
This mod adds the chatcommand light_cave to automatically light up a cave with e.g. torches.
This mod adds the chatcommand light_cave to automatically light up a cave with e.g. torches.
maxlight is the maximum light a lighting node is placed (to be able to see light strength at specific position, try my superpick mod)
**License:** see [LICENSE.txt](https://raw.githubusercontent.com/HybridDog/cave_lighting/master/LICENSE.txt)
**Download:** [zip](https://github.com/HybridDog/cave_lighting/archive/master.zip), [tar.gz](https://github.com/HybridDog/cave_lighting/archive/master.tar.gz)
**License:** see [LICENSE.txt](https://raw.githubusercontent.com/HybridDog/cave_lighting/master/LICENSE.txt)
**Download:** [zip](https://github.com/HybridDog/cave_lighting/archive/master.zip), [tar.gz](https://github.com/HybridDog/cave_lighting/archive/master.tar.gz)
![I'm a screenshot!](https://forum.minetest.net/download/file.php?mode=view&id=2922&sid=3e0175b669497750711e30b69568e805)
@ -13,5 +13,5 @@ If you got ideas or found bugs, please tell them to me.
[How to install a mod?](http://wiki.minetest.net/Installing_Mods)
TODO:
— streamline position selecting
TODO:
* Take inventory items

53
fill_3d.lua Normal file
View File

@ -0,0 +1,53 @@
-- Algorithm created by sofar and changed by others:
-- https://github.com/minetest/minetest/commit/d7908ee49480caaab63d05c8a53d93103579d7a9
local function search(go, p, apply_move, moves)
local num_moves = #moves
-- We make a stack, and manually maintain size for performance.
-- Stored in the stack, we will maintain tables with pos, and
-- last neighbor visited. This way, when we get back to each
-- node, we know which directions we have already walked, and
-- which direction is the next to walk.
local s = {}
local n = 0
-- The neighbor order we will visit from our table.
local v = 1
while true do
-- Push current pos onto the stack.
n = n + 1
s[n] = {p = p, v = v}
-- Select next node from neighbor list.
p = apply_move(p, moves[v])
-- Now we check out the node. If it is in need of an update,
-- it will let us know in the return value (true = updated).
if not go(p) then
-- If we don't need to "recurse" (walk) to it then pop
-- our previous pos off the stack and continue from there,
-- with the v value we were at when we last were at that
-- node
repeat
local pop = s[n]
p = pop.p
v = pop.v
s[n] = nil
n = n - 1
-- If there's nothing left on the stack, and no
-- more sides to walk to, we're done and can exit
if n == 0 and v == num_moves then
return
end
until v < num_moves
-- The next round walk the next neighbor in list.
v = v + 1
else
-- If we did need to walk the neighbor, then
-- start walking it from the walk order start (1),
-- and not the order we just pushed up the stack.
v = 1
end
end
end
return search

279
init.lua
View File

@ -1,88 +1,101 @@
local path = minetest.get_modpath"cave_lighting"
local search_dfs = dofile(path .. "/fill_3d.lua")
local function inform(name, msg)
minetest.chat_send_player(name, msg)
minetest.log("info", "[cave_lighting] "..name..": "..msg)
end
-- tests if theres a node an e.g. torch is allowed to be placed on
-- Like minetest.get_node but also works in unloaded areas
local function get_node_loaded(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
minetest.load_area(pos)
return minetest.get_node(pos)
end
-- Tests if theres a node an e.g. torch is allowed to be placed on
local function pos_placeable(pos)
local undernode = minetest.get_node(pos).name
local undernode = get_node_loaded(pos).name
if undernode == "air" then
return false
end
local data = minetest.registered_nodes[undernode]
if not data then
return false
end
if data.drawtype == "normal"
or not data.drawtype then
if data
and (data.drawtype == "normal" or not data.drawtype)
and data.pointable and not data.buildable_to then
return true
end
return false
end
-- tests if it's a possible place for a light node
local moves_touch = {
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = -1, z = 0},
{x = 0, y = 1, z = 0},
{x = 0, y = 0, z = -1},
{x = 0, y = 0, z = 1},
}
local moves_near = {}
for x = -1,1 do
for y = -1,1 do
for z = -1,1 do
if x*x + y*y + z*z > 0 then
moves_near[#moves_near+1] = {x = x, y = y, z = z}
end
end
end
end
-- Tests if it's a possible place for a light node
local function pos_allowed(pos, maxlight, name)
local light = minetest.get_node_light(pos, 0.5)
if not light
or light > maxlight
or minetest.get_node(pos).name ~= "air"
or get_node_loaded(pos).name ~= "air"
or minetest.is_protected(pos, name) then
return false
return
end
for i = -1,1,2 do
for _,p2 in pairs{
{x=pos.x+i, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y+i, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z+i},
} do
if pos_placeable(p2) then
return p2
end
for k = 1, 6 do
local p2 = vector.add(pos, moves_touch[k])
if pos_placeable(p2) then
return p2
end
end
return false
end
-- finds out possible places for a light node in a cave
local function get_ps(pos, maxlight, name, max)
local tab = {}
local num = 1
local todo = {pos}
local nt = 1
local tab_avoid = {}
while nt ~= 0 do
local p = todo[nt]
todo[nt] = nil
nt = nt-1
for i = -1,1 do
for j = -1,1 do
for k = -1,1 do
local p2 = {x=p.x+i, y=p.y+j, z=p.z+k}
local vi = minetest.hash_node_position(p2)
if not tab_avoid[vi] then
local atpos = pos_allowed(p2, maxlight, name)
if atpos then
tab[num] = {above=p2, under=atpos}
num = num+1
nt = nt+1
todo[nt] = p2
tab_avoid[vi] = true
if max
and num > max then
return false
end
end
end
end
end
-- Finds out possible places for a light node in a cave with an efficient
-- variant of Depth First Search
local function search_positions(startpos, maxlight, pname, max_positions)
local visited = {}
local found = {}
local num_found = 0
local function on_visit(pos)
local vi = minetest.hash_node_position(pos)
if visited[vi] then
return false
end
visited[vi] = true
if num_found > max_positions then
return false
end
local under_pos = pos_allowed(pos, maxlight, pname)
if under_pos then
num_found = num_found+1
found[num_found] = {under = under_pos, above = pos}
return true
end
return false
end
return tab
on_visit(startpos)
search_dfs(on_visit, startpos, vector.add, moves_near)
-- Do not return the positions if the search has found too many, to avoid
-- fragmented lighting
return num_found <= max_positions and num_found > 0 and found
end
-- Lights up a cave
@ -97,22 +110,19 @@ local function place_torches(pos, maxlight, player, name)
node = inv:get_stack("main", wi+1):get_name()
data = minetest.registered_nodes[node]
if not data then
inform(name,
"You need to have a node next to or as your wielded item.")
return
return false,
"You need to have a node next to or as your wielded item."
end
end
local nodelight = data.light_source
if not nodelight
or nodelight < maxlight then
inform(name, "You need a node emitting light (enough light).")
return
return false, "You need a node emitting light (enough light)."
end
-- Get possible positions
local ps = get_ps(pos, maxlight, name, 200^3)
local ps = search_positions(pos, maxlight, name, 200^3)
if not ps then
inform(name, "It doesn't seem to be dark there or the cave is too big.")
return
return false, "It doesn't seem to be dark there or the cave is too big."
end
local sound = data.sounds
if sound then
@ -121,37 +131,45 @@ local function place_torches(pos, maxlight, player, name)
local count = 0
-- [[ -- should search for optimal places for torches
local l1 = math.max(2*maxlight-nodelight+1, 1)
local found = true
while found do
found = false
for n,pt in pairs(ps) do
local pos = pt.above
local light = minetest.get_node_light(pos, 0.5) or 0
if light == l1 then
count = count+1
if sound
and count < 50 then
minetest.sound_play(sound.name, {pos=pos, gain=sound.gain/count})
end
pt.type = "node"
minetest.item_place_node(ItemStack(node), player, pt)
found = true
ps[n] = nil
elseif light > maxlight then
ps[n] = nil
-- The light depends on the manhattan distance to the light source.
-- If for example maxlight=4 and nodelight=7 and a light stripe is
-- (2,3,4,5,6,7), then I assume it is advisable to first put light nodes
-- where the light is 3: (6,7,6,5,6,7)
local l1 = math.max(maxlight - (nodelight - maxlight) + 2, 0)
local n = #ps
for k = n, 1, -1 do
local pt = ps[k]
local pos = pt.above
local light = minetest.get_node_light(pos, 0.5) or 0
if light == l1 then
count = count+1
if sound
and count < 50 then
minetest.sound_play(sound.name, {pos = pos,
gain = sound.gain / count})
end
pt.type = "node"
minetest.item_place_node(ItemStack(node), player, pt)
ps[k] = ps[n]
ps[n] = nil
n = n-1
elseif light > maxlight then
ps[k] = ps[n]
ps[n] = nil
n = n-1
end
end--]]
for _,pt in pairs(ps) do
for k = 1, n do
local pt = ps[k]
local pos = pt.above
local light = minetest.get_node_light(pos, 0.5) or 0
if light <= maxlight then
count = count+1
if sound
and count < 50 then
minetest.sound_play(sound.name, {pos=pos, gain=sound.gain/count})
minetest.sound_play(sound.name, {pos = pos,
gain = sound.gain / count})
end
pt.type = "node"
minetest.item_place_node(ItemStack(node), player, pt)
@ -181,9 +199,9 @@ local function get_pointed_target(player)
return
end
local def_under = minetest.registered_nodes[
minetest.get_node(pointed.under).name]
get_node_loaded(pointed.under).name]
local def_above = minetest.registered_nodes[
minetest.get_node(pointed.above).name]
get_node_loaded(pointed.above).name]
if not def_under or not def_above
or not def_above.buildable_to or def_under.buildable_to then
-- Cannot place a node here
@ -193,45 +211,48 @@ local function get_pointed_target(player)
return pointed.above, pointed.under
end
-- searches the position the player looked at and lights the cave
local function light_cave(player, name, maxlight)
local pos = get_pointed_target(player, name)
-- Searches the position the player looked at and lights the cave
local function light_cave(player, maxlight)
local pos = get_pointed_target(player)
if not pos then
return false, "No valid position for a torch placement found"
end
inform(name, "Lighting a cave…")
local t = place_torches(pos, maxlight, player, name)
if t then
if t[1] == 0 then
return false, "No nodes placed."
end
return true, t[1].." "..t[2].."s placed. (maxlight="..maxlight..", used_light="..t[3]..")"
inform(player:get_player_name(), "Lighting a cave…")
local t, errormsg = place_torches(pos, maxlight, player)
if not t then
return false, errormsg
end
if t[1] == 0 then
return false, "No nodes placed."
end
return true, ("%d \"%s\"s placed. (maxlight: %d, placed nodes' light: %d)"
):format(t[1], t[2], maxlight, t[3])
end
-- the chatcommand
-- Chatcommand to light a full cave
minetest.register_chatcommand("light_cave",{
description = "light a cave",
params = "[maxlight]",
params = "[maxlight=7]",
privs = {give=true, interact=true},
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then
return false, "Player not found"
end
return light_cave(player, name, tonumber(param) or 7)
return light_cave(player, tonumber(param) or 7)
end
})
-- lazy torch placing
local light_making_players, timer
-- Lazy torch placing
-- the chatcommand
local light_making_players
-- Chatcommand to automatically light the way while playing
minetest.register_chatcommand("auto_light_placing",{
description = "automatically places lights",
params = "[maxlight]",
params = "[maxlight=3]",
privs = {give=true, interact=true},
func = function(name, param)
local player = minetest.get_player_by_name(name)
@ -239,39 +260,35 @@ minetest.register_chatcommand("auto_light_placing",{
return false, "Player not found"
end
light_making_players = light_making_players or {}
light_making_players[name] = tonumber(param) or 7
light_making_players[name] = tonumber(param) or 3
timer = -0.5
return true, "Placing lights automatically"
end
})
minetest.register_globalstep(function(dtime)
-- abort if noone uses it
local function autoplace_step()
-- Abort if noone uses it
if not light_making_players then
return
end
-- abort that it doesn't shoot too often (change it if your pc runs faster)
timer = timer+dtime
if timer < 0.1 then
return
end
timer = 0
for name,maxlight in pairs(light_making_players) do
for name, maxlight in pairs(light_making_players) do
local player = minetest.get_player_by_name(name)
local pt = {type = "node"}
pt.above, pt.under = get_pointed_target(player, name)
if pt.above
and pos_placeable(pt.under) then
local node = player:get_inventory():get_stack("main", player:get_wield_index()):get_name()
pt.above, pt.under = get_pointed_target(player)
if pt.above then
local node = player:get_inventory():get_stack("main",
player:get_wield_index()):get_name()
local data = minetest.registered_nodes[node]
local failed
if not data then
-- support the chatcommand tool
node = player:get_inventory():get_stack("main", player:get_wield_index()+1):get_name()
node = player:get_inventory():get_stack("main",
player:get_wield_index()+1):get_name()
data = minetest.registered_nodes[node]
if not data then
inform(name, "You need to have a node next to or as your wielded item.")
inform(name, "You need to have a node next to or as " ..
"your wielded item.")
failed = true
end
end
@ -279,18 +296,20 @@ minetest.register_globalstep(function(dtime)
local nodelight = data.light_source
if not nodelight
or nodelight < maxlight then
inform(name, "You need a node emitting light (enough light).")
inform(name,
"You need a node emitting light (enough light).")
failed = true
end
local light = minetest.get_node_light(pt.above, 0.5) or 0
if light <= maxlight
and minetest.get_node(pt.above).name == "air" then
and get_node_loaded(pt.above).name == "air" then
local sound = data.sounds
if sound then
sound = sound.place
end
if sound then
minetest.sound_play(sound.name, {pos=pt.above, gain=sound.gain})
minetest.sound_play(sound.name,
{pos = pt.above, gain = sound.gain})
end
minetest.item_place_node(ItemStack(node), player, pt)
end
@ -303,4 +322,10 @@ minetest.register_globalstep(function(dtime)
end
end
end
end)
end
local function autoplace()
autoplace_step()
minetest.after(0.1, autoplace)
end
autoplace()