Add replace tool

This commit is contained in:
MrRar 2024-01-09 21:58:27 -06:00
parent dacddb58a1
commit 403a24e860
9 changed files with 561 additions and 214 deletions

View File

@ -21,6 +21,7 @@ This mod was inspired by the Fill Start and Fill End blocks in Manic Digger.
| Copy | edit:copy | ![](textures/edit_copy.png) |
| Paste | edit:paste | ![](textures/edit_paste.png) |
| Fill | edit:fill | ![](textures/edit_fill.png) |
| Replace | edit:replace | ![](textures/edit_replace.png) |
| Open | edit:open | ![](textures/edit_open.png) |
| Save | edit:save | ![](textures/edit_save.png) |
| Undo | edit:undo | ![](textures/edit_undo.png) |
@ -48,11 +49,18 @@ When the copy tool is placed at opposite corners of an area, they select the are
The paste tool is used for pasting the area copied by the copy tool or a schematic loaded with the open tool. When a paste tool is placed, the copied area or schematic is placed at the corner of the paste tool. The copied area can be rotated by punching while holding the paste tool.
### Fill Node
### Fill Tool
Fill nodes are used to fill a 3D area with a certain item. Start by placing two fill nodes at opposite corners of the desired area. The selected area includes the positions of the fill nodes themselves as shown in the figure.
The fill tool is used to fill a 3D area with a certain item. Start by placing the fill tool two times at opposite corners of the desired area. The selected area includes the positions of the fill markers themselves as shown in the figure.
Once a second fill node is placed, a dialog appears listing all items in the players inventory. Clicking an item will cause it to be used used for filling the selected area. Clicking on a blank slot will cause the selected area to be filled with air. To cancel the fill, press the "X".
Once a second fill marker is placed, a dialog appears listing all items in the players inventory. A search field is also available to search all items. Clicking an item will cause it to be used used for filling the selected area. Clicking on a blank inventory slot will cause the selected area to be filled with air. To cancel the fill, press the "X".
### Replace Tool
The replace tool is used to replace certain nodes in a 3D area with a selected item. Start by placing the replace tool two times at opposite corners of the desired area. The selected area includes the positions of the replace markers themselves as shown in the figure.
Once a second replace marker is placed, a dialog appears listing all node types in the selected area. Check the nodes that should be replaced and then press the "OK" button to proceed with the next step. Next a dialog will pop up showing all the items in the players inventory. A search field is also available to search all items. Clicking an item will cause it to be used used to replace the nodes that were checked earlier. Clicking on a blank inventory slot will cause the checked nodes to be replaced with air. To cancel the replace, press the "X".
### Open Tool

View File

@ -1,7 +1,6 @@
local function place_circle(player, pos, node)
local player_data = edit.player_data[player]
if
not player or
player:get_player_control().aux1 or
not player_data or
not player_data.circle_luaentity
@ -70,15 +69,11 @@ end
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger or not digger:is_player() then return end
local player_data = edit.player_data[digger]
if player_data.ignore_node_placement then return end
return place_circle(digger, pos, {name = "air"})
return place_circle(digger, pos, {name = "air"}) or true
end)
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer then return end
local player_data = edit.player_data[placer]
if player_data.ignore_node_placement then return end
return place_circle(placer, pos, newnode)
end)
@ -108,8 +103,12 @@ minetest.register_tool("edit:circle",{
tiles = {"edit_circle.png"},
inventory_image = "edit_circle.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = circle_tool_on_place,
on_secondary_use = circle_tool_on_place,
_edit_get_pointed_pos = function(player)
return edit.get_half_node_pointed_pos(player)
end,
})
minetest.register_entity("edit:circle", {

View File

@ -45,10 +45,17 @@ minetest.register_tool("edit:copy",{
description = "Edit Copy",
tiles = {"edit_copy.png"},
inventory_image = "edit_copy.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3},
range = 10,
groups = {edit_place_preview = 1,},
on_place = copy_on_place,
on_secondary_use = copy_on_place,
_edit_get_selection_points = function(player)
local d = edit.player_data[player]
return d.copy_luaentity1 and d.copy_luaentity1._pos
end,
_edit_get_pointed_pos = function(player)
return edit.get_pointed_thing_node(player).under
end,
})
minetest.register_entity("edit:copy", {

193
fill.lua
View File

@ -1,120 +1,22 @@
local function fill_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = edit.pointed_thing_to_pos(pointed_thing)
local player_data = edit.player_data[player]
if player_data.fill1 and pos then
player_data.fill2 = edit.add_marker("edit:fill", pos, player)
if not player_data.fill2 then return end
local diff = vector.subtract(player_data.fill1._pos, pos)
local size = vector.add(vector.apply(diff, math.abs), 1)
if size.x * size.y * size.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.fill1.object:remove()
return
end
local inv = minetest.get_inventory({type = "player", name = player:get_player_name()})
local formspec = "size[8,6]label[2,0.5;Select item for filling]button_exit[7,0;1,1;quit;X]"
for y = 1, 4 do
for x = 1, 8 do
local name = inv:get_stack("main", ((y - 1) * 8) + x):get_name()
formspec =
formspec ..
"item_image_button[" ..
(x - 1) .. "," ..
(y + 1) .. ";1,1;" ..
name .. ";" ..
name .. ";]"
end
end
minetest.show_formspec(player:get_player_name(), "edit:fill", formspec)
elseif pos then
player_data.fill1 = edit.add_marker("edit:fill", pos, player)
end
end
minetest.register_tool("edit:fill", {
description = "Edit Fill",
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
range = 10,
on_place = fill_on_place,
on_secondary_use = fill_on_place,
})
minetest.register_entity("edit:fill", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
self.remove_called = true
if player_data then
if player_data.fill1 and not player_data.fill1.remove_called then
player_data.fill1.object:remove()
end
if player_data.fill2 and not player_data.fill2.remove_called then
player_data.fill2.object:remove()
end
player_data.fill1 = nil
player_data.fill2 = nil
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "edit:fill" then return false end
minetest.close_formspec(player:get_player_name(), "edit:fill")
local function player_select_node_formspec(player)
edit.player_select_node(player, "Select item to use for fill", function(player, name)
local d = edit.player_data[player]
if
not d.fill1 or not d.fill2 or
not edit.has_privilege(player)
then return true end
then return end
local p1 = d.fill1._pos
local p2 = d.fill2._pos
d.fill1.object:remove()
local name
local def
for key, val in pairs(fields) do
if key == "quit" then return true end
if key == "" then key = "air" end
if not name then return end
name = key
def = minetest.registered_items[name]
local def = minetest.registered_items[name]
if def then break end
end
if not def then return true end
if not def then return end
local is_node = minetest.registered_nodes[name]
@ -125,8 +27,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
param2 = minetest.dir_to_wallmounted(player:get_look_dir(), true)
end
local on_place = def.on_place or function() end
local start = vector.new(
math.min(p1.x, p2.x),
math.min(p1.y, p2.y),
@ -174,5 +74,84 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end
end
end
return true
return
end)
end
local function fill_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = edit.pointed_thing_to_pos(pointed_thing)
local player_data = edit.player_data[player]
if player_data.fill1 and pos then
player_data.fill2 = edit.add_marker("edit:fill", pos, player)
if not player_data.fill2 then return end
local diff = vector.subtract(player_data.fill1._pos, pos)
local volume = vector.add(vector.apply(diff, math.abs), 1)
if volume.x * volume.y * volume.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.fill1.object:remove()
return
end
player_select_node_formspec(player)
elseif pos then
player_data.fill1 = edit.add_marker("edit:fill", pos, player)
end
end
minetest.register_tool("edit:fill", {
description = "Edit Fill",
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = fill_on_place,
on_secondary_use = fill_on_place,
_edit_get_selection_points = function(player)
local d = edit.player_data[player]
return d.fill1 and d.fill1._pos, d.fill2 and d.fill2._pos
end,
})
minetest.register_entity("edit:fill", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
self.remove_called = true
if player_data then
if player_data.fill1 and not player_data.fill1.remove_called then
player_data.fill1.object:remove()
end
if player_data.fill2 and not player_data.fill2.remove_called then
player_data.fill2.object:remove()
end
player_data.fill1 = nil
player_data.fill2 = nil
end
end,
})

158
init.lua
View File

@ -50,25 +50,10 @@ minetest.register_on_joinplayer(function(player)
end)
minetest.register_on_leaveplayer(function(player)
edit.delete_paste_preview(player)
local d = edit.player_data[player]
if d.select_preview then
d.select_preview:remove()
for key, value in pairs(edit.player_data[player]) do
if type(value) == "table" and value.object then
value.object:remove()
end
if d.place_preview then
d.place_preview:remove()
end
if d.copy_luaentity1 then
d.copy_luaentity1.object:remove()
end
if d.circle_luaentity then
d.circle_luaentity.object:remove()
end
if d.fill1 then
d.fill1.object:remove()
end
if d.fill2 then
d.fill2.object:remove()
end
edit.player_data[player] = nil
end)
@ -133,14 +118,14 @@ local old_register_on_dignode = minetest.register_on_dignode
local registered_on_dignode = {}
minetest.register_on_dignode = function(func)
table.insert(registered_on_dignode, func)
old_register_on_dignode(func)
return old_register_on_dignode(func)
end
local old_register_on_placenode = minetest.register_on_placenode
local registered_on_placenode = {}
minetest.register_on_placenode = function(func)
table.insert(registered_on_placenode, func)
old_register_on_placenode(func)
return old_register_on_placenode(func)
end
function edit.place_node_like_player(player, node, pos)
@ -188,6 +173,138 @@ function edit.add_marker(id, pos, player)
return luaentity
end
local function player_select_node_formspec(player)
local d = edit.player_data[player]
local search_value = d.player_select_node_search_value
local doing_search = #search_value > 0
local inv = minetest.get_inventory({type = "player", name = player:get_player_name()})
local size = doing_search and 12 * 8 or inv:get_size("main")
local width = doing_search and 12 or inv:get_width("main")
if width <= 0 then width = 8 end
local formspec_width = math.max(width, 8) + 0.4
local formspec_height = math.ceil(size / width) + 3.4
local search_results = {}
if doing_search then
local search_words = {}
for word in search_value:gmatch("([^%s]+)") do
table.insert(search_words, word:lower())
end
local search_results_done = false
for id, def in pairs(minetest.registered_items) do
if minetest.get_item_group(id, "not_in_creative_inventory") == 0 then
local add_node_to_results = true
for i, word in pairs(search_words) do
local description = def.description:lower() or ""
if not description:find(word) and not id:find(word) then
add_node_to_results = false
break
end
end
if add_node_to_results then
table.insert(search_results, id)
if #search_results > size then
search_results_done = true
break
end
end
end
if search_results_done then
break
end
end
end
local title = doing_search and "Search Results:" or "Inventory:"
if #search_results > size then title = title .. " (some omited)" end
local formspec = "formspec_version[4]size[" .. formspec_width .. "," .. formspec_height .. "]" ..
"button[0,0;0,0;minetest_sucks;" .. math.random() .. "]" .. -- Force Minetest to show this formspec
"label[0.5,0.7;" .. d.player_select_node_message .. "]" ..
"button_exit[" .. formspec_width - 1.2 .. ",0.2;1,1;quit;X]" ..
"field_close_on_enter[search_field;false]" ..
"label[0.2,1.7;Search all items]" ..
"field[0.2,2;3.5,0.8;search_field;;" .. search_value .. "]" ..
"image_button[3.7,2;0.8,0.8;search.png^[resize:48x48;search_button;]" ..
"button[4.5,2;0.8,0.8;cancel_search;X]" ..
"label[5.6,2.8;" .. title .. "]"
for i = 1, size do
local name
if doing_search then
name = search_results[i]
else
name = inv:get_stack("main", i):get_name()
end
if not name then break end
if name == "" then name = "air" end
local index = i - 1
local x = 0.2 + index % width
local y = 3.2 + math.floor(index / width)
formspec =
formspec ..
"item_image_button[" ..
x .. "," ..
y .. ";1,1;" ..
name .. ";" ..
name .. ";]"
end
edit.reliable_show_formspec(player, "edit:player_select_node", formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "edit:player_select_node" then return false end
local d = edit.player_data[player]
for key, val in pairs(fields) do
if key:find(":") or key == "air" then
if d.player_select_node_callback then
d.player_select_node_callback(player, key)
d.player_select_node_callback = nil
minetest.close_formspec(player:get_player_name(), "edit:player_select_node")
end
return true
end
end
if fields.quit then
if d.player_select_node_callback then
d.player_select_node_callback(player, nil)
d.player_select_node_callback = nil
end
return true
elseif fields.cancel_search then
fields.search_field = ""
end
if
fields.search_field and
fields.search_field ~= d.player_select_node_search_value
then
d.player_select_node_search_value = fields.search_field
player_select_node_formspec(player)
return true
end
return true
end)
function edit.player_select_node(player, message, callback)
local d = edit.player_data[player]
if d.player_select_node_callback then
d.player_select_node_callback(player, nil)
end
d.player_select_node_callback = callback
d.player_select_node_search_value = d.player_select_node_search_value or ""
d.player_select_node_message = message
player_select_node_formspec(player)
end
edit.modpath = minetest.get_modpath("edit")
dofile(edit.modpath .. "/copy.lua")
dofile(edit.modpath .. "/fill.lua")
@ -200,3 +317,4 @@ dofile(edit.modpath .. "/undo.lua")
dofile(edit.modpath .. "/circle.lua")
dofile(edit.modpath .. "/mirror.lua")
dofile(edit.modpath .. "/screwdriver.lua")
dofile(edit.modpath .. "/replace.lua")

View File

@ -2,7 +2,6 @@ local function do_mirror(player, pos, node)
local d = edit.player_data[player]
if
not player or
player:get_player_control().aux1 or
not d or
not d.mirror_luaentity
@ -14,6 +13,12 @@ local function do_mirror(player, pos, node)
local center = d.mirror_luaentity._pos
local offset = vector.subtract(pos, center)
-- Undo
local length = math.max(math.abs(offset.x), math.abs(offset.z))
local start = vector.subtract(center, vector.new(length, -offset.y, length))
local size = vector.new(length * 2 + 1, 1, length * 2 + 1)
d.undo_schematic = edit.schematic_from_map(start, size)
if d.mirror_mode == "x" then
offset.x = -offset.x
edit.place_node_like_player(player, node, vector.add(center, offset))
@ -42,20 +47,17 @@ local function do_mirror(player, pos, node)
edit.place_node_like_player(player, node, vector.add(center, offset))
end
end
d.ignore_node_placement = nil
end
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger or not digger:is_player() then return end
local player_data = edit.player_data[digger]
if player_data.ignore_node_placement then return end
return do_mirror(digger, pos, {name = "air"})
end)
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer or not placer:is_player() then return end
local player_data = edit.player_data[placer]
if player_data.ignore_node_placement then return end
return do_mirror(placer, pos, newnode)
end)
@ -69,13 +71,8 @@ local function mirror_tool_on_place(itemstack, player, pointed_thing)
local pos = edit.get_half_node_pointed_pos(player)
local obj_ref = minetest.add_entity(pos, "edit:mirror")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
luaentity:_update_borders()
d.mirror_luaentity = luaentity
d.mirror_luaentity = edit.add_marker("edit:mirror", pos, player)
d.mirror_luaentity:_update_borders()
d.mirror_hud = player:hud_add({
hud_elem_type = "text",
@ -93,8 +90,12 @@ minetest.register_tool("edit:mirror", {
tiles = {"edit_mirror.png"},
inventory_image = "edit_mirror.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = mirror_tool_on_place,
on_secondary_use = mirror_tool_on_place,
_edit_get_pointed_pos = function(player)
return edit.get_half_node_pointed_pos(player)
end,
})
minetest.register_entity("edit:mirror_border", {
@ -106,7 +107,6 @@ minetest.register_entity("edit:mirror_border", {
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
pointable = false,
backface_culling = true,

View File

@ -18,7 +18,7 @@ function edit.rotate_paste_preview(player)
local d = edit.player_data[player]
local rot = d.schematic._rotation
local offset
d.paste_preview:set_yaw(-math.rad(rot))
d.paste_preview.object:set_yaw(-math.rad(rot))
local size = d.schematic.size
if rot == 90 or rot == 270 then
size = vector.new(size.z, size.y, size.x)
@ -98,24 +98,11 @@ local function create_paste_preview(player)
objref:set_attach(base_objref, "", attach_pos, attach_rot)
end
end
edit.player_data[player].paste_preview = base_objref
edit.player_data[player].paste_preview = base_objref:get_luaentity()
edit.player_data[player].schematic._rotation = 0
edit.rotate_paste_preview(player)
end
function edit.delete_paste_preview(player)
local paste_preview = edit.player_data[player].paste_preview
if not paste_preview or not paste_preview:get_pos() then return end
local objrefs = paste_preview:get_children()
for i, objref in pairs(objrefs) do
objref:remove()
end
edit.player_data[player].paste_preview:remove()
edit.player_data[player].paste_preview_visable = false
edit.player_data[player].paste_preview = nil
end
minetest.register_entity("edit:select_preview", {
initial_properties = {
visual = "cube",
@ -138,7 +125,13 @@ minetest.register_entity("edit:paste_preview_base", {
static_save = false,
visual_size = {x = 1, y = 1},
textures = { "blank.png", "blank.png", "blank.png", "blank.png", "blank.png", "blank.png" },
}
},
on_deactivate = function(self)
local objrefs = self.object:get_children()
for i, objref in pairs(objrefs) do
objref:remove()
end
end
})
minetest.register_entity("edit:preview_node", {
@ -159,20 +152,20 @@ local function hide_paste_preview(player)
-- This does not work right.
-- Some child entities do not become visable when you set is_visable back to true
for _, objref in pairs(d.paste_preview:get_children()) do
for _, objref in pairs(d.paste_preview.object:get_children()) do
objref:set_properties({is_visible = false})
end
d.paste_preview:set_attach(player)
d.paste_preview.object:set_attach(player)
player:hud_remove(d.paste_preview_hud)
d.paste_preview_hud = nil
end
local function show_paste_preview(player)
local d = edit.player_data[player]
for _, objref in pairs(d.paste_preview:get_children()) do
for _, objref in pairs(d.paste_preview.object:get_children()) do
objref:set_properties({is_visible = true})
end
d.paste_preview:set_detach()
d.paste_preview.object:set_detach()
d.paste_preview_hud = player:hud_add({
hud_elem_type = "text",
text = "Punch (left click) to rotate.",
@ -188,15 +181,23 @@ local function show_paste_preview(player)
local pos = objref:get_pos()
if pos then objref:set_pos(pos) end
end,
d.paste_preview
d.paste_preview.object
)
end
function edit.delete_paste_preview(player)
local d = edit.player_data[player]
if d.paste_preview then
d.paste_preview.object:remove()
d.paste_preview = nil
end
end
local function hide_select_preview(player)
local d = edit.player_data[player]
d.select_preview_shown = false
d.select_preview:set_properties({ is_visible = false })
d.select_preview:set_attach(player)
d.select_preview.object:set_properties({ is_visible = false })
d.select_preview.object:set_attach(player)
player:hud_remove(d.select_preview_hud)
d.select_preview_hud = nil
end
@ -204,16 +205,18 @@ end
local function update_select_preview(player, pos, size)
local d = edit.player_data[player]
if not d.select_preview or not d.select_preview:get_pos() then
d.select_preview = minetest.add_entity(player:get_pos(), "edit:select_preview")
if not d.select_preview or not d.select_preview.object:get_pos() then
local obj_ref = minetest.add_entity(player:get_pos(), "edit:select_preview")
if not obj_ref then return end
d.select_preview = obj_ref:get_luaentity()
d.select_preview_shown = true
elseif not d.select_preview_shown then
d.select_preview:set_detach()
d.select_preview:set_properties({is_visible = true})
d.select_preview.object:set_detach()
d.select_preview.object:set_properties({is_visible = true})
d.select_preview_shown = true
end
local preview = d.select_preview
local preview = d.select_preview.object
if vector.equals(pos, preview:get_pos()) then
return
end
@ -295,18 +298,18 @@ minetest.register_globalstep(function(dtime)
if item == "edit:paste" and d.schematic then
local pos = edit.pointed_thing_to_pos(edit.get_pointed_thing_node(player))
if pos then
if not d.paste_preview or not d.paste_preview:get_pos() then
if not d.paste_preview or not d.paste_preview.object:get_pos() then
create_paste_preview(player)
end
if not d.paste_preview_hud then show_paste_preview(player) end
local old_pos = d.paste_preview:get_pos()
local old_pos = d.paste_preview.object:get_pos()
pos = vector.add(pos, d.paste_preview_offset)
set_schematic_offset(player)
pos = vector.add(pos, d.schematic_offset)
if not vector.equals(old_pos, pos) then
d.paste_preview:set_pos(pos)
d.paste_preview.object:set_pos(pos)
end
elseif d.paste_preview_hud then hide_paste_preview(player) end
elseif d.paste_preview_hud then hide_paste_preview(player) end
@ -315,47 +318,36 @@ minetest.register_globalstep(function(dtime)
local node1_pos
local node2_pos
local pointed_pos
local fill_selected = item == "edit:fill"
local copy_selected = item == "edit:copy"
local circle_selected = item == "edit:circle"
local mirror_selected = item == "edit:mirror"
if fill_selected then
if d.fill1 then
node1_pos = d.fill1._pos
end
if d.fill2 then
node2_pos = d.fill2._pos
end
elseif copy_selected then
if d.copy_luaentity1 then
node1_pos = d.copy_luaentity1._pos
end
local tool_def = minetest.registered_items[item] or minetest.registered_items["air"]
if tool_def._edit_get_selection_points then
node1_pos, node2_pos = tool_def._edit_get_selection_points(player)
end
if not node2_pos or not node1_pos then
local pointed_thing = edit.get_pointed_thing_node(player)
if circle_selected or mirror_selected then
pointed_pos = edit.get_half_node_pointed_pos(player)
elseif copy_selected then
pointed_pos = pointed_thing.under
if tool_def._edit_get_pointed_pos then
pointed_pos = tool_def._edit_get_pointed_pos(player)
else
local pointed_thing = edit.get_pointed_thing_node(player)
pointed_pos = edit.pointed_thing_to_pos(pointed_thing)
end
end
if (fill_selected or copy_selected or circle_selected or mirror_selected) and not node2_pos and pointed_pos then
if not d.place_preview or not d.place_preview:get_pos() then
d.place_preview = minetest.add_entity(player:get_pos(), "edit:place_preview")
if minetest.get_item_group(item, "edit_place_preview") ~= 0 and not node2_pos and pointed_pos then
if not d.place_preview or not d.place_preview.object:get_pos() then
local obj_ref = minetest.add_entity(player:get_pos(), "edit:place_preview")
if not obj_ref then return end
d.place_preview = obj_ref:get_luaentity()
d.place_preview_shown = true
d.place_preview_item = nil
elseif not d.place_preview_shown then
d.place_preview:set_properties({ is_visible = true })
d.place_preview:set_detach()
d.place_preview.object:set_properties({ is_visible = true })
d.place_preview.object:set_detach()
d.place_preview_shown = true
end
if not vector.equals(d.place_preview:get_pos(), pointed_pos) then
d.place_preview:set_pos(pointed_pos)
if not vector.equals(d.place_preview.object:get_pos(), pointed_pos) then
d.place_preview.object:set_pos(pointed_pos)
end
if d.place_preview_item ~= item then
@ -363,13 +355,13 @@ minetest.register_globalstep(function(dtime)
"^[opacity:150"
d.place_preview_item = item
d.place_preview:set_properties({
d.place_preview.object:set_properties({
textures = { tex, tex, tex, tex, tex, tex }
})
end
elseif d.place_preview_shown then
d.place_preview:set_properties({ is_visible = false })
d.place_preview:set_attach(player)
d.place_preview.object:set_properties({ is_visible = false })
d.place_preview.object:set_attach(player)
d.place_preview_shown = false
end

244
replace.lua Normal file
View File

@ -0,0 +1,244 @@
local function show_select_source_nodes_formspec(player)
local player_data = edit.player_data[player]
local p1 = player_data.replace1._pos
local p2 = player_data.replace2._pos
local start = vector.new(
math.min(p1.x, p2.x),
math.min(p1.y, p2.y),
math.min(p1.z, p2.z)
)
local _end = vector.new(
math.max(p1.x, p2.x),
math.max(p1.y, p2.y),
math.max(p1.z, p2.z)
)
local size = vector.add(vector.subtract(_end, start), 1)
if size.x * size.y * size.z > edit.max_operation_volume then
edit.display_size_error(player)
player_data.replace1.object:remove()
return
end
local all_nodes = {}
for x = start.x, _end.x do
for y = start.y, _end.y do
for z = start.z, _end.z do
local name = minetest.get_node(vector.new(x, y, z)).name
if all_nodes[name] then
all_nodes[name] = all_nodes[name] + 1
else
all_nodes[name] = 1
end
end
end
end
if player_data.replace_source_nodes == "all" then
player_data.replace_source_nodes = all_nodes
elseif not player_data.replace_source_nodes then
player_data.replace_source_nodes = {}
end
local formspec = "formspec_version[4]size[10.1,10.8]" ..
-- Formspecs are only sent if the first part of the formspec is different.
-- I mitigate this by placing an invizible button with a random
-- label at the beginning of the formspec.
"button[0,0;0,0;minetest_sucks;" .. math.random() .. "]" ..
"label[0.5,0.7;Select node types to replace]" ..
"button_exit[8.9,0.2;1,1;quit;X]" ..
"scroll_container[0.2,1.4;9,8;scrollbar;vertical]"
local index = 0
for name, count in pairs(all_nodes) do
local x = index % 9
local y = math.floor(index / 9)
local def = minetest.registered_nodes[name]
local description = def and def.description or name
local selected = player_data.replace_source_nodes[name] and "true" or ""
formspec = formspec ..
"item_image[" .. x .. "," .. y .. ";1,1;" .. name .. "]" ..
"checkbox[" .. x + 0.1 .. "," .. y + 0.3 .. ";" .. name .. ";;" .. selected .. "]" ..
"tooltip[" .. x .. "," .. y .. ";1,1;" .. description .. "]"
index = index + 1
end
formspec = formspec ..
"scroll_container_end[]" ..
"scrollbaroptions[max=" .. (math.ceil(index / 9) - 8) * 10 .. "]" ..
"scrollbar[9.3,1.4;0.6,8;vertical;scrollbar;0]" ..
"button[0.2,9.6;3.5,0.8;select_all;Select All]" ..
"button[3.9,9.6;3.5,0.8;select_none;Select None]" ..
"button[7.9,9.6;2,1;continue;OK]"
minetest.show_formspec(player:get_player_name(), "edit:replace_source_nodes", formspec)
end
local function replace_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = pointed_thing.under
local player_data = edit.player_data[player]
if player_data.replace1 and pos then
player_data.replace2 = edit.add_marker("edit:replace", pos, player)
if not player_data.replace2 then return end
show_select_source_nodes_formspec(player)
elseif pos then
player_data.replace1 = edit.add_marker("edit:replace", pos, player)
end
end
minetest.register_tool("edit:replace", {
description = "Edit Replace",
tiles = {"edit_replace.png"},
inventory_image = "edit_replace.png",
range = 10,
groups = {edit_place_preview = 1,},
on_place = replace_on_place,
on_secondary_use = replace_on_place,
_edit_get_selection_points = function(player)
local d = edit.player_data[player]
return d.replace1 and d.replace1._pos, d.replace2 and d.replace2._pos
end,
_edit_get_pointed_pos = function(player)
return edit.get_pointed_thing_node(player).under
end,
})
minetest.register_entity("edit:replace", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_replace.png",
"edit_replace.png",
"edit_replace.png",
"edit_replace.png",
"edit_replace.png",
"edit_replace.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
self.remove_called = true
if player_data then
if player_data.replace1 and not player_data.replace1.remove_called then
player_data.replace1.object:remove()
end
if player_data.replace2 and not player_data.replace2.remove_called then
player_data.replace2.object:remove()
end
player_data.replace1 = nil
player_data.replace2 = nil
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "edit:replace_source_nodes" then return false end
local d = edit.player_data[player]
if
not d.replace1 or not d.replace2 or
not edit.has_privilege(player)
then return true end
if fields.quit then
d.replace1.object:remove()
d.replace_source_nodes = nil
return true
elseif fields.select_all then
d.replace_source_nodes = "all"
show_select_source_nodes_formspec(player)
return true
elseif fields.select_none then
d.replace_source_nodes = nil
show_select_source_nodes_formspec(player)
return true
end
for key, value in pairs(fields) do
if key:find(":") or key == "air" then
if value == "true" then
d.replace_source_nodes[key] = true
else
d.replace_source_nodes[key] = nil
end
return true
end
end
if not fields.continue then return true end
edit.player_select_node(player, "Select item to replace nodes", function(player, name)
if
not d.replace1 or not d.replace2 or
not d.replace_source_nodes or
not edit.has_privilege(player)
then return end
local p1 = d.replace1._pos
local p2 = d.replace2._pos
d.replace1.object:remove()
local replace_source_nodes = d.replace_source_nodes
d.replace_source_nodes = nil
if not name then return end
local def = minetest.registered_items[name]
if not def then return end
local is_node = minetest.registered_nodes[name]
local start = vector.new(
math.min(p1.x, p2.x),
math.min(p1.y, p2.y),
math.min(p1.z, p2.z)
)
local _end = vector.new(
math.max(p1.x, p2.x),
math.max(p1.y, p2.y),
math.max(p1.z, p2.z)
)
local size = vector.add(vector.subtract(_end, start), 1)
d.undo_schematic = edit.schematic_from_map(start, size)
for x = start.x, _end.x do
for y = start.y, _end.y do
for z = start.z, _end.z do
local pos = vector.new(x, y, z)
local node = minetest.get_node(pos)
local old_name = node.name
node.name = name
if replace_source_nodes[old_name] then
if is_node then
minetest.swap_node(pos, node)
else
edit.place_node_like_player(player, node, pos)
end
end
end
end
end
end)
return true
end)

BIN
textures/edit_replace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B