-- 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