mirror of
https://github.com/Uberi/Minetest-WorldEdit.git
synced 2025-06-29 14:40:54 +02:00
Compare commits
11 Commits
1.3
...
new_schems
Author | SHA1 | Date | |
---|---|---|---|
d42ac33d62 | |||
5b19c17117 | |||
4de0eb489f | |||
adc6e00423 | |||
5c06ae59ef | |||
3c5f6b9665 | |||
e387a57e15 | |||
f43bc5278e | |||
2f2f5a7def | |||
7f87f1658e | |||
4378750498 |
@ -640,10 +640,34 @@ function worldedit.clear_objects(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local function should_delete(obj)
|
||||
-- Avoid players and WorldEdit entities
|
||||
if obj:is_player() then
|
||||
return false
|
||||
end
|
||||
local entity = obj:get_luaentity()
|
||||
return not entity or not entity.name:find("^worldedit:")
|
||||
end
|
||||
|
||||
-- Offset positions to include full nodes (positions are in the center of nodes)
|
||||
local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
|
||||
local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
|
||||
|
||||
local count = 0
|
||||
if minetest.get_objects_in_area then
|
||||
local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z},
|
||||
{x=pos2x, y=pos2y, z=pos2z})
|
||||
|
||||
for _, obj in pairs(objects) do
|
||||
if should_delete(obj) then
|
||||
obj:remove()
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- Fallback implementation via get_objects_inside_radius
|
||||
-- Center of region
|
||||
local center = {
|
||||
x = pos1x + ((pos2x - pos1x) / 2),
|
||||
@ -655,12 +679,8 @@ function worldedit.clear_objects(pos1, pos2)
|
||||
(center.x - pos1x) ^ 2 +
|
||||
(center.y - pos1y) ^ 2 +
|
||||
(center.z - pos1z) ^ 2)
|
||||
local count = 0
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
|
||||
local entity = obj:get_luaentity()
|
||||
-- Avoid players and WorldEdit entities
|
||||
if not obj:is_player() and (not entity or
|
||||
not entity.name:find("^worldedit:")) then
|
||||
if should_delete(obj) then
|
||||
local pos = obj:get_pos()
|
||||
if pos.x >= pos1x and pos.x <= pos2x and
|
||||
pos.y >= pos1y and pos.y <= pos2y and
|
||||
|
@ -1,9 +1,7 @@
|
||||
--- Schematic serialization and deserialiation.
|
||||
-- @module worldedit.serialization
|
||||
|
||||
worldedit.LATEST_SERIALIZATION_VERSION = 5
|
||||
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
|
||||
|
||||
worldedit.LATEST_SERIALIZATION_VERSION = 6
|
||||
|
||||
--[[
|
||||
Serialization version history:
|
||||
@ -15,6 +13,7 @@ Serialization version history:
|
||||
`name`, `param1`, `param2`, and `meta` fields.
|
||||
5: Added header and made `param1`, `param2`, and `meta` fields optional.
|
||||
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
|
||||
6: Much more complicated but also better format
|
||||
--]]
|
||||
|
||||
|
||||
@ -66,17 +65,105 @@ function worldedit.serialize(pos1, pos2)
|
||||
has_meta[hash_node_position(meta_positions[i])] = true
|
||||
end
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
-- Decide axis of saved rows
|
||||
local dim = vector.add(vector.subtract(pos2, pos1), 1)
|
||||
local axis
|
||||
if dim.x * dim.y < math.min(dim.y * dim.z, dim.x * dim.z) then
|
||||
axis = "z"
|
||||
elseif dim.x * dim.z < math.min(dim.x * dim.y, dim.y * dim.z) then
|
||||
axis = "y"
|
||||
elseif dim.y * dim.z < math.min(dim.x * dim.y, dim.x * dim.z) then
|
||||
axis = "x"
|
||||
else
|
||||
axis = "x" -- X or Z are usually most efficient
|
||||
end
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
|
||||
-- Helper functions (1)
|
||||
local MATCH_DIST = 8
|
||||
local function match_init(array, first_value)
|
||||
array[1] = first_value
|
||||
return {first_value}
|
||||
end
|
||||
local function match_try(cache, prev_pushed, value)
|
||||
local i = #cache
|
||||
while i >= 1 do
|
||||
if cache[i] == value then
|
||||
local ret = -(#cache - i + 1)
|
||||
local was_value = type(prev_pushed) ~= "number" or prev_pushed >= 0
|
||||
return ret, (was_value and ret == -1) or prev_pushed == ret
|
||||
end
|
||||
i = i - 1
|
||||
end
|
||||
return nil, false
|
||||
end
|
||||
local function match_push(cache, match, value)
|
||||
if match ~= nil then -- don't advance cache
|
||||
return match
|
||||
end
|
||||
local idx = #cache + 1
|
||||
cache[idx] = value
|
||||
if idx > MATCH_DIST then
|
||||
table.remove(cache, 1)
|
||||
end
|
||||
return value
|
||||
end
|
||||
-- Helper functions (2)
|
||||
local function cur_new(pos, pos1)
|
||||
return {
|
||||
a = axis,
|
||||
p = {pos.x - pos1.x, pos.y - pos1.y, pos.z - pos1.z},
|
||||
c = 1,
|
||||
data = {},
|
||||
param1 = {},
|
||||
param2 = {},
|
||||
meta = {},
|
||||
}
|
||||
end
|
||||
local function is_emptyish(t)
|
||||
-- returns true if <t> contains only one element and that one element is == 0
|
||||
local seen = false
|
||||
for _, value in pairs(t) do
|
||||
if not seen then
|
||||
if value ~= 0 then
|
||||
return false
|
||||
end
|
||||
seen = true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
local function cur_finish(result, cur)
|
||||
if is_emptyish(cur.param1) then
|
||||
cur.param1 = nil
|
||||
end
|
||||
if is_emptyish(cur.param2) then
|
||||
cur.param2 = nil
|
||||
end
|
||||
if next(cur.meta) == nil then
|
||||
cur.meta = nil
|
||||
end
|
||||
result[#result + 1] = cur
|
||||
end
|
||||
|
||||
-- Serialize stuff
|
||||
local pos = {}
|
||||
local count = 0
|
||||
local result = {}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local cur
|
||||
local cache_data, cache_param1, cache_param2
|
||||
local prev_data, prev_param1, prev_param2
|
||||
pos[other1] = pos1[other1]
|
||||
while pos[other1] <= pos2[other1] do
|
||||
pos[other2] = pos1[other2]
|
||||
while pos[other2] <= pos2[other2] do
|
||||
pos[axis] = pos1[axis]
|
||||
while pos[axis] <= pos2[axis] do
|
||||
|
||||
local node = get_node(pos)
|
||||
if node.name ~= "air" and node.name ~= "ignore" then
|
||||
count = count + 1
|
||||
|
||||
local meta
|
||||
if has_meta[hash_node_position(pos)] then
|
||||
@ -93,32 +180,75 @@ function worldedit.serialize(pos1, pos2)
|
||||
end
|
||||
end
|
||||
|
||||
result[count] = {
|
||||
x = pos.x - pos1.x,
|
||||
y = pos.y - pos1.y,
|
||||
z = pos.z - pos1.z,
|
||||
name = node.name,
|
||||
param1 = node.param1 ~= 0 and node.param1 or nil,
|
||||
param2 = node.param2 ~= 0 and node.param2 or nil,
|
||||
meta = meta,
|
||||
}
|
||||
if cur == nil then -- Start a new row
|
||||
cur = cur_new(pos, pos1, axis, other1, other2)
|
||||
|
||||
cache_data = match_init(cur.data, node.name)
|
||||
cache_param1 = match_init(cur.param1, node.param1)
|
||||
cache_param2 = match_init(cur.param2, node.param2)
|
||||
prev_data = cur.data[1]
|
||||
prev_param1 = cur.param1[1]
|
||||
prev_param2 = cur.param2[1]
|
||||
|
||||
cur.meta[1] = meta
|
||||
else -- Append to existing row
|
||||
local next_c = cur.c + 1
|
||||
cur.c = next_c
|
||||
local value, m, can_omit
|
||||
|
||||
value = node.name
|
||||
m, can_omit = match_try(cache_data, prev_data, node.name)
|
||||
if not can_omit then
|
||||
prev_data = match_push(cache_data, m, value)
|
||||
cur.data[next_c] = prev_data
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
|
||||
value = node.param1
|
||||
m, can_omit = match_try(cache_param1, prev_param1, value)
|
||||
if not can_omit then
|
||||
prev_param1 = match_push(cache_param1, m, value)
|
||||
cur.param1[next_c] = prev_param1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
|
||||
value = node.param2
|
||||
m, can_omit = match_try(cache_param2, prev_param2, value)
|
||||
if not can_omit then
|
||||
prev_param2 = match_push(cache_param2, m, value)
|
||||
cur.param2[next_c] = prev_param2
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
|
||||
cur.meta[next_c] = meta
|
||||
end
|
||||
count = count + 1
|
||||
else
|
||||
if cur ~= nil then -- Finish row
|
||||
cur_finish(result, cur)
|
||||
cur = nil
|
||||
end
|
||||
end
|
||||
pos[axis] = pos[axis] + 1
|
||||
|
||||
end
|
||||
if cur ~= nil then -- Finish leftover row
|
||||
cur_finish(result, cur)
|
||||
cur = nil
|
||||
end
|
||||
pos[other2] = pos[other2] + 1
|
||||
end
|
||||
pos[other1] = pos[other1] + 1
|
||||
end
|
||||
|
||||
-- Serialize entries
|
||||
result = minetest.serialize(result)
|
||||
return LATEST_SERIALIZATION_HEADER .. result, count
|
||||
return tonumber(worldedit.LATEST_SERIALIZATION_VERSION) .. "," ..
|
||||
string.format("%d,%d,%d:", dim.x, dim.y, dim.z) .. result, count
|
||||
end
|
||||
|
||||
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
|
||||
-- by ChillCode, available under the MIT license.
|
||||
local function deserialize_workaround(content)
|
||||
local nodes
|
||||
if not jit then
|
||||
if not minetest.global_exists("jit") then
|
||||
nodes = minetest.deserialize(content, true)
|
||||
else
|
||||
-- XXX: This is a filthy hack that works surprisingly well
|
||||
@ -147,8 +277,7 @@ end
|
||||
|
||||
--- Loads the schematic in `value` into a node list in the latest format.
|
||||
-- @return A node list in the latest format, or nil on failure.
|
||||
local function load_schematic(value)
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
local function legacy_load_schematic(version, header, content)
|
||||
local nodes = {}
|
||||
if version == 1 or version == 2 then -- Original flat table format
|
||||
local tables = minetest.deserialize(content, true)
|
||||
@ -190,6 +319,8 @@ local function load_schematic(value)
|
||||
end
|
||||
elseif version == 4 or version == 5 then -- Nested table format
|
||||
nodes = deserialize_workaround(content)
|
||||
elseif version >= 6 then
|
||||
error("legacy_load_schematic called for non-legacy schematic")
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@ -202,14 +333,29 @@ end
|
||||
-- @return High corner position.
|
||||
-- @return The number of nodes.
|
||||
function worldedit.allocate(origin_pos, value)
|
||||
local nodes = load_schematic(value)
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
if version == 6 then
|
||||
local content = deserialize_workaround(content)
|
||||
local pos2 = {
|
||||
x = origin_pos.x + tonumber(header[1]) - 1,
|
||||
y = origin_pos.y + tonumber(header[2]) - 1,
|
||||
z = origin_pos.z + tonumber(header[3]) - 1,
|
||||
}
|
||||
local count = 0
|
||||
for _, row in ipairs(content) do
|
||||
count = count + row.c
|
||||
end
|
||||
return origin_pos, pos2, count
|
||||
else
|
||||
local nodes = legacy_load_schematic(version, header, content)
|
||||
if not nodes or #nodes == 0 then return nil end
|
||||
return worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
return worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Internal
|
||||
function worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
function worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
|
||||
local huge = math.huge
|
||||
local pos1x, pos1y, pos1z = huge, huge, huge
|
||||
local pos2x, pos2y, pos2z = -huge, -huge, -huge
|
||||
@ -232,11 +378,22 @@ end
|
||||
--- Loads the nodes represented by string `value` at position `origin_pos`.
|
||||
-- @return The number of nodes deserialized.
|
||||
function worldedit.deserialize(origin_pos, value)
|
||||
local nodes = load_schematic(value)
|
||||
if not nodes then return nil end
|
||||
if #nodes == 0 then return #nodes end
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
if version == 6 then
|
||||
local content = deserialize_workaround(content)
|
||||
local pos2 = {
|
||||
x = origin_pos.x + tonumber(header[1]) - 1,
|
||||
y = origin_pos.y + tonumber(header[2]) - 1,
|
||||
z = origin_pos.z + tonumber(header[3]) - 1,
|
||||
}
|
||||
worldedit.keep_loaded(origin_pos, pos2)
|
||||
|
||||
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
return worldedit.deserialize_with_content(origin_pos, content)
|
||||
else
|
||||
local nodes = legacy_load_schematic(version, header, content)
|
||||
if not nodes or #nodes == 0 then return nil end
|
||||
|
||||
local pos1, pos2 = worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
|
||||
@ -251,5 +408,80 @@ function worldedit.deserialize(origin_pos, value)
|
||||
end
|
||||
end
|
||||
return #nodes
|
||||
end
|
||||
end
|
||||
|
||||
-- Internal
|
||||
function worldedit.deserialize_with_content(origin_pos, content)
|
||||
-- Helper functions
|
||||
local function resolve_refs(array)
|
||||
-- find (and cache) highest index
|
||||
local max_i = 1
|
||||
for i, _ in pairs(array) do
|
||||
if i > max_i then max_i = i end
|
||||
end
|
||||
array.max_i = max_i
|
||||
-- resolve references
|
||||
local cache = {}
|
||||
for i = 1, max_i do
|
||||
local v = array[i]
|
||||
if v ~= nil then
|
||||
if type(v) == "number" and v < 0 then -- is a reference
|
||||
array[i] = cache[#cache + v + 1]
|
||||
else
|
||||
cache[#cache + 1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function read_in_array(array, idx)
|
||||
if idx > array.max_i then
|
||||
return array[array.max_i]
|
||||
end
|
||||
-- go backwards until we find something
|
||||
repeat
|
||||
local v = array[idx]
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
idx = idx - 1
|
||||
until idx == 0
|
||||
assert(false)
|
||||
end
|
||||
|
||||
-- Actually deserialize
|
||||
local count = 0
|
||||
local entry = {}
|
||||
local add_node, get_meta = minetest.add_node, minetest.get_meta
|
||||
for _, row in ipairs(content) do
|
||||
local axis = row.a
|
||||
local pos = {
|
||||
x = origin_pos.x + row.p[1],
|
||||
y = origin_pos.y + row.p[2],
|
||||
z = origin_pos.z + row.p[3],
|
||||
}
|
||||
if row.param1 == nil then row.param1 = {0} end
|
||||
if row.param2 == nil then row.param2 = {0} end
|
||||
if row.meta == nil then row.meta = {} end
|
||||
resolve_refs(row.data)
|
||||
resolve_refs(row.param1)
|
||||
resolve_refs(row.param2)
|
||||
|
||||
for i = 1, row.c do
|
||||
entry.name = read_in_array(row.data, i)
|
||||
entry.param1 = read_in_array(row.param1, i)
|
||||
entry.param2 = read_in_array(row.param2, i)
|
||||
add_node(pos, entry)
|
||||
|
||||
local meta = row.meta[i]
|
||||
if meta then
|
||||
get_meta(pos):from_table(meta)
|
||||
end
|
||||
|
||||
pos[axis] = pos[axis] + 1
|
||||
end
|
||||
|
||||
count = count + row.c
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
@ -1343,23 +1343,6 @@ worldedit.register_command("restore", {
|
||||
end,
|
||||
})
|
||||
|
||||
local function detect_misaligned_schematic(name, pos1, pos2)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
-- Check that allocate/save can position the schematic correctly
|
||||
-- The expected behaviour is that the (0,0,0) corner of the schematic stays
|
||||
-- sat pos1, this only works when the minimum position is actually present
|
||||
-- in the schematic.
|
||||
local node = minetest.get_node(pos1)
|
||||
local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
|
||||
if not have_node_at_origin then
|
||||
worldedit.player_notify(name,
|
||||
"Warning: The schematic contains excessive free space and WILL be "..
|
||||
"misaligned when allocated or loaded. To avoid this, shrink your "..
|
||||
"area to cover exactly the nodes to be saved."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
worldedit.register_command("save", {
|
||||
params = "<file>",
|
||||
description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
|
||||
@ -1378,7 +1361,6 @@ worldedit.register_command("save", {
|
||||
func = function(name, param)
|
||||
local result, count = worldedit.serialize(worldedit.pos1[name],
|
||||
worldedit.pos2[name])
|
||||
detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
|
||||
|
||||
local path = minetest.get_worldpath() .. "/schems"
|
||||
-- Create directory if it does not already exist
|
||||
|
@ -11,6 +11,7 @@ local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings
|
||||
local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
|
||||
local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise)
|
||||
local gui_filename = {} --mapping of player names to file names
|
||||
local gui_param2 = {} --mapping of player names to param2 values
|
||||
|
||||
--set default values
|
||||
setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end})
|
||||
@ -25,6 +26,7 @@ setmetatable(gui_count2, {__index = function() return "6" end})
|
||||
setmetatable(gui_count3, {__index = function() return "4" end})
|
||||
setmetatable(gui_angle, {__index = function() return 90 end})
|
||||
setmetatable(gui_filename, {__index = function() return "building" end})
|
||||
setmetatable(gui_param2, {__index = function() return "0" end})
|
||||
|
||||
local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4}
|
||||
local axis_values = {"x", "y", "z", "?"}
|
||||
@ -904,3 +906,31 @@ worldedit.register_gui_function("worldedit_gui_clearobjects", {
|
||||
execute_worldedit_command("clearobjects", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_param2", {
|
||||
name = "Set Param2",
|
||||
privs = we_privs("param2"),
|
||||
get_formspec = function(name)
|
||||
local value = gui_param2[name] or "0"
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_param2") ..
|
||||
"textarea[0.5,1;5,2;;;Some values may break the node!]"..
|
||||
string.format("field[0.5,2.5;2,0.8;worldedit_gui_param2_value;New Param2;%s]", minetest.formspec_escape(value)) ..
|
||||
"field_close_on_enter[worldedit_gui_param2_value;false]" ..
|
||||
"button_exit[3.5,2.5;3,0.8;worldedit_gui_param2_submit;Set Param2]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_param2", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_param2_value = gui_param2,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_param2", fields, cg)
|
||||
if fields.worldedit_gui_param2_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_param2")
|
||||
|
||||
execute_worldedit_command("param2", name, gui_param2[name])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
Reference in New Issue
Block a user