2021-07-14 20:05:50 +01:00
|
|
|
local mod_name = minetest.get_current_modname()
|
|
|
|
|
|
|
|
-- Node replacements that emit light
|
|
|
|
-- Sets of lighting_node={ node=original_node, level=light_level }
|
|
|
|
local lighting_nodes = {}
|
|
|
|
|
|
|
|
-- The nodes that can be replaced with lighting nodes
|
|
|
|
-- Sets of original_node={ [1]=lighting_node_1, [2]=lighting_node_2, ... }
|
|
|
|
local lightable_nodes = {}
|
|
|
|
|
|
|
|
-- Prefixes used for each node so we can avoid overlap
|
|
|
|
-- Pairs of prefix=original_node
|
|
|
|
local lighting_prefixes = {}
|
|
|
|
|
|
|
|
-- node_name=true pairs of lightable nodes that are liquids and can flood some light sources
|
|
|
|
local lightable_liquids = {}
|
|
|
|
|
|
|
|
-- How often will the positions of lights be recalculated
|
2018-01-13 15:19:06 +01:00
|
|
|
local update_interval = 0.2
|
2021-07-14 20:05:50 +01:00
|
|
|
|
|
|
|
-- How long until a previously lit node should be updated - reduces flicker
|
|
|
|
local removal_delay = update_interval * 0.5
|
|
|
|
|
|
|
|
-- How often will a node attempt to check itself for deletion
|
|
|
|
local cleanup_interval = update_interval * 3
|
|
|
|
|
|
|
|
-- How far in the future will the position be projected based on the velocity
|
|
|
|
local velocity_projection = update_interval * 1
|
|
|
|
|
|
|
|
-- How many light levels should an item held in the hand be reduced by, compared to the placed node
|
|
|
|
-- does not apply to manually registered light levels
|
2018-01-13 15:19:06 +01:00
|
|
|
local level_delta = 2
|
2021-07-14 20:05:50 +01:00
|
|
|
|
|
|
|
-- item=light_level pairs of registered wielded lights
|
2018-05-08 16:52:08 +02:00
|
|
|
local shiny_items = {}
|
2018-01-10 10:04:36 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- List of custom callbacks for each update step
|
|
|
|
local update_callbacks = {}
|
|
|
|
local update_player_callbacks = {}
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- position={id=light_level} sets of known about light sources and their levels by position
|
|
|
|
local active_lights = {}
|
2021-06-20 16:32:29 +02:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
--[[ Sets of entities being tracked, in the form:
|
|
|
|
entity_id = {
|
|
|
|
obj = entity,
|
|
|
|
items = {
|
|
|
|
category_id..entity_id = {
|
|
|
|
level = light_level,
|
|
|
|
item? = item_name
|
2018-09-04 22:13:37 +02:00
|
|
|
}
|
2021-07-14 20:05:50 +01:00
|
|
|
},
|
|
|
|
update = true | false,
|
|
|
|
pos? = position_vector,
|
|
|
|
offset? = offset_vector,
|
|
|
|
}
|
|
|
|
]]
|
|
|
|
local tracked_entities = {}
|
|
|
|
|
|
|
|
-- position=true pairs of positions that need to be recaculated this update step
|
|
|
|
local light_recalcs = {}
|
|
|
|
|
|
|
|
--[[
|
|
|
|
Using 2-digit hex codes for categories
|
|
|
|
Starts at 00, ends at FF
|
|
|
|
This makes it easier extract `uid` from `cat_id..uid` by slicing off 2 characters
|
|
|
|
The category ID must be of a fixed length (2 characters)
|
|
|
|
]]
|
|
|
|
local cat_id = 0
|
|
|
|
local cat_codes = {}
|
|
|
|
local function get_light_category_id(cat)
|
|
|
|
-- If the category id does not already exist generate a new one
|
|
|
|
if not cat_codes[cat] then
|
|
|
|
if cat_id >= 256 then
|
|
|
|
error("Wielded item category limit exceeded, maximum 256 wield categories")
|
|
|
|
end
|
|
|
|
local code = string.format("%02x", cat_id)
|
|
|
|
cat_id = cat_id+1
|
|
|
|
cat_codes[cat] = code
|
|
|
|
end
|
|
|
|
-- If the category id does exist, return it
|
|
|
|
return cat_codes[cat]
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Log an error coming from this mod
|
|
|
|
local function error_log(message, ...)
|
|
|
|
minetest.log("error", "[Wielded Light] " .. (message:format(...)))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Is a node lightable and a liquid capable of flooding some light sources
|
|
|
|
local function is_lightable_liquid(pos)
|
|
|
|
local node = minetest.get_node_or_nil(pos)
|
|
|
|
if not node then return end
|
|
|
|
return lightable_liquids[node.name]
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Check if an entity instance still exists in the world
|
|
|
|
local function is_entity_valid(entity)
|
2021-10-13 02:23:48 +08:00
|
|
|
return entity and (entity.obj:is_player() or (entity.obj:get_luaentity() and entity.obj:get_luaentity().name) or false)
|
2021-07-14 20:05:50 +01:00
|
|
|
end
|
|
|
|
|
2022-06-23 11:49:46 -06:00
|
|
|
-- Check whether a node was registered by the wield_light mod
|
|
|
|
local function is_wieldlight_node(pos_vec)
|
|
|
|
local name = string.sub(minetest.get_node(pos_vec).name, 1, #mod_name)
|
|
|
|
return name == mod_name
|
|
|
|
end
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Get the projected position of an entity based on its velocity, rounded to the nearest block
|
|
|
|
local function entity_pos(obj, offset)
|
2021-07-26 20:16:00 +01:00
|
|
|
local velocity
|
|
|
|
if (minetest.features.direct_velocity_on_players or not obj:is_player()) and obj.get_velocity then
|
|
|
|
velocity = obj:get_velocity()
|
|
|
|
else
|
|
|
|
velocity = obj:get_player_velocity()
|
|
|
|
end
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
return wielded_light.get_light_position(
|
|
|
|
vector.round(
|
|
|
|
vector.add(
|
|
|
|
vector.add(
|
2021-07-26 20:16:00 +01:00
|
|
|
offset or { x=0, y=0, z=0 },
|
2021-07-14 20:05:50 +01:00
|
|
|
obj:get_pos()
|
|
|
|
),
|
|
|
|
vector.multiply(
|
2021-07-26 20:16:00 +01:00
|
|
|
velocity or { x=0, y=0, z=0 },
|
2021-07-14 20:05:50 +01:00
|
|
|
velocity_projection
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Add light to active light list and mark position for update
|
|
|
|
local function add_light(pos, id, light_level)
|
|
|
|
if not active_lights[pos] then
|
|
|
|
active_lights[pos] = {}
|
|
|
|
end
|
|
|
|
if active_lights[pos][id] ~= light_level then
|
|
|
|
-- minetest.log("error", "add "..id.." "..pos.." "..tostring(light_level))
|
|
|
|
active_lights[pos][id] = light_level
|
|
|
|
light_recalcs[pos] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Remove light from active light list and mark position for update
|
|
|
|
local function remove_light(pos, id)
|
|
|
|
if not active_lights[pos] then return end
|
|
|
|
-- minetest.log("error", "rem "..id.." "..pos)
|
|
|
|
active_lights[pos][id] = nil
|
|
|
|
minetest.after(removal_delay, function ()
|
|
|
|
light_recalcs[pos] = true
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Track an entity's position and update its light, will be called on every update step
|
|
|
|
local function update_entity(entity)
|
|
|
|
local pos = entity_pos(entity.obj, entity.offset)
|
|
|
|
local pos_str = pos and minetest.pos_to_string(pos)
|
2021-07-26 20:16:00 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- If the position has changed, remove the old light and mark the entity for update
|
|
|
|
if entity.pos and pos_str ~= entity.pos then
|
|
|
|
entity.update = true
|
|
|
|
for id,_ in pairs(entity.items) do
|
|
|
|
remove_light(entity.pos, id)
|
|
|
|
end
|
|
|
|
end
|
2021-07-26 20:16:00 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Update the recorded position
|
|
|
|
entity.pos = pos_str
|
2021-07-26 20:16:00 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- If the position is still loaded, pump the timer up so it doesn't get removed
|
|
|
|
if pos then
|
|
|
|
-- If the entity is marked for an update, add the light in the position if it emits light
|
|
|
|
if entity.update then
|
|
|
|
for id, item in pairs(entity.items) do
|
|
|
|
if item.level > 0 and not (item.floodable and is_lightable_liquid(pos)) then
|
|
|
|
add_light(pos_str, id, item.level)
|
|
|
|
else
|
|
|
|
remove_light(pos_str, id)
|
2018-09-04 22:13:37 +02:00
|
|
|
end
|
2018-01-13 15:19:06 +01:00
|
|
|
end
|
|
|
|
end
|
2022-06-23 11:49:46 -06:00
|
|
|
end
|
|
|
|
if active_lights[pos_str] then
|
|
|
|
if is_wieldlight_node(pos) then
|
|
|
|
minetest.get_node_timer(pos):start(cleanup_interval)
|
|
|
|
end
|
2018-01-13 15:19:06 +01:00
|
|
|
end
|
2021-07-14 20:05:50 +01:00
|
|
|
entity.update = false
|
2018-01-13 15:19:06 +01:00
|
|
|
end
|
|
|
|
|
2022-06-23 11:49:46 -06:00
|
|
|
|
|
|
|
-- Save the original nodes timer if it has one
|
|
|
|
local function save_timer(pos_vec)
|
|
|
|
local timer = minetest.get_node_timer(pos_vec)
|
|
|
|
if timer:is_started() then
|
|
|
|
local meta = minetest.get_meta(pos_vec)
|
|
|
|
meta:set_float("saved_timer_timeout", timer:get_timeout())
|
|
|
|
meta:set_float("saved_timer_elapsed", timer:get_elapsed())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Restore the original nodes timer if it had one
|
|
|
|
local function restore_timer(pos_vec)
|
|
|
|
local meta = minetest.get_meta(pos_vec)
|
|
|
|
local timeout = meta:get_float("saved_timer_timeout")
|
|
|
|
if timeout > 0 then
|
|
|
|
local elapsed = meta:get_float("saved_timer_elapsed")
|
|
|
|
local timer = minetest.get_node_timer(pos_vec)
|
|
|
|
timer:set(timeout, elapsed)
|
|
|
|
meta:set_string("saved_timer_timeout","")
|
|
|
|
meta:set_string("saved_timer_elapsed","")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Replace a lighting node with its original counterpart
|
|
|
|
local function reset_lighting_node(pos)
|
|
|
|
local existing_node = minetest.get_node(pos)
|
|
|
|
local lighting_node = wielded_light.get_lighting_node(existing_node.name)
|
|
|
|
if not lighting_node then
|
2018-05-08 16:52:08 +02:00
|
|
|
return
|
|
|
|
end
|
2022-06-23 11:49:46 -06:00
|
|
|
minetest.swap_node(pos, { name = lighting_node.node,param2 = existing_node.param2 })
|
|
|
|
restore_timer(pos)
|
2021-07-14 20:05:50 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Will be run once the node timer expires
|
|
|
|
local function cleanup_timer_callback(pos, elapsed)
|
|
|
|
local pos_str = minetest.pos_to_string(pos)
|
|
|
|
local lights = active_lights[pos_str]
|
|
|
|
-- If no active lights for this position, remove itself
|
|
|
|
if not lights then
|
|
|
|
reset_lighting_node(pos)
|
|
|
|
else
|
|
|
|
-- Clean up any tracked entities for this position that no longer exist
|
|
|
|
for id,_ in pairs(lights) do
|
|
|
|
local uid = string.sub(id,3)
|
|
|
|
local entity = tracked_entities[uid]
|
|
|
|
if not is_entity_valid(entity) then
|
|
|
|
remove_light(pos_str, id)
|
|
|
|
end
|
2021-06-20 16:32:29 +02:00
|
|
|
end
|
2021-07-14 20:05:50 +01:00
|
|
|
minetest.get_node_timer(pos):start(cleanup_interval)
|
2021-06-20 16:32:29 +02:00
|
|
|
end
|
2021-07-14 20:05:50 +01:00
|
|
|
end
|
2018-05-08 16:52:08 +02:00
|
|
|
|
2022-06-23 11:49:46 -06:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Recalculate the total light level for a given position and update the light level there
|
|
|
|
local function recalc_light(pos)
|
|
|
|
-- If not in active lights list we can't do anything
|
|
|
|
if not active_lights[pos] then return end
|
2018-05-08 16:52:08 +02:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Calculate the light level of the node
|
|
|
|
local any_light = false
|
|
|
|
local max_light = 0
|
|
|
|
for id, light_level in pairs(active_lights[pos]) do
|
|
|
|
any_light = true
|
|
|
|
if light_level > max_light then
|
|
|
|
max_light = light_level
|
|
|
|
end
|
2018-05-08 16:52:08 +02:00
|
|
|
end
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Convert the position back to a vector
|
|
|
|
local pos_vec = minetest.string_to_pos(pos)
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- If no items in this position, delete it from the list and remove any light node
|
|
|
|
if not any_light then
|
|
|
|
active_lights[pos] = nil
|
|
|
|
reset_lighting_node(pos_vec)
|
|
|
|
return
|
|
|
|
end
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- If no light in this position remove any light node
|
|
|
|
if max_light == 0 then
|
|
|
|
reset_lighting_node(pos_vec)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Limit the light level
|
|
|
|
max_light = math.min(max_light, minetest.LIGHT_MAX)
|
|
|
|
|
|
|
|
-- Get the current light level in this position
|
2022-06-23 11:49:46 -06:00
|
|
|
local existing_node = minetest.get_node(pos_vec)
|
|
|
|
local name = existing_node.name
|
2021-07-14 20:05:50 +01:00
|
|
|
local old_value = wielded_light.level_of_lighting_node(name) or 0
|
|
|
|
|
|
|
|
-- If the light level has changed, set the coresponding light node and initiate the cleanup timer
|
|
|
|
if old_value ~= max_light then
|
2021-08-10 19:50:26 +01:00
|
|
|
local node_name
|
|
|
|
if lightable_nodes[name] then
|
|
|
|
node_name = name
|
|
|
|
elseif lighting_nodes[name] then
|
|
|
|
node_name = lighting_nodes[name].node
|
|
|
|
end
|
|
|
|
if node_name then
|
2022-06-23 11:49:46 -06:00
|
|
|
if not is_wieldlight_node(pos_vec) then
|
|
|
|
save_timer(pos_vec)
|
|
|
|
end
|
|
|
|
|
2021-08-10 19:50:26 +01:00
|
|
|
minetest.swap_node(pos_vec, {
|
2022-06-23 11:49:46 -06:00
|
|
|
name = lightable_nodes[node_name][max_light],
|
|
|
|
param2 = existing_node.param2
|
2021-08-10 19:50:26 +01:00
|
|
|
})
|
|
|
|
minetest.get_node_timer(pos_vec):start(cleanup_interval)
|
|
|
|
else
|
|
|
|
active_lights[pos] = nil
|
|
|
|
end
|
2018-09-04 22:52:15 +02:00
|
|
|
end
|
2018-01-10 10:04:36 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local timer = 0
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Will be run on every global step
|
|
|
|
local function global_timer_callback(dtime)
|
|
|
|
-- Only run once per update interval, global step will be called much more often than that
|
2018-01-10 10:04:36 +01:00
|
|
|
timer = timer + dtime;
|
|
|
|
if timer < update_interval then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
timer = 0
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Run all custom player callbacks for each player
|
|
|
|
local connected_players = minetest.get_connected_players()
|
|
|
|
for _,callback in pairs(update_player_callbacks) do
|
|
|
|
for _, player in pairs(connected_players) do
|
|
|
|
callback(player)
|
|
|
|
end
|
|
|
|
end
|
2018-05-12 14:33:52 -07:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Run all custom callbacks
|
|
|
|
for _,callback in pairs(update_callbacks) do
|
|
|
|
callback()
|
2018-01-10 10:04:36 +01:00
|
|
|
end
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Look at each tracked entity and update its position
|
|
|
|
for uid, entity in pairs(tracked_entities) do
|
|
|
|
if is_entity_valid(entity) then
|
|
|
|
update_entity(entity)
|
|
|
|
else
|
|
|
|
-- If the entity no longer exists, stop tracking it
|
|
|
|
tracked_entities[uid] = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Recalculate light levels
|
|
|
|
for pos,_ in pairs(light_recalcs) do
|
|
|
|
recalc_light(pos)
|
|
|
|
end
|
|
|
|
light_recalcs = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
--- Shining API ---
|
|
|
|
wielded_light = {}
|
|
|
|
|
|
|
|
-- Registers a callback to be called every time the update interval is passed
|
|
|
|
function wielded_light.register_lightstep(callback)
|
|
|
|
table.insert(update_callbacks, callback)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Registers a callback to be called for each player every time the update interval is passed
|
|
|
|
function wielded_light.register_player_lightstep(callback)
|
|
|
|
table.insert(update_player_callbacks, callback)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Returns the node name for a given light level
|
|
|
|
function wielded_light.lighting_node_of_level(light_level, prefix)
|
|
|
|
return mod_name..":"..(prefix or "")..light_level
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Gets the light level for a given node name, inverse of lighting_node_of_level
|
|
|
|
function wielded_light.level_of_lighting_node(node_name)
|
|
|
|
local lighting_node = wielded_light.get_lighting_node(node_name)
|
|
|
|
if lighting_node then
|
|
|
|
return lighting_node.level
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Check if a node name is one of the wielded light nodes
|
|
|
|
function wielded_light.get_lighting_node(node_name)
|
|
|
|
return lighting_nodes[node_name]
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Register any node as lightable, register all light level variations for it
|
|
|
|
function wielded_light.register_lightable_node(node_name, property_overrides, custom_prefix)
|
|
|
|
-- Node name must be string
|
|
|
|
if type(node_name) ~= "string" then
|
|
|
|
error_log("You must provide a node name to be registered as lightable, '%s' given.", type(node_name))
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Node must already be registered
|
|
|
|
local original_definition = minetest.registered_nodes[node_name]
|
|
|
|
if not original_definition then
|
|
|
|
error_log("The node '%s' cannot be registered as lightable because it does not exist.", node_name)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Decide the prefix for the lighting node
|
|
|
|
local prefix = custom_prefix or node_name:gsub(":", "_", 1, true) .. "_"
|
|
|
|
if lighting_prefixes[prefix] then
|
|
|
|
error_log("The lighting prefix '%s' cannot be used for '%s' as it is already used for '%s'.", prefix, node_name, lighting_prefixes[prefix])
|
|
|
|
return
|
|
|
|
end
|
|
|
|
lighting_prefixes[prefix] = node_name
|
|
|
|
|
|
|
|
-- Default for property overrides
|
|
|
|
if not property_overrides then property_overrides = {} end
|
|
|
|
|
|
|
|
-- Copy the node definition and provide required settings for a lighting node
|
|
|
|
local new_definition = table.copy(original_definition)
|
|
|
|
new_definition.on_timer = cleanup_timer_callback
|
|
|
|
new_definition.paramtype = "light"
|
|
|
|
new_definition.mod_origin = mod_name
|
|
|
|
new_definition.groups = new_definition.groups or {}
|
|
|
|
new_definition.groups.not_in_creative_inventory = 1
|
2022-06-23 11:49:46 -06:00
|
|
|
-- Make sure original node is dropped if a lit node is dug
|
|
|
|
if not new_definition.drop then
|
|
|
|
new_definition.drop = node_name
|
|
|
|
end
|
2021-07-14 20:05:50 +01:00
|
|
|
|
|
|
|
-- Allow any properties to be overridden on registration
|
|
|
|
for prop, val in pairs(property_overrides) do
|
|
|
|
new_definition[prop] = val
|
|
|
|
end
|
|
|
|
|
|
|
|
-- If it's a liquid, we need to stop it flowing
|
|
|
|
if new_definition.groups.liquid then
|
|
|
|
new_definition.liquid_range = 0
|
|
|
|
lightable_liquids[node_name] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Register the lighting nodes
|
|
|
|
lightable_nodes[node_name] = {}
|
|
|
|
for i=1, minetest.LIGHT_MAX do
|
|
|
|
local lighting_node_name = wielded_light.lighting_node_of_level(i, prefix)
|
2021-07-26 20:16:00 +01:00
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Index for quick finding later
|
|
|
|
lightable_nodes[node_name][i] = lighting_node_name
|
|
|
|
lighting_nodes[lighting_node_name] = {
|
|
|
|
node = node_name,
|
|
|
|
level = i
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Copy the base definition and apply the light level
|
|
|
|
local level_definition = table.copy(new_definition)
|
|
|
|
level_definition.light_source = i
|
|
|
|
|
|
|
|
-- If it's a liquid, we need to stop it replacing itself with the original
|
|
|
|
if level_definition.groups.liquid then
|
|
|
|
level_definition.liquid_alternative_source = lighting_node_name
|
|
|
|
level_definition.liquid_alternative_flowing = lighting_node_name
|
|
|
|
end
|
|
|
|
|
2021-07-16 11:49:36 +01:00
|
|
|
minetest.register_node(":"..lighting_node_name, level_definition)
|
2021-07-14 20:05:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Check if node can have a wielded light node placed in it
|
|
|
|
function wielded_light.is_lightable_node(node_pos)
|
|
|
|
local name = minetest.get_node(node_pos).name
|
|
|
|
if lightable_nodes[name] then
|
|
|
|
return true
|
|
|
|
elseif wielded_light.get_lighting_node(name) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Gets the closest position to pos that's a lightable node
|
|
|
|
function wielded_light.get_light_position(pos)
|
|
|
|
local around_vector = {
|
|
|
|
{x=0, y=0, z=0},
|
|
|
|
{x=0, y=1, z=0}, {x=0, y=-1, z=0},
|
|
|
|
{x=1, y=0, z=0}, {x=-1, y=0, z=0},
|
2022-06-23 11:49:46 -06:00
|
|
|
{x=0, y=0, z=1}, {x=0, y=0, z=-1},
|
2021-07-14 20:05:50 +01:00
|
|
|
}
|
|
|
|
for _, around in ipairs(around_vector) do
|
|
|
|
local light_pos = vector.add(pos, around)
|
|
|
|
if wielded_light.is_lightable_node(light_pos) then
|
|
|
|
return light_pos
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Gets the emitted light level of a given item name
|
|
|
|
function wielded_light.get_light_def(item_name)
|
|
|
|
-- Invalid item? No light
|
|
|
|
if not item_name or item_name == "" then
|
|
|
|
return 0, false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- If the item is cached return the cached level
|
|
|
|
local cached_definition = shiny_items[item_name]
|
|
|
|
if cached_definition then
|
|
|
|
return cached_definition.level, cached_definition.floodable
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Get the item definition
|
|
|
|
local stack = ItemStack(item_name)
|
|
|
|
local itemdef = stack:get_definition()
|
|
|
|
|
|
|
|
-- If invalid, no light
|
|
|
|
if not itemdef then
|
|
|
|
return 0, false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Get the light level of an item from its definition
|
|
|
|
-- Reduce the light level by level_delta - original functionality
|
|
|
|
-- Limit between 0 and the max light level
|
|
|
|
return math.min(math.max((itemdef.light_source or 0) - level_delta, 0), minetest.LIGHT_MAX), itemdef.floodable
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Register an item as shining
|
|
|
|
function wielded_light.register_item_light(item_name, light_level, floodable)
|
|
|
|
if shiny_items[item_name] then
|
|
|
|
if light_level then
|
|
|
|
shiny_items[item_name].level = light_level
|
|
|
|
end
|
|
|
|
if floodable ~= nil then
|
|
|
|
shiny_items[item_name].floodable = floodable
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if floodable == nil then
|
|
|
|
local stack = ItemStack(item_name)
|
|
|
|
local itemdef = stack:get_definition()
|
|
|
|
floodable = itemdef.floodable
|
|
|
|
end
|
|
|
|
shiny_items[item_name] = {
|
|
|
|
level = light_level,
|
|
|
|
floodable = floodable or false
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Mark an item as floodable or not
|
|
|
|
function wielded_light.register_item_floodable(item_name, floodable)
|
|
|
|
if floodable == nil then floodable = true end
|
|
|
|
if shiny_items[item_name] then
|
|
|
|
shiny_items[item_name].floodable = floodable
|
|
|
|
else
|
|
|
|
local calced_level = wielded_light.get_light_def(item_name)
|
|
|
|
shiny_items[item_name] = {
|
|
|
|
level = calced_level,
|
|
|
|
floodable = floodable
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Keep track of an item entity. Should be called once for an item
|
|
|
|
function wielded_light.track_item_entity(obj, cat, item)
|
2021-07-16 11:49:36 +01:00
|
|
|
if not is_entity_valid({ obj=obj }) then return end
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
local light_level, light_is_floodable = wielded_light.get_light_def(item)
|
|
|
|
-- If the item does not emit light do not track it
|
|
|
|
if light_level <= 0 then return end
|
|
|
|
|
|
|
|
-- Generate the uid for the item and the id for the light category
|
|
|
|
local uid = tostring(obj)
|
|
|
|
local id = get_light_category_id(cat)..uid
|
|
|
|
|
|
|
|
-- Create the main tracking object for this item instance if it does not already exist
|
|
|
|
if not tracked_entities[uid] then
|
|
|
|
tracked_entities[uid] = { obj=obj, items={}, update = true }
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Create the item tracking object for this item + category
|
|
|
|
tracked_entities[uid].items[id] = { level=light_level, floodable=light_is_floodable }
|
|
|
|
|
|
|
|
-- Add the light in on creation so it's immediate
|
|
|
|
local pos = entity_pos(obj)
|
|
|
|
local pos_str = pos and minetest.pos_to_string(pos)
|
|
|
|
if pos_str then
|
|
|
|
if not (light_is_floodable and is_lightable_liquid(pos)) then
|
|
|
|
add_light(pos_str, id, light_level)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
tracked_entities[uid].pos = pos_str
|
|
|
|
end
|
|
|
|
|
|
|
|
-- A player's light should appear near their head not their feet
|
|
|
|
local player_height_offset = { x=0, y=1, z=0 }
|
|
|
|
|
|
|
|
-- Keep track of a user / player entity. Should be called as often as the user updates
|
|
|
|
function wielded_light.track_user_entity(obj, cat, item)
|
|
|
|
-- Generate the uid for the player and the id for the light category
|
|
|
|
local uid = tostring(obj)
|
|
|
|
local id = get_light_category_id(cat)..uid
|
|
|
|
|
|
|
|
-- Create the main tracking object for this player instance if it does not already exist
|
|
|
|
if not tracked_entities[uid] then
|
|
|
|
tracked_entities[uid] = { obj=obj, items={}, offset = player_height_offset, update = true }
|
|
|
|
end
|
|
|
|
|
|
|
|
local tracked_entity = tracked_entities[uid]
|
|
|
|
local tracked_item = tracked_entity.items[id]
|
|
|
|
|
|
|
|
-- If the item being tracked for the player changes, update the item tracking object for this item + category
|
|
|
|
if not tracked_item or tracked_item.item ~= item then
|
|
|
|
local light_level, light_is_floodable = wielded_light.get_light_def(item)
|
|
|
|
tracked_entity.items[id] = { level=light_level, item=item, floodable=light_is_floodable }
|
|
|
|
tracked_entity.update = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Setup --
|
|
|
|
|
|
|
|
-- Wielded item shining globalstep
|
|
|
|
minetest.register_globalstep(global_timer_callback)
|
2018-01-13 15:19:06 +01:00
|
|
|
|
2018-05-08 16:52:08 +02:00
|
|
|
-- Dropped item on_step override
|
|
|
|
-- https://github.com/minetest/minetest/issues/6909
|
|
|
|
local builtin_item = minetest.registered_entities["__builtin:item"]
|
2019-01-10 07:40:15 +01:00
|
|
|
local item = {
|
2020-12-03 20:29:56 +01:00
|
|
|
on_step = function(self, dtime, ...)
|
|
|
|
builtin_item.on_step(self, dtime, ...)
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Register an item once for tracking
|
|
|
|
-- If it's already being tracked, exit
|
|
|
|
if self.wielded_light then return end
|
|
|
|
self.wielded_light = true
|
|
|
|
local stack = ItemStack(self.itemstring)
|
|
|
|
local item_name = stack:get_name()
|
|
|
|
wielded_light.track_item_entity(self.object, "item", item_name)
|
2018-05-08 16:52:08 +02:00
|
|
|
end
|
2019-01-10 07:40:15 +01:00
|
|
|
}
|
|
|
|
setmetatable(item, {__index = builtin_item})
|
2018-05-08 16:52:08 +02:00
|
|
|
minetest.register_entity(":__builtin:item", item)
|
|
|
|
|
2021-07-14 20:05:50 +01:00
|
|
|
-- Track a player's wielded item
|
|
|
|
wielded_light.register_player_lightstep(function (player)
|
|
|
|
wielded_light.track_user_entity(player, "wield", player:get_wielded_item():get_name())
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Register helper nodes
|
|
|
|
wielded_light.register_lightable_node("air", nil, "")
|
2023-11-25 18:46:48 +01:00
|
|
|
|
|
|
|
if minetest.get_modpath("default") then
|
|
|
|
wielded_light.register_lightable_node("default:water_source", nil, "water_")
|
|
|
|
wielded_light.register_lightable_node("default:river_water_source", nil, "river_water_")
|
|
|
|
elseif minetest.get_modpath("hades_core") then
|
|
|
|
wielded_light.register_lightable_node("hades_core:water_source", nil, "water_")
|
|
|
|
else
|
|
|
|
error_log("Not running in a supported game, lightable water disabled")
|
|
|
|
end
|
2018-05-08 16:52:08 +02:00
|
|
|
|
2018-01-13 15:19:06 +01:00
|
|
|
---TEST
|
|
|
|
--wielded_light.register_item_light('default:dirt', 14)
|