Version 2.0 update
This commit is contained in:
parent
6d67c9a769
commit
28595814c1
116
LICENSE
116
LICENSE
@ -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
22
LICENSE.txt
Normal 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.
|
@ -94,6 +94,6 @@ Edit tools and nodes can only be used by players with `edit` privilege.
|
|||||||
|
|
||||||
## License
|
## 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.
|
minetest-mods community.
|
||||||
|
|
||||||
|
84
copy.lua
Normal file
84
copy.lua
Normal 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,
|
||||||
|
})
|
BIN
figure1.png
BIN
figure1.png
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
BIN
figure2.png
BIN
figure2.png
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
182
fill.lua
Normal file
182
fill.lua
Normal 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)
|
882
init.lua
882
init.lua
@ -1,118 +1,9 @@
|
|||||||
-------------------
|
edit = {}
|
||||||
-- Edit Mod v1.1 --
|
edit.player_data = {}
|
||||||
-------------------
|
|
||||||
|
|
||||||
local player_data = {}
|
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)
|
||||||
local paste_preview_max_entities = tonumber(minetest.settings:get("edit_paste_preview_max_entities") or 2000)
|
edit.fast_node_fill_threshold = tonumber(minetest.settings:get("edit_fast_node_fill_threshold") 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
|
|
||||||
|
|
||||||
minetest.register_privilege("edit", {
|
minetest.register_privilege("edit", {
|
||||||
description = "Allows usage of edit mod nodes",
|
description = "Allows usage of edit mod nodes",
|
||||||
@ -120,7 +11,7 @@ minetest.register_privilege("edit", {
|
|||||||
give_to_admin = true,
|
give_to_admin = true,
|
||||||
})
|
})
|
||||||
|
|
||||||
local function has_privilege(player)
|
function edit.has_privilege(player)
|
||||||
local name = player:get_player_name()
|
local name = player:get_player_name()
|
||||||
if minetest.check_player_privs(name, {edit = true}) then
|
if minetest.check_player_privs(name, {edit = true}) then
|
||||||
return true
|
return true
|
||||||
@ -130,37 +21,49 @@ local function has_privilege(player)
|
|||||||
end
|
end
|
||||||
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."
|
local msg = "Operation too large. The maximum operation volume can be changed in Minetest settings."
|
||||||
minetest.chat_send_player(player:get_player_name(), msg)
|
minetest.chat_send_player(player:get_player_name(), msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function on_place_checks(player)
|
function edit.on_place_checks(player)
|
||||||
return player and
|
return player and
|
||||||
player:is_player() and
|
player:is_player() and
|
||||||
has_privilege(player)
|
edit.has_privilege(player)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function schematic_from_map(pos, size)
|
function edit.reliable_show_formspec(player, name, formspec)
|
||||||
local schematic = {data = {}}
|
-- We need to do this nonsense because there is bug in Minetest
|
||||||
schematic.size = size
|
-- Sometimes no formspec is shown if you call minetest.show_formspec
|
||||||
schematic._pos = pos
|
-- 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 start = vector.new(1, 1, 1)
|
minetest.register_on_joinplayer(function(player)
|
||||||
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = size})
|
edit.player_data[player] = {
|
||||||
|
schematic_offset = vector.new(0, 0, 0)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
for i in voxel_area:iterp(start, size) do
|
minetest.register_on_leaveplayer(function(player)
|
||||||
local offset = voxel_area:position(i)
|
edit.delete_paste_preview(player)
|
||||||
local node_pos = vector.subtract(vector.add(pos, offset), start)
|
local d = edit.player_data[player]
|
||||||
local node = minetest.get_node(node_pos)
|
if d.select_preview then
|
||||||
node.param1 = nil
|
d.select_preview:remove()
|
||||||
schematic.data[i] = node
|
|
||||||
end
|
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)
|
||||||
|
|
||||||
return schematic
|
function edit.get_pointed_thing_node(player)
|
||||||
end
|
|
||||||
|
|
||||||
local function get_pointed_thing_node(player)
|
|
||||||
local look_dir = player:get_look_dir()
|
local look_dir = player:get_look_dir()
|
||||||
local pos1 = player:get_pos()
|
local pos1 = player:get_pos()
|
||||||
local eye_height = player:get_properties().eye_height
|
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 }
|
return { type = "node", under = pos, above = pos }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function copy_on_place(itemstack, player, pointed_thing)
|
function edit.pointed_thing_to_pos(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)
|
|
||||||
local pos = pointed_thing.under
|
local pos = pointed_thing.under
|
||||||
local node = minetest.get_node_or_nil(pos)
|
local node = minetest.get_node_or_nil(pos)
|
||||||
local def = node and minetest.registered_nodes[node.name]
|
local def = node and minetest.registered_nodes[node.name]
|
||||||
@ -275,620 +95,12 @@ local function pointed_thing_to_pos(pointed_thing)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function paste_on_place(itemstack, player, pointed_thing)
|
edit.modpath = minetest.get_modpath("edit")
|
||||||
if not on_place_checks(player) then return end
|
dofile(edit.modpath .. "/copy.lua")
|
||||||
|
dofile(edit.modpath .. "/fill.lua")
|
||||||
if not pointed_thing.above then
|
dofile(edit.modpath .. "/open.lua")
|
||||||
pointed_thing = get_pointed_thing_node(player)
|
dofile(edit.modpath .. "/paste.lua")
|
||||||
end
|
dofile(edit.modpath .. "/preview.lua")
|
||||||
|
dofile(edit.modpath .. "/save.lua")
|
||||||
if not player_data[player].schematic then
|
dofile(edit.modpath .. "/schematic.lua")
|
||||||
minetest.chat_send_player(player:get_player_name(), "Nothing to paste.")
|
dofile(edit.modpath .. "/undo.lua")
|
||||||
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)
|
|
||||||
|
40
object_rotations.lua
Normal file
40
object_rotations.lua
Normal 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
178
open.lua
Normal 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
40
paste.lua
Normal 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
385
preview.lua
Normal 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
118
save.lua
Normal 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
121
schematic.lua
Normal 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
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 145 KiB |
@ -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.
|
# The maximum volume of any edit operation. Increase to allow larger operations.
|
||||||
edit_max_operation_volume (Max edit operation volume) int 20000
|
edit_max_operation_volume (Max edit operation volume) int 20000
|
||||||
|
|
||||||
# Fast filling of nodes. This uses VoxelManip for fast node placement.
|
# When the fill operation has a larger volume then the specified number, fast node fill will be used.
|
||||||
# No node placement callbacks are called so some nodes might be broken.
|
# To disable fast node placement, set the threshold to be equil to the max operation volume.
|
||||||
edit_use_fast_node_fill (Use fast node fill) bool false
|
# 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
19
undo.lua
Normal 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
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user