Initial publish
|
@ -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).
|
||||
|
|
@ -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" )
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
name = landscape_shaping
|
||||
description = Landscape shaping tools and API
|
||||
optional_depends =
|
||||
title = Landscape Shaping
|
||||
author = cronvel
|
|
@ -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
|
||||
|
After Width: | Height: | Size: 483 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.6 KiB |
|
@ -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,
|
||||
})
|
||||
|