naturalslopeslib/register_slopes.lua

477 lines
17 KiB
Lua

-- Default color index conversion: match values for 0-7 and set to 0 for other values.
local function default_color_convert(color_index, to_slope)
if to_slope then
if color_index > 7 then
return 0
else
return color_index
end
else
return color_index
end
end
-- Table of replacement from solid block to slopes.
-- Populated on slope node registration with add_replacement
-- @param colored_source (boolean) true when paramtype2 is color for the source node
-- color_convert is a function(int, int, bool) to convert the color palette values,
-- it is ignored when colored_source is false.
local source_list = {}
local replacements = {}
local replacement_ids = {}
local function add_replacement(source_name, update_chance, chance_factors, fixed_replacements, colored_source, color_to_slope, color_convert)
if not colored_source then
color_convert = nil
elseif color_convert == nil then
color_convert = default_color_convert
end
local subname = string.sub(source_name, string.find(source_name, ':') + 1)
local straight_name = nil
local ic_name = nil
local oc_name = nil
local pike_name = nil
if fixed_replacements then
straight_name = fixed_replacements[1]
ic_name = fixed_replacements[2]
oc_name = fixed_replacements[3]
pike_name = fixed_replacements[4]
else
straight_name = naturalslopeslib.get_straight_slope_name(subname)
ic_name = naturalslopeslib.get_inner_corner_slope_name(subname)
oc_name = naturalslopeslib.get_outer_corner_slope_name(subname)
pike_name = naturalslopeslib.get_pike_slope_name(subname)
end
local source_id = minetest.get_content_id(source_name)
local straight_id = minetest.get_content_id(straight_name)
local ic_id = minetest.get_content_id(ic_name)
local oc_id = minetest.get_content_id(oc_name)
local pike_id = minetest.get_content_id(pike_name)
-- Full to slopes
local dest_data = {
source = source_name,
straight = straight_name,
inner = ic_name,
outer = oc_name,
pike = pike_name,
chance = update_chance,
chance_factors = chance_factors,
_colored_source = colored_source,
_color_convert = color_convert
}
local dest_data_id = {
source = source_id,
straight = straight_id,
inner = ic_id,
outer = oc_id,
pike = pike_id,
chance = update_chance,
chance_factors = chance_factors,
_colored_source = colored_source,
_color_convert = color_convert
}
table.insert(source_list, source_name)
-- Block
replacements[source_name] = dest_data
replacement_ids[source_id] = dest_data_id
-- Straight
replacements[straight_name] = dest_data
replacement_ids[straight_id] = dest_data_id
-- Inner
replacements[ic_name] = dest_data
replacement_ids[ic_id] = dest_data_id
-- Outer
replacements[oc_name] = dest_data
replacement_ids[oc_id] = dest_data_id
-- Pike
replacements[pike_name] = dest_data
replacement_ids[pike_id] = dest_data_id
end
--- Get the list of nodes in block shape that have slopes registered for.
function naturalslopeslib.list_registered_nodes()
return table.copy(source_list)
end
--- Get replacement description of a node.
-- Contains replacement names in either source or (straight, inner, outer)
-- and chance.
function naturalslopeslib.get_replacement(source_node_name)
return replacements[source_node_name]
end
--- Get replacement description of a node by content id for VoxelManip.
-- Contains replacement ids in either source or (straight, inner, outer)
-- and chance.
function naturalslopeslib.get_replacement_id(source_id)
return replacement_ids[source_id]
end
function naturalslopeslib.get_all_shapes(source_node_name)
if replacements[source_node_name] then
local rp = replacements[source_node_name]
return {rp.source, rp.straight, rp.inner, rp.outer, rp.pike}
else
return {source_node_name}
end
end
function naturalslopeslib.get_all_slopes(source_node_name)
if replacements[source_node_name] then
local rp = replacements[source_node_name]
return {rp.straight, rp.inner, rp.outer, rp.pike}
else
return {}
end
end
--[[ Bounding boxes
--]]
local slope_straight_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5},
},
}
local slope_inner_corner_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0.5, 0.5, 0.5},
{-0.5, 0, -0.5, 0, 0.5, 0},
},
}
local slope_outer_corner_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
{-0.5, 0, 0, 0, 0.5, 0.5},
},
}
local slope_pike_box = {
type = "fixed",
fixed = {
{-0.5, -0.5, -0.5, 0.5, 0, 0.5},
},
}
local function apply_default_slope_def(base_node_name, node_def, slope_group_value)
node_def.paramtype = 'light'
if node_def.paramtype2 == 'color' or node_def.paramtype2 == 'colorfacedir' then
node_def.paramtype2 = 'colorfacedir'
else
node_def.paramtype2 = 'facedir'
end
if not node_def.groups then node_def.groups = {} end
node_def.groups.natural_slope = slope_group_value
if not node_def.groups["family:" .. base_node_name] then
node_def.groups["family:" .. base_node_name] = 1
end
return node_def
end
--- {Private} Update the node definition for a straight slope
local function get_straight_def(base_node_name, node_def)
node_def = apply_default_slope_def(base_node_name, node_def, 1)
local rendering = naturalslopeslib.setting_rendering_mode()
if rendering == 'Smooth' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_straight.obj'
elseif rendering == 'Rough' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_straight_rough.obj'
else
node_def.drawtype = 'nodebox'
node_def.node_box = slope_straight_box
end
node_def.selection_box = slope_straight_box
node_def.collision_box = slope_straight_box
return node_def
end
--- {Private} Update the node definition for an inner corner
local function get_inner_def(base_node_name, node_def)
node_def = apply_default_slope_def(base_node_name, node_def, 2)
local rendering = naturalslopeslib.setting_rendering_mode()
if rendering == 'Smooth' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_inner.obj'
elseif rendering == 'Rough' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_inner_rough.obj'
else
node_def.drawtype = 'nodebox'
node_def.node_box = slope_inner_corner_box
end
node_def.selection_box = slope_inner_corner_box
node_def.collision_box = slope_inner_corner_box
return node_def
end
--- {Private} Update the node definition for an outer corner
local function get_outer_def(base_node_name, node_def)
node_def = apply_default_slope_def(base_node_name, node_def, 3)
local rendering = naturalslopeslib.setting_rendering_mode()
if rendering == 'Smooth' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_outer.obj'
elseif rendering == 'Rough' then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_outer_rough.obj'
else
node_def.drawtype = 'nodebox'
node_def.node_box = slope_outer_corner_box
end
node_def.selection_box = slope_outer_corner_box
node_def.collision_box = slope_outer_corner_box
return node_def
end
--- {Private} Update the node definition for a pike
local function get_pike_def(base_node_name, node_def, update_chance)
node_def = apply_default_slope_def(base_node_name, node_def, 4)
if naturalslopeslib.setting_smooth_rendering() then
node_def.drawtype = 'mesh'
node_def.mesh = 'naturalslopeslib_pike.obj'
else
node_def.drawtype = 'nodebox'
node_def.node_box = slope_pike_box
end
node_def.selection_box = slope_pike_box
node_def.collision_box = slope_pike_box
return node_def
end
-- Expand `tiles` to use the {name = "image"} format for each tile
local function convert_to_expanded_tiles_def(tiles)
if tiles then
for i, tile_def in ipairs(tiles) do
if type(tile_def) == "string" then
tiles[i] = {name = tile_def}
end
end
end
end
function naturalslopeslib.get_slope_defs(base_node_name, def_changes)
local base_node_def = minetest.registered_nodes[base_node_name]
if not base_node_def then
minetest.log("error", "Trying to get slopes for an unknown node " .. (base_node_name or "nil"))
return
end
local full_copy = table.copy(base_node_def)
local changes_copy = table.copy(def_changes)
for key, value in pairs(def_changes) do
if value == "nil" then
full_copy[key] = nil
else
full_copy[key] = value
end
end
-- Handle default drop overrides
if not base_node_def.drop and not def_changes.drop and naturalslopeslib.default_definition.drop_source then
-- If drop is not set and was not reseted
full_copy.drop = base_node_name
end
-- Convert all tile definition to the list format to be able to override properties
if not full_copy.tiles or #full_copy.tiles == 0 then
full_copy.tiles = {{}}
end
convert_to_expanded_tiles_def(full_copy.tiles)
if not changes_copy.tiles or #changes_copy.tiles == 0 then
changes_copy.tiles = {{}}
end
convert_to_expanded_tiles_def(changes_copy.tiles)
local default_tile_changes = table.copy(naturalslopeslib.default_definition.tiles)
if not default_tile_changes or #default_tile_changes == 0 then
default_tile_changes = {{}}
end
convert_to_expanded_tiles_def(default_tile_changes)
-- Make tile changes and default changes the same size
local desired_size = math.max(#full_copy.tiles, #changes_copy.tiles, #default_tile_changes)
while #changes_copy.tiles < desired_size do
table.insert(changes_copy.tiles, table.copy(changes_copy.tiles[#changes_copy.tiles]))
end
while #default_tile_changes < desired_size do
-- no need to copy because defaults won't be alterated
table.insert(default_tile_changes, default_tile_changes[#default_tile_changes])
end
while #full_copy.tiles < desired_size do
table.insert(full_copy.tiles, table.copy(full_copy.tiles[#full_copy.tiles]))
end
-- Apply default tile changes
for i = 1, desired_size, 1 do
if default_tile_changes[i].align_style ~= nil and changes_copy.tiles[i].align_style == nil then
full_copy.tiles[i].align_style = default_tile_changes[i].align_style
end
if default_tile_changes[i].backface_culling ~= nil and changes_copy.tiles[i].backface_culling == nil then
full_copy.tiles[i].backface_culling = default_tile_changes[i].backface_culling
end
if default_tile_changes[i].scale and changes_copy.tiles[i].scale == nil then
full_copy.tiles[i].scale = default_tile_changes[i].scale
end
end
-- Handle default groups
for group, value in pairs(naturalslopeslib.default_definition.groups) do
if not def_changes.groups or def_changes.groups[group] == nil then
full_copy.groups[group] = value
end
end
-- Handle other values
for key, value in pairs(naturalslopeslib.default_definition) do
if key ~= "groups" and key ~= "drop_source" and key ~= "tiles" then
if changes_copy[key] == nil then
if type(value) == "table" then
full_copy[key] = table.copy(value)
else
full_copy[key] = value
end
end
end
end
-- Use a copy because tables are passed by reference. Otherwise the node
-- description is shared and updated after each call
return {
get_straight_def(base_node_name, table.copy(full_copy)),
get_inner_def(base_node_name, table.copy(full_copy)),
get_outer_def(base_node_name, table.copy(full_copy)),
get_pike_def(base_node_name, table.copy(full_copy))
}
end
local function default_factors(factors)
local f = {}
if factors == nil then factors = {} end
for _, name in ipairs({"mapgen", "time", "stomp", "place"}) do
if factors[name] ~= nil then
f[name] = factors[name]
else
f[name] = 1
end
end
return f
end
--- Register slopes from a full block node.
-- @param base_node_name: The full block node name.
-- @param node_desc: base for slope node descriptions.
-- @param update_chance: inverted chance for the node to be updated.
-- @param factors (optional): chance factor for each type.
-- @param color_convert (optional): the function to convert color palettes
-- @return Table of slope names: [straight, inner, outer, pike] or nil on error.
function naturalslopeslib.register_slope(base_node_name, def_changes, update_chance, factors, color_convert)
if not update_chance then
minetest.log('error', 'Natural slopes: chance is not set for node ' .. base_node_name)
return
end
local base_node_def = minetest.registered_nodes[base_node_name]
if not base_node_def then
minetest.log("error", "Trying to register slopes for an unknown node " .. (base_node_name or "nil"))
return
end
local chance_factors = default_factors(factors)
-- Get new definitions
local subname = string.sub(base_node_name, string.find(base_node_name, ':') + 1)
local slope_names = {
naturalslopeslib.get_straight_slope_name(subname),
naturalslopeslib.get_inner_corner_slope_name(subname),
naturalslopeslib.get_outer_corner_slope_name(subname),
naturalslopeslib.get_pike_slope_name(subname)
}
local slope_defs = naturalslopeslib.get_slope_defs(base_node_name, def_changes)
-- Register all slopes
local stomp_factor = naturalslopeslib.setting_stomp_factor()
for i, name in ipairs(slope_names) do
minetest.register_node(name, slope_defs[i])
-- Register walk listener
if naturalslopeslib.setting_enable_shape_on_walk() then
poschangelib.register_stomp(name,
naturalslopeslib.update_shape_on_walk,
{name = name .. '_upd_shape',
chance = update_chance * chance_factors.stomp * stomp_factor, priority = 500})
end
end
-- Register replacements
local colored = base_node_def.paramtype2 == "color"
add_replacement(base_node_name, update_chance, chance_factors, slope_names, colored, color_convert)
-- Enable on walk update for base node
if naturalslopeslib.setting_enable_shape_on_walk() and not naturalslopeslib.setting_revert() then
poschangelib.register_stomp(base_node_name,
naturalslopeslib.update_shape_on_walk,
{name = base_node_name .. '_upd_shape',
chance = update_chance * chance_factors.stomp * stomp_factor, priority = 500})
end
-- Enable surface update
local time_factor = naturalslopeslib.setting_time_factor()
if naturalslopeslib.setting_enable_surface_update() and not naturalslopeslib.setting_revert() then
twmlib.register_twm({
nodenames = {base_node_name, slope_defs[1], slope_defs[2], slope_defs[3], slope_defs[4]},
chance = update_chance * chance_factors.time * time_factor,
action = naturalslopeslib.update_shape
})
end
-- Enable revert LBM
if naturalslopeslib.setting_revert() then
minetest.register_lbm({
label = 'naturalslopes_revert',
name = minetest.get_current_modname() .. ':revert_slopes_' .. string.gsub(base_node_name, ':', '_'),
nodenames = slope_names,
run_at_every_load = true,
action = function (pos, node)
minetest.swap_node(pos, { name = base_node_name })
end
})
end
return naturalslopeslib.get_replacement(base_node_name)
end
--- Add a slopping behaviour to existing nodes.
function naturalslopeslib.set_slopes(base_node_name, straight_name, inner_name, outer_name, pike_name, update_chance, factors, color_convert)
-- Defensive checks
if not minetest.registered_nodes[base_node_name] then
if not base_node_name then
minetest.log('error', 'naturalslopeslib.set_slopes failed: base node_name is nil.')
else
minetest.log('error', 'naturalslopeslib.set_slopes failed: ' .. base_node_name .. ' is not registered.')
end
return
end
if not minetest.registered_nodes[straight_name]
or not minetest.registered_nodes[inner_name]
or not minetest.registered_nodes[outer_name]
or not minetest.registered_nodes[pike_name] then
minetest.log('error', 'naturalslopeslib.set_slopes failed: one of the slopes for ' .. base_node_name .. ' is not registered.')
return
end
if not update_chance then
minetest.log('error', 'Natural slopes: chance is not set for node ' .. base_node_name)
return
end
local chance_factors = default_factors(factors)
-- Set shape update data
local slope_names = {straight_name, inner_name, outer_name, pike_name}
local colored = minetest.registered_nodes[base_node_name].paramtype2 == "color"
add_replacement(base_node_name, update_chance, chance_factors, slope_names, colored, color_convert)
-- Set surface update
if naturalslopeslib.setting_enable_surface_update() and not naturalslopeslib.setting_revert() then
local time_factor = naturalslopeslib.setting_time_factor()
twmlib.register_twm({
nodenames = {base_node_name, straight_name, inner_name, outer_name, pike_name},
chance = update_chance * chance_factors.time * time_factor,
action = naturalslopeslib.update_shape
})
end
-- Set walk listener for the 5 nodes
if naturalslopeslib.setting_enable_shape_on_walk() and not naturalslopeslib.setting_revert() then
local stomp_factor = naturalslopeslib.setting_stomp_factor()
local stomp_desc = {name = base_node_name .. '_upd_shape',
chance = update_chance * chance_factors.stomp * stomp_factor, priority = 500}
poschangelib.register_stomp(base_node_name, naturalslopeslib.update_shape_on_walk, stomp_desc)
for i, name in pairs(slope_names) do
poschangelib.register_stomp(name, naturalslopeslib.update_shape_on_walk, stomp_desc)
end
end
return naturalslopeslib.get_replacement(base_node_name)
end