commit 64273159ddb63c9c544cc877b222f65de3bd0bdc Author: OldCoder Date: Sun Sep 4 22:03:04 2022 -0700 Imported from trollstream "ContentDB" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..50c6736 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,212 @@ +Changelog +========= + +The semantic of version number is 'Major.minor'. Minor updates are retro-compatible, while major updates may break things. + +[1.5] - 2022-03-28 +------------------ + +### Added +- DanRPI added rough slopes 3D models. +- Rough slopes rendering mode, `naturalslopes.setting_rendering_mode` to get selected rendering mode (cubic, smooth or rough). + +### Changed +- `naturalslopes.setting_smooth_rendering` is deprecated in favor of `naturalslopeslib.setting_rendering_mode` (see API documentation). +- Settingtype moved from boolean `naturalslopes_smooth_rendering` to enum `naturalslopes_rendering_mode`. + + +[1.4] - 2022-02-20 +------------------ + +### Added +- `naturalslopeslib.get_all_slopes` to list all shapes except the full block one. +- `Revert` setting to prevent new slopes being generated and turn slopes into full blocks on loading. + +### Fixed +- Documentation about `naturalslopeslib.get_all_shapes` that can be called from any shape. +- Warning about mod.conf name entry. + + +[1.3] - 2021-08-08 +------------------ + +### Added +- `naturalslopeslib.default_definition` and `naturalslopeslib.reset_defaults` to factorize common definition. +- `color_convert` function as parameter to slope definitions to convert color values between 256 and 8 values. + +### Fixed +- `naturalslopeslib.chance_update_shape` and `naturalslopeslib.update_shape` return true only when the node actually changed. +- Keeping color value when switching shape. +- Removing properties with "nil" with `naturalslopeslib.register_slopes`. + + +[1.2] - 2021-02-23 +------------------ + +### Added +- Support for colored nodes (with palette size limitation). +- `naturalslopeslib.propagate_overrides` to remove the need for depedencies. +- Stomp, dig/place and time factor in settings. + +### Fixed +- Timed update triggering. +- Some local variable declaration warning. + + +[1.1] - 2021-02-07 +------------------ + +### Added +- `set_manual_map_generation`. +- `get_slope_defs`. +- Chance factors for different kind of updates. +- Changelog. + +### Changed +- Slope update is done last on map generation. +- `is_free*` returns nil when a neighbour node is not available. +- `is_free_for_erosion` is now deprecated, use `is_free_for_shape_update` instead. +- Edges of areas are updated progressively instead of not at all. + + +[1.0] - 2020-12-30 +------------------ + +Requires Minetest 5. + +### Added +- `get_regular_node_name` from a slope name. +- Ceiling slopes. +- Family group for all slopes. +- `get_all_shapes`. +- Progressive map generation method. +- `register_sloped_stomp`. +- Extensive API documentation. + +### Removed +- Slope nodes for Minetest Game. + +### Changed +- `get_slope_names` return each name indexed by type. +- Namespace change from `naturalslopes` to `naturalslopeslib`. +- Timed update uses `twmlib`. +- Registration is shortened by passing changes from the original definition instead of a full copy. +- Use underscore for domain name in settingstype for consistency with other mods. + + +[0.9] - 2017-08-30 +------------------ + +### Added +- Backface culling for slope nodes. +- Slope node names are returned upon registration. +- `get_slope_names`. + +### Changed +- `default:dirt*` are more likely to be updated. +- `natural_slope` group now indicates the type of slope. + +### Fixed +- Registering slopes outside the mod. + + +[0.8] - 2017-08-25 +------------------ + +### Added +- Reintroduced the smooth rendering, not enabled by default. + +### Changed +- Pick a random surface node instead of an area for timed update. + + +[0.7] - 2017-08-15 +------------------ + +### Added +- `skip` parameter for update_shape. +- `default:clay` and `default:snowblock` slopes. + +### Removed +- Smooth rendering. + +### Changed +- Updated the description of the mod. +- The ABM transformation is replaced by a random area selection from time to time. + +### Fixed +- Some textures for `default` slopes. + + +[0.6] - 2017-08-12 +------------------ + +### Added +- Pike shape for isolated nodes. + +### Changed +- An update is forced when a node is placed above an other one. + +### Fixed +- Prevent slopes to propagate with grass for `default`. + + +[0.5] - 2017-08-09 +------------------ + +### Changed +- Update slope definitions for `default` to drop the full node when slopes are dug. + +### Fixed +- Light for slope nodes. +- Updating a slope to an other one. + + +[0.4] - 2017-08-06 +------------------ + +### Added +- Setting to enable or disable update on map generation. + +### Changed +- Nodes on the edge of an area are not updated instead of being updated incorrectly + +### Fixed +- Enabling slopes for `default` setting. +- Node definition being shared between slope, erasing some distinctions. + + +[0.3] - 2017-08-05 +------------------ + +### Added +- Update chance argument for `register_slopes`. +- `register_slopes` uses a node definition instead of a list of definition parameters. +- Slopes for `default:dirt_with_snow`, `default:dirt_with_dry_grass`, `default:dirt_with_rainforest_litter` +- `get_replacement` and `get_replacement_id` +- Update shape on map generation. + +### Changed +- `default` is now an optional dependency. +- Full grass texture for slopes. + +### Fixed +- Walk advanced settings, chat command. + + +[0.2] - 2017-07-25 +------------------ + +### Added +- Nodes return to their full block shape whene something is above. +- Cubic shape rendering from `stairs`. +- Update shape on walk with `poschangelib v0.1`. + +### Changed +- Use settingstype for options. + + +[0.1] - 2017-07-21 +------------------ + +Initial release. diff --git a/README.md b/README.md new file mode 100644 index 0000000..45b1d5b --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +Natural slopes library +====================== + +* Version 1.5 +* With thanks to all modders, mainly from the stairs mod for study. + +This mod add the ability for nodes to turn into slopes and back to full block +shape by themselves according to the surroundings and the material hardness. It creates +more natural looking landscapes and smoothes movements by removing some edges. + +Slopes can be generated in various ways. Those events can be turned on or off in +settings. The shape is updated on generation, with time, by stepping on edges or +when digging and placing nodes. + +As Minetest main unit is the block, having half-sized blocks can break a lot of things. +Thus half-blocks like slopes are still considered as a single block. A single slope +can turn back to a full node and vice-versa and half-blocks are not considered +buildable upon (they will transform back into full block). + +See naturalslopeslib_api.txt for the documentation of the API. + +## Dependencies + +None, this is a standalone library for other mods to build upon. It doesn't +have any effect by itself. + +## Optional dependencies: + +* `poschangelib`: to enable shape update when walking on nodes +* `twmlib`: to enable update from time to time + +## Source code + +* Written by Karamel +* Licenced under LGPLv2 or, at your discretion, any later version. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +https://www.gnu.org/licenses/licenses.html#LGPL + +## Media + +* Models licensed under CC-0. +* Rough slopes models by DanRPI \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..08b3c20 --- /dev/null +++ b/init.lua @@ -0,0 +1,165 @@ +-- Global namespace for functions +naturalslopeslib = { + _register_on_generated = true, + _propagate_overrides = false, + default_definition = {} -- initialized below +} + +local poschangelib_available = false +local twmlib_available = false +for _, name in ipairs(minetest.get_modnames()) do + if name == "poschangelib" then + poschangelib_available = true + elseif name == "twmlib" then + twmlib_available = true + end +end + +function naturalslopeslib.reset_defaults() + naturalslopeslib.default_definition = { + drop_source = false, + tiles = {}, + groups = {} + } +end +naturalslopeslib.reset_defaults() + +--- Get the name of the regular node from a slope, or nil. +function naturalslopeslib.get_regular_node_name(node_name) + if string.find(node_name, ":slope_") == nil then + return nil + end + for _, regex in ipairs({"^(.-:)slope_inner_(.*)$", "^(.-:)slope_outer_(.*)$", "^(.-:)slope_pike_(.*)$", "^(.-:)slope_(.*)$"}) do + local match, match2 = string.match(node_name, regex) + if match and minetest.registered_nodes[match .. match2] ~= nil then + return match .. match2 + end + end + return nil +end +--- {Private} Get the default node name for slopes from a subname. +-- For example 'dirt' will be named 'naturalslopeslib:slope_dirt' +-- See naturalslopeslib.get_all_shapes to get the actual node names. +function naturalslopeslib.get_straight_slope_name(subname) + return minetest.get_current_modname() .. ':slope_' .. subname +end +function naturalslopeslib.get_inner_corner_slope_name(subname) + return minetest.get_current_modname() .. ':slope_inner_' .. subname +end +function naturalslopeslib.get_outer_corner_slope_name(subname) + return minetest.get_current_modname() .. ':slope_outer_' .. subname +end +function naturalslopeslib.get_pike_slope_name(subname) + return minetest.get_current_modname() .. ':slope_pike_' .. subname +end + +-- Set functions to get configuration and default values +function naturalslopeslib.setting_enable_surface_update() + if not twmlib_available then return false end + local value = minetest.settings:get_bool('naturalslopeslib_enable_surface_update') + if value == nil then return true end + return value +end +function naturalslopeslib.setting_enable_shape_on_walk() + if not poschangelib_available then return false end + local value = minetest.settings:get_bool('naturalslopeslib_enable_shape_on_walk') + if value == nil then return true end + return value +end +function naturalslopeslib.setting_enable_shape_on_generation() + local value = minetest.settings:get_bool('naturalslopeslib_register_default_slopes') + if value == nil then value = true end + return value +end +function naturalslopeslib.setting_generation_method() + local value = minetest.settings:get('naturalslopeslib_generation_method') + if value == nil then value = 'VoxelManip' end + return value +end +function naturalslopeslib.setting_generation_factor() + return tonumber(minetest.settings:get('naturalslopeslib_update_shape_generate_factor')) or 0.05 +end +function naturalslopeslib.setting_stomp_factor() + return tonumber(minetest.settings:get('naturalslopeslib_update_shape_stomp_factor')) or 1.0 +end +function naturalslopeslib.setting_dig_place_factor() + return tonumber(minetest.settings:get('naturalslopeslib_update_shape_dig_place_factor')) or 1.0 +end +function naturalslopeslib.setting_time_factor() + return tonumber(minetest.settings:get('naturalslopeslib_update_shape_time_factor')) or 1.0 +end +function naturalslopeslib.setting_generation_skip() + return tonumber(minetest.settings:get('naturalslopeslib_update_shape_generate_skip')) or 0 +end +function naturalslopeslib.setting_enable_shape_on_dig_place() + local value = minetest.settings:get_bool('naturalslopeslib_enable_shape_on_dig_place') + if value == nil then value = true end + return value +end +function naturalslopeslib.setting_revert() + local value = minetest.settings:get_bool('naturalslopeslib_revert') + if value == nil then value = false end + return value +end +--- @deprecated, use naturalslopeslib.setting_rendering_mode() +function naturalslopeslib.setting_smooth_rendering() + local mode = naturalslopeslib.setting_rendering_mode() + return (mode == 'Smooth' or mode == 'Rough') +end +function naturalslopeslib.setting_rendering_mode() + local value = minetest.settings:get('naturalslopeslib_rendering_mode') + if value == nil then + -- Backward compatibility, load from naturalslopeslib_smooth_rendering + value = minetest.settings:get_bool('naturalslopeslib_smooth_rendering') + if value == true then + value = 'Smooth' + else + value = 'Cubic' + end + end + if value == nil then value = 'Cubic' end + return value +end + +function naturalslopeslib.set_manual_map_generation() + naturalslopeslib._register_on_generated = false +end + +function naturalslopeslib.propagate_overrides() + if naturalslopeslib._propagate_overrides then + return + end + naturalslopeslib._propagate_overrides = true + local old_override = minetest.override_item + minetest.override_item = function(name, redefinition) + local shapes = naturalslopeslib.get_all_shapes(name) + if #shapes == 1 then + old_override(name, redefinition) + return + end + local slope_redef = table.copy(redefinition) + -- Prevent slopes fixed attribute override + slope_redef.drawtype = nil + slope_redef.nodebox = nil + slope_redef.mesh = nil + slope_redef.selection_box = nil + slope_redef.collision_box = nil + slope_redef.paramtype = nil + if slope_redef.paramtype2 ~= nil then + if slope_redef.paramtype2 == "color" or slope_redef.paramtype2 == "colorfacedir" then + slope_redef.paramtype2 = "colorfacedir" + else + slope_redef.paramtype2 = "facedir" + end + end + old_override(name, redefinition) + for i=2, #shapes, 1 do + old_override(shapes[i], slope_redef) + end + end +end + +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/update_shape.lua") +-- Include registration methods +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/register_slopes.lua") +dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/sloped_stomp.lua") diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..08353f3 --- /dev/null +++ b/mod.conf @@ -0,0 +1,4 @@ +name = naturalslopeslib +description = Library for sloped variation of nodes and automatic shape-shifting according to surroundings +optional_depends = twmlib, poschangelib +min_minetest_version = 5.0 diff --git a/models/naturalslopeslib_inner.obj b/models/naturalslopeslib_inner.obj new file mode 100644 index 0000000..87ab303 --- /dev/null +++ b/models/naturalslopeslib_inner.obj @@ -0,0 +1,27 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v 0.5 0.5 -0.5 +v -0.5 0.5 0.5 +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vn 0 -1 0 +vn 1 0 0 +vn 0 0 -1 +vn -1 0 0 +vn 0 0 1 +vn -1 1 -1 +vn 1 1 -1 +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 3/1/2 2/3/2 6/4/2 5/2/2 +f 2/1/3 1/3/3 6/4/3 +f 1/1/4 4/3/4 7/4/4 +f 4/1/5 3/3/5 5/4/5 7/2/5 +f 6/2/6 1/1/6 5/4/6 +f 5/2/7 1/3/7 7/4/7 diff --git a/models/naturalslopeslib_inner_rough.obj b/models/naturalslopeslib_inner_rough.obj new file mode 100644 index 0000000..92f98f5 --- /dev/null +++ b/models/naturalslopeslib_inner_rough.obj @@ -0,0 +1,43 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v 0.5 0.5 -0.5 +v -0.5 0.5 0.5 +v -0.1 0.2 0.2 +v 0.2 0.2 -0.1 + +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vt 0.3 0.4 +vt 0.7 0.8 + +vn 0 -1 0 +vn 1 0 0 +vn 0 0 -1 +vn -1 0 0 +vn 0 0 1 +vn -1 1 -1 +vn 1 1 -1 +vn 0.5 1 -0.5 +vn -0.5 1 0.5 + +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 3/1/2 2/3/2 6/4/2 5/2/2 +f 2/1/3 1/3/3 6/4/3 +f 1/1/4 4/3/4 7/4/4 +f 4/1/5 3/3/5 5/4/5 7/2/5 + +f 6/4/6 1/3/6 9/6/9 +f 5/2/7 6/4/6 9/6/9 + +f 5/2/7 8/5/8 7/1/7 +f 8/5/8 1/3/6 7/1/7 + +f 5/2/7 9/6/9 8/5/8 +f 9/6/9 1/3/6 8/5/8 diff --git a/models/naturalslopeslib_outer.obj b/models/naturalslopeslib_outer.obj new file mode 100644 index 0000000..f4f7bcb --- /dev/null +++ b/models/naturalslopeslib_outer.obj @@ -0,0 +1,21 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vn 0 -1 0 +vn 1 0 0 +vn 0 1 -1 +vn -1 1 0 +vn 0 0 1 +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 3/1/2 2/3/2 5/2/2 +f 2/1/3 1/3/3 5/4/3 +f 1/1/4 4/3/4 5/4/4 +f 4/1/5 3/3/5 5/4/5 diff --git a/models/naturalslopeslib_outer_rough.obj b/models/naturalslopeslib_outer_rough.obj new file mode 100644 index 0000000..32a07a5 --- /dev/null +++ b/models/naturalslopeslib_outer_rough.obj @@ -0,0 +1,35 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v -0.1 -0.2 0.2 +v 0.2 -0.2 -0.1 + +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vt 0.4 0.3 +vt 0.8 0.7 + +vn 0 -1 0 +vn 1 0 0 +vn 0 1 -1 +vn -1 1 0 +vn 0 0 1 +vn 0.5 1 -0.5 +vn -0.5 1 0.5 + +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 3/1/2 2/3/2 5/2/2 +f 4/1/5 3/3/5 5/4/5 + +f 5/2/2 2/1/3 7/6/7 +f 2/1/3 1/3/3 7/6/7 +f 1/1/4 4/3/4 6/5/6 +f 4/3/4 5/2/2 6/5/6 +f 5/2/2 7/6/7 6/5/6 +f 7/6/7 1/3/3 6/5/6 diff --git a/models/naturalslopeslib_pike.obj b/models/naturalslopeslib_pike.obj new file mode 100644 index 0000000..5b58592 --- /dev/null +++ b/models/naturalslopeslib_pike.obj @@ -0,0 +1,21 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0 0 0 +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vn 0 -1 0 +vn 1 0.5 0 +vn 0 0.5 -1 +vn -1 0.5 0 +vn 0 0.5 1 +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 3/1/2 2/3/2 5/2/2 +f 2/1/3 1/3/3 5/4/3 +f 1/1/4 4/3/4 5/4/4 +f 4/1/5 3/3/5 5/4/5 diff --git a/models/naturalslopeslib_pike_rough.obj b/models/naturalslopeslib_pike_rough.obj new file mode 100644 index 0000000..0599e7c --- /dev/null +++ b/models/naturalslopeslib_pike_rough.obj @@ -0,0 +1,37 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v -0.2 0 0 +v 0.1 0 -0.15 +v 0.1 0 0.15 + +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 + +vt 0.65 0.68 +vt 0.65 0.32 +vt 0.22 0.5 + +vn 0 -1 0 +vn 1 0.5 0 +vn 0 0.5 -1 +vn -1 0.5 0 +vn 0 0.5 1 +vn 0 1 0 + +s off +f 1/2/1 2/4/1 3/3/1 4/1/1 + +f 1/2/4 4/1/4 5/5/6 +f 3/3/2 2/4/2 7/7/6 +f 2/4/3 1/2/3 6/6/6 + +f 4/1/5 3/3/5 7/7/6 +f 1/2/4 5/5/6 6/6/6 +f 4/1/5 7/7/6 5/5/6 +f 2/4/3 6/6/6 7/7/6 +f 5/5/6 7/7/6 6/6/6 diff --git a/models/naturalslopeslib_straight.obj b/models/naturalslopeslib_straight.obj new file mode 100644 index 0000000..608598e --- /dev/null +++ b/models/naturalslopeslib_straight.obj @@ -0,0 +1,22 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v -0.5 0.5 0.5 +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vn 0 -1 0 +vn 1 0 0 +vn -1 0 0 +vn 0 0 1 +vn 0 1 1 +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 1/1/3 4/3/3 6/4/3 +f 3/1/2 2/3/2 5/2/2 +f 2/1/4 1/3/4 6/4/4 5/2/4 +f 4/1/5 3/3/5 5/4/5 6/2/5 diff --git a/models/naturalslopeslib_straight_rough.obj b/models/naturalslopeslib_straight_rough.obj new file mode 100644 index 0000000..44a62b8 --- /dev/null +++ b/models/naturalslopeslib_straight_rough.obj @@ -0,0 +1,37 @@ +# Licensed under CC-0 +v -0.5 -0.5 -0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v -0.5 -0.5 0.5 +v 0.5 0.5 0.5 +v -0.5 0.5 0.5 +v -0.2 0.28 0.12 +v 0.2 -0.28 -0.12 + +vt 0 0 +vt 0 1 +vt 1 0 +vt 1 1 +vt 0.25 0.65 +vt 0.65 0.25 + +vn 0 -1 0 +vn 1 0 0 +vn -1 0 0 +vn 0 0 1 +vn 0 1 1 +vn 0.15 0.4 -0.4 +vn -0.15 0.4 -0.4 + +s off +f 1/1/1 2/3/1 3/4/1 4/2/1 +f 1/1/3 4/3/3 6/4/3 +f 3/1/2 2/3/2 5/2/2 +f 4/1/5 3/3/5 5/4/5 6/2/5 + +f 1/1/3 6/2/3 7/5/5 +f 1/1/7 8/6/5 2/3/7 +f 2/3/2 8/6/5 5/4/2 +f 5/4/7 7/5/5 6/2/7 +f 5/4/6 8/6/5 7/5/5 +f 8/6/5 1/1/6 7/5/5 diff --git a/naturalslopeslib_api.txt b/naturalslopeslib_api.txt new file mode 100644 index 0000000..ddbfdd0 --- /dev/null +++ b/naturalslopeslib_api.txt @@ -0,0 +1,396 @@ +Naturalslopeslib Lua API +======================== + +Table of contents +-- Introduction +-- Usage +-- Definitions +---- ReplacementTable +-- Registration API +---- naturalslopeslib.default_definition +---- naturalslopeslib.reset_defaults +---- naturalslopeslib.register_slope +---- naturalslopeslib.set_slopes +---- naturalslopeslib.register_sloped_stomp +---- naturalslopeslib.propagate_overrides +-- Getters +---- naturalslopeslib.get_slope_defs +---- naturalslopeslib.get_regular_node_name +---- naturalslopeslib.get_replacement +---- naturalslopeslib.get_replacement_id +---- naturalslopeslib.get_all_shapes +---- naturalslopeslib.list_registered_slopes +-- Shape update API +---- naturalslopeslib.is_free_for_shape_update +---- naturalslopeslib.area_is_free_for_shape_update +---- naturalslopeslib.get_replacement_node +---- naturalslopeslib.chance_update_shape +---- naturalslopeslib.update_shape +---- naturalslopeslib.update_shape_on_walk +-- Map generation +---- naturalslopeslib.set_manual_map_generation +---- naturalslopeslib.area_chance_update_shape +---- naturalslopeslib.register_progressive_area_update +-- Settings getters +---- naturalslopeslib.setting_enable_surface_update +---- naturalslopeslib.setting_enable_shape_on_walk +---- naturalslopeslib.setting_enable_shape_on_generation +---- naturalslopeslib.setting_generation_method +---- naturalslopeslib.setting_generation_factor +---- naturalslopeslib.setting_stomp_factor +---- naturalslopeslib.setting_dig_place_factor +---- naturalslopeslib.setting_time_factor +---- naturalslopeslib.setting_generation_skip +---- naturalslopeslib.setting_enable_shape_on_dig_place +---- naturalslopeslib.setting_rendering_mode +-- Chat commands +---- updshape + + +Introduction +------------ + +Naturalslopeslib adds the ability for given nodes to turn into slopes and back to full block shape by itself according to the surroundings and the material hardness. It creates natural landscape and smoothes movements. + +Slopes can be generated in various ways. Those events can be turned on or off in settings. The shape is updated on generation, with time, by stepping on edges or when digging and placing nodes. + +As Minetest main unit is the block, having half-sized blocks can break a lot of things. Thus half-blocks like slopes are still considered as a single block. A single slope can turn back to a full node and vice-versa and half-blocks are not considered buildable upon (they will transform back into full block). + +Usage +----- + +You may register slopes in two ways: letting the mod generating all the stuff or getting the definitions and registering the nodes in the calling mod. With the first method, slope nodes will be registered within naturalslopeslib while with the second method, you can set the slope names from the calling mod. In both cases, the shape update behaviour is handled automatically by the library according to the settings and the availability of poschangelib and twmlib. + +For the first method, just call naturalslopeslib.register_slopes. For example: + + naturalslopeslib.register_slopes("default:dirt") + +You can use `naturalslopeslib.get_all_shapes` to get the name of the slope nodes. + +For the second method, get the slope definitions from naturalslopeslib.get_slope_defs and register the four nodes manually with the desired names with minetest.register_node. When done, call naturalslopeslib.set_slopes to link all the different shapes. + +For example: + + local slope_defs = naturalslopeslib.get_slope_defs("defaut:dirt") + local slope_names = { + "default:dirt_slope", "default:dirt_inner_corner", + "default:dirt_outer_corner", "default:dirt_pike" + } + for i, def in ipairs(slope_defs) do + minetest.register_node(slope_names[i], def) + end + naturalslopeslib.set_slopes("default:dirt", + slope_names[1], slope_names[2], + slope_names[3], slope_names[4], factors) + +Regarding dependencies, the slopes are defined by copying the current definition of the original node. This means that modifications applied to the original node after the slopes are registered are not applied to slopes. If you want the slopes to act like the original nodes no matter what happen to their definition, you can call naturalslopes.propagate_overrides() before or after registering slopes. That way all future call to minetest.override_item (even from other unknown mods) will also apply to slopes silently, removing the need to explicitely define mod requirements. + + +Definitions +----------- + +### ReplacementTable + +A table containing references to various shape. The type of references can either be a name or an internal id. + + { + source = full node reference, + straight = straight slope reference, + inner = inner corner reference, + outer = outer corner reference, + pike = pike/slab reference, + chance = inverted chance of happening, + chance_factors = multiplicator for `chance` for each type + {mapgen = w, stomp = x, time = y, place = z}. + By default all of these factors are 1 (no effect). + } + + +Registration API +---------------- + +### naturalslopeslib.default_definition + +This tables holds default definition override for newly registered slopes. When using register_slope, they are added to def_changes if not already set to avoid copy/pasting a lot of things and automate some behaviours. + + { + drop_source = true/false + -- When true, if no drop is defined, it is set to the source node + -- instead of the slope. + -- For example, digging a dirt slope gives a dirt block (when true) + -- or the dirt slope (when false) + tiles = {{align_style="world"}} + -- As for tile definitions, the list can hold up to 6 values, + -- but only align_style, backface_culling and scale are read. + groups = {not_in_creative_inventory = 1} + -- The list of groups to add with their value. + -- Set a group value to 0 to remove it + other keys + -- Override this key when no change is explicitely set. + } + +Note that changes to default_definitions are not retroactives. If the defaults are changed on the run, all slopes that were previously registered are not affected. + +Good practices are setting the defaults before registering your slopes, then calling naturalslopeslib.reset_defaults() to prevent your settings to effect further declarations. + +### naturalslopeslib.reset_defaults() + +Resets `naturalslopeslib.default_definition` to the less-impacting values. + +These defaults are as follow: + + { + drop_source = false, + tiles = {}, + groups = {} + } + +### naturalslopeslib.register_slope(base_node_name, def_changes, update_chance, factors, color_convert) + +Registers all slope shapes and automatic stomping for a full node. + +* `base_node_name` the full block node name. +* `def_changes` changes to apply from the base node definition. + * All the attributes are copied to the sloped nodes expect those ones which are replaced: + * `drawtype` set to "nodebox" or "mesh" according to the rendering mode + * `nodebox` or `mesh` is replaced + * `selection_box` and `collision_box` matching to the according mesh + * `paramtype` is set to "light", and `paramtype2` to "facedir" or "colorfacedir" + * the group `"natural_slope"` is added (1 = straight, 2 = inner corner, 3 = outer corner, 4 = pike) + * the group `"family:"` is added + * Then they are changed from def_changes. Use `"nil"` string to explicitely erase a value (an not `nil`). +* `update_chance` inverted chance for the node to be updated. +* `factors` optional table for chance factors. By default each factor is 1. +* `color_convert` optional function to convert color palettes (see below). Ignored when paramtype2 from the base node is not `"color"`. By default, it matches the first 8 values, and other color values are set to 0. +* returns ReplacementTable. + +About color palettes: The palette for slopes can only have 8 colors while the original one can hold up to 256 colors. A reduced palette must be provided for nodes which paramtype2 is "color" even if not all colors are used. To control how the palette values are converted, you may pass a function(int, bool) as `color_convert`. When the second parameter is true, the first parameter is the full block color index (from 0 to 255) and it must return an index for the slope color (from 0 to 7). When false the first parameter is the slope color index (from 0 to 7) and it must return an index for the full block color index (from 0 to 255). + +### naturalslopeslib.set_slopes(base_node_name, straight_name, inner_name, outer_name, pike_name, update_chance, factors) + +* Link existing nodes. Same as register_slopes but without registering new nodes. Use it when the shapes are already registered from eslewhere. The node definitions are not changed at all. +* `base_node_name` the full node name. +* `straight_name` the straight slope node name. +* `inner_name` the inner corner node name. +* `outer_name` the outer corner node name. +* `pike_name` the pike/slab node name. +* `update_chance` the inverted chance of happening. +* `factors` optional table for chance factors. By default each factor is 1. +* returns a `ReplacementTable`. + +### naturalslopeslib.register_sloped_stomp(source_node_name, dest_node_name, stomp_desc) + +Register `stomp_desc` from all shapes of `source_node_name` to `dest_node_name`. + +It requires `poschangelib`. If the mod is not activated, this function will do nothing. + +### naturalslopeslib.propagate_overrides() + +Once called, calling `minetest.override_item` from that point will also apply the modifications to the corresponding slopes. Once called, this behaviour cannot be disabled. + + +Getters +------- + +### naturalslopeslib.get_slope_defs(base_node_name, def_changes) + +* `base_node_name` the full block node name. +* `def_changes` changes to apply from the base node definition. + * All the attributes are copied to the sloped nodes expect those ones which are replaced: + * `drawtype` set to "nodebox" or "mesh" according to the rendering mode + * `nodebox` or `mesh` is replaced + * `selection_box` and `collision_box` matching to the according mesh + * `paramtype` is set to "light", and `paramtype2` to "facedir" or "colorfacedir" + * the group "natural_slope" is added (1 = straight, 2 = inner corner, 3 = outer corner, 4 = pike) + * the group "family:" is added + * Then they are changed from `def_changes`. Use `"nil"` string to explicitely erase a value (an not `nil`). +* returns a table of node definitions for straight slope, inner corner, outer corner and pike in that order. + +Warning: The palette for slopes can only have 8 colors while the original one can hold up to 256 colors. A reduced palette must be provided for nodes which paramtype2 is `"color"` even if not all colors are used. + +### naturalslopeslib.get_regular_node_name(slope_node_name) + +* `slope_node_name` a node name. +* returns the name of the regular node (the unslopped one). Nil if it is not a slope node. +* It may be unnaccurate as it checks only if the name follows the internal pattern for slope names. + +### naturalslopeslib.get_replacement(source_node_name) + +* `source_node_name` a registered node name. +* returns a `ReplacementTable`. Nil if no slopes are registered. + +### naturalslopeslib.get_replacement_id(source_id) + +* `source_id` the id of the node. +* returns a `ReplacementTable` with node ids as values. Nil if no slopes are registered. + +### naturalslopeslib.get_all_shapes(source_node_name) + +Returns all variant shape names in a table {block, straight slope, inner corner, outer corner, pike}. Returns {source_node_name} if there are no other shapes for this node. + +* `source_node_name` a node name, can be a full block or a slope. + +### naturalslopeslib.get_all_slopes(source_node_name) + +Returns all sloped variant shape names in a table {straight slope, inner corner, outer corner, pike}. Returns {} if there are no slopes for this node. + +* `source_node_name` a node name, can be a full block or a slope. + +### naturalslopeslib.list_registered_nodes() + +Returns the list of nodes in block shape that have slopes registered for. + + +Shape update API +---------------- + +### naturalslopeslib.is_free_for_shape_update(pos) + +Checks if a node is considered free for defining which shape could be picked. + +* `pos` the position of the node to check (probably a neighbour of a candidate to shape update). +* returns `true` if the node is free, `false` if occupied, `nil` if unknown (not loaded) + +### naturalslopeslib.area_is_free_for_shape_update(area, data, index) + +Checks if a node is considered free for defining which shape could be picked. + +* `area` VoxelArea to use. +* `data` Data from VoxelManip. +* `index` position in area. +* returns `true` if the node is free, `false` if occupied, `nil` if unknown (not loaded) + +Was previously named naturalslopeslib.area_is_free_for_erosion. + +### naturalslopeslib.get_replacement_node(pos, node, [area, data, param2_data]) + +Get the replacement node according to it's surroundings. This function exists in two formats, for a single position or a VoxelArea. + +In both case, it returns the parameters to update the node or nil when no replacement is available. + +* For a single node + * `pos` the position of the node or index with VoxelArea. + * `node` the node at that position. + * returns a node for minetest.set_node. +* For a VoxelArea + * `index` (the `pos` argument) the index within the area. + * `content_id` (the `node` argument) the node at that position or content id with VoxelArea. + * `area` the VoxelArea, nil for single position update (determines which type of the two previous arguments are). + * `data` Data from VoxelManip, nil for single position update. + * `param2_data` param2 data from VoxelManip, nil for single position update. + * Returns a table with id and param2_data. + +### naturalslopeslib.chance_update_shape(pos, node, factor, type) + +Do shape update when random roll passes on a single node. + +* `pos` the position to update. +* `node` the node at pos. +* `factor` optional chance factor, when > 1 it have more chances to happen +* `type` optional update type for chance factors. Either "mapgen", "stomp", "place" or "time". When not set, the chance factor is ignored (as if it is 1). It is cumulative with `factor`. +* returns true if an update was done, false otherwise. + +### naturalslopeslib.update_shape(pos, node) + +Do shape update disregarding chances. + +* `pos` the position to update. +* `node` the node at pos. +* returns true if an update was done, false otherwise. + +### naturalslopeslib.update_shape_on_walk(player, pos, node, desc, trigger_meta) + +Callback for poschangelib, to get the same effect as naturalslopeslib.update_shape. + + +Map generation +-------------- + +These functions allows to tweak the map generation to change the default behaviour. +Which is updating an area on generation after other map generation functions. + +### naturalslopeslib.set_manual_map_generation() + +Disables the default registration to handle the mapgen manually. Once it is called, other mods should take care of handling shape update on generation. Otherwise nothing is done. + +### naturalslopeslib.area_chance_update_shape(minp, maxp, factor, skip, type) + +Massive shape update with VoxelManip. This is the VoxelManip on generation method. + +* `minp` lower boundary of area. +* `mapx` higher boundary of area. +* `factor` Inverted factor for chance (0.1 means 10 times more likely to update) +* `skip` optional random skip, roughfly ignore skip/2 to skip nodes. +* `type` optional update type for chance factors. Either "mapgen", "stomp", "place" or "time". When not set, the chance factor is ignored (as if it is 1). It is cumulative with `factor`. + +### naturalslopeslib.register_progressive_area_update(minp, maxp, factor, skip, type) + +Mark an area to be updated progressively. This is the Progressive on generation method. The area is not updated instantly but added to a list. + +* `minp` lower boundary of area. +* `mapx` higher boundary of area. +* `factor` Inverted factor for chance (0.1 means 10 times more likely to update) +* `skip` optional random skip, roughfly ignore skip/2 to skip nodes. +* `type` optional update type for chance factors. Either "mapgen", "stomp", "place" or "time". When not set, the chance factor is ignored (as if it is 1). It is cumulative with `factor`. + + +Settings getters +---------------- + +These functions get the current settings with the default value if not set. + +### naturalslopeslib.setting_enable_surface_update() + +* Returns `true` or `false`. Always `false` if twmlib is not available. + +### naturalslopeslib.setting_enable_shape_on_walk() + +* Returns `true` or `false`. Always `false` if poschangelib is not available. + +### naturalslopeslib.setting_enable_shape_on_generation() + +* Returns `true` or `false`. It may not reflect the actual behaviour if the default mapgen behaviour was disabled by naturalslopeslib.set_manual_map_generation. + +### naturalslopeslib.setting_generation_method() + +* Returns `"VoxelManip"` or `"Progressive"`. It may not reflect the actual behaviour if the default mapgen behaviour was disabled by naturalslopeslib.set_manual_map_generation. + +### naturalslopeslib.setting_generation_factor() + +* Returns the chance factor for map generation to reflect the landscape age. It is cumulative with the `"mapgen"` chance factor of each node if any is defined. It may not reflect the actual behaviour if the default mapgen behaviour was disabled by naturalslopeslib.set_manual_map_generation. + +### naturalslopeslib.setting_stomp_factor() + +* Returns the chace factor when walking on nodes. It is cumulative with the `"stomp"` chance factor of each node if any is defined. +* This factor is applied upon node registration. + +### naturalslopeslib.setting_dig_place_factor() + +* Returns the chace factor when the neighbouring nodes change. It is cumulative with the `"place"` chance factor of each node if any is defined. + +### naturalslopeslib.setting_time_factor() + +* Returns the chace factor on timed update. It is cumulative with the `"time"` chance factor of each node if any is defined. +* This factor is applied upon node registration. + +### naturalslopeslib.setting_generation_skip() + +* Returns the approximate number of nodes skipped for each node. It may not reflect the actual behaviour if the default mapgen behaviour was disabled by naturalslopeslib.set_manual_map_generation. + +### naturalslopeslib.setting_enable_shape_on_dig_place() + +* Returns `true` or `false`. This setting is read only on startup and may not reflect the actual value if it was changed while the server is running. + +### naturalslopeslib.setting_rendering_mode() + +* Returns `Cubic`, `Smooth` or `Rough`. This setting is read only when registering nodes and may not reflect the actual value if it was changed while the server is running. +* Was previously `naturalslopeslib.setting_smooth_rendering`, that is now deprecated but still available and returns `true` for `Smooth` and `Rough` modes. + + +Chat commands +------------- + +### /updshape + +* requires `server` privilege. +* Force updating the node the player is standing upon. diff --git a/register_slopes.lua b/register_slopes.lua new file mode 100644 index 0000000..404f279 --- /dev/null +++ b/register_slopes.lua @@ -0,0 +1,476 @@ +-- 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 + diff --git a/revert.lua b/revert.lua new file mode 100644 index 0000000..e69de29 diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..97ae205 Binary files /dev/null and b/screenshot.png differ diff --git a/settingtypes.txt b/settingtypes.txt new file mode 100644 index 0000000..bbd7c15 --- /dev/null +++ b/settingtypes.txt @@ -0,0 +1,63 @@ +## Update shape ABM + +# Enable or disable the automatic update of the shape of nodes at the surface (requires twmlib mod). +naturalslopeslib_enable_surface_update (Timed surface update) bool true + + +## Update shape on walk + +# Enable or disable shape update when walking on blocks (requires poschangelib mod). +naturalslopeslib_enable_shape_on_walk (Enable shaping by walking on edges) bool true + + +## Update shape on world generation + +# Enable or disable shape update when the world is generated. +# This will probably put a lot of pressure on the server. Hopefully it doesn't happen that frequently. +naturalslopeslib_enable_shape_on_generate (Shape update on generation) bool true + +# Define which method is used when generating a new area. +# - VoxelManip (default) is the most efficient one, but the area will be available only once it is completely updated and may be resource intensive. This is the preferred method as map generation is optimized not to impact the game. +# - Progressive is way much slower, but the area is available directly in it's untransformed shape. The nodes will be updated one after the other while the game is rather idle. This method is preferred on old single core CPU that cannot benefit from map generation optimization, if the areas are too long to emerge or if you like to see mountains smoothing themselves progressively. +naturalslopeslib_generation_method (Generation method) enum VoxelManip VoxelManip,Progressive + +# Inverted chance factor on generation. The lesser, the more likely the node will be +# generated in their updated shape. 0.1 means 10 times more likely. +naturalslopeslib_update_shape_generate_factor (Generation factor) float 0.05 0 + +# Inverted chance factor when stomping. The lesser, the more likely the node will be +# updated when walking on it. 0.1 means 10 times more likely. +naturalslopeslib_update_shape_stomp_factor (Stomp factor) float 1.0 0 + +# Inverted chance factor on digging/placing. The lesser, the more likely the +# nodes will be updated when the neighbour nodes change. 0.1 means 10 times +# more likely. +naturalslopeslib_update_shape_dig_place_factor (Dig/place factor) float 1.0 0 + +# Inverted chance factor on time. The lesser, the more likely the node will be +# updated with time. 0.1 means 10 times more likely. +naturalslopeslib_update_shape_time_factor (Time factor) float 1.0 0 + +# Skip n nodes when generating an area. The higher, the less nodes can be updated but it dramatically saves server resources. +naturalslopeslib_update_shape_generate_skip (Generation skip nodes) int 0 0 + +## Update shape on dig and place + +# Enable or disable shape update of neighbor nodes when one is placed or updated. +naturalslopeslib_enable_shape_on_dig_place (Shape update on dig and place) bool true + +## Revert changes + +# Prevent new updates and revert slopes to their original block shape when they are loaded. +# Activate this function if you would like to deactivate natural slopes in existing worlds, but leave all the slopes mods loaded to prevent unknown nodes to appearing until all revert is done. +# Revert cannot be undone once blocks are loaded. +naturalslopeslib_revert (Revert) bool false + +## Rendering + +# Rendering mode for slopes. It only affects the visuals. The collision boxes are always matching the cubic style. +# Anything different from Cubic will shows sharp discontinuities between slopes and non-sloped nodes, and will mislead players because the bottom part of a slope is in fact half a node higher than it looks like. +# - Cubic (default) divides blocs in 8 and give stair-like look +# - Smooth uses smooth triangles. +# - Rough shows more accidented slopes. +naturalslopeslib_rendering_mode (Rendering mode) enum Cubic Cubic,Smooth,Rough diff --git a/sloped_stomp.lua b/sloped_stomp.lua new file mode 100644 index 0000000..2fe845e --- /dev/null +++ b/sloped_stomp.lua @@ -0,0 +1,23 @@ +if minetest.get_modpath("poschangelib") == nil then + -- Register the function that does nothing to prevent crashes + naturalslopeslib.register_sloped_stomp = function(source_node_name, dest_node_name, stomp_desc) + end +else + +naturalslopeslib.register_sloped_stomp = function(source_node_name, dest_node_name, stomp_desc) + local source_slopes = naturalslopeslib.get_replacement(source_node_name) + local dest_slopes = naturalslopeslib.get_replacement(dest_node_name) + if source_slopes == nil then + minetest.log("warning", "[register_sloped_stomp] No slope registered for " .. source_node_name) + return + end + if dest_slopes == nil then + minetest.log("warning", "[register_sloped_stomp] No slope registered for " .. dest_node_name) + return + end + for _, slope_type in pairs({"source", "straight", "inner", "outer", "pike"}) do + poschangelib.register_stomp(source_slopes[slope_type], dest_slopes[slope_type], stomp_desc) + end +end + +end -- if mod exists end diff --git a/update_shape.lua b/update_shape.lua new file mode 100644 index 0000000..40f431d --- /dev/null +++ b/update_shape.lua @@ -0,0 +1,577 @@ +--[[ +Describes the falling/eroding effect for slopes +--]] + +--[[ +Pick replacement, node and area +--]] + +-- Manage color for param2 +-- @param replacement the replacement table +-- @param source the name or id of the node being transformed +-- @param dest the name or id of the new shape +-- @param param2_source the param2 value before transformation +-- @param param2_dest the param2 value for facedir (if any) after transformation +-- @return a new param2 value for dest node with color if necessary +local function manage_param2_color(replacement, source, dest, param2_source, param2_dest) + if not replacement._colored_source then + return param2_dest + end + if dest == replacement.source then + -- param2_source will hold a 'color' value + if source == replacement.source then + -- from 'color' to 'color' + return param2_source + else + -- from 'color' to 'colorfacedir' + local new_color_index = replacement._color_convert(param2_source, true) % 8 + return param2_dest + (new_color_index * 32) + end + else + -- param2_source will hold a 'colorfacedir' value + if dest == replacement.source then + -- from 'colorfacedir' to 'color' + local old_color = math.floor(param2_source / 32) + return replacement._color_convert(old_color, false) % 256 + else + -- from 'colorfacedir' to an other 'colorfacedir' + local color = math.floor(param2_source / 32) + return param2_dest + (color * 32) + end + end +end + +--- {Private} Pick a replacement node. +-- @param type The replacement shape. Either 'block', 'straight', 'ic' or 'oc' +-- @param name The name (or id for area) of the node to replace. +-- @param old_param2 The current value of param2 for the node to replace +-- @param param2 Facedir value to orient the new node. +-- @param for_area True when picking for an area, changes the parameter types +-- @return node {name=new_name, param2=new_param2} or area data {id=new_id, param2_data=new_param2} +-- or nil if dest node is not found. +local function pick_replacement(slope_type, name, old_param2, param2, for_area) + local replacement + if for_area then + replacement = naturalslopeslib.get_replacement_id(name) + else + replacement = naturalslopeslib.get_replacement(name) + end + if not replacement then return nil end + local dest_node_name = nil + if slope_type == 'block' and replacement.source then + dest_node_name = replacement.source + elseif slope_type == 'pike' and replacement.pike then + dest_node_name = replacement.pike + elseif slope_type == 'straight' and replacement.straight then + dest_node_name = replacement.straight + elseif slope_type == 'ic' and replacement.inner then + dest_node_name = replacement.inner + elseif slope_type == 'oc' and replacement.outer then + dest_node_name = replacement.outer + end + if dest_node_name then + if param2 == nil then param2 = 0 end + local color_param2 = manage_param2_color(replacement, name, dest_node_name, old_param2, param2) + if for_area then + return {id = dest_node_name, param2_data = color_param2} + else + return {name = dest_node_name, param2 = color_param2} + end + end + return nil +end + + +--[[ +Surrounding checks and get replacement +--]] + +--- Check if a node is considered empty to switch shape. +-- @param pos The position to check +function naturalslopeslib.is_free_for_shape_update(pos) + if not pos then return nil end + local node = minetest.get_node_or_nil(pos) + if node == nil then + return nil + end + return node.name == 'air' +end + +local air_id = minetest.get_content_id('air') +function naturalslopeslib.area_is_free_for_shape_update(area, data, index) + if not area:containsi(index) then + return nil + end + return data[index] == air_id +end +-- Deprecated name +naturalslopeslib.area_is_free_for_erosion = naturalslopeslib.area_is_free_for_shape_update + +--- Get the replacement node according to it's surroundings. +-- @param pos The position of the node or index with VoxelArea. +-- @param node The node at that position or content id with VoxelArea. +-- @param area The VoxelArea, nil for single position update. +-- @param data Data from VoxelManip, nil for single position update. +-- @param param2_data Param2 data from VoxelManip, nil for single position update. +-- @return A node to use with minetest.set_node +-- or a table with id and param2_data if called with an area. +-- Nil if no replacement is found or a neighbour cannot be read. +function naturalslopeslib.get_replacement_node(pos, node, area, data, param2_data) + -- Set functions and data according to update mode: single or VoxelManip + local is_free = nil + local new_pos = nil + local replacement = nil + local node_name = nil -- Either name or id + local for_area = false + local old_param2 = 0 + if area then + for_area = true + is_free = function (at_index) -- always use with new_pos + return naturalslopeslib.area_is_free_for_shape_update(area, data, at_index) + end + new_pos = function(add) -- Get new index from current with add position + local area_pos = area:position(pos) + return area:indexp(vector.add(area_pos, add)) + end + node_name = node + old_param2 = param2_data[pos] + else + is_free = naturalslopeslib.is_free_for_shape_update + new_pos = function(add) return vector.add(pos, add) end + node_name = node.name + old_param2 = node.param2 + end + local is_ground -- ground or ceiling node + local pointing_y = -1 + -- If there's something above and below, get back to full block + local above_free = is_free(new_pos({x=0, y=1, z=0})) + local below_free = is_free(new_pos({x=0, y=-1, z=0})) + if above_free == nil or below_free == nil then + return nil + end + if above_free and not below_free then + is_ground = true + pointing_y = 1 + elseif below_free and not above_free then + is_ground = false + pointing_y = 5 + else -- nothing below and above + return pick_replacement("block", node_name, old_param2, 0, for_area) + end + -- Check blocks around + local airXP = is_free(new_pos({x=1, y=0, z=0})) + if airXP == nil then return nil end + local airXM = is_free(new_pos({x=-1, y=0, z=0})) + if airXM == nil then return nil end + local airZP = is_free(new_pos({x=0, y=0, z=1})) + if airZP == nil then return nil end + local airZM = is_free(new_pos({x=0, y=0, z=-1})) + if airZM == nil then return nil end + local free_neighbors = 0 + for index, free in next, {airXP, airXM, airZP, airZM} do + if free then free_neighbors = free_neighbors + 1 end + end + -- For four or three free neighbors, pike (slab) + if free_neighbors == 4 or free_neighbors == 3 then + local param2 = 0 + if is_ground == false then param2 = 20 end + return pick_replacement("pike", node_name, old_param2, param2, for_area) + -- For two free neighbors + elseif free_neighbors == 2 then + -- at opposite sides, block + local param2 + if (airXP and airXM) or (airZP and airZM) then + return pick_replacement('block', node_name, old_param2, 0, for_area) + -- side by side, outer corner + elseif (airXP and airZP) then + if is_ground then param2 = 3 else param2 = 22 end + return pick_replacement("oc", node_name, old_param2, param2, for_area) + elseif (airXP and airZM) then + if is_ground then param2 = 0 else param2 = 21 end + return pick_replacement("oc", node_name, old_param2, param2, for_area) + elseif (airXM and airZP) then + if is_ground then param2 = 2 else param2 = 23 end + return pick_replacement("oc", node_name, old_param2, param2, for_area) + elseif (airXM and airZM) then + if is_ground then param2 = 1 else param2 = 20 end + return pick_replacement("oc", node_name, old_param2, param2, for_area) + end + -- For one free neighbor, straight slope + elseif free_neighbors == 1 then + local param2 = 0 + if airXP then if is_ground then param2 = 3 else param2 = 15 end + elseif airXM then if is_ground then param2 = 1 else param2 = 17 end + elseif airZP then if is_ground then param2 = 2 else param2 = 6 end + elseif airZM then if is_ground then param2 = 0 else param2 = 8 end + end + return pick_replacement("straight", node_name, old_param2, param2, for_area) + -- For no free neighbor check for a free diagonal for an inner corner + -- or fully surrounded for a rebuild + else + local airXPZP = is_free(new_pos({x=1, y=0, z=1})) + local airXPZM = is_free(new_pos({x=1, y=0, z=-1})) + local airXMZP = is_free(new_pos({x=-1, y=0, z=1})) + local airXMZM = is_free(new_pos({x=-1, y=0, z=-1})) + local param2 + if airXPZP and not airXPZM and not airXMZP and not airXMZM then + if is_ground then param2 = 3 else param2 = 15 end + return pick_replacement("ic", node_name, old_param2, param2, for_area) + elseif not airXPZP and airXPZM and not airXMZP and not airXMZM then + if is_ground then param2 = 0 else param2 = 8 end + return pick_replacement("ic", node_name, old_param2, param2, for_area) + elseif not airXPZP and not airXPZM and airXMZP and not airXMZM then + if is_ground then param2 = 2 else param2 = 23 end + return pick_replacement("ic", node_name, old_param2, param2, for_area) + elseif not airXPZP and not airXPZM and not airXMZP and airXMZM then + if is_ground then param2 = 1 else param2 = 17 end + return pick_replacement("ic", node_name, old_param2, param2, for_area) + else + return pick_replacement('block', node_name, old_param2, 0, for_area) + end + end +end + + +--[[ +Do the replacement +--]] + +-- Do shape update when random roll passes on a single node. +function naturalslopeslib.chance_update_shape(pos, node, factor, type) + if factor == nil then factor = 1 end + local replacement = naturalslopeslib.get_replacement(node.name) + if not replacement then return false end + local chance_factor = 1 + if type == "mapgen" or type == "stomp" or type == "place" or type == "time" then + chance_factor = replacement.chance_factors[type] + end + if (math.random() * (replacement.chance * factor * chance_factor)) < 1.0 then + return naturalslopeslib.update_shape(pos, node) + end + return false +end + +--- Try to update the shape of a node according to it's surroundings. +-- @param pos The position of the node. +-- @param node The node at that position. +-- @return True if the node was updated, false otherwise. +function naturalslopeslib.update_shape(pos, node) + local replacement = naturalslopeslib.get_replacement_node(pos, node) + if replacement and (replacement.name ~= node.name or node.param2 ~= replacement.param2) then + minetest.set_node(pos, replacement) + return true + else + return false + end +end + +local function get_edges(minp, maxp) + -- corner000 = minp + local corner001 = {x = minp.x, y = minp.y, z = maxp.z} + local corner010 = {x = minp.x, y = maxp.y, z = minp.z} + local corner011 = {x = minp.x, y = maxp.y, z = maxp.z} + local corner100 = {x = maxp.x, y = minp.y, z = minp.z} + local corner101 = {x = maxp.x, y = minp.y, z = maxp.z} + local corner110 = {x = maxp.x, y = maxp.y, z = minp.z} + -- corner111 = maxp + return { -- min pos, max pos, normal[x, y ,z] + -- The 8 corners + {minp, minp, {-1, -1, -1}}, + {corner001, corner001, {-1, -1, 1}}, + {corner010, corner010, {-1, 1, -1}}, + {corner011, corner011, {-1, 1, 1}}, + {corner100, corner100, { 1, -1, -1}}, + {corner101, corner101, { 1, -1, 1}}, + {corner110, corner110, { 1, 1, -1}}, + {maxp, maxp, { 1, 1, 1}}, + -- The 8 segments + {{x = minp.x + 1, y = minp.y, z = minp.z}, {x = maxp.x - 1, y = minp.y, z = minp.z}, { 0, -1, -1}}, + {{x = minp.x + 1, y = maxp.y, z = minp.z}, {x = maxp.x - 1, y = maxp.y, z = minp.z}, { 0, 1, -1}}, + {{x = minp.x, y = minp.y + 1, z = minp.z}, {x = minp.x, y = maxp.y - 1, z = minp.z}, {-1, 0, -1}}, + {{x = maxp.x, y = minp.y + 1, z = minp.z}, {x = maxp.x, y = maxp.y - 1, z = minp.z}, { 1, 0, -1}}, + {{x = minp.x + 1, y = minp.y, z = maxp.z}, {x = maxp.x - 1, y = minp.y, z = maxp.z}, { 0, -1, 1}}, + {{x = minp.x + 1, y = maxp.y, z = maxp.z}, {x = maxp.x - 1, y = maxp.y, z = maxp.z}, { 0, 1, 1}}, + {{x = minp.x, y = minp.y + 1, z = maxp.z}, {x = minp.x, y = maxp.y - 1, z = maxp.z}, { -1, 0, 1}}, + {{x = maxp.x, y = minp.y + 1, z = maxp.z}, {x = maxp.x, y = maxp.y - 1, z = maxp.z}, { 1, 0, 1}}, + -- The 6 faces + {{x = minp.x + 1, y = minp.y, z = minp.z + 1}, {x = maxp.x - 1, y = minp.y, z = maxp.z - 1}, { 0, -1, 0}}, + {{x = minp.x + 1, y = maxp.y, z = minp.z + 1}, {x = maxp.x - 1, y = maxp.y, z = maxp.z - 1}, { 0, 1, 0}}, + {{x = minp.x, y = minp.y + 1, z = minp.z + 1}, {x = minp.x, y = maxp.y - 1, z = maxp.z - 1}, { -1, 0, 0}}, + {{x = maxp.x, y = minp.y + 1, z = minp.z + 1}, {x = maxp.x, y = maxp.y - 1, z = maxp.z - 1}, { 1, 0, 0}}, + {{x = minp.x + 1, y = minp.y + 1, z = minp.z}, {x = maxp.x - 1, y = maxp.y - 1, z = minp.z}, { 0, 0, -1}}, + {{x = minp.x + 1, y = minp.y + 1, z = maxp.z}, {x = maxp.x - 1, y = maxp.y - 1, z = maxp.z}, { 0, 0, 1}} + } +end + +--- Massive shape update with VoxelManip. +-- @param minp Lower boundary of area. +-- @param mapx Higher boundary of area. +-- @param factor Factor for chance (0.1 means 10 times more likely to update) +-- @param skip (optional) Don't parse all nodes, skip randomly skip/2 to skip nodes +-- @param progressive_edges (optional) When true, edges are generated progressively (default) +-- @param type (optional) Transformation type for chance factor. +-- at every loop. +function naturalslopeslib.area_chance_update_shape(minp, maxp, factor, skip, progressive_edges, type) + if not skip then skip = 0 end + if progressive_edges == nil then progressive_edges = true end + -- Run on every block + local vm, emin, emax = minetest.get_voxel_manip() + local e1, e2 = vm:read_from_map(minp, maxp) + local area = VoxelArea:new{MinEdge = e1, MaxEdge = e2} + local data = vm:get_data() + local param2_data = vm:get_param2_data() + local i = area:indexp(e1) + local imax = area:indexp(e2) + if progressive_edges then + local edges = get_edges(minp, maxp) + for _, edge in ipairs(edges) do + naturalslopeslib.register_progressive_area_update(edge[1], edge[2], factor, skip, type, {x = edge[3][1], y = edge[3][2], z = edge[3][3]}) + end + end + while i <= imax do + local x = (i-1) % area.ystride + local y = (i-1) % area.zstride + if x == 0 or x == area.ystride - 1 + or y == 0 or y == area.zstride - 1 then + -- Skip edges + else + local replacement = naturalslopeslib.get_replacement_id(data[i]) + if replacement ~= nil then + local chance_factor = 1 + if type == "mapgen" or type == "stomp" or type == "place" or type == "time" then + chance_factor = replacement.chance_factors[type] + end + if math.random() * (replacement.chance * factor * chance_factor) < 1.0 then + local new_data = naturalslopeslib.get_replacement_node(i, data[i], area, data, param2_data) + if new_data then + data[i] = new_data.id + if new_data.param2_data then + param2_data[i] = new_data.param2_data + end + end + end + end + end + i = i + 1 + math.random(skip / 2, skip) + end + vm:set_data(data) + vm:set_param2_data(param2_data) + vm:write_to_map() +end + +naturalslopeslib.progressive_area_updates = {} + +function naturalslopeslib.register_progressive_area_update(minp, maxp, factor, skip, type, edge_normal) + if edge_normal ~= nil or minp.x == maxp.x or minp.y == maxp.y or minp.z == maxp.z then + -- Explicit edge or ignored + table.insert(naturalslopeslib.progressive_area_updates, {minp = minp, maxp = maxp, + factor = factor, skip = skip, i = 1, edge_normal = edge_normal}) + return + end + -- else register the inner cube and all edges + -- The inner cube + table.insert(naturalslopeslib.progressive_area_updates, { + minp = vector.add(minp, 1), + maxp = vector.add(maxp, -1), + factor = factor, skip = skip, i = 1, edge_normal = nil}) + local edges = get_edges(minp, maxp) + -- Register + for _, edge in ipairs(edges) do + table.insert(naturalslopeslib.progressive_area_updates, { + minp = edge[1], maxp = edge[2], + factor = factor, type = type, skip = skip, i = 1, + edge_normal = {x = edge[3][1], y = edge[3][2], z = edge[3][3]} + }) + end +end + + +local function check_area_edges(area) + if area.edge_normal == nil then + return true + end + local edge = area.edge_normal + local pos = area.minp + local requirements = math.abs(edge.x) + math.abs(edge.y) + math.abs(edge.z) + local found = 0 + if edge.x ~= 0 then + if minetest.get_node_or_nil(vector.add(pos, {x = edge.x, y = 0, z = 0})) ~= nil then + found = found + 1 + end + end + if edge.y ~= 0 then + if minetest.get_node_or_nil(vector.add(pos, {x = 0, y = edge.y, z = 0})) ~= nil then + found = found + 1 + end + end + if edge.z ~= 0 then + if minetest.get_node_or_nil(vector.add(pos, {x = 0, y = 0, z = edge.z})) ~= nil then + found = found + 1 + end + end + return found == requirements +end + +local function progressive_area_update(start_time) + if #naturalslopeslib.progressive_area_updates == 0 then + return true + end + if start_time == nil then + start_time = os.clock() + end + -- pick an area around a player at random and process it + local players = minetest.get_connected_players() + local processed_area_index = nil + local alt_processed_area_index = nil + for area_index, area in ipairs(naturalslopeslib.progressive_area_updates) do + for _, p in ipairs(players) do + local minp = area.minp + local maxp = area.maxp + local ppos = p:get_pos() + if ppos.x >= minp.x and ppos.x <= maxp.x and ppos.y >= minp.y and ppos.y <= maxp.y and ppos.z >= minp.z and ppos.z <= maxp.z then + -- Prefer an area in which a player is + if (check_area_edges(area)) then + processed_area_index = area_index + break + end + elseif alt_processed_area_index == nil and ppos.x + 16 >= minp.x and ppos.x - 16 <= maxp.x and ppos.y + 16 >= minp.y and ppos.y - 16 <= maxp.y and ppos.z + 16 >= minp.z and ppos.z - 16 <= maxp.z then + -- Else pick an area near a player + if (check_area_edges(area)) then + alt_processed_area_index = area_index + end + end + end + if processed_area_index ~= nil then + local area = naturalslopeslib.progressive_area_updates[processed_area_index] + end + end + if processed_area_index == nil then + if alt_processed_area_index ~= nil then + processed_area_index = alt_processed_area_index + else + processed_area_index = 1 -- try to reduce the queue as fast as possible + end + end + local area = naturalslopeslib.progressive_area_updates[processed_area_index] + local i = area.i + local y_size = area.maxp.y - area.minp.y + 1 + local z_size = area.maxp.z - area.minp.z + 1 + local imax = y_size * z_size * (area.maxp.x - area.minp.x + 1) + while i <= imax do + local x = math.floor((i - 1) / (y_size * z_size)) + local y = math.floor((i - 1) / z_size) % y_size + local z = (i - 1) % (z_size) + local pos = {x = area.minp.x + x, y = area.minp.y + y, z = area.minp.z + z} + local node = minetest.get_node(pos) + naturalslopeslib.chance_update_shape(pos, node, area.factor, area.type) + i = i + 1 + math.random(area.skip / 2, area.skip) + if (os.clock() - start_time) > 0.1 and i <= imax then + area.i = i + return false + end + end + table.remove(naturalslopeslib.progressive_area_updates, processed_area_index) + if os.clock() - start_time < 0.1 then + progressive_area_update(start_time) + end + return true +end + +local generation_dtime = 0 +local function generation_globalstep(dtime) + generation_dtime = generation_dtime + dtime + if generation_dtime > 0.1 then + progressive_area_update() + generation_dtime = 0 + end +end +minetest.register_globalstep(generation_globalstep) + +minetest.register_on_shutdown(function() + if #naturalslopeslib.progressive_area_updates > 0 then + minetest.log("info", "Processing slope generation for queued areas") + for i, area in ipairs(naturalslopeslib.progressive_area_updates) do + minetest.log("info", (#naturalslopeslib.progressive_area_updates - i + 1) .. " remaining area(s)") + naturalslopeslib.area_chance_update_shape(area.minp, area.maxp, area.factor, area.skip, false, area.type) + end + end +end) + +--[[ +Triggers registration +--]] + +-- Stomp function to get the replacement node name +function naturalslopeslib.update_shape_on_walk(player, pos, node, desc, trigger_meta) + return naturalslopeslib.get_replacement_node(pos, node) +end + +-- Chat command +minetest.register_chatcommand('updshape', { + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then return false, 'Player not found' end + if not minetest.check_player_privs(player, {server=true}) then return false, 'Update shape requires server privileges' end + local pos = player:get_pos() + local node_pos = {['x'] = pos.x, ['y'] = pos.y - 1, ['z'] = pos.z} + local node = minetest.get_node(node_pos) + if naturalslopeslib.update_shape(node_pos, node) then + return true, 'Shape updated.' + end + return false, node.name .. " cannot have it's shape updated." + end, +}) + +-- On generation big update +local function register_on_generation() + if not naturalslopeslib._register_on_generated then + return + end + if naturalslopeslib.setting_enable_shape_on_generation() then + if naturalslopeslib.setting_generation_method() == "Progressive" then + minetest.register_on_generated(function(minp, maxp, seed) + naturalslopeslib.register_progressive_area_update(minp, maxp, naturalslopeslib.setting_generation_factor(), naturalslopeslib.setting_generation_skip(), "mapgen") + end) + else + minetest.register_on_generated(function(minp, maxp, seed) + naturalslopeslib.area_chance_update_shape(minp, maxp, naturalslopeslib.setting_generation_factor(), naturalslopeslib.setting_generation_skip(), true, "mapgen") + end) + end + end +end +if not naturalslopeslib.setting_revert() then + minetest.register_on_mods_loaded(register_on_generation) +end + +--- On place neighbor update +local function on_place_or_dig(pos, force_below) + local function update(pos, x, y, z, factor) + local new_pos = vector.add(pos, vector.new(x, y, z)) + naturalslopeslib.chance_update_shape(new_pos, minetest.get_node(new_pos), factor, "place") + end + -- Update 8 neighbors plus above and below + local place_factor = naturalslopeslib.setting_dig_place_factor() + update(pos, 0, 0, 0, place_factor) + update(pos, 1, 0, 0, place_factor) + update(pos, 0, 0, 1, place_factor) + update(pos, -1, 0, 0, place_factor) + update(pos, 0, 0, -1, place_factor) + update(pos, 1, 0, 1, place_factor) + update(pos, 1, 0, -1, place_factor) + update(pos, -1, 0, 1, place_factor) + update(pos, -1, 0, -1, place_factor) + if force_below then update(pos, 0, -1, 0, 0) + else update(pos, 0, -1, 0, place_factor) + end + update(pos, 0, 1, 0, place_factor) +end + +if naturalslopeslib.setting_enable_shape_on_dig_place() and not naturalslopeslib.setting_revert() then + minetest.register_on_placenode(function(pos, new_node, placer, old_node, item_stack, pointed_thing) + on_place_or_dig(pos, true) + end) + minetest.register_on_dignode(function(pos, old_node, digger) + on_place_or_dig(pos) + end) +end +