477 lines
17 KiB
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
|
|
|