Aaron Suen 87fc2d586c Witness always works within "touch" range
If the transformation happens at the player's actual
location, they don't have to "see" it if they can "feel"
it.  This fixes witness triggers happening inside the
player's inventory.
2023-10-09 22:43:51 -04:00

131 lines
3.7 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local math, minetest, nodecore, pairs, table, type, vector
= math, minetest, nodecore, pairs, table, type, vector
local math_pi, table_remove
= math.pi, table.remove
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local seethru = {}
minetest.after(0, function()
for name, def in pairs(minetest.registered_nodes) do
if (minetest.get_item_group(name, "witness_opaque") == 0)
and (
minetest.get_item_group(name, "witness_transparent") > 0
or def.sunlight_propagates
or (
def.paramtype == "light"
and def.alpha
and def.alpha > 0
and def.alpha < 255
)
) then
seethru[name] = true
end
end
end)
local metakey = modname .. "_witness"
local cache = {}
local function witnessdata(player)
local pname = player:get_player_name()
local meta = player:get_meta()
local data = cache[pname]
if not data then
data = meta:get_string(metakey) or ""
data = data and data ~= "" and minetest.deserialize(data)
or {queue = {}, lookup = {}}
cache[pname] = data
end
return data, function() return meta:set_string(metakey, minetest.serialize(data)) end
end
local function canwitnessnow(player, pos)
local ppos = player:get_pos()
ppos.y = ppos.y + player:get_properties().eye_height
if vector.distance(ppos, pos) < 0.5 then return true end
local look = player:get_look_dir()
local targ = vector.normalize(vector.subtract(pos, ppos))
if vector.angle(look, targ) > math_pi / 8 then return end
local rp = vector.round(pos)
for pt in minetest.raycast(pos, ppos, false, true) do
if not pt.under then return end
if vector.equals(pt.under, rp) then return true end
local node = minetest.get_node(pt.under)
if not seethru[node.name] then return end
end
return true
end
local function witnesslater(player, pos, disc)
disc = nodecore.flatkeys(disc)
local data, save = witnessdata(player)
local newdata = {
node = minetest.get_node(pos).name,
stack = nodecore.stack_get(pos):get_name(),
disc = disc
}
local posstr = minetest.pos_to_string(pos)
local olddata = data.lookup[posstr]
if olddata and (olddata.node == newdata.node) and (olddata.stack
== newdata.stack) and (type(olddata.disc) == "table") then
for k in pairs(disc) do
olddata.disc[k] = true
end
else
data.queue[#data.queue + 1] = pos
while #data.queue > 100 do table_remove(data.queue, 1) end
data.lookup[posstr] = newdata
end
save()
end
local function witness_core(pos, disc, maxdist)
maxdist = maxdist or 16
for _, player in pairs(minetest.get_connected_players()) do
local ppos = player:get_pos()
if vector.distance(ppos, pos) <= maxdist then
local deferred = nodecore.player_discover_deferred(
player, disc, "witness:")
if deferred then
if canwitnessnow(player, pos) then
deferred()
else
witnesslater(player, pos, disc)
end
end
end
end
end
function nodecore.witness(pos, disc, maxdist)
if (not pos.x) and pos[1] and pos[1].x then
for i = 1, #pos do
witness_core(pos[i], disc, maxdist)
end
else
return witness_core(pos, disc, maxdist)
end
end
local function delayed_witness(pos, node, player)
local data, save = witnessdata(player)
local posstr = minetest.pos_to_string(pos)
local found = data.lookup[posstr]
if not found then return end
data.lookup[posstr] = nil
save()
node = node or minetest.get_node(pos)
if (found.node ~= node.name) or (nodecore.stack_get(pos):get_name()
~= found.stack) then return end
return nodecore.player_discover(player, found.disc, "witness:")
end
minetest.register_on_punchnode(delayed_witness)
nodecore.register_on_node_stare(function(player, pt)
delayed_witness(pt.under, minetest.get_node(pt.under), player)
end)