edit-cd2025/replace.lua
2024-01-09 21:58:27 -06:00

245 lines
6.7 KiB
Lua

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)