Aaron Suen 37543c0f43 Make dynamic shadows much faster
Machine automation running based on these
dynamic shadows should be allowed to respond
much faster.  This especially affects skyrealm
aggregate scorching.
2022-12-24 18:40:57 -05:00

244 lines
7.0 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, minetest, nodecore, pairs, setmetatable, vector
= ItemStack, minetest, nodecore, pairs, setmetatable, vector
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
-- Register nodes that can be replaced by dynamic lights
local canreplace = {air = 0}
local true_airlike = {
drawtype = "airlike",
pointable = false,
walkable = false,
climbable = false,
buildable_to = true,
floodable = true,
air_equivalent = true,
paramtype = "light",
light_source = 0,
sunlight_propagates = true,
}
minetest.after(0, function()
for k, v in pairs(minetest.registered_nodes) do
local ok = not canreplace[k]
for dk, dv in pairs(true_airlike) do
ok = ok and v[dk] == dv
end
if ok then canreplace[k] = 0 end
end
end)
-- API for checking if dynamic lights are valid
local ttl = 0.25
local active_lights = {}
local function setup_light(pos, check)
active_lights[minetest.hash_node_position(pos)] = {
exp = nodecore.gametime + ttl,
check = check
}
nodecore.dnt_set(pos, modname .. ":dynalight_check")
end
local function check_light(pos)
local data = active_lights[minetest.hash_node_position(pos)]
if not data then return minetest.remove_node(pos) end
if nodecore.gametime < data.exp then return end
if data.check and data.check() then
data.exp = nodecore.gametime + ttl
nodecore.dnt_set(pos, modname .. ":dynalight_check")
return
end
minetest.remove_node(pos)
return true
end
nodecore.register_dnt({
name = modname .. ":dynalight_check",
nodenames = {"group:dynamic_light"},
ignore_stasis = true,
time = ttl,
autostart = true,
autostart_time = 0,
action = check_light
})
-- Register dynamic light nodes
local function dynamic_light_node(level) return modname .. ":light" .. level end
nodecore.dynamic_light_node = dynamic_light_node
for level = 1, nodecore.light_sun - 1 do
local name = dynamic_light_node(level)
local def = {
description = minetest.registered_nodes.air.description,
light_source = level,
air_equivalent = true,
groups = {dynamic_light = level}
}
for k, v in pairs(true_airlike) do def[k] = def[k] or v end
minetest.register_node(":" .. name, def)
canreplace[name] = level
end
minetest.register_alias("nc_torch:wield_light", dynamic_light_node(8))
-- API for adding dynamic lights to world
local function dynamic_light_add(pos, level, check, exact)
if not pos then return end
local old = minetest.get_node(pos)
local name = old.name
local curlight = canreplace[name]
if not curlight then
if exact then return end
return dynamic_light_add({x = pos.x, y = pos.y - 1, z = pos.z}, level, check, true)
or dynamic_light_add({x = pos.x, y = pos.y + 1, z = pos.z}, level, check, true)
or dynamic_light_add({x = pos.x + 1, y = pos.y, z = pos.z}, level, check, true)
or dynamic_light_add({x = pos.x - 1, y = pos.y, z = pos.z}, level, check, true)
or dynamic_light_add({x = pos.x, y = pos.y, z = pos.z + 1}, level, check, true)
or dynamic_light_add({x = pos.x, y = pos.y, z = pos.z - 1}, level, check, true)
end
if level < 1 then return end
if level > nodecore.light_sun - 1 then level = nodecore.light_sun - 1 end
local setname = dynamic_light_node(level)
pos = vector.round(pos)
if curlight <= level then
local ll = nodecore.get_node_light(pos)
if ll and ll > level then return end
end
if curlight > level and not check_light(pos) then return end
if name ~= setname then nodecore.set_node_check(pos, {name = setname}, old) end
setup_light(pos, check)
return true
end
nodecore.dynamic_light_add = dynamic_light_add
-- Automatic player wield lights
local function lightsrc(stack)
local def = minetest.registered_items[stack:get_name()] or {}
return def.light_source or 0
end
local function player_wield_light_pos(player, speed)
local pos = player:get_pos()
pos.y = pos.y + player:get_properties().eye_height
local ld = player:get_look_dir()
speed = player:get_player_control().up and speed or 0.5
pos.x = pos.x + ld.x * speed
pos.z = pos.z + ld.z * speed
return vector.round(pos)
end
local function player_wield_light(player, data)
local glow = 0
local srcidx, srcstack
for idx, stack in pairs(player:get_inventory():get_list("main")) do
local src = lightsrc(stack)
if src > glow then
glow = src
srcidx = idx
srcstack = stack:get_name()
end
end
if glow < 1 then return end
local speed = data and data.physics and data.physics.speed or 1
local pos = player_wield_light_pos(player, speed)
local pname = player:get_player_name()
return dynamic_light_add(pos, glow, function()
local pl = minetest.get_player_by_name(pname)
if not pl then return end
local pp = player_wield_light_pos(pl, speed)
if not vector.equals(pos, pp) then return end
return pl:get_inventory():get_stack("main", srcidx)
:get_name() == srcstack
end)
end
nodecore.register_playerstep({
label = "player wield light",
action = function(player, data)
if nodecore.player_visible(player) then
return player_wield_light(player, data)
end
end
})
-- Automatic entity light sources
local function entlight(self, ...)
local stack = ItemStack(self.node and self.node.name or self.itemstring or "")
local src = lightsrc(stack)
if src > 0 then
local pos = self.object:get_pos()
if not pos then return ... end
pos = vector.round(pos)
nodecore.dynamic_light_add(pos, src, function()
local curpos = self.object and self.object:get_pos()
return curpos and vector.equals(vector.round(curpos), pos)
end)
end
return ...
end
for _, name in pairs({"item", "falling_node"}) do
local def = minetest.registered_entities["__builtin:" .. name]
local ndef = {
on_step = function(self, ...)
return entlight(self, def.on_step(self, ...))
end
}
setmetatable(ndef, def)
minetest.register_entity(":__builtin:" .. name, ndef)
end
-- shade for light-scattering effect
local shadenode = modname .. ":shade"
minetest.register_node(shadenode, nodecore.underride({
description = minetest.registered_nodes.air.description,
air_equivalent = true,
sunlight_propagates = false
}, true_airlike))
nodecore.register_dnt({
name = modname .. ":shadenode_check",
nodenames = {shadenode},
time = 1,
autostart = true,
action = function(pos)
return minetest.remove_node(pos)
end
})
function nodecore.dynamic_shade_add(pos, above)
local old = minetest.get_node(pos)
local name = old.name
if canreplace[name] then
return nodecore.set_node_check(pos, {name = shadenode}, old)
end
if above and above > 0 then
return nodecore.dynamic_shade_add({
x = pos.x, y = pos.y + 1, z = pos.z
}, above - 1)
end
end
-- When digging a node that emits light when placed, and would
-- emit light when held, there can be a brief flicker of darkness
-- when MT predicts the dug node is gone but the dynamic light
-- hasn't been send to the player yet.
nodecore.register_on_register_item(function(_, def)
if def.light_source and def.light_source > 0
and def.node_dig_prediction == nil then
def.node_dig_prediction = modname .. ":light" .. def.light_source
end
end)