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) | | Copy | edit:copy | ![](textures/edit_copy.png) |
| Paste | edit:paste | ![](textures/edit_paste.png) | | Paste | edit:paste | ![](textures/edit_paste.png) |
| Fill | edit:fill | ![](textures/edit_fill.png) | | Fill | edit:fill | ![](textures/edit_fill.png) |
| Replace | edit:replace | ![](textures/edit_replace.png) |
| Open | edit:open | ![](textures/edit_open.png) | | Open | edit:open | ![](textures/edit_open.png) |
| Save | edit:save | ![](textures/edit_save.png) | | Save | edit:save | ![](textures/edit_save.png) |
| Undo | edit:undo | ![](textures/edit_undo.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. 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 ### Open Tool

View File

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

View File

@ -45,10 +45,17 @@ minetest.register_tool("edit:copy",{
description = "Edit Copy", description = "Edit Copy",
tiles = {"edit_copy.png"}, tiles = {"edit_copy.png"},
inventory_image = "edit_copy.png", inventory_image = "edit_copy.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3},
range = 10, range = 10,
groups = {edit_place_preview = 1,},
on_place = copy_on_place, on_place = copy_on_place,
on_secondary_use = 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", { minetest.register_entity("edit:copy", {

193
fill.lua
View File

@ -1,120 +1,22 @@
local function fill_on_place(itemstack, player, pointed_thing) local function player_select_node_formspec(player)
if not edit.on_place_checks(player) then return end edit.player_select_node(player, "Select item to use for fill", function(player, name)
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 d = edit.player_data[player] local d = edit.player_data[player]
if if
not d.fill1 or not d.fill2 or not d.fill1 or not d.fill2 or
not edit.has_privilege(player) not edit.has_privilege(player)
then return true end then return end
local p1 = d.fill1._pos local p1 = d.fill1._pos
local p2 = d.fill2._pos local p2 = d.fill2._pos
d.fill1.object:remove() d.fill1.object:remove()
local name if not name then return end
local def
for key, val in pairs(fields) do
if key == "quit" then return true end
if key == "" then key = "air" end
name = key local def = minetest.registered_items[name]
def = minetest.registered_items[name]
if def then break end if not def then return end
end
if not def then return true end
local is_node = minetest.registered_nodes[name] 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) param2 = minetest.dir_to_wallmounted(player:get_look_dir(), true)
end end
local on_place = def.on_place or function() end
local start = vector.new( local start = vector.new(
math.min(p1.x, p2.x), math.min(p1.x, p2.x),
math.min(p1.y, p2.y), math.min(p1.y, p2.y),
@ -174,5 +74,84 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
end end
end end
end end
return true return
end) 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) end)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player)
edit.delete_paste_preview(player) for key, value in pairs(edit.player_data[player]) do
local d = edit.player_data[player] if type(value) == "table" and value.object then
if d.select_preview then value.object:remove()
d.select_preview:remove()
end 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 end
edit.player_data[player] = nil edit.player_data[player] = nil
end) end)
@ -133,14 +118,14 @@ local old_register_on_dignode = minetest.register_on_dignode
local registered_on_dignode = {} local registered_on_dignode = {}
minetest.register_on_dignode = function(func) minetest.register_on_dignode = function(func)
table.insert(registered_on_dignode, func) table.insert(registered_on_dignode, func)
old_register_on_dignode(func) return old_register_on_dignode(func)
end end
local old_register_on_placenode = minetest.register_on_placenode local old_register_on_placenode = minetest.register_on_placenode
local registered_on_placenode = {} local registered_on_placenode = {}
minetest.register_on_placenode = function(func) minetest.register_on_placenode = function(func)
table.insert(registered_on_placenode, func) table.insert(registered_on_placenode, func)
old_register_on_placenode(func) return old_register_on_placenode(func)
end end
function edit.place_node_like_player(player, node, pos) function edit.place_node_like_player(player, node, pos)
@ -188,6 +173,138 @@ function edit.add_marker(id, pos, player)
return luaentity return luaentity
end 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") edit.modpath = minetest.get_modpath("edit")
dofile(edit.modpath .. "/copy.lua") dofile(edit.modpath .. "/copy.lua")
dofile(edit.modpath .. "/fill.lua") dofile(edit.modpath .. "/fill.lua")
@ -200,3 +317,4 @@ dofile(edit.modpath .. "/undo.lua")
dofile(edit.modpath .. "/circle.lua") dofile(edit.modpath .. "/circle.lua")
dofile(edit.modpath .. "/mirror.lua") dofile(edit.modpath .. "/mirror.lua")
dofile(edit.modpath .. "/screwdriver.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] local d = edit.player_data[player]
if if
not player or
player:get_player_control().aux1 or player:get_player_control().aux1 or
not d or not d or
not d.mirror_luaentity not d.mirror_luaentity
@ -14,6 +13,12 @@ local function do_mirror(player, pos, node)
local center = d.mirror_luaentity._pos local center = d.mirror_luaentity._pos
local offset = vector.subtract(pos, center) 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 if d.mirror_mode == "x" then
offset.x = -offset.x offset.x = -offset.x
edit.place_node_like_player(player, node, vector.add(center, offset)) 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)) edit.place_node_like_player(player, node, vector.add(center, offset))
end end
end end
d.ignore_node_placement = nil d.ignore_node_placement = nil
end end
minetest.register_on_dignode(function(pos, oldnode, digger) minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger or not digger:is_player() then return end 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"}) return do_mirror(digger, pos, {name = "air"})
end) end)
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing) minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer or not placer:is_player() then return end 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) return do_mirror(placer, pos, newnode)
end) 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 pos = edit.get_half_node_pointed_pos(player)
local obj_ref = minetest.add_entity(pos, "edit:mirror") d.mirror_luaentity = edit.add_marker("edit:mirror", pos, player)
if not obj_ref then return end d.mirror_luaentity:_update_borders()
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
luaentity:_update_borders()
d.mirror_luaentity = luaentity
d.mirror_hud = player:hud_add({ d.mirror_hud = player:hud_add({
hud_elem_type = "text", hud_elem_type = "text",
@ -93,8 +90,12 @@ minetest.register_tool("edit:mirror", {
tiles = {"edit_mirror.png"}, tiles = {"edit_mirror.png"},
inventory_image = "edit_mirror.png", inventory_image = "edit_mirror.png",
range = 10, range = 10,
groups = {edit_place_preview = 1,},
on_place = mirror_tool_on_place, on_place = mirror_tool_on_place,
on_secondary_use = 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", { minetest.register_entity("edit:mirror_border", {
@ -106,7 +107,6 @@ minetest.register_entity("edit:mirror_border", {
static_save = false, static_save = false,
use_texture_alpha = true, use_texture_alpha = true,
glow = -1, glow = -1,
backface_culling = false,
hp_max = 1, hp_max = 1,
pointable = false, pointable = false,
backface_culling = true, backface_culling = true,

View File

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