Initial publish

master
Cédric Ronvel 2020-09-02 16:40:59 +02:00
commit d062eeb732
17 changed files with 1125 additions and 0 deletions

18
README.md Normal file
View File

@ -0,0 +1,18 @@
This mod add various tools with God-like power to reshape the landscape.
This is meant for creative mode, so those *magic wands* have no crafting recipe yet.
Also, **you should backup your world** before using this, because a mistake can really mess things up.
Current magic wands are:
* **Transmute wand:** left click copy a node, right click change pointed node into the copy node, existing rotation is preserved
* **Blow wand:** blow nodes away, left click is like TNT, blowing away anything within the radius, right-click do the same but only for the upward hemisphere instead of the whole sphere, the later is useful to flatten an area
* **Flat hemisphere wand:** fill the downward hemisphere with the pointed node's type (left-click) or the copied node (right-click), useful to fill holes
* **Flat square wand:** fill the area of a thin horizontal square at the y-level of the pointed node, with the pointed node's type (left-click) or the copied node (right-click), useful to pave things quickly
* **Water wand:** left-click: remove all water around (sphere), right-click create a water hemisphere
* **Lava wand:** works like the water wand for lava
* **Fall wand:** all nodes within the radius will fall if there isn't anything solid below. Beware, in-range cavern will collapse! Non-blocky things that receive the falling node will be destroyed (snow slabs, grass, ...)
* **Cover wand:** cover the landscape within the radius with a layer of dirt (left-click) or a layer of the copied node (right-click)
* **Smooth wand:** smooth the heightmap of the surrounding landscape, left-click: smooth just a bit (just one smooth pass), right-click: smooth more (multiple pass depending on the current wand radius, each pass average with 20 neighbors instead of the 8 closest), this is my favorite, since this is the one that was the most effective to fix the map seam (but also the harder to code!). Grass and thing like snow slabs are preserved in the process, but don't mess with tree! They are way too complicated to manage. But all simple things works since for each voxel on top of the heigtmap, two voxels above and two voxels below are preserved (they move along).

9
init.lua Normal file
View File

@ -0,0 +1,9 @@
-- Global landscape_shaping namespace
landscape_shaping = {}
landscape_shaping.path = minetest.get_modpath( minetest.get_current_modname() )
-- Load files
dofile( landscape_shaping.path .. "/reshape.lua" )
dofile( landscape_shaping.path .. "/wands.lua" )

5
mod.conf Normal file
View File

@ -0,0 +1,5 @@
name = landscape_shaping
description = Landscape shaping tools and API
optional_depends =
title = Landscape Shaping
author = cronvel

730
reshape.lua Normal file
View File

@ -0,0 +1,730 @@
local rng = PseudoRandom(os.time())
local air_content_id , fire_content_id
local block_water_source_content_id , water_source_content_id , water_flowing_content_id
local block_river_water_source_content_id , river_water_source_content_id , river_water_flowing_content_id
local block_lava_source_content_id , lava_source_content_id , lava_flowing_content_id
-- It is possible for node to record here the chance for a drop to preserved
landscape_shaping.drop_chance = {}
local content_id_data = {}
minetest.register_on_mods_loaded( function()
air_content_id = minetest.get_content_id("air")
fire_content_id = minetest.get_content_id("fire:basic_flame")
water_source_content_id = minetest.get_content_id("default:water_source")
water_flowing_content_id = minetest.get_content_id("default:water_flowing")
river_water_source_content_id = minetest.get_content_id("default:river_water_source")
river_water_flowing_content_id = minetest.get_content_id("default:river_water_flowing")
lava_source_content_id = minetest.get_content_id("default:lava_source")
lava_flowing_content_id = minetest.get_content_id("default:lava_flowing")
--if minetest.get_modpath("dynamic_liquid") then
-- block_water_source_content_id = minetest.get_content_id("dynamic_liquid:block_water_source")
-- block_river_water_source_content_id = minetest.get_content_id("dynamic_liquid:block_river_water_source")
-- block_lava_source_content_id = minetest.get_content_id("dynamic_liquid:block_lava_source")
--end
-- Fill a list with data for content IDs, after all nodes are registered
for name, def in pairs(minetest.registered_nodes) do
content_id_data[minetest.get_content_id(name)] = {
name = name,
walkable = def.walkable,
buildable_to = def.buildable_to,
drops = def.drops,
flammable = def.groups.flammable,
on_blast = def.on_blast,
}
end
end)
landscape_shaping.area_filter = {}
landscape_shaping.area_filter.sphere = function( fx_radius , x , y , z )
return vector.length( vector.new( x, y, z ) ) <= fx_radius
end
landscape_shaping.area_filter.sphere_with_random_edge = function( fx_radius , x , y , z )
local r = vector.length( vector.new( x, y, z ) )
return r <= fx_radius and r <= fx_radius * ( rng:next(80, 100) / 100 )
end
landscape_shaping.area_filter.hemisphere = function( fx_radius , x , y , z )
return y > 0 and vector.length( vector.new( x, y, z ) ) <= fx_radius
end
landscape_shaping.area_filter.hemisphere_or_equal = function( fx_radius , x , y , z )
return y >= 0 and vector.length( vector.new( x, y, z ) ) <= fx_radius
end
landscape_shaping.area_filter.down_hemisphere = function( fx_radius , x , y , z )
return y < 0 and vector.length( vector.new( x, y, z ) ) <= fx_radius
end
landscape_shaping.area_filter.down_hemisphere_or_equal = function( fx_radius , x , y , z )
return y <= 0 and vector.length( vector.new( x, y, z ) ) <= fx_radius
end
landscape_shaping.area_filter.flat_square = function( fx_radius , x , y , z )
return y == 0
end
landscape_shaping.content_filter = {}
landscape_shaping.content_filter.any = function( content_id )
return true
end
landscape_shaping.content_filter.air = function( content_id )
return content_id == air_content_id
end
landscape_shaping.content_filter.non_air = function( content_id )
return content_id ~= air_content_id
end
landscape_shaping.content_filter.solid = function( content_id )
return content_id_data[content_id] and content_id_data[content_id].walkable
end
landscape_shaping.content_filter.non_solid = function( content_id )
return content_id_data[content_id] and not content_id_data[content_id].walkable
end
landscape_shaping.content_filter.solid_block = function( content_id )
return content_id_data[content_id] and content_id_data[content_id].walkable and not content_id_data[content_id].buildable_to
end
landscape_shaping.content_filter.non_solid_block = function( content_id )
return content_id_data[content_id] and not content_id_data[content_id].walkable and content_id_data[content_id].buildable_to
end
landscape_shaping.content_filter.buildable_to = function( content_id )
return content_id_data[content_id] and content_id_data[content_id].buildable_to
end
landscape_shaping.content_filter.non_buildable_to = function( content_id )
return content_id_data[content_id] and not content_id_data[content_id].buildable_to
end
landscape_shaping.content_filter.water = function( content_id )
return (
content_id == block_water_source_content_id
or content_id == water_source_content_id
or content_id == water_flowing_content_id
or content_id == block_river_water_source_content_id
or content_id == river_water_source_content_id
or content_id == river_water_flowing_content_id
)
end
landscape_shaping.content_filter.non_water = function( content_id )
return not (
content_id == block_water_source_content_id
or content_id == water_source_content_id
or content_id == water_flowing_content_id
or content_id == block_river_water_source_content_id
or content_id == river_water_source_content_id
or content_id == river_water_flowing_content_id
)
end
landscape_shaping.content_filter.lava = function( content_id )
return (
content_id == block_lava_source_content_id
or content_id == lava_source_content_id
or content_id == lava_flowing_content_id
)
end
landscape_shaping.content_filter.non_lava = function( content_id )
return not (
content_id == block_lava_source_content_id
or content_id == lava_source_content_id
or content_id == lava_flowing_content_id
)
end
landscape_shaping.heightmap_filter = {}
-- Average of 8 closest neighbors
landscape_shaping.heightmap_filter.avg_8 = function( heightmap , size , pass_number )
local new_heightmap = {}
local border_size = pass_number
-- Now compute the height map smoothing
for z = -size, size do
new_heightmap[ z ] = {}
for x = -size, size do
if z < -size + border_size or z > size - border_size or x < -size + border_size or x > size - border_size then
-- We are on the border, just copy
new_heightmap[ z ][ x ] = heightmap[ z ][ x ]
else
-- Smooth with neighbor
new_heightmap[ z ][ x ] = (
heightmap[ z ][ x ]
+ heightmap[ z + 1 ][ x ]
+ heightmap[ z - 1 ][ x ]
+ heightmap[ z ][ x + 1 ]
+ heightmap[ z ][ x - 1 ]
+ heightmap[ z + 1 ][ x + 1 ]
+ heightmap[ z + 1 ][ x - 1 ]
+ heightmap[ z - 1 ][ x + 1 ]
+ heightmap[ z - 1 ][ x - 1 ]
) / 9
end
end
end
return new_heightmap
end
-- Average of 20 closest neighbors
landscape_shaping.heightmap_filter.avg_20 = function( heightmap , size , pass_number )
local new_heightmap = {}
local border_size = 1 + pass_number
-- Now compute the height map smoothing
for z = -size, size do
new_heightmap[ z ] = {}
for x = -size, size do
if z < -size + border_size or z > size - border_size or x < -size + border_size or x > size - border_size then
-- We are on the border, just copy
new_heightmap[ z ][ x ] = heightmap[ z ][ x ]
else
-- Smooth with neighbor
new_heightmap[ z ][ x ] = (
heightmap[ z ][ x ]
+ heightmap[ z + 1 ][ x ]
+ heightmap[ z - 1 ][ x ]
+ heightmap[ z ][ x + 1 ]
+ heightmap[ z ][ x - 1 ]
+ heightmap[ z + 1 ][ x + 1 ]
+ heightmap[ z + 1 ][ x - 1 ]
+ heightmap[ z - 1 ][ x + 1 ]
+ heightmap[ z - 1 ][ x - 1 ]
+ heightmap[ z + 2 ][ x ]
+ heightmap[ z - 2 ][ x ]
+ heightmap[ z ][ x + 2 ]
+ heightmap[ z ][ x - 2 ]
+ heightmap[ z + 2 ][ x + 1 ]
+ heightmap[ z + 1 ][ x + 2 ]
+ heightmap[ z + 2 ][ x - 1 ]
+ heightmap[ z + 1 ][ x - 2 ]
+ heightmap[ z - 2 ][ x + 1 ]
+ heightmap[ z - 1 ][ x + 2 ]
+ heightmap[ z - 2 ][ x - 1 ]
+ heightmap[ z - 1 ][ x - 2 ]
) / 21
end
end
end
return new_heightmap
end
local function random_position(center, pos, radius)
local def
local reg_nodes = minetest.registered_nodes
local i = 0
repeat
-- Give up and use the center if this takes too long
if i > 4 then
pos.x, pos.z = center.x, center.z
break
end
pos.x = center.x + math.random(-radius, radius)
pos.z = center.z + math.random(-radius, radius)
def = reg_nodes[minetest.get_node(pos).name]
i = i + 1
until def and not def.walkable
end
local function add_drops(drops, node_drops)
if not node_drops then return end
for _, item in pairs(node_drops) do
local item_stack = ItemStack(item)
local item_name = item_stack:get_name()
if landscape_shaping.drop_chance[item_name] == nil or rng:next(0,100) < landscape_shaping.drop_chance[item_name] then
local drop = drops[item_name]
if drop == nil then
drops[item_name] = item_stack
else
drop:set_count(drop:get_count() + item_stack:get_count())
end
end
end
end
local function eject_drops(drops, pos, radius)
local drop_pos = vector.new(pos)
for _, item in pairs(drops) do
local count = math.min(item:get_count(), item:get_stack_max())
while count > 0 do
local take = math.max(1,math.min(radius * radius,
count,
item:get_stack_max()))
random_position(pos, drop_pos, radius)
local dropitem = ItemStack(item)
dropitem:set_count(take)
local obj = minetest.add_item(drop_pos, dropitem)
if obj then
obj:get_luaentity().collect = true
obj:set_acceleration({x = 0, y = -10, z = 0})
obj:set_velocity({x = math.random(-3, 3),
y = math.random(0, 10),
z = math.random(-3, 3)})
end
count = count - take
end
end
end
landscape_shaping.blow = function(player, pos, radius, params)
-- parameter management
params = params or {}
local area_filter = params.area_filter or landscape_shaping.area_filter.sphere
local content_filter = params.content_filter or landscape_shaping.content_filter.non_air
local replacement_node_name = params.replacement_node or params.replacement_node_name or "air"
local use_node_blast = params.use_blast or params.use_node_blast or false
local can_drop = params.can_drop or false
local ignore_protection = params.ignore_protection or false
pos = vector.round(pos)
local count = 1
local voxel_manip = VoxelManip()
local min_pos = vector.subtract(pos, radius)
local max_pos = vector.add(pos, radius)
min_pos, max_pos = voxel_manip:read_from_map(min_pos, max_pos)
local voxel_area = VoxelArea:new({MinEdge = min_pos, MaxEdge = max_pos})
local voxel_data = voxel_manip:get_data()
local drops = {}
local on_blast_queue = {}
local on_construct_queue = {}
local current_pos = {x = 0, y = 0, z = 0}
local replacement_content_id = minetest.get_content_id(replacement_node_name)
local current_content_id , current_def
for y = -radius, radius do
current_pos.y = pos.y + y
for z = -radius, radius do
current_pos.z = pos.z + z
local voxel_index = voxel_area:index(pos.x + (-radius), current_pos.y, current_pos.z)
for x = -radius, radius do
current_pos.x = pos.x + x
current_content_id = voxel_data[voxel_index]
current_def = content_id_data[ current_content_id ]
if
area_filter( radius , x , y , z )
and content_filter( current_content_id )
and ( ignore_protection or not minetest.is_protected( current_pos, player) )
then
if current_def then
if use_node_blast and current_def.on_blast then
-- use blast on this node
on_blast_queue[#on_blast_queue + 1] = {
pos = vector.new(current_pos),
on_blast = current_def.on_blast
}
elseif can_drop then
-- ... or try to drop item
local node_drops = minetest.get_node_drops(current_def.name, "")
add_drops( drops , node_drops )
end
end
voxel_data[voxel_index] = replacement_content_id
end
voxel_index = voxel_index + 1
end
end
end
voxel_manip:set_data(voxel_data)
voxel_manip:write_to_map()
voxel_manip:update_map()
voxel_manip:update_liquids()
-- call check_single_for_falling for everything within 1.5x blast radius
for y = -radius * 1.5, radius * 1.5 do
for z = -radius * 1.5, radius * 1.5 do
for x = -radius * 1.5, radius * 1.5 do
local rad = {x = x, y = y, z = z}
local s = vector.add(pos, rad)
local r = vector.length(rad)
if r / radius < 1.4 then
minetest.check_single_for_falling(s)
end
end
end
end
for _, queued_data in pairs(on_blast_queue) do
local dist = math.max(1, vector.distance(queued_data.pos, pos))
local intensity = (radius * radius) / (dist * dist)
local node_drops = queued_data.on_blast(queued_data.pos, intensity, pos)
if can_drop then
add_drops( drops , node_drops )
end
end
if can_drop then
eject_drops(drops, pos, radius)
end
--for _, queued_data in pairs(on_construct_queue) do
-- queued_data.fn(queued_data.pos)
--end
minetest.log("action", "Landscape blowed by " .. ( player:get_player_name() or "(unknown)" ) .. " at " .. minetest.pos_to_string(pos) .. " with radius " .. radius)
return drops, radius
end
landscape_shaping.fall = function(player, pos, radius, params)
-- parameter management
params = params or {}
local area_filter = params.area_filter or landscape_shaping.area_filter.sphere
local content_filter = params.content_filter or landscape_shaping.content_filter.non_air
local fall_radius = params.fall_radius or math.floor( 32 + 1.5 * radius )
local ignore_protection = params.ignore_protection or false
pos = vector.round(pos)
local count = 1
local voxel_manip = VoxelManip()
local min_pos = vector.subtract(pos, radius)
min_pos.y = pos.y - fall_radius
local max_pos = vector.add(pos, radius)
min_pos, max_pos = voxel_manip:read_from_map(min_pos, max_pos)
local voxel_area = VoxelArea:new({MinEdge = min_pos, MaxEdge = max_pos})
local voxel_data = voxel_manip:get_data()
local current_pos = {x = 0, y = 0, z = 0}
local current_content_id , current_def , current_fall_content_id
for y = -radius, radius do
current_pos.y = pos.y + y
for z = -radius, radius do
current_pos.z = pos.z + z
local voxel_index = voxel_area:index(pos.x + (-radius), current_pos.y, current_pos.z)
for x = -radius, radius do
current_pos.x = pos.x + x
current_content_id = voxel_data[voxel_index]
current_def = content_id_data[ current_content_id ]
if
area_filter( radius , x , y , z )
and content_filter( current_content_id )
and ( ignore_protection or not minetest.is_protected( current_pos, player) )
then
local current_fall_y_pos , fall_voxel_index , last_fall_voxel_index
for fall_y = y - 1, - fall_radius, -1 do
current_fall_y_pos = pos.y + fall_y
fall_voxel_index = voxel_area:index(current_pos.x, current_fall_y_pos, current_pos.z)
current_fall_content_id = voxel_data[fall_voxel_index]
if landscape_shaping.content_filter.non_solid_block( current_fall_content_id ) then
last_fall_voxel_index = fall_voxel_index
end
end
if last_fall_voxel_index ~= nil then
voxel_data[voxel_index] = air_content_id
voxel_data[last_fall_voxel_index] = current_content_id
end
end
voxel_index = voxel_index + 1
end
end
end
voxel_manip:set_data(voxel_data)
voxel_manip:write_to_map()
voxel_manip:update_map()
voxel_manip:update_liquids()
-- call check_single_for_falling for everything within 1.5x blast radius
for y = radius , radius * 1.5 do
for z = -radius , radius do
for x = -radius , radius do
local rad = {x = x, y = y, z = z}
local s = vector.add(pos, rad)
minetest.check_single_for_falling(s)
end
end
end
minetest.log("action", "Landscape falling done by " .. ( player:get_player_name() or "(unknown)" ) .. " at " .. minetest.pos_to_string(pos) .. " with radius " .. radius)
return radius
end
landscape_shaping.cover = function(player, pos, radius, params)
-- parameter management
params = params or {}
local area_filter = params.area_filter or landscape_shaping.area_filter.sphere
local content_filter = params.content_filter or landscape_shaping.content_filter.buildable_to
local replacement_node_name = params.replacement_node or params.replacement_node_name or "default:dirt"
local ignore_protection = params.ignore_protection or false
pos = vector.round(pos)
local count = 1
local voxel_manip = VoxelManip()
local min_pos = vector.subtract(pos, radius)
min_pos.y = min_pos.y - 1
local max_pos = vector.add(pos, radius)
min_pos, max_pos = voxel_manip:read_from_map(min_pos, max_pos)
local voxel_area = VoxelArea:new({MinEdge = min_pos, MaxEdge = max_pos})
local voxel_data = voxel_manip:get_data()
local current_pos = {x = 0, y = 0, z = 0}
local replacement_content_id = minetest.get_content_id(replacement_node_name)
local current_content_id , current_def , current_below_content_id
-- We start from top to bottom for obvious reason, if not, we would cover over previously covered nodes
for y = radius, -radius, -1 do
current_pos.y = pos.y + y
for z = -radius, radius do
current_pos.z = pos.z + z
local voxel_index = voxel_area:index(pos.x + (-radius), current_pos.y, current_pos.z)
local below_voxel_index
for x = -radius, radius do
current_pos.x = pos.x + x
current_content_id = voxel_data[voxel_index]
current_def = content_id_data[ current_content_id ]
if
area_filter( radius , x , y , z )
and content_filter( current_content_id )
and ( ignore_protection or not minetest.is_protected( current_pos, player) )
then
below_voxel_index = voxel_area:index(current_pos.x, current_pos.y - 1, current_pos.z)
current_below_content_id = voxel_data[below_voxel_index]
if landscape_shaping.content_filter.solid_block( current_below_content_id ) then
voxel_data[voxel_index] = replacement_content_id
end
end
voxel_index = voxel_index + 1
end
end
end
voxel_manip:set_data(voxel_data)
voxel_manip:write_to_map()
voxel_manip:update_map()
voxel_manip:update_liquids()
minetest.log("action", "Landscape cover done by " .. ( player:get_player_name() or "(unknown)" ) .. " at " .. minetest.pos_to_string(pos) .. " with radius " .. radius)
return radius
end
landscape_shaping.heightmap = function(player, pos, radius, params)
-- parameter management
params = params or {}
--local area_filter = params.area_filter or landscape_shaping.area_filter.sphere
local content_filter = params.content_filter or landscape_shaping.content_filter.solid_block
local heightmap_filter = params.heightmap_filter or landscape_shaping.heightmap_filter.avg_8
local replacement_node_name = params.replacement_node or params.replacement_node_name or "air"
local pass = params.pass or 1
local ignore_protection = params.ignore_protection or false
pos = vector.round(pos)
local max_y_radius = math.floor( 16 + 1.5 * radius )
local min_y_radius = math.floor( 32 + 1.5 * radius ) -- always try to check/smooth more in depth
local count = 1
local voxel_manip = VoxelManip()
local min_pos = vector.subtract(pos, radius + 1)
local max_pos = vector.add(pos, radius + 1)
min_pos.y = pos.y - min_y_radius
max_pos.y = pos.y + max_y_radius
min_pos, max_pos = voxel_manip:read_from_map(min_pos, max_pos)
local voxel_area = VoxelArea:new({MinEdge = min_pos, MaxEdge = max_pos})
local voxel_data = voxel_manip:get_data()
local current_pos = {x = 0, y = 0, z = 0}
local replacement_content_id = minetest.get_content_id(replacement_node_name)
local current_content_id , current_def , voxel_index
local heightmap = {}
-- Height map building: we start from top to bottom and stop on the first solid block
for z = -radius - 1, radius + 1 do
current_pos.z = pos.z + z
heightmap[ z ] = {}
for x = -radius - 1, radius + 1 do
current_pos.x = pos.x + x
heightmap[ z ][ x ] = - min_y_radius - 1
for y = max_y_radius, -min_y_radius, -1 do
current_pos.y = pos.y + y
voxel_index = voxel_area:index( current_pos.x, current_pos.y, current_pos.z )
current_content_id = voxel_data[voxel_index]
current_def = content_id_data[ current_content_id ]
if content_filter( current_content_id ) then
-- found it, mark height map and break!
heightmap[ z ][ x ] = y
break
end
end
end
end
local new_heightmap = heightmap
-- Now perform the height map smooth passes
for current_pass = 1, pass do
new_heightmap = heightmap_filter( new_heightmap , radius + 1, current_pass )
end
-- Apply the new height map
for z = -radius, radius do
current_pos.z = pos.z + z
for x = -radius, radius do
local current_y = heightmap[ z ][ x ]
-- We don't change things with height map level at min+1 or max-1 because they are unsafe
if current_y > -min_y_radius + 1 and current_y < max_y_radius - 1 then
-- The new height map is filled with floating point numbers
local new_y = math.floor( 0.5 + new_heightmap[ z ][ x ] )
local new_voxel_index
local above_current_voxel_index , above_current_content_id , above_new_voxel_index
local below_current_voxel_index , below_current_content_id , below_new_voxel_index
local filler_voxel_index
if current_y ~= new_y then
-- We always preserve the 2 voxels above and 2 voxels below in the raise/lower process, to have produce a better effect.
-- Above voxels can be grass, snow slab or water
voxel_index = voxel_area:index( pos.x + x, pos.y + current_y, pos.z + z)
current_content_id = voxel_data[voxel_index]
above_current_voxel_index = voxel_area:index( pos.x + x, pos.y + current_y + 1, pos.z + z)
above_current_content_id = voxel_data[above_current_voxel_index]
above2_current_voxel_index = voxel_area:index( pos.x + x, pos.y + current_y + 2, pos.z + z)
above2_current_content_id = voxel_data[above2_current_voxel_index]
below_current_voxel_index = voxel_area:index( pos.x + x, pos.y + current_y - 1, pos.z + z)
below_current_content_id = voxel_data[below_current_voxel_index]
below2_current_voxel_index = voxel_area:index( pos.x + x, pos.y + current_y - 2, pos.z + z)
below2_current_content_id = voxel_data[below2_current_voxel_index]
new_voxel_index = voxel_area:index( pos.x + x, pos.y + new_y, pos.z + z)
above_new_voxel_index = voxel_area:index( pos.x + x, pos.y + new_y + 1, pos.z + z)
above2_new_voxel_index = voxel_area:index( pos.x + x, pos.y + new_y + 2, pos.z + z)
below_new_voxel_index = voxel_area:index( pos.x + x, pos.y + new_y - 1, pos.z + z)
below2_new_voxel_index = voxel_area:index( pos.x + x, pos.y + new_y - 2, pos.z + z)
if current_y > new_y then
-- So we lower the current voxel
-- The order matters
voxel_data[below2_new_voxel_index] = below2_current_content_id
voxel_data[below_new_voxel_index] = below_current_content_id
voxel_data[new_voxel_index] = current_content_id
voxel_data[above_new_voxel_index] = above_current_content_id
voxel_data[above2_new_voxel_index] = above2_current_content_id
-- Fill voxels above the new height using air/replacement
for y = new_y + 3, current_y + 1 do
filler_voxel_index = voxel_area:index( pos.x + x, pos.y + y, pos.z + z)
voxel_data[filler_voxel_index] = replacement_content_id
end
elseif current_y < new_y then
-- So we raise the current voxel
-- The order matters
voxel_data[above2_new_voxel_index] = above2_current_content_id
voxel_data[above_new_voxel_index] = above_current_content_id
voxel_data[new_voxel_index] = current_content_id
voxel_data[below_new_voxel_index] = below_current_content_id
voxel_data[below2_new_voxel_index] = below2_current_content_id
-- Fill voxels below the new height, using what was below the current node
for y = new_y - 3, current_y, -1 do
filler_voxel_index = voxel_area:index( pos.x + x, pos.y + y, pos.z + z)
voxel_data[filler_voxel_index] = below2_current_content_id
end
end
end
end
end
end
voxel_manip:set_data(voxel_data)
voxel_manip:write_to_map()
voxel_manip:update_map()
voxel_manip:update_liquids()
minetest.log("action", "Landscape smoothing done by " .. ( player:get_player_name() or "(unknown)" ) .. " at " .. minetest.pos_to_string(pos) .. " with radius " .. radius)
return radius
end

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

0
settingtypes.txt Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

363
wands.lua Normal file
View File

@ -0,0 +1,363 @@
landscape_shaping.player_copied_node_name = {}
landscape_shaping.player_reshape_radius = {}
local function transmute_wand_interact(player, pointed_thing, mode)
if pointed_thing.type ~= "node" then return end
-- A true player is required
local player_name = player and player:get_player_name()
if not player_name then return end
local pos = pointed_thing.under
local is_sneak = player and player:get_player_control().sneak or false
-- Check for node protection
if minetest.is_protected(pos, player_name) then
minetest.chat_send_player(player_name, "You're not authorized to alter nodes in this area")
minetest.record_protection_violation(pos, player_name)
return
end
-- Retrieve group info and styles
local node = minetest.get_node(pos)
local node_name = node.name
local style , group_name , group , new_style , new_node_name
if mode == "copy" then
-- Copy node
landscape_shaping.player_copied_node_name[ player_name ] = node_name
minetest.chat_send_player(player_name, "Transmute wand: node " .. node_name .. " copied")
return
elseif mode == "transmute" then
-- Paste node
--if not minetest.get_player_privs(player_name).creative then
-- minetest.chat_send_player(player_name, "The transmute wand require the 'creative' privilege")
-- return
--end
new_node_name = landscape_shaping.player_copied_node_name[ player_name ]
if not new_node_name then
minetest.chat_send_player(player_name, "No transmute node copied yet, left-click to copy a node")
return
end
-- Already the correct node, exit now!
if new_node_name == node_name then return end
end
-- Check if rotation could be preserved
local nodedef = minetest.registered_nodes[node_name]
local new_nodedef = minetest.registered_nodes[new_node_name]
local rotation , new_rotation
if nodedef and new_nodedef then
if ( nodedef.paramtype2 == "facedir" or nodedef.paramtype2 == "colorfacedir" )
and ( new_nodedef.paramtype2 == "facedir" or new_nodedef.paramtype2 == "colorfacedir" )
then
rotation = node.param2 % 32 --rotation are on the last 5 digits
end
end
-- Set the new node
minetest.set_node(pos, {name= new_node_name})
local new_node = minetest.get_node(pos)
-- Copy rotation if needed!
if rotation ~= nil then
new_rotation = new_node.param2 % 32
if new_rotation ~= rotation then
new_node.param2 = new_node.param2 - new_rotation + rotation
minetest.swap_node(pos, new_node)
end
end
--minetest.sound_play("jonez_carve", {pos = pos, gain = 0.7, max_hear_distance = 5})
end
local function reshape_interact(player, pointed_thing, mode, is_right)
-- A true player is required
local player_name = player and player:get_player_name()
if not player_name then return end
local is_sneak = player:get_player_control().sneak or false
local radius = landscape_shaping.player_reshape_radius[ player_name ] or 3
if is_sneak then
if is_right then
radius = radius + 1 + math.floor( radius / 6 )
elseif radius > 1 then
radius = radius - 1 - math.floor( radius / 8 )
end
minetest.chat_send_player(player_name, "New radius for reshaping wands: " .. radius )
landscape_shaping.player_reshape_radius[ player_name ] = radius
return
end
if pointed_thing.type ~= "node" then return end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
local node_name = node.name
if mode == "blow-sphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
use_blast = true
})
elseif mode == "blow-hemisphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.hemisphere,
use_blast = true
})
elseif mode == "fill-down-hemisphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.down_hemisphere_or_equal,
content_filter = landscape_shaping.content_filter.any,
replacement_node = node_name
})
elseif mode == "fill-down-hemisphere-with-copy" then
node_name = landscape_shaping.player_copied_node_name[ player_name ]
if not node_name then
minetest.chat_send_player(player_name, "No node copied yet")
return
end
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.down_hemisphere_or_equal,
content_filter = landscape_shaping.content_filter.any,
replacement_node = node_name
})
elseif mode == "flat-square" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.flat_square,
content_filter = landscape_shaping.content_filter.any,
replacement_node = node_name
})
elseif mode == "flat-square-with-copy" then
node_name = landscape_shaping.player_copied_node_name[ player_name ]
if not node_name then
minetest.chat_send_player(player_name, "No node copied yet")
return
end
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.flat_square,
content_filter = landscape_shaping.content_filter.any,
replacement_node = node_name
})
elseif mode == "fall-sphere" then
landscape_shaping.fall(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
content_filter = landscape_shaping.content_filter.non_air,
fall_radius = 32 + 2 * radius
})
elseif mode == "cover-sphere" then
landscape_shaping.cover(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
--content_filter = landscape_shaping.content_filter.non_solid_block,
--replacement_node = node_name,
})
elseif mode == "cover-sphere-copy" then
node_name = landscape_shaping.player_copied_node_name[ player_name ]
if not node_name then
minetest.chat_send_player(player_name, "No node copied yet")
return
end
landscape_shaping.cover(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
--content_filter = landscape_shaping.content_filter.non_solid_block,
replacement_node = node_name,
})
elseif mode == "smooth-sphere" then
landscape_shaping.heightmap(player, pos, radius, {
--replacement_node = node_name,
})
elseif mode == "smoother-sphere" then
landscape_shaping.heightmap(player, pos, radius, {
heightmap_filter = landscape_shaping.heightmap_filter.avg_20 ,
pass = 2 + math.floor( radius / 5 )
})
elseif mode == "water-hemisphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.hemisphere,
content_filter = landscape_shaping.content_filter.air,
replacement_node = "default:water_source"
})
elseif mode == "evaporate-water-sphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
content_filter = landscape_shaping.content_filter.water
})
elseif mode == "lava-hemisphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
content_filter = landscape_shaping.content_filter.air,
replacement_node = "default:lava_source"
})
elseif mode == "evaporate-lava-sphere" then
landscape_shaping.blow(player, pos, radius, {
area_filter = landscape_shaping.area_filter.sphere,
content_filter = landscape_shaping.content_filter.lava
})
end
minetest.chat_send_player(player_name, "Reshaped!")
end
minetest.register_craftitem("landscape_shaping:transmute_wand", {
description = "Copy wand",
inventory_image = "landscape_shaping_transmute_wand.png",
wield_image = "landscape_shaping_transmute_wand.png",
on_use = function(itemstack, player, pointed_thing)
transmute_wand_interact(player, pointed_thing,"copy")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
transmute_wand_interact(player, pointed_thing,"transmute",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:blow_wand", {
description = "Blow wand",
inventory_image = "landscape_shaping_blow_wand.png",
wield_image = "landscape_shaping_blow_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"blow-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"blow-hemisphere",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:flat_hemisphere_wand", {
description = "Flat hemisphere wand",
inventory_image = "landscape_shaping_flat_hemisphere_wand.png",
wield_image = "landscape_shaping_flat_hemisphere_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"fill-down-hemisphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"fill-down-hemisphere-with-copy",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:flat_square_wand", {
description = "Flat square wand",
inventory_image = "landscape_shaping_flat_square_wand.png",
wield_image = "landscape_shaping_flat_square_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"flat-square")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"flat-square-with-copy",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:water_wand", {
description = "Water wand",
inventory_image = "landscape_shaping_water_wand.png",
wield_image = "landscape_shaping_water_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"evaporate-water-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"water-hemisphere",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:lava_wand", {
description = "Lava wand",
inventory_image = "landscape_shaping_lava_wand.png",
wield_image = "landscape_shaping_lava_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"evaporate-lava-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"lava-hemisphere",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:fall_wand", {
description = "Fall wand",
inventory_image = "landscape_shaping_fall_wand.png",
wield_image = "landscape_shaping_fall_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"fall-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"fall-sphere",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:cover_wand", {
description = "Cover wand",
inventory_image = "landscape_shaping_cover_wand.png",
wield_image = "landscape_shaping_cover_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"cover-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"cover-sphere-copy",true)
return itemstack
end,
})
minetest.register_craftitem("landscape_shaping:smooth_wand", {
description = "Smooth wand",
inventory_image = "landscape_shaping_smooth_wand.png",
wield_image = "landscape_shaping_smooth_wand.png",
on_use = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"smooth-sphere")
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
reshape_interact(player, pointed_thing,"smoother-sphere",true)
return itemstack
end,
})