Version 2.0 update

This commit is contained in:
Johannes Fritz 2023-03-20 12:15:40 -05:00
parent 6d67c9a769
commit 28595814c1
17 changed files with 1245 additions and 958 deletions

116
LICENSE
View File

@ -1,116 +0,0 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>

22
LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2023 MrRar
object_rotations.lua Copyright (c) 2022 Skamiz Kazzarch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -94,6 +94,6 @@ Edit tools and nodes can only be used by players with `edit` privilege.
## License
CC0 by MrRar check [License](LICENSE) file, this mod was started by MrRar,
MIT by MrRar check [License](LICENSE.txt) file, this mod was started by MrRar,
minetest-mods community.

84
copy.lua Normal file
View File

@ -0,0 +1,84 @@
local function copy_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
if not pointed_thing.under then
pointed_thing = edit.get_pointed_thing_node(player)
end
local pos = pointed_thing.under
local d = edit.player_data[player]
if d.copy_luaentity1 and pos then
local p1 = d.copy_luaentity1._pos
local p2 = pos
d.copy_luaentity1.object:remove()
d.copy_luaentity1 = nil
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), vector.new(1, 1, 1))
if size.x * size.y * size.z > edit.max_operation_volume then
edit.display_size_error(player)
return
end
d.schematic = edit.schematic_from_map(start, size)
edit.delete_paste_preview(player)
local function vector_to_string(v) return "(" .. v.x .. ", " .. v.y .. ", " .. v.z .. ")" end
minetest.chat_send_player(
player:get_player_name(),
vector_to_string(start) .. " to " .. vector_to_string(_end) .. " copied." )
else
local obj_ref = minetest.add_entity(pos, "edit:copy")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
d.copy_luaentity1 = luaentity
end
end
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,
on_place = copy_on_place,
on_secondary_use = copy_on_place,
})
minetest.register_entity("edit:copy", {
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_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
},
},
on_death = function(self, killer)
if edit.player_data[self._placer] then
edit.player_data[self._placer].copy_luaentity1 = nil
end
end,
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

182
fill.lua Normal file
View File

@ -0,0 +1,182 @@
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 itemstack, pos = minetest.item_place_node(itemstack, player, pointed_thing)
local player_data = edit.player_data
if player_data[player].fill1_pos and pos then
local diff = vector.subtract(player_data[player].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)
minetest.remove_node(player_data[player].fill1_pos)
player_data[player].fill1_pos = nil
minetest.remove_node(pos)
return
end
player_data[player].fill2_pos = pos
player_data[player].fill_pointed_thing = pointed_thing
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[player].fill1_pos = pos
end
end
minetest.register_node("edit:fill",{
description = "Edit Fill",
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3, dig_immediate = 3},
range = 10,
on_place = fill_on_place,
on_secondary_use = fill_on_place,
on_destruct = function(pos)
for player, data in pairs(edit.player_data) do
local p1 = data.fill1_pos
local p2 = data.fill2_pos
if p1 and vector.equals(p1, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p1)
return
end
if p2 and vector.equals(p2, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p2)
return
end
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 p1 = d.fill1_pos
local p2 = d.fill2_pos
local pointed_thing = d.fill_pointed_thing
if
not p1 or not p2 or
not pointed_thing or
not edit.has_privilege(player)
then return true end
d.fill1_pos = nil
d.fill2_pos = nil
d.fill_pointed_thing = nil
minetest.remove_node(p1)
minetest.remove_node(p2)
local name
local def
for key, val in pairs(fields) do
if key == "quit" then return true end
if key == "" then key = "air" end
name = key
def = minetest.registered_items[name]
if def then break end
end
if not def then return true end
local is_node = minetest.registered_nodes[name]
local param2
if def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir" then
param2 = minetest.dir_to_facedir(player:get_look_dir())
elseif def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" then
param2 = minetest.dir_to_wallmounted(player:get_look_dir(), true)
end
local on_place = def.on_place
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)
local volume = size.x * size.y * size.z
if is_node and volume >= edit.fast_node_fill_threshold then
local voxel_manip = VoxelManip()
local vm_start, vm_end = voxel_manip:read_from_map(start, _end)
local param2s = voxel_manip:get_param2_data()
local content_ids = voxel_manip:get_data()
local content_id = minetest.get_content_id(name)
local ones = vector.new(1, 1, 1)
local vm_size = vector.add(vector.subtract(vm_end, vm_start), ones)
local voxel_area = VoxelArea:new({MinEdge = ones, MaxEdge = vm_size})
local va_start = vector.add(vector.subtract(start, vm_start), ones)
local va_end = vector.subtract(vector.add(va_start, size), ones)
for i in voxel_area:iterp(va_start, va_end) do
content_ids[i] = content_id
param2s[i] = param2
end
voxel_manip:set_data(content_ids)
voxel_manip:set_param2_data(param2s)
voxel_manip:write_to_map(true)
voxel_manip:update_liquids()
else
-- Work top to bottom so we can remove falling nodes
for x = _end.x, start.x, -1 do
for y = _end.y, start.y, -1 do
for z = _end.z, start.z, -1 do
local pos = vector.new(x, y, z)
if is_node then
minetest.set_node(pos, {name = name, param2 = param2})
else
minetest.remove_node(pos)
end
if on_place then
local itemstack = ItemStack(name)
pointed_thing.intersection_point = vector.new(x + 0.5, y, z + 0.5)
pointed_thing.above = pos
pointed_thing.under = pos
on_place(itemstack, player, pointed_thing)
end
end
end
end
end
return true
end)

884
init.lua
View File

@ -1,118 +1,9 @@
-------------------
-- Edit Mod v1.1 --
-------------------
edit = {}
edit.player_data = {}
local player_data = {}
local paste_preview_max_entities = tonumber(minetest.settings:get("edit_paste_preview_max_entities") or 2000)
local max_operation_volume = tonumber(minetest.settings:get("edit_max_operation_volume") or 20000)
local use_fast_node_fill = minetest.settings:get_bool("edit_use_fast_node_fill", false)
local function create_paste_preview(player)
local player_pos = player:get_pos()
local base_objref = minetest.add_entity(player_pos, "edit:preview_base")
local schematic = player_data[player].schematic
local count = 0
for i, map_node in pairs(schematic.data) do
if map_node.name ~= "air" then count = count + 1 end
end
local probability = paste_preview_max_entities / count
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = schematic.size})
local size = schematic.size
for i in voxel_area:iterp(start, size) do
local pos = voxel_area:position(i)
if schematic._rotation == 90 then
pos = vector.new(pos.z, pos.y, size.x - pos.x + 1)
elseif schematic._rotation == 180 then
pos = vector.new(size.x - pos.x + 1, pos.y, size.z - pos.z + 1)
elseif schematic._rotation == 270 then
pos = vector.new(size.z - pos.z + 1, pos.y, pos.x)
end
local name = schematic.data[i].name
if name ~= "air" and math.random() < probability then
local attach_pos = vector.multiply(vector.subtract(vector.add(pos, schematic._offset), 1), 10)
local objref = minetest.add_entity(player_pos, "edit:preview_node")
objref:set_properties({wield_item = name})
objref:set_attach(base_objref, "", attach_pos)
end
end
player_data[player].paste_preview = base_objref
player_data[player].paste_preview_yaw = 0
end
local function delete_paste_preview(player)
local paste_preview = 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
player_data[player].paste_preview:remove()
player_data[player].paste_preview_visable = false
player_data[player].paste_preview = nil
end
local function set_schematic_rotation(schematic, angle)
if not schematic._rotation then schematic._rotation = 0 end
schematic._rotation = schematic._rotation + angle
if schematic._rotation < 0 then
schematic._rotation = schematic._rotation + 360
elseif schematic._rotation > 270 then
schematic._rotation = schematic._rotation - 360
end
local size = schematic.size
if schematic._rotation == 90 or schematic._rotation == 270 then
size = vector.new(size.z, size.y, size.x)
end
--[[local old_schematic = player_data[player].schematic
local new_schematic = {data = {}}
player_data[player].schematic = new_schematic
local old_size = old_schematic.size
local new_size
if direction == "L" or direction == "R" then
new_size = vector.new(old_size.z, old_size.y, old_size.x)
elseif direction == "U" or direction == "D" then
new_size = vector.new(old_size.y, old_size.x, old_size.z)
end
new_schematic.size = new_size
local sign = vector.apply(old_schematic._offset, math.sign)
new_schematic._offset = vector.apply(
vector.multiply(new_size, sign),
function(n) return n < 0 and n or 1 end
)
local start = vector.new(1, 1, 1)
local old_voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = old_size})
local new_voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = new_size})
for old_index in old_voxel_area:iterp(start, old_schematic.size) do
local old_pos = old_voxel_area:position(old_index)
local new_pos
local node = old_schematic.data[old_index]
if direction == "L" then
new_pos = vector.new(old_pos.z, old_pos.y, old_size.x - old_pos.x + 1)
elseif direction == "R" then
new_pos = vector.new(old_size.z - old_pos.z + 1, old_pos.y, old_pos.x)
elseif direction == "U" then
new_pos = vector.new(old_pos.y, old_size.x - old_pos.x + 1, old_pos.z)
elseif direction == "D" then
new_pos = vector.new(old_size.y - old_pos.y + 1, old_pos.x, old_pos.z)
end
local new_index = new_voxel_area:indexp(new_pos)
new_schematic.data[new_index] = node
end
delete_paste_preview(player)]]
end
edit.paste_preview_max_entities = tonumber(minetest.settings:get("edit_paste_preview_max_entities") or 2000)
edit.max_operation_volume = tonumber(minetest.settings:get("edit_max_operation_volume") or 20000)
edit.fast_node_fill_threshold = tonumber(minetest.settings:get("edit_fast_node_fill_threshold") or 2000)
minetest.register_privilege("edit", {
description = "Allows usage of edit mod nodes",
@ -120,7 +11,7 @@ minetest.register_privilege("edit", {
give_to_admin = true,
})
local function has_privilege(player)
function edit.has_privilege(player)
local name = player:get_player_name()
if minetest.check_player_privs(name, {edit = true}) then
return true
@ -130,37 +21,49 @@ local function has_privilege(player)
end
end
local function display_size_error(player)
function edit.display_size_error(player)
local msg = "Operation too large. The maximum operation volume can be changed in Minetest settings."
minetest.chat_send_player(player:get_player_name(), msg)
end
local function on_place_checks(player)
function edit.on_place_checks(player)
return player and
player:is_player() and
has_privilege(player)
edit.has_privilege(player)
end
local function schematic_from_map(pos, size)
local schematic = {data = {}}
schematic.size = size
schematic._pos = pos
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = size})
for i in voxel_area:iterp(start, size) do
local offset = voxel_area:position(i)
local node_pos = vector.subtract(vector.add(pos, offset), start)
local node = minetest.get_node(node_pos)
node.param1 = nil
schematic.data[i] = node
function edit.reliable_show_formspec(player, name, formspec)
-- We need to do this nonsense because there is bug in Minetest
-- Sometimes no formspec is shown if you call minetest.show_formspec
-- from minetest.register_on_player_receive_fields
minetest.after(0.1, function()
if not player or not player:is_player() then return end
minetest.show_formspec(player:get_player_name(), name, formspec)
end)
end
return schematic
end
minetest.register_on_joinplayer(function(player)
edit.player_data[player] = {
schematic_offset = vector.new(0, 0, 0)
}
end)
local function get_pointed_thing_node(player)
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()
end
if d.place_preview then
d.place_preview:remove()
end
if d.copy_luaentity1 then
d.copy_luaentity1.object:remove()
end
edit.player_data[player] = nil
end)
function edit.get_pointed_thing_node(player)
local look_dir = player:get_look_dir()
local pos1 = player:get_pos()
local eye_height = player:get_properties().eye_height
@ -176,90 +79,7 @@ local function get_pointed_thing_node(player)
return { type = "node", under = pos, above = pos }
end
local function copy_on_place(itemstack, player, pointed_thing)
if not on_place_checks(player) then return end
if not pointed_thing.under then
pointed_thing = get_pointed_thing_node(player)
end
local pos = pointed_thing.under
if player_data[player].copy_luaentity1 and pos then
local p1 = player_data[player].copy_luaentity1._pos
local p2 = pos
player_data[player].copy_luaentity1.object:remove()
player_data[player].copy_luaentity1 = nil
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), vector.new(1, 1, 1))
if size.x * size.y * size.z > max_operation_volume then
display_size_error(player)
return
end
player_data[player].schematic = schematic_from_map(start, size)
player_data[player].schematic._offset = vector.new(0, 0, 0)
delete_paste_preview(player)
local function vector_to_string(v) return "(" .. v.x .. ", " .. v.y .. ", " .. v.z .. ")" end
minetest.chat_send_player(
player:get_player_name(),
vector_to_string(start) .. " to " .. vector_to_string(_end) .. " copied." )
else
local obj_ref = minetest.add_entity(pos, "edit:copy")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
player_data[player].copy_luaentity1 = luaentity
end
end
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,
on_place = copy_on_place,
on_secondary_use = copy_on_place,
})
minetest.register_entity("edit:copy", {
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_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
"edit_copy.png",
},
},
on_death = function(self, killer)
player_data[self._placer].copy_luaentity1 = nil
end,
})
local function pointed_thing_to_pos(pointed_thing)
function edit.pointed_thing_to_pos(pointed_thing)
local pos = pointed_thing.under
local node = minetest.get_node_or_nil(pos)
local def = node and minetest.registered_nodes[node.name]
@ -275,620 +95,12 @@ local function pointed_thing_to_pos(pointed_thing)
end
end
local function paste_on_place(itemstack, player, pointed_thing)
if not on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = get_pointed_thing_node(player)
end
if not player_data[player].schematic then
minetest.chat_send_player(player:get_player_name(), "Nothing to paste.")
return
end
local schematic = player_data[player].schematic
local pos = pointed_thing_to_pos(pointed_thing)
if not pos then return end
local pos = vector.add(pos, schematic._offset)
local size = schematic.size
if schematic._rotation == 90 or schematic._rotation == 270 then
size = vector.new(size.z, size.y, size.x)
end
player_data[player].undo_schematic = schematic_from_map(pos, size)
minetest.place_schematic(pos, schematic, tostring(schematic._rotation or 0), nil, true)
end
minetest.register_tool("edit:paste", {
description = "Edit Paste",
tiles = {"edit_paste.png"},
inventory_image = "edit_paste.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3},
range = 10,
on_place = paste_on_place,
on_secondary_use = paste_on_place,
on_use = function(itemstack, player, pointed_thing)
local d = player_data[player]
if not d.schematic then return end
set_schematic_rotation(d.schematic, 90)
delete_paste_preview(player)
end
})
local function reliable_show_formspec(player, name, formspec)
-- We need to do this nonsense because there is bug in Minetest
-- Sometimes no formspec is shown if you call minetest.show_formspec
-- from minetest.register_on_player_receive_fields
minetest.after(0.1, function()
if not player or not player:is_player() then return end
minetest.show_formspec(player:get_player_name(), name, formspec)
end)
end
local function delete_schematics_dialog(player)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "size[10,10]label[0.5,0.5;Delete Schematics from:\n" ..
minetest.formspec_escape(path) .. "]button_exit[9,0;1,1;quit;X]" ..
"textlist[0.5,2;9,7;schems;" .. table.concat(dir_list, ",") .. "]"
reliable_show_formspec(player, "edit:delete_schem", formspec)
end
local function open_on_place(itemstack, player, pointed_thing)
if not on_place_checks(player) then return end
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "size[10,10]label[0.5,0.5;Load a schematic into copy buffer from:\n" ..
minetest.formspec_escape(path) .. "]button_exit[9,0;1,1;quit;X]" ..
"textlist[0.5,2;9,7;schems;" .. table.concat(dir_list, ",") .. "]" ..
"button_exit[3,9.25;4,1;delete;Delete schematics...]"
minetest.show_formspec(player:get_player_name(), "edit:open", formspec)
end
minetest.register_tool("edit:open",{
description = "Edit Open",
inventory_image = "edit_open.png",
range = 10,
on_place = open_on_place,
on_secondary_use = open_on_place
})
local function undo_on_place(itemstack, player, pointed_thing)
if not on_place_checks(player) then return end
local schem = player_data[player].undo_schematic
if schem then
player_data[player].undo_schematic = schematic_from_map(schem._pos, schem.size)
minetest.place_schematic(schem._pos, schem, nil, nil, true)
else
minetest.chat_send_player(player:get_player_name(), "Nothing to undo.")
end
end
minetest.register_tool("edit:undo",{
description = "Edit Undo",
inventory_image = "edit_undo.png",
range = 10,
on_place = undo_on_place,
on_secondary_use = undo_on_place
})
local function show_save_dialog(player, filename, save_error)
if not player_data[player].schematic then
minetest.chat_send_player(player:get_player_name(), "Nothing to save.")
return
end
filename = filename or "untitled"
local path = minetest.get_worldpath() .. "/schems"
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "size[8,3]label[0.5,0.1;Save schematic in:\n" ..
minetest.formspec_escape(path) .. "]button_exit[7,0;1,1;cancel;X]" ..
"field[0.5,1.5;5.5,1;schem_filename;;" .. filename .. "]" ..
"button_exit[5.7,1.2;2,1;save;Save]"
if save_error then
formspec = formspec ..
"label[0.5,2.5;" .. save_error .. "]"
end
reliable_show_formspec(player, "edit:save", formspec)
end
minetest.register_tool("edit:save",{
description = "Edit Save",
inventory_image = "edit_save.png",
range = 10,
on_place = function(itemstack, player, pointed_thing)
if on_place_checks(player) then show_save_dialog(player) end
end,
on_secondary_use = function(itemstack, player, pointed_thing)
if on_place_checks(player) then show_save_dialog(player) end
end
})
local function fill_on_place(itemstack, player, pointed_thing)
if not on_place_checks(player) then return end
if not pointed_thing.above then
pointed_thing = get_pointed_thing_node(player)
end
local itemstack, pos = minetest.item_place_node(itemstack, player, pointed_thing)
if player_data[player].fill1_pos and pos then
local diff = vector.subtract(player_data[player].fill1_pos, pos)
local size = vector.add(vector.apply(diff, math.abs), 1)
if size.x * size.y * size.z > max_operation_volume then
display_size_error(player)
minetest.remove_node(player_data[player].fill1_pos)
player_data[player].fill1_pos = nil
minetest.remove_node(pos)
return
end
player_data[player].fill2_pos = pos
player_data[player].fill_pointed_thing = pointed_thing
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[player].fill1_pos = pos
end
end
minetest.register_node("edit:fill",{
description = "Edit Fill",
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3, dig_immediate = 3},
range = 10,
on_place = fill_on_place,
on_secondary_use = fill_on_place,
on_destruct = function(pos)
for player, data in pairs(player_data) do
local p1 = data.fill1_pos
local p2 = data.fill2_pos
if p1 and vector.equals(p1, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p1)
return
end
if p2 and vector.equals(p2, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p2)
return
end
end
end
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "edit:fill" then
minetest.close_formspec(player:get_player_name(), "edit:fill")
local p1 = player_data[player].fill1_pos
local p2 = player_data[player].fill2_pos
local pointed_thing = player_data[player].fill_pointed_thing
if
not p1 or not p2 or
not pointed_thing or
not has_privilege(player)
then return true end
player_data[player].fill1_pos = nil
player_data[player].fill2_pos = nil
player_data[player].fill_pointed_thing = nil
minetest.remove_node(p1)
minetest.remove_node(p2)
local name
local def
for key, val in pairs(fields) do
if key == "quit" then return true end
if key == "" then key = "air" end
name = key
def = minetest.registered_items[name]
if def then break end
end
if not def then return true end
local is_node = minetest.registered_nodes[name]
local param2
if def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir" then
param2 = minetest.dir_to_facedir(player:get_look_dir())
elseif def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" then
param2 = minetest.dir_to_wallmounted(player:get_look_dir(), true)
end
local on_place = def.on_place
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)
player_data[player].undo_schematic = schematic_from_map(start, size)
if is_node and use_fast_node_fill then
local voxel_manip = VoxelManip()
local vm_start, vm_end = voxel_manip:read_from_map(start, _end)
local param2s = voxel_manip:get_param2_data()
local content_ids = voxel_manip:get_data()
local content_id = minetest.get_content_id(name)
local ones = vector.new(1, 1, 1)
local vm_size = vector.add(vector.subtract(vm_end, vm_start), ones)
local voxel_area = VoxelArea:new({MinEdge = ones, MaxEdge = vm_size})
local va_start = vector.add(vector.subtract(start, vm_start), ones)
local va_end = vector.subtract(vector.add(va_start, size), ones)
for i in voxel_area:iterp(va_start, va_end) do
content_ids[i] = content_id
param2s[i] = param2
end
voxel_manip:set_data(content_ids)
voxel_manip:set_param2_data(param2s)
voxel_manip:write_to_map(true)
voxel_manip:update_liquids()
else
for x = start.x, _end.x, 1 do
for y = start.y, _end.y, 1 do
for z = start.z, _end.z, 1 do
local pos = vector.new(x, y, z)
if is_node then
minetest.set_node(pos, {name = name, param2 = param2})
else
minetest.remove_node(pos)
end
if on_place then
local itemstack = ItemStack(name)
pointed_thing.intersection_point = vector.new(x + 0.5, y, z + 0.5)
pointed_thing.above = pos
pointed_thing.under = pos
on_place(itemstack, player, pointed_thing)
end
end
end
end
end
return true
elseif formname == "edit:open" then
minetest.close_formspec(player:get_player_name(), "edit:open")
if
fields.cancel
or not has_privilege(player)
then return true end
if fields.delete then
delete_schematics_dialog(player)
return true
end
if not fields.schems then return end
local index = tonumber(fields.schems:sub(5, #(fields.schems)))
if not index then return true end
index = math.floor(index)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if index > 0 and index <= #dir_list then
local file_path = path .. "/" .. dir_list[index]
local schematic = minetest.read_schematic(file_path, {})
if not schematic then return true end
player_data[player].schematic = schematic
player_data[player].schematic._offset = vector.new(0, 0, 0)
minetest.chat_send_player(player:get_player_name(), "\"" .. dir_list[index] .. "\" loaded.")
delete_paste_preview(player)
end
return true
elseif formname == "edit:save" then
minetest.close_formspec(player:get_player_name(), "edit:save")
local schematic = player_data[player].schematic
local schem_filename = fields.schem_filename
if
fields.cancel or
not schem_filename or
not schematic or
not has_privilege(player)
then return end
local path = minetest.get_worldpath() .. "/schems"
local schem_filename = schem_filename .. ".mts"
local dir_list = minetest.get_dir_list(path)
for _, filename in pairs(dir_list) do
if filename == schem_filename then
show_save_dialog(player, fields.schem_filename, fields.schem_filename .. " already exists.")
return true
end
end
local mts = minetest.serialize_schematic(schematic, "mts", {})
if not mts then return true end
minetest.mkdir(path)
local schem_path = path .. "/" .. schem_filename
local f = io.open(schem_path, "wb");
if not f then
minetest.chat_send_player(player:get_player_name(), "IO error saving schematic.")
return true
end
f:write(mts);
f:close()
minetest.chat_send_player(player:get_player_name(), "\"" .. schem_filename .. "\" saved.")
return true
elseif formname == "edit:delete_schem" then
if
fields.cancel
or not has_privilege(player)
then return true end
if not fields.schems then return end
local index = tonumber(fields.schems:sub(5, #(fields.schems)))
if not index then return true end
index = math.floor(index)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if index > 0 and index <= #dir_list then
player_data[player].schem_for_delete = path .. "/" .. dir_list[index]
formspec = "size[8,3]label[0.5,0.5;Confirm delete \"" ..
dir_list[index] .. "\"]" ..
"button_exit[1,2;2,1;delete;Delete]" ..
"button_exit[5,2;2,1;quit;Cancel]"
reliable_show_formspec(player, "edit:confirm_delete_schem", formspec)
end
return true
elseif formname == "edit:confirm_delete_schem" then
if not has_privilege(player) then return end
if fields.delete then
os.remove(player_data[player].schem_for_delete)
end
player_data[player].schem_for_delete = nil
delete_schematics_dialog(player)
end
return false
end)
minetest.register_entity("edit:select_preview", {
initial_properties = {
visual = "cube",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
}
})
minetest.register_entity("edit:preview_base", {
initial_properties = {
visual = "sprite",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
visual_size = {x = 1, y = 1},
textures = {"blank.png"},
}
})
minetest.register_entity("edit:preview_node", {
initial_properties = {
visual = "item",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
visual_size = {x = 0.69, y = 0.69},
glow = -1,
}
})
local function hide_paste_preview(player)
local d = player_data[player]
--d.paste_preview:set_properties({is_visible = false})
-- 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
objref:set_properties({is_visible = false})
end
d.paste_preview:set_attach(player)
player:hud_remove(d.paste_preview_hud)
d.paste_preview_hud = nil
end
local function show_paste_preview(player)
local d = player_data[player]
for _, objref in pairs(d.paste_preview:get_children()) do
objref:set_properties({is_visible = true})
end
d.paste_preview:set_detach()
d.paste_preview_hud = player:hud_add({
hud_elem_type = "text",
text = "Punch (left click) to rotate.",
position = {x = 0.5, y = 0.8},
z_index = 100,
number = 0xffffff
})
-- Minetset bug: set_pos does not get to the client
-- sometimes after showing a ton of children
minetest.after(0.3,
function(objref)
local pos = objref:get_pos()
if pos then objref:set_pos(pos) end
end,
d.paste_preview
)
end
local function hide_select_preview(player)
local d = player_data[player]
d.select_preview_shown = false
d.select_preview:set_properties({is_visible = false})
d.select_preview:set_attach(player)
end
local function set_select_preview_size(preview, size)
local preview_size = vector.add(size, vector.new(0.01, 0.01, 0.01))
local function combine(width, height)
local tex = ""
for x = 0, math.floor(width / 8) do
for y = 0, math.floor(height / 8) do
if #tex > 0 then tex = tex .. ":" end
tex = tex ..
(x * 8 * 16) ..
"," .. (y * 8 * 16) ..
"=edit_select_preview.png"
end
end
return "[combine:" .. (width * 16) .. "x" .. (height * 16) .. ":" .. tex
end
local x_tex = combine(size.z, size.y)
local y_tex = combine(size.x, size.z)
local z_tex = combine(size.x, size.y)
preview:set_properties({
visual_size = preview_size,
textures = {
y_tex, y_tex,
x_tex, x_tex,
z_tex, z_tex
}
})
end
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local item = player:get_wielded_item():get_name()
local d = player_data[player]
-- Paste preview
if item == "edit:paste" and d.schematic then
local pos = pointed_thing_to_pos(get_pointed_thing_node(player))
if pos then
if not d.paste_preview or not d.paste_preview:get_pos() then
create_paste_preview(player)
end
if not d.paste_preview_hud then show_paste_preview(player) end
local old_pos = player_data[player].paste_preview:get_pos()
if not vector.equals(old_pos, pos) then
player_data[player].paste_preview: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
-- Select preview
local node1_pos
local node2_pos
local use_under = false
if item == "edit:fill" and d.fill1_pos then
node1_pos = d.fill1_pos
if d.fill2_pos then node2_pos = d.fill2_pos end
elseif item == "edit:copy" and d.copy_luaentity1 then
node1_pos = d.copy_luaentity1._pos
use_under = true
end
if node1_pos then
if not node2_pos then
local pointed_thing = get_pointed_thing_node(player)
if use_under then
node2_pos = pointed_thing.under
else
node2_pos = pointed_thing_to_pos(pointed_thing)
end
end
if node2_pos then
local diff = vector.subtract(node1_pos, node2_pos)
local size = vector.apply(diff, math.abs)
size = vector.add(size, vector.new(1, 1, 1))
local test = vector.apply(diff, math.abs)
local has_volume = test.x > 1 and test.y > 1 and test.z > 1
local size_too_big = size.x * size.y * size.z > max_operation_volume
if not size_too_big then
if not d.select_preview or not d.select_preview:get_pos() then
d.select_preview = minetest.add_entity(node2_pos, "edit:select_preview")
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_shown = true
end
local preview_pos = vector.add(node2_pos, vector.multiply(diff, 0.5))
local preview = d.select_preview
if not vector.equals(preview_pos, preview:get_pos()) then
preview:set_pos(preview_pos)
set_select_preview_size(preview, size)
end
elseif d.select_preview_shown then hide_select_preview(player) end
elseif d.select_preview_shown then hide_select_preview(player) end
elseif d.select_preview_shown then hide_select_preview(player) end
end
end)
minetest.register_on_joinplayer(function(player)
player_data[player] = {}
end)
minetest.register_on_leaveplayer(function(player)
delete_paste_preview(player)
if player_data[player].select_preview then
player_data[player].select_preview:remove()
end
player_data[player] = nil
end)
edit.modpath = minetest.get_modpath("edit")
dofile(edit.modpath .. "/copy.lua")
dofile(edit.modpath .. "/fill.lua")
dofile(edit.modpath .. "/open.lua")
dofile(edit.modpath .. "/paste.lua")
dofile(edit.modpath .. "/preview.lua")
dofile(edit.modpath .. "/save.lua")
dofile(edit.modpath .. "/schematic.lua")
dofile(edit.modpath .. "/undo.lua")

40
object_rotations.lua Normal file
View File

@ -0,0 +1,40 @@
-- who would have thunked, but rotation for attachd objects works differently to direct object rotation
local rotations = {}
local d = 180
local r = d / 2
rotations.facedir = {
[0] = vector.new(0, 0, 0),
vector.new( 0, r, 0),
vector.new( 0, d, 0),
vector.new( 0, -r, 0),
vector.new( r, 0, 0),
vector.new( r, 0, r),
vector.new( r, 0, d),
vector.new( r, 0, -r),
vector.new(-r, 0, 0),
vector.new(-r, 0, -r),
vector.new(-r, 0, d),
vector.new(-r, 0, r),
vector.new( 0, 0, -r),
vector.new( 0, r, -r),
vector.new( 0, d, -r),
vector.new( 0, -r, -r),
vector.new( 0, 0, r),
vector.new( 0, r, r),
vector.new( 0, d, r),
vector.new( 0, -r, r),
vector.new( 0, 0, d),
vector.new( 0, r, d),
vector.new( 0, d, d),
vector.new( 0, -r, d),
}
-- TODO: signlike nodes always display as an extrusion of texture, like in dropped item form
-- displaying them correctly will require makeing a custom display obect for them
return rotations

178
open.lua Normal file
View File

@ -0,0 +1,178 @@
local function delete_schematics_dialog(player)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "formspec_version[4]size[10,10]" ..
"label[0.5,1;Delete Schematics from:\n" ..
minetest.formspec_escape(path) .. "]button_exit[8.8,0.2;1,1;quit;X]" ..
"textlist[0.5,2;9,7;schems;" .. table.concat(dir_list, ",") .. "]"
edit.reliable_show_formspec(player, "edit:delete_schem", formspec)
end
local function open_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "formspec_version[4]size[10,11]" ..
"label[0.5,1;Load a schematic from:\n" ..
minetest.formspec_escape(path) .. "]button_exit[8.8,0.2;1,1;quit;X]" ..
"textlist[0.5,2;9,7;schems;" .. table.concat(dir_list, ",") .. "]" ..
"button_exit[2,9.5;6,1;delete;Delete schematics...]"
minetest.show_formspec(player:get_player_name(), "edit:open", formspec)
end
minetest.register_tool("edit:open",{
description = "Edit Open",
inventory_image = "edit_open.png",
range = 10,
on_place = open_on_place,
on_secondary_use = open_on_place
})
local function read_minetest_schematic(file_path)
local schematic = minetest.read_schematic(file_path, {})
if schematic then
schematic._meta = {}
schematic._rotation = 0
end
return schematic
end
local function read_world_edit_schematic(file_path)
local f = io.open(file_path)
if not f then return false end
local data = f:read("*all")
f:close()
if not data then return false end
data = data:gsub("^[^:]*:", "")
data = minetest.deserialize(data)
if not data then return false end
-- Get the schematic size
local x_max, y_max, z_max = 0, 0, 0
for i, node in pairs(data) do
local x, y, z = node.x, node.y, node.z
if x > x_max then x_max = x end
if y > y_max then y_max = y end
if z > z_max then z_max = z end
end
local schem_data = {}
local meta = {}
local size = vector.new(x_max + 1, y_max + 1, z_max + 1)
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = size})
for i, node in pairs(data) do
local x, y, z = node.x + 1, node.y + 1, node.z + 1
local index = voxel_area:index(x, y, z)
schem_data[index] = {}
schem_data[index].name = node.name
schem_data[index].param2 = node.param2
if node.meta then
local key = minetest.hash_node_position(vector.new(x, y, z))
meta[key] = node.meta
end
end
-- Replace empty space with air nodes
for i in voxel_area:iterp(start, size) do
if not schem_data[i] then
schem_data[i] = { name = "air" }
end
end
return {
size = size,
data = schem_data,
_meta = meta,
_rotation = 0,
}
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "edit:open" then
minetest.close_formspec(player:get_player_name(), "edit:open")
if
fields.cancel
or not edit.has_privilege(player)
then return true end
if fields.delete then
delete_schematics_dialog(player)
return true
end
if not fields.schems then return end
local index = tonumber(fields.schems:sub(5, #(fields.schems)))
if not index then return true end
index = math.floor(index)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if index > 0 and index <= #dir_list then
local file_path = path .. "/" .. dir_list[index]
local schematic
if file_path:sub(-4, -1) == ".mts" then
schematic = read_minetest_schematic(file_path)
elseif file_path:sub(-3, -1) == ".we" then
schematic = read_world_edit_schematic(file_path)
end
if not schematic then
minetest.chat_send_player(player:get_player_name(),
"\"" .. dir_list[index] .. "\" failed to load" )
return true
end
edit.player_data[player].schematic = schematic
minetest.chat_send_player(player:get_player_name(),
"\"" .. dir_list[index] .. "\" loaded." )
edit.delete_paste_preview(player)
end
return true
elseif formname == "edit:delete_schem" then
if
fields.cancel
or not edit.has_privilege(player)
then return true end
if not fields.schems then return end
local index = tonumber(fields.schems:sub(5, #(fields.schems)))
if not index then return true end
index = math.floor(index)
local path = minetest.get_worldpath() .. "/schems"
local dir_list = minetest.get_dir_list(path)
if index > 0 and index <= #dir_list then
edit.player_data[player].schem_for_delete = path .. "/" .. dir_list[index]
formspec = "formspec_version[4]size[8,3.5]label[0.6,1;Confirm delete \"" ..
dir_list[index] .. "\"]" ..
"button_exit[0.5,2;3,1;delete;Delete]" ..
"button_exit[4.5,2;3,1;quit;Cancel]"
edit.reliable_show_formspec(player, "edit:confirm_delete_schem", formspec)
end
return true
elseif formname == "edit:confirm_delete_schem" then
if not edit.has_privilege(player) then return end
if fields.delete then
os.remove(edit.player_data[player].schem_for_delete)
end
edit.player_data[player].schem_for_delete = nil
delete_schematics_dialog(player)
return true
end
return false
end)

40
paste.lua Normal file
View File

@ -0,0 +1,40 @@
local function paste_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
if not edit.player_data[player].schematic then
minetest.chat_send_player(player:get_player_name(), "Nothing to paste.")
return
end
local d = edit.player_data[player]
local schematic = d.schematic
local pos = edit.pointed_thing_to_pos(pointed_thing)
if not pos then return end
local pos = vector.add(pos, d.schematic_offset)
local size = schematic.size
if schematic._rotation == 90 or schematic._rotation == 270 then
size = vector.new(size.z, size.y, size.x)
end
edit.player_data[player].undo_schematic = edit.schematic_from_map(pos, size)
edit.schematic_to_map(pos, schematic)
end
minetest.register_tool("edit:paste", {
description = "Edit Paste",
tiles = {"edit_paste.png"},
inventory_image = "edit_paste.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3},
range = 10,
on_place = paste_on_place,
on_secondary_use = paste_on_place,
on_use = function(itemstack, player, pointed_thing)
local d = edit.player_data[player]
if not d.schematic then return end
edit.set_schematic_rotation(d.schematic, 90)
edit.rotate_paste_preview(player)
end
})

385
preview.lua Normal file
View File

@ -0,0 +1,385 @@
local param2_to_rotation = dofile(edit.modpath .. "/object_rotations.lua").facedir
minetest.register_entity("edit:place_preview", {
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,
pointable = false,
}
})
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))
local size = d.schematic.size
if rot == 90 or rot == 270 then
size = vector.new(size.z, size.y, size.x)
end
if rot == 0 then
offset = vector.new(-1, -1, -1)
elseif rot == 90 then
offset = vector.new(-1, -1, size.z)
elseif rot == 180 then
offset = vector.new(size.x, -1, size.z)
elseif rot == 270 then
offset = vector.new(size.x, -1, -1)
end
d.paste_preview_offset = offset
end
local function create_paste_preview(player)
local player_pos = player:get_pos()
local base_objref = minetest.add_entity(player_pos, "edit:paste_preview_base")
local schematic = edit.player_data[player].schematic
local vector_1 = vector.new(1, 1, 1)
local size = schematic.size
local voxel_area = VoxelArea:new({MinEdge = vector_1, MaxEdge = size})
local schem_data = schematic.data
local count = size.x * size.y * size.z
local node_black_list = {}
-- Remove air from the schematic preview
for i, map_node in pairs(schem_data) do
if map_node.name == "air" then
count = count - 1
node_black_list[i] = true
end
end
-- Hollow out sold areas in the schematic preview
local strides = {
1, -1,
voxel_area.ystride, -voxel_area.ystride,
voxel_area.zstride, -voxel_area.zstride,
}
if math.min(size.x, size.y, size.z) > 2 then
local start = vector.new(2, 2, 2)
local _end = vector.subtract(size, 1)
for i in voxel_area:iterp(start, _end) do
if not node_black_list[i] then
local include_node = false
for _, n in pairs(strides) do
if schem_data[i + n].name == "air" then
include_node = true
break
end
end
if not include_node then
count = count - 1
node_black_list[i] = true
end
end
end
end
local probability = edit.paste_preview_max_entities / count
for i in voxel_area:iterp(vector_1, size) do
local pos = voxel_area:position(i)
local name = schematic.data[i].name
if not node_black_list[i] and math.random() < probability then
local attach_pos = vector.multiply(pos, 10)
local attach_rot
local objref = minetest.add_entity(player_pos, "edit:preview_node")
objref:set_properties({wield_item = name})
local node_def = minetest.registered_nodes[name]
if node_def and node_def.paramtype2 == "facedir" then
local param2 = schematic.data[i].param2
attach_rot = param2_to_rotation[param2]
end
objref:set_attach(base_objref, "", attach_pos, attach_rot)
end
end
edit.player_data[player].paste_preview = base_objref
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",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
}
})
minetest.register_entity("edit:paste_preview_base", {
initial_properties = {
visual = "cube",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
visual_size = {x = 1, y = 1},
textures = { "blank.png", "blank.png", "blank.png", "blank.png", "blank.png", "blank.png" },
}
})
minetest.register_entity("edit:preview_node", {
initial_properties = {
visual = "item",
physical = false,
pointable = false,
collide_with_objects = false,
static_save = false,
visual_size = { x = 0.68, y = 0.68 },
glow = -1,
}
})
local function hide_paste_preview(player)
local d = edit.player_data[player]
--d.paste_preview:set_properties({is_visible = false})
-- 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
objref:set_properties({is_visible = false})
end
d.paste_preview: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
objref:set_properties({is_visible = true})
end
d.paste_preview:set_detach()
d.paste_preview_hud = player:hud_add({
hud_elem_type = "text",
text = "Punch (left click) to rotate.",
position = {x = 0.5, y = 0.8},
z_index = 100,
number = 0xffffff
})
-- Minetset bug: set_pos does not get to the client
-- sometimes after showing a ton of children
minetest.after(0.3,
function(objref)
local pos = objref:get_pos()
if pos then objref:set_pos(pos) end
end,
d.paste_preview
)
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)
player:hud_remove(d.select_preview_hud)
d.select_preview_hud = nil
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")
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_shown = true
end
local preview = d.select_preview
if vector.equals(pos, preview:get_pos()) then
return
end
preview:set_pos(pos)
local preview_size = vector.add(size, vector.new(0.01, 0.01, 0.01))
local function combine(width, height)
local tex = ""
for x = 0, math.floor(width / 8) do
for y = 0, math.floor(height / 8) do
if #tex > 0 then tex = tex .. ":" end
tex = tex ..
(x * 8 * 16) ..
"," .. (y * 8 * 16) ..
"=edit_select_preview.png"
end
end
return "[combine:" .. (width * 16) .. "x" .. (height * 16) .. ":" .. tex
end
local x_tex = combine(size.z, size.y)
local y_tex = combine(size.x, size.z)
local z_tex = combine(size.x, size.y)
preview:set_properties({
visual_size = preview_size,
textures = {
y_tex, y_tex,
x_tex, x_tex,
z_tex, z_tex
}
})
if not d.select_preview_hud then
d.select_preview_hud = player:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.7},
z_index = 100,
number = 0xffffff
})
end
player:hud_change(
d.select_preview_hud,
"text", "X: " .. size.x .. ", Y: " .. size.y .. ", Z: " .. size.z )
end
local function set_schematic_offset(player)
local d = edit.player_data[player]
local yaw = player:get_look_horizontal()
local offset = vector.new(0, 0, 0)
local rot = d.schematic._rotation
local x_max, z_max
if rot == 90 or rot == 270 then
x_max = -d.schematic.size.z + 1
z_max = -d.schematic.size.x + 1
else
x_max = -d.schematic.size.x + 1
z_max = -d.schematic.size.z + 1
end
if yaw < math.pi then
offset.x = x_max
end
if yaw < math.pi * 1.5 and yaw > math.pi * 0.5 then
offset.z = z_max
end
d.schematic_offset = offset
end
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
local item = player:get_wielded_item():get_name()
local d = edit.player_data[player]
-- Paste preview
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
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()
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)
end
elseif d.paste_preview_hud then hide_paste_preview(player) end
elseif d.paste_preview_hud then hide_paste_preview(player) end
-- Select preview
local node1_pos
local node2_pos
local pointed_pos
local use_under = false
local fill_selected = item == "edit:fill"
local copy_selected = item == "edit:copy"
if fill_selected then
node1_pos = d.fill1_pos
node2_pos = d.fill2_pos
elseif copy_selected then
if d.copy_luaentity1 then
node1_pos = d.copy_luaentity1._pos
end
use_under = true
end
if not node2_pos or not node1_pos then
local pointed_thing = edit.get_pointed_thing_node(player)
if use_under then
pointed_pos = pointed_thing.under
else
pointed_pos = edit.pointed_thing_to_pos(pointed_thing)
end
end
if (fill_selected or copy_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")
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_shown = true
end
if not vector.equals(d.place_preview:get_pos(), pointed_pos) then
d.place_preview:set_pos(pointed_pos)
end
if d.place_preview_item ~= item then
local tex = minetest.registered_items[item].tiles[1] ..
"^[opacity:150"
d.place_preview_item = item
d.place_preview: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_shown = false
end
if not node2_pos then
node2_pos = pointed_pos
end
if node1_pos and node2_pos then
local diff = vector.subtract(node1_pos, node2_pos)
local size = vector.apply(diff, math.abs)
size = vector.add(size, vector.new(1, 1, 1))
local size_too_big = size.x * size.y * size.z > edit.max_operation_volume
if not size_too_big then
local preview_pos = vector.add(node2_pos, vector.multiply(diff, 0.5))
update_select_preview(player, preview_pos, size)
elseif d.select_preview_shown then hide_select_preview(player) end
elseif d.select_preview_shown then hide_select_preview(player) end
end
end)

118
save.lua Normal file
View File

@ -0,0 +1,118 @@
local function show_save_dialog(player, filename, save_error, file_format_index)
if not edit.player_data[player].schematic then
minetest.chat_send_player(player:get_player_name(), "Nothing to save.")
return
end
filename = filename or "untitled"
file_format_index = file_format_index or 1
local path = minetest.get_worldpath() .. "/schems"
if #path > 40 then path = "..." .. path:sub(#path - 40, #path) end
local formspec = "formspec_version[4]size[10,6]label[0.5,1;Save schematic in:\n" ..
minetest.formspec_escape(path) .. "]button_exit[8.8,0.2;1,1;cancel;X]" ..
"field[0.5,2.5;9,1;schem_filename;;" .. filename .. "]" ..
"dropdown[0.5,4;4,1;file_format;WorldEdit (.we),Minetest (.mts);" ..
file_format_index .. ";true]" ..
"button_exit[5.5,4;4,1;save;Save]"
if save_error then
formspec = formspec ..
"label[3,5.5;" .. save_error .. "]"
end
edit.reliable_show_formspec(player, "edit:save", formspec)
end
minetest.register_tool("edit:save", {
description = "Edit Save",
inventory_image = "edit_save.png",
range = 10,
on_place = function(itemstack, player, pointed_thing)
if edit.on_place_checks(player) then show_save_dialog(player) end
end,
on_secondary_use = function(itemstack, player, pointed_thing)
if edit.on_place_checks(player) then show_save_dialog(player) end
end
})
local function serialize_world_edit_schematic(schematic)
local we = {}
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = schematic.size})
local data = schematic.data
local meta = schematic._meta
for i in voxel_area:iterp(start, schematic.size) do
local pos = voxel_area:position(i)
local name = data[i].name
local meta_key = minetest.hash_node_position(pos)
if name ~= "air" then
table.insert(we, {
x = pos.x - 1,
y = pos.y - 1,
z = pos.z - 1,
name = name,
param2 = data[i].param2,
meta = meta[meta_key]
})
end
end
return "5:" .. minetest.serialize(we)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "edit:save" then return false end
minetest.close_formspec(player:get_player_name(), "edit:save")
local schematic = edit.player_data[player].schematic
local schem_filename = fields.schem_filename
if
fields.cancel or
not schem_filename or
not schematic or
not fields.file_format or
not edit.has_privilege(player)
then return end
if not fields.key_enter and not fields.save then
show_save_dialog(player, fields.schem_filename, nil, fields.file_format)
return true
end
local path = minetest.get_worldpath() .. "/schems"
local file_ext = fields.file_format == "1" and ".we" or ".mts"
local schem_filename = schem_filename .. file_ext
local dir_list = minetest.get_dir_list(path)
for _, filename in pairs(dir_list) do
if filename == schem_filename then
show_save_dialog(player, fields.schem_filename,
"\"" .. schem_filename .. "\" already exists.", fields.file_format )
return true
end
end
local data
if file_ext == ".we" then
data = serialize_world_edit_schematic(schematic)
else
data = minetest.serialize_schematic(schematic, "mts", {})
end
if not data then return true end
minetest.mkdir(path)
local schem_path = path .. "/" .. schem_filename
local f = io.open(schem_path, "wb")
if not f then
minetest.chat_send_player(player:get_player_name(), "IO error saving schematic.")
return true
end
f:write(data)
f:close()
minetest.chat_send_player(player:get_player_name(),
"\"" .. schem_filename .. "\" saved." )
return true
end)

121
schematic.lua Normal file
View File

@ -0,0 +1,121 @@
function edit.schematic_from_map(pos, size)
local schematic = {data = {}}
schematic.size = size
schematic._pos = pos
schematic._meta = {}
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = size})
for i in voxel_area:iterp(start, size) do
local offset = voxel_area:position(i)
local node_pos = vector.subtract(vector.add(pos, offset), start)
local node = minetest.get_node(node_pos)
node.param1 = nil
schematic.data[i] = node
local meta = minetest.get_meta(node_pos):to_table()
local has_meta = false
-- Convert metadata item stacks to item strings
for name, inventory in pairs(meta.inventory) do
for i, stack in ipairs(inventory) do
has_meta = true
if stack.to_string then
inventory[i] = stack:to_string()
end
end
end
if meta.fields and next(meta.fields) ~= nil then
has_meta = true
end
if not has_meta then
for k in pairs(meta) do
if k ~= "inventory" and k ~= "fields" then
has_meta = true
break
end
end
end
if has_meta then
local key = minetest.hash_node_position(offset)
schematic._meta[key] = meta
end
end
return schematic
end
function edit.set_schematic_rotation(schematic, angle)
if not schematic._rotation then schematic._rotation = 0 end
schematic._rotation = schematic._rotation + angle
if schematic._rotation < 0 then
schematic._rotation = schematic._rotation + 360
elseif schematic._rotation > 270 then
schematic._rotation = schematic._rotation - 360
end
--[[local old_schematic = player_data[player].schematic
local new_schematic = {data = {}}
player_data[player].schematic = new_schematic
local old_size = old_schematic.size
local new_size
if direction == "L" or direction == "R" then
new_size = vector.new(old_size.z, old_size.y, old_size.x)
elseif direction == "U" or direction == "D" then
new_size = vector.new(old_size.y, old_size.x, old_size.z)
end
new_schematic.size = new_size
local sign = vector.apply(old_schematic._offset, math.sign)
new_schematic._offset = vector.apply(
vector.multiply(new_size, sign),
function(n) return n < 0 and n or 1 end
)
local start = vector.new(1, 1, 1)
local old_voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = old_size})
local new_voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = new_size})
for old_index in old_voxel_area:iterp(start, old_schematic.size) do
local old_pos = old_voxel_area:position(old_index)
local new_pos
local node = old_schematic.data[old_index]
if direction == "L" then
new_pos = vector.new(old_pos.z, old_pos.y, old_size.x - old_pos.x + 1)
elseif direction == "R" then
new_pos = vector.new(old_size.z - old_pos.z + 1, old_pos.y, old_pos.x)
elseif direction == "U" then
new_pos = vector.new(old_pos.y, old_size.x - old_pos.x + 1, old_pos.z)
elseif direction == "D" then
new_pos = vector.new(old_size.y - old_pos.y + 1, old_pos.x, old_pos.z)
end
local new_index = new_voxel_area:indexp(new_pos)
new_schematic.data[new_index] = node
end
delete_paste_preview(player)]]
end
function edit.schematic_to_map(pos, schematic)
minetest.place_schematic(pos, schematic, tostring(schematic._rotation), nil, true)
local size = schematic.size
for hash, metadata in pairs(schematic._meta) do
local offset = minetest.get_position_from_hash(hash)
offset = vector.subtract(offset, 1)
if schematic._rotation == 90 then
offset = vector.new(offset.z, offset.y, size.x - offset.x - 1)
elseif schematic._rotation == 180 then
offset = vector.new(size.x - offset.x - 1, offset.y, size.z - offset.z - 1)
elseif schematic._rotation == 270 then
offset = vector.new(size.z - offset.z - 1, offset.y, offset.x)
end
local node_pos = vector.add(pos, offset)
local meta = minetest.get_meta(node_pos)
meta:from_table(metadata)
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -5,6 +5,8 @@ edit_paste_preview_max_entities (Paste preview max entities) int 2000
# The maximum volume of any edit operation. Increase to allow larger operations.
edit_max_operation_volume (Max edit operation volume) int 20000
# Fast filling of nodes. This uses VoxelManip for fast node placement.
# No node placement callbacks are called so some nodes might be broken.
edit_use_fast_node_fill (Use fast node fill) bool false
# When the fill operation has a larger volume then the specified number, fast node fill will be used.
# To disable fast node placement, set the threshold to be equil to the max operation volume.
# To disable slow node placement, set the threshold to 0.
# With fast node placement, callbacks are not called so some nodes might be broken.
edit_fast_node_fill_threshold (Fast node fill threshold volume) int 2000

19
undo.lua Normal file
View File

@ -0,0 +1,19 @@
local function undo_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
local schem = edit.player_data[player].undo_schematic
if schem then
edit.player_data[player].undo_schematic = edit.schematic_from_map(schem._pos, schem.size)
minetest.place_schematic(schem._pos, schem, nil, nil, true)
else
minetest.chat_send_player(player:get_player_name(), "Nothing to undo.")
end
end
minetest.register_tool("edit:undo", {
description = "Edit Undo",
inventory_image = "edit_undo.png",
range = 10,
on_place = undo_on_place,
on_secondary_use = undo_on_place
})