From 64273159ddb63c9c544cc877b222f65de3bd0bdc Mon Sep 17 00:00:00 2001 From: OldCoder Date: Sun, 4 Sep 2022 22:03:04 -0700 Subject: [PATCH] Imported from trollstream "ContentDB" --- CHANGELOG.md | 212 ++++++++ README.md | 52 ++ init.lua | 165 ++++++ mod.conf | 4 + models/naturalslopeslib_inner.obj | 27 + models/naturalslopeslib_inner_rough.obj | 43 ++ models/naturalslopeslib_outer.obj | 21 + models/naturalslopeslib_outer_rough.obj | 35 ++ models/naturalslopeslib_pike.obj | 21 + models/naturalslopeslib_pike_rough.obj | 37 ++ models/naturalslopeslib_straight.obj | 22 + models/naturalslopeslib_straight_rough.obj | 37 ++ naturalslopeslib_api.txt | 396 ++++++++++++++ register_slopes.lua | 476 +++++++++++++++++ revert.lua | 0 screenshot.png | Bin 0 -> 42972 bytes settingtypes.txt | 63 +++ sloped_stomp.lua | 23 + update_shape.lua | 577 +++++++++++++++++++++ 19 files changed, 2211 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 models/naturalslopeslib_inner.obj create mode 100644 models/naturalslopeslib_inner_rough.obj create mode 100644 models/naturalslopeslib_outer.obj create mode 100644 models/naturalslopeslib_outer_rough.obj create mode 100644 models/naturalslopeslib_pike.obj create mode 100644 models/naturalslopeslib_pike_rough.obj create mode 100644 models/naturalslopeslib_straight.obj create mode 100644 models/naturalslopeslib_straight_rough.obj create mode 100644 naturalslopeslib_api.txt create mode 100644 register_slopes.lua create mode 100644 revert.lua create mode 100644 screenshot.png create mode 100644 settingtypes.txt create mode 100644 sloped_stomp.lua create mode 100644 update_shape.lua 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 0000000000000000000000000000000000000000..97ae205040c534aae9741a199ff38ad876825484 GIT binary patch literal 42972 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 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 +