mirror of
https://github.com/Uberi/Minetest-WorldEdit.git
synced 2025-07-04 00:51:04 +02:00
Compare commits
8 Commits
abc9efeeb8
...
new_schems
Author | SHA1 | Date | |
---|---|---|---|
d42ac33d62 | |||
5b19c17117 | |||
4de0eb489f | |||
adc6e00423 | |||
5c06ae59ef | |||
3c5f6b9665 | |||
e387a57e15 | |||
f43bc5278e |
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
name: test
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Run tests
|
|
||||||
run: MINETEST_VER=latest ./.util/run_tests.sh
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
tempdir=/tmp/mt
|
|
||||||
confpath=$tempdir/minetest.conf
|
|
||||||
worldpath=$tempdir/world
|
|
||||||
|
|
||||||
use_docker=y
|
|
||||||
[ -x ../../bin/minetestserver ] && use_docker=
|
|
||||||
|
|
||||||
rm -rf $tempdir
|
|
||||||
mkdir -p $worldpath
|
|
||||||
# the docker image doesn't have devtest
|
|
||||||
[ -n "$use_docker" ] || printf '%s\n' gameid=devtest >$worldpath/world.mt
|
|
||||||
printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt
|
|
||||||
printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath
|
|
||||||
|
|
||||||
if [ -n "$use_docker" ]; then
|
|
||||||
chmod -R 777 $tempdir
|
|
||||||
docker run --rm -i \
|
|
||||||
-v $confpath:/etc/minetest/minetest.conf \
|
|
||||||
-v $tempdir:/var/lib/minetest/.minetest \
|
|
||||||
-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit \
|
|
||||||
registry.gitlab.com/minetest/minetest/server:${MINETEST_VER}
|
|
||||||
else
|
|
||||||
mkdir $worldpath/worldmods
|
|
||||||
ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit
|
|
||||||
../../bin/minetestserver --config $confpath --world $worldpath --logfile /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
test -f $worldpath/tests_ok || exit 1
|
|
||||||
exit 0
|
|
@ -23,7 +23,7 @@ There is a nice installation guide over at the [Minetest Wiki](http://wiki.minet
|
|||||||
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button.
|
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button.
|
||||||
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
|
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
|
||||||
|
|
||||||
If you are having trouble, try asking for help in the [IRC channel](https://web.libera.chat/#minetest) (faster but may not always have helpers online)
|
If you are having trouble, try asking for help in the [IRC channel](https://webchat.freenode.net/?channels=#minetest) (faster but may not always have helpers online)
|
||||||
or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help).
|
or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help).
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
|
@ -38,7 +38,3 @@ if minetest.settings:get_bool("log_mods") then
|
|||||||
print("[WorldEdit] Loaded!")
|
print("[WorldEdit] Loaded!")
|
||||||
end
|
end
|
||||||
|
|
||||||
if minetest.settings:get_bool("worldedit_run_tests") then
|
|
||||||
dofile(path .. "/test.lua")
|
|
||||||
minetest.after(0, worldedit.run_tests)
|
|
||||||
end
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
--- Schematic serialization and deserialiation.
|
--- Schematic serialization and deserialiation.
|
||||||
-- @module worldedit.serialization
|
-- @module worldedit.serialization
|
||||||
|
|
||||||
worldedit.LATEST_SERIALIZATION_VERSION = 5
|
worldedit.LATEST_SERIALIZATION_VERSION = 6
|
||||||
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
|
|
||||||
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Serialization version history:
|
Serialization version history:
|
||||||
@ -15,6 +13,7 @@ Serialization version history:
|
|||||||
`name`, `param1`, `param2`, and `meta` fields.
|
`name`, `param1`, `param2`, and `meta` fields.
|
||||||
5: Added header and made `param1`, `param2`, and `meta` fields optional.
|
5: Added header and made `param1`, `param2`, and `meta` fields optional.
|
||||||
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
|
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
|
has_meta[hash_node_position(meta_positions[i])] = true
|
||||||
end
|
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 count = 0
|
||||||
local result = {}
|
local result = {}
|
||||||
while pos.x <= pos2.x do
|
local cur
|
||||||
pos.y = pos1.y
|
local cache_data, cache_param1, cache_param2
|
||||||
while pos.y <= pos2.y do
|
local prev_data, prev_param1, prev_param2
|
||||||
pos.z = pos1.z
|
pos[other1] = pos1[other1]
|
||||||
while pos.z <= pos2.z do
|
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)
|
local node = get_node(pos)
|
||||||
if node.name ~= "air" and node.name ~= "ignore" then
|
if node.name ~= "air" and node.name ~= "ignore" then
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
local meta
|
local meta
|
||||||
if has_meta[hash_node_position(pos)] then
|
if has_meta[hash_node_position(pos)] then
|
||||||
@ -93,36 +180,76 @@ function worldedit.serialize(pos1, pos2)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
result[count] = {
|
if cur == nil then -- Start a new row
|
||||||
x = pos.x - pos1.x,
|
cur = cur_new(pos, pos1, axis, other1, other2)
|
||||||
y = pos.y - pos1.y,
|
|
||||||
z = pos.z - pos1.z,
|
cache_data = match_init(cur.data, node.name)
|
||||||
name = node.name,
|
cache_param1 = match_init(cur.param1, node.param1)
|
||||||
param1 = node.param1 ~= 0 and node.param1 or nil,
|
cache_param2 = match_init(cur.param2, node.param2)
|
||||||
param2 = node.param2 ~= 0 and node.param2 or nil,
|
prev_data = cur.data[1]
|
||||||
meta = meta,
|
prev_param1 = cur.param1[1]
|
||||||
}
|
prev_param2 = cur.param2[1]
|
||||||
end
|
|
||||||
pos.z = pos.z + 1
|
cur.meta[1] = meta
|
||||||
end
|
else -- Append to existing row
|
||||||
pos.y = pos.y + 1
|
local next_c = cur.c + 1
|
||||||
end
|
cur.c = next_c
|
||||||
pos.x = pos.x + 1
|
local value, m, can_omit
|
||||||
end
|
|
||||||
-- Serialize entries
|
value = node.name
|
||||||
result = minetest.serialize(result)
|
m, can_omit = match_try(cache_data, prev_data, node.name)
|
||||||
return LATEST_SERIALIZATION_HEADER .. result, count
|
if not can_omit then
|
||||||
|
prev_data = match_push(cache_data, m, value)
|
||||||
|
cur.data[next_c] = prev_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 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 function deserialize_workaround(content)
|
||||||
local nodes
|
local nodes
|
||||||
if not minetest.global_exists("jit") then
|
if not minetest.global_exists("jit") then
|
||||||
nodes = minetest.deserialize(content, true)
|
nodes = minetest.deserialize(content, true)
|
||||||
elseif not content:match("^%s*return%s*{") then
|
|
||||||
-- The data doesn't look like we expect it to so we can't apply the workaround.
|
|
||||||
-- hope for the best
|
|
||||||
minetest.log("warning", "WorldEdit: deserializing data but can't apply LuaJIT workaround")
|
|
||||||
nodes = minetest.deserialize(content, true)
|
|
||||||
else
|
else
|
||||||
-- XXX: This is a filthy hack that works surprisingly well
|
-- XXX: This is a filthy hack that works surprisingly well
|
||||||
-- in LuaJIT, `minetest.deserialize` will fail due to the register limit
|
-- in LuaJIT, `minetest.deserialize` will fail due to the register limit
|
||||||
@ -133,7 +260,7 @@ local function deserialize_workaround(content)
|
|||||||
local startpos, startpos1 = 1, 1
|
local startpos, startpos1 = 1, 1
|
||||||
local endpos
|
local endpos
|
||||||
while true do -- go through each individual node entry (except the last)
|
while true do -- go through each individual node entry (except the last)
|
||||||
startpos, endpos = escaped:find("}%s*,%s*{", startpos)
|
startpos, endpos = escaped:find("},%s*{", startpos)
|
||||||
if not startpos then
|
if not startpos then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -150,8 +277,7 @@ end
|
|||||||
|
|
||||||
--- Loads the schematic in `value` into a node list in the latest format.
|
--- 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.
|
-- @return A node list in the latest format, or nil on failure.
|
||||||
local function load_schematic(value)
|
local function legacy_load_schematic(version, header, content)
|
||||||
local version, header, content = worldedit.read_header(value)
|
|
||||||
local nodes = {}
|
local nodes = {}
|
||||||
if version == 1 or version == 2 then -- Original flat table format
|
if version == 1 or version == 2 then -- Original flat table format
|
||||||
local tables = minetest.deserialize(content, true)
|
local tables = minetest.deserialize(content, true)
|
||||||
@ -193,6 +319,8 @@ local function load_schematic(value)
|
|||||||
end
|
end
|
||||||
elseif version == 4 or version == 5 then -- Nested table format
|
elseif version == 4 or version == 5 then -- Nested table format
|
||||||
nodes = deserialize_workaround(content)
|
nodes = deserialize_workaround(content)
|
||||||
|
elseif version >= 6 then
|
||||||
|
error("legacy_load_schematic called for non-legacy schematic")
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@ -205,14 +333,29 @@ end
|
|||||||
-- @return High corner position.
|
-- @return High corner position.
|
||||||
-- @return The number of nodes.
|
-- @return The number of nodes.
|
||||||
function worldedit.allocate(origin_pos, value)
|
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
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
-- Internal
|
-- Internal
|
||||||
function worldedit.allocate_with_nodes(origin_pos, nodes)
|
function worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
|
||||||
local huge = math.huge
|
local huge = math.huge
|
||||||
local pos1x, pos1y, pos1z = huge, huge, huge
|
local pos1x, pos1y, pos1z = huge, huge, huge
|
||||||
local pos2x, pos2y, pos2z = -huge, -huge, -huge
|
local pos2x, pos2y, pos2z = -huge, -huge, -huge
|
||||||
@ -235,11 +378,22 @@ end
|
|||||||
--- Loads the nodes represented by string `value` at position `origin_pos`.
|
--- Loads the nodes represented by string `value` at position `origin_pos`.
|
||||||
-- @return The number of nodes deserialized.
|
-- @return The number of nodes deserialized.
|
||||||
function worldedit.deserialize(origin_pos, value)
|
function worldedit.deserialize(origin_pos, value)
|
||||||
local nodes = load_schematic(value)
|
local version, header, content = worldedit.read_header(value)
|
||||||
if not nodes then return nil end
|
if version == 6 then
|
||||||
if #nodes == 0 then return #nodes end
|
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)
|
worldedit.keep_loaded(pos1, pos2)
|
||||||
|
|
||||||
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
|
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
|
||||||
@ -255,4 +409,79 @@ function worldedit.deserialize(origin_pos, value)
|
|||||||
end
|
end
|
||||||
return #nodes
|
return #nodes
|
||||||
end
|
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
|
||||||
|
@ -1,448 +0,0 @@
|
|||||||
---------------------
|
|
||||||
-- Helpers
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
local vec = vector.new
|
|
||||||
local vecw = function(axis, n, base)
|
|
||||||
local ret = vec(base)
|
|
||||||
ret[axis] = n
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
local pos2str = minetest.pos_to_string
|
|
||||||
local get_node = minetest.get_node
|
|
||||||
local set_node = minetest.set_node
|
|
||||||
|
|
||||||
---------------------
|
|
||||||
-- Nodes
|
|
||||||
---------------------
|
|
||||||
local air = "air"
|
|
||||||
local testnode1
|
|
||||||
local testnode2
|
|
||||||
local testnode3
|
|
||||||
-- Loads nodenames to use for tests
|
|
||||||
local function init_nodes()
|
|
||||||
testnode1 = minetest.registered_aliases["mapgen_stone"]
|
|
||||||
testnode2 = minetest.registered_aliases["mapgen_dirt"]
|
|
||||||
testnode3 = minetest.registered_aliases["mapgen_cobble"] or minetest.registered_aliases["mapgen_dirt_with_grass"]
|
|
||||||
assert(testnode1 and testnode2 and testnode3)
|
|
||||||
end
|
|
||||||
-- Writes repeating pattern into given area
|
|
||||||
local function place_pattern(pos1, pos2, pattern)
|
|
||||||
local pos = vec()
|
|
||||||
local node = {name=""}
|
|
||||||
local i = 1
|
|
||||||
for z = pos1.z, pos2.z do
|
|
||||||
pos.z = z
|
|
||||||
for y = pos1.y, pos2.y do
|
|
||||||
pos.y = y
|
|
||||||
for x = pos1.x, pos2.x do
|
|
||||||
pos.x = x
|
|
||||||
node.name = pattern[i]
|
|
||||||
set_node(pos, node)
|
|
||||||
i = i % #pattern + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---------------------
|
|
||||||
-- Area management
|
|
||||||
---------------------
|
|
||||||
assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
|
|
||||||
local area = {}
|
|
||||||
do
|
|
||||||
local areamin, areamax
|
|
||||||
local off
|
|
||||||
local c_air = minetest.get_content_id(air)
|
|
||||||
local vbuffer = {}
|
|
||||||
-- Assign a new area for use, will emerge and then call ready()
|
|
||||||
area.assign = function(min, max, ready)
|
|
||||||
areamin = min
|
|
||||||
areamax = max
|
|
||||||
minetest.emerge_area(min, max, function(bpos, action, remaining)
|
|
||||||
assert(action ~= minetest.EMERGE_ERRORED)
|
|
||||||
if remaining > 0 then return end
|
|
||||||
minetest.after(0, function()
|
|
||||||
area.clear()
|
|
||||||
ready()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
-- Reset area contents and state
|
|
||||||
area.clear = function()
|
|
||||||
local vmanip = minetest.get_voxel_manip(areamin, areamax)
|
|
||||||
local vpos1, vpos2 = vmanip:get_emerged_area()
|
|
||||||
local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
|
|
||||||
if #vbuffer ~= vcount then
|
|
||||||
vbuffer = {}
|
|
||||||
for i = 1, vcount do
|
|
||||||
vbuffer[i] = c_air
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vmanip:set_data(vbuffer)
|
|
||||||
vmanip:write_to_map()
|
|
||||||
off = vec(0, 0, 0)
|
|
||||||
end
|
|
||||||
-- Returns an usable area [pos1, pos2] that does not overlap previous ones
|
|
||||||
area.get = function(sizex, sizey, sizez)
|
|
||||||
local size
|
|
||||||
if sizey == nil or sizez == nil then
|
|
||||||
size = {x=sizex, y=sizex, z=sizex}
|
|
||||||
else
|
|
||||||
size = {x=sizex, y=sizey, z=sizez}
|
|
||||||
end
|
|
||||||
local pos1 = vector.add(areamin, off)
|
|
||||||
local pos2 = vector.subtract(vector.add(pos1, size), 1)
|
|
||||||
if pos2.x > areamax.x or pos2.y > areamax.y or pos2.z > areamax.z then
|
|
||||||
error("Internal failure: out of space")
|
|
||||||
end
|
|
||||||
off = vector.add(off, size)
|
|
||||||
return pos1, pos2
|
|
||||||
end
|
|
||||||
-- Returns an axis and count (= n) relative to the last-requested area that is unoccupied
|
|
||||||
area.dir = function(n)
|
|
||||||
local pos1 = vector.add(areamin, off)
|
|
||||||
if pos1.x + n <= areamax.x then
|
|
||||||
off.x = off.x + n
|
|
||||||
return "x", n
|
|
||||||
elseif pos1.x + n <= areamax.y then
|
|
||||||
off.y = off.y + n
|
|
||||||
return "y", n
|
|
||||||
elseif pos1.z + n <= areamax.z then
|
|
||||||
off.z = off.z + n
|
|
||||||
return "z", n
|
|
||||||
end
|
|
||||||
error("Internal failure: out of space")
|
|
||||||
end
|
|
||||||
-- Returns [XYZ] margin (list of pos pairs) of n around last-requested area
|
|
||||||
-- (may actually be larger but doesn't matter)
|
|
||||||
area.margin = function(n)
|
|
||||||
local pos1, pos2 = area.get(n)
|
|
||||||
return {
|
|
||||||
{ vec(areamin.x, areamin.y, pos1.z), pos2 }, -- X/Y
|
|
||||||
{ vec(areamin.x, pos1.y, areamin.z), pos2 }, -- X/Z
|
|
||||||
{ vec(pos1.x, areamin.y, areamin.z), pos2 }, -- Y/Z
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Split an existing area into two non-overlapping [pos1, half1], [half2, pos2] parts; returns half1, half2
|
|
||||||
area.split = function(pos1, pos2)
|
|
||||||
local axis
|
|
||||||
if pos2.x - pos1.x >= 1 then
|
|
||||||
axis = "x"
|
|
||||||
elseif pos2.y - pos1.y >= 1 then
|
|
||||||
axis = "y"
|
|
||||||
elseif pos2.z - pos1.z >= 1 then
|
|
||||||
axis = "z"
|
|
||||||
else
|
|
||||||
error("Internal failure: area too small to split")
|
|
||||||
end
|
|
||||||
local hspan = math.floor((pos2[axis] - pos1[axis] + 1) / 2)
|
|
||||||
local half1 = vecw(axis, pos1[axis] + hspan - 1, pos2)
|
|
||||||
local half2 = vecw(axis, pos1[axis] + hspan, pos2)
|
|
||||||
return half1, half2
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---------------------
|
|
||||||
-- Checks
|
|
||||||
---------------------
|
|
||||||
local check = {}
|
|
||||||
-- Check that all nodes in [pos1, pos2] are the node(s) specified
|
|
||||||
check.filled = function(pos1, pos2, nodes)
|
|
||||||
if type(nodes) == "string" then
|
|
||||||
nodes = { nodes }
|
|
||||||
end
|
|
||||||
local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
|
|
||||||
local total = worldedit.volume(pos1, pos2)
|
|
||||||
local sum = 0
|
|
||||||
for _, n in pairs(counts) do
|
|
||||||
sum = sum + n
|
|
||||||
end
|
|
||||||
if sum ~= total then
|
|
||||||
error((total - sum) .. " " .. table.concat(nodes, ",") .. " nodes missing in " ..
|
|
||||||
pos2str(pos1) .. " -> " .. pos2str(pos2))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Check that none of the nodes in [pos1, pos2] are the node(s) specified
|
|
||||||
check.not_filled = function(pos1, pos2, nodes)
|
|
||||||
if type(nodes) == "string" then
|
|
||||||
nodes = { nodes }
|
|
||||||
end
|
|
||||||
local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
|
|
||||||
for nodename, n in pairs(counts) do
|
|
||||||
if n ~= 0 then
|
|
||||||
error(counts[nodename] .. " " .. nodename .. " nodes found in " ..
|
|
||||||
pos2str(pos1) .. " -> " .. pos2str(pos2))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Check that all of the areas are only made of node(s) specified
|
|
||||||
check.filled2 = function(list, nodes)
|
|
||||||
for _, pos in ipairs(list) do
|
|
||||||
check.filled(pos[1], pos[2], nodes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Check that none of the areas contain the node(s) specified
|
|
||||||
check.not_filled2 = function(list, nodes)
|
|
||||||
for _, pos in ipairs(list) do
|
|
||||||
check.not_filled(pos[1], pos[2], nodes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Checks presence of a repeating pattern in [pos1, po2] (cf. place_pattern)
|
|
||||||
check.pattern = function(pos1, pos2, pattern)
|
|
||||||
local pos = vec()
|
|
||||||
local i = 1
|
|
||||||
for z = pos1.z, pos2.z do
|
|
||||||
pos.z = z
|
|
||||||
for y = pos1.y, pos2.y do
|
|
||||||
pos.y = y
|
|
||||||
for x = pos1.x, pos2.x do
|
|
||||||
pos.x = x
|
|
||||||
local node = get_node(pos)
|
|
||||||
if node.name ~= pattern[i] then
|
|
||||||
error(pattern[i] .. " not found at " .. pos2str(pos) .. " (i=" .. i .. ")")
|
|
||||||
end
|
|
||||||
i = i % #pattern + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---------------------
|
|
||||||
-- The actual tests
|
|
||||||
---------------------
|
|
||||||
local tests = {}
|
|
||||||
local function register_test(name, func, opts)
|
|
||||||
assert(type(name) == "string")
|
|
||||||
assert(func == nil or type(func) == "function")
|
|
||||||
if not opts then
|
|
||||||
opts = {}
|
|
||||||
else
|
|
||||||
opts = table.copy(opts)
|
|
||||||
end
|
|
||||||
opts.name = name
|
|
||||||
opts.func = func
|
|
||||||
table.insert(tests, opts)
|
|
||||||
end
|
|
||||||
-- How this works:
|
|
||||||
-- register_test registers a test with a name and function
|
|
||||||
-- The function should return if the test passes or otherwise cause a Lua error
|
|
||||||
-- The basic structure is: get areas + do operations + check results
|
|
||||||
-- Helpers:
|
|
||||||
-- area.get must be used to retrieve areas that can be operated on (these will be cleared before each test)
|
|
||||||
-- check.filled / check.not_filled can be used to check the result
|
|
||||||
-- area.margin + check.filled2 is useful to make sure nodes weren't placed too far
|
|
||||||
-- place_pattern + check.pattern is useful to test ops that operate on existing data
|
|
||||||
|
|
||||||
|
|
||||||
register_test("Internal self-test")
|
|
||||||
register_test("is area loaded?", function()
|
|
||||||
local pos1, _ = area.get(1)
|
|
||||||
assert(get_node(pos1).name == "air")
|
|
||||||
end, {dry=true})
|
|
||||||
|
|
||||||
register_test("area.split", function()
|
|
||||||
for i = 2, 6 do
|
|
||||||
local pos1, pos2 = area.get(1, 1, i)
|
|
||||||
local half1, half2 = area.split(pos1, pos2)
|
|
||||||
assert(pos1.x == half1.x and pos1.y == half1.y)
|
|
||||||
assert(half1.x == half2.x and half1.y == half2.y)
|
|
||||||
assert(half1.z + 1 == half2.z)
|
|
||||||
if i % 2 == 0 then
|
|
||||||
assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end, {dry=true})
|
|
||||||
|
|
||||||
register_test("check.filled", function()
|
|
||||||
local pos1, pos2 = area.get(1, 2, 1)
|
|
||||||
set_node(pos1, {name=testnode1})
|
|
||||||
set_node(pos2, {name=testnode2})
|
|
||||||
check.filled(pos1, pos1, testnode1)
|
|
||||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
|
||||||
check.not_filled(pos1, pos1, air)
|
|
||||||
check.not_filled(pos1, pos2, {air, testnode3})
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("pattern", function()
|
|
||||||
local pos1, pos2 = area.get(3, 2, 1)
|
|
||||||
local pattern = {testnode1, testnode3}
|
|
||||||
place_pattern(pos1, pos2, pattern)
|
|
||||||
assert(get_node(pos1).name == testnode1)
|
|
||||||
check.pattern(pos1, pos2, pattern)
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
register_test("Generic node manipulations")
|
|
||||||
register_test("worldedit.set", function()
|
|
||||||
local pos1, pos2 = area.get(10)
|
|
||||||
local m = area.margin(1)
|
|
||||||
|
|
||||||
worldedit.set(pos1, pos2, testnode1)
|
|
||||||
|
|
||||||
check.filled(pos1, pos2, testnode1)
|
|
||||||
check.filled2(m, air)
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.set mix", function()
|
|
||||||
local pos1, pos2 = area.get(10)
|
|
||||||
local m = area.margin(1)
|
|
||||||
|
|
||||||
worldedit.set(pos1, pos2, {testnode1, testnode2})
|
|
||||||
|
|
||||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
|
||||||
check.filled2(m, air)
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.replace", function()
|
|
||||||
local pos1, pos2 = area.get(10)
|
|
||||||
local half1, half2 = area.split(pos1, pos2)
|
|
||||||
|
|
||||||
worldedit.set(pos1, half1, testnode1)
|
|
||||||
worldedit.set(half2, pos2, testnode2)
|
|
||||||
worldedit.replace(pos1, pos2, testnode1, testnode3)
|
|
||||||
|
|
||||||
check.not_filled(pos1, pos2, testnode1)
|
|
||||||
check.filled(pos1, half1, testnode3)
|
|
||||||
check.filled(half2, pos2, testnode2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.replace inverse", function()
|
|
||||||
local pos1, pos2 = area.get(10)
|
|
||||||
local half1, half2 = area.split(pos1, pos2)
|
|
||||||
|
|
||||||
worldedit.set(pos1, half1, testnode1)
|
|
||||||
worldedit.set(half2, pos2, testnode2)
|
|
||||||
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
|
|
||||||
|
|
||||||
check.filled(pos1, half1, testnode1)
|
|
||||||
check.filled(half2, pos2, testnode3)
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- FIXME?: this one looks overcomplicated
|
|
||||||
register_test("worldedit.copy", function()
|
|
||||||
local pos1, pos2 = area.get(4)
|
|
||||||
local axis, n = area.dir(2)
|
|
||||||
local m = area.margin(1)
|
|
||||||
local b = pos1[axis]
|
|
||||||
|
|
||||||
-- create one slice with testnode1, one with testnode2
|
|
||||||
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
|
|
||||||
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
|
|
||||||
worldedit.copy(pos1, pos2, axis, n)
|
|
||||||
|
|
||||||
-- should have three slices now
|
|
||||||
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
|
|
||||||
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
|
|
||||||
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
|
|
||||||
check.filled2(m, "air")
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.copy2", function()
|
|
||||||
local pos1, pos2 = area.get(6)
|
|
||||||
local m1 = area.margin(1)
|
|
||||||
local pos1_, pos2_ = area.get(6)
|
|
||||||
local m2 = area.margin(1)
|
|
||||||
|
|
||||||
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
|
|
||||||
place_pattern(pos1, pos2, pattern)
|
|
||||||
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
|
|
||||||
|
|
||||||
check.pattern(pos1, pos2, pattern)
|
|
||||||
check.pattern(pos1_, pos2_, pattern)
|
|
||||||
check.filled2(m1, "air")
|
|
||||||
check.filled2(m2, "air")
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.move (overlap)", function()
|
|
||||||
local pos1, pos2 = area.get(7)
|
|
||||||
local axis, n = area.dir(2)
|
|
||||||
local m = area.margin(1)
|
|
||||||
|
|
||||||
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
|
|
||||||
place_pattern(pos1, pos2, pattern)
|
|
||||||
worldedit.move(pos1, pos2, axis, n)
|
|
||||||
|
|
||||||
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), "air")
|
|
||||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
|
||||||
check.filled2(m, "air")
|
|
||||||
end)
|
|
||||||
|
|
||||||
register_test("worldedit.move", function()
|
|
||||||
local pos1, pos2 = area.get(10)
|
|
||||||
local axis, n = area.dir(10)
|
|
||||||
local m = area.margin(1)
|
|
||||||
|
|
||||||
local pattern = {testnode1, testnode3, testnode3, testnode2}
|
|
||||||
place_pattern(pos1, pos2, pattern)
|
|
||||||
worldedit.move(pos1, pos2, axis, n)
|
|
||||||
|
|
||||||
check.filled(pos1, pos2, "air")
|
|
||||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
|
||||||
check.filled2(m, "air")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- TODO: the rest (also testing param2 + metadata)
|
|
||||||
|
|
||||||
|
|
||||||
---------------------
|
|
||||||
-- Main function
|
|
||||||
---------------------
|
|
||||||
worldedit.run_tests = function()
|
|
||||||
do
|
|
||||||
local v = minetest.get_version()
|
|
||||||
print("Running " .. #tests .. " tests for WorldEdit " ..
|
|
||||||
worldedit.version_string .. " on " .. v.project .. " " .. (v.hash or v.string))
|
|
||||||
end
|
|
||||||
|
|
||||||
init_nodes()
|
|
||||||
|
|
||||||
-- emerge area from (0,0,0) ~ (56,56,56) and keep it loaded
|
|
||||||
-- Note: making this area smaller speeds up tests
|
|
||||||
local wanted = vec(56, 56, 56)
|
|
||||||
for x = 0, math.floor(wanted.x/16) do
|
|
||||||
for y = 0, math.floor(wanted.y/16) do
|
|
||||||
for z = 0, math.floor(wanted.z/16) do
|
|
||||||
assert(minetest.forceload_block({x=x*16, y=y*16, z=z*16}, true))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
area.assign(vec(0, 0, 0), wanted, function()
|
|
||||||
|
|
||||||
local failed = 0
|
|
||||||
for _, test in ipairs(tests) do
|
|
||||||
if not test.func then
|
|
||||||
local s = "---- " .. test.name .. " "
|
|
||||||
print(s .. string.rep("-", 60 - #s))
|
|
||||||
else
|
|
||||||
if not test.dry then
|
|
||||||
area.clear()
|
|
||||||
end
|
|
||||||
local ok, err = pcall(test.func)
|
|
||||||
print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
|
|
||||||
if not ok then
|
|
||||||
print(" " .. err)
|
|
||||||
failed = failed + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Done, " .. failed .. " tests failed.")
|
|
||||||
if failed == 0 then
|
|
||||||
io.close(io.open(minetest.get_worldpath() .. "/tests_ok", "w"))
|
|
||||||
end
|
|
||||||
minetest.request_shutdown()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- for debug purposes
|
|
||||||
minetest.register_on_joinplayer(function(player)
|
|
||||||
minetest.set_player_privs(player:get_player_name(),
|
|
||||||
minetest.string_to_privs("fly,fast,noclip,basic_debug,debug,interact"))
|
|
||||||
end)
|
|
||||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
|
||||||
minetest.chat_send_player(puncher:get_player_name(), pos2str(pos))
|
|
||||||
end)
|
|
@ -1343,23 +1343,6 @@ worldedit.register_command("restore", {
|
|||||||
end,
|
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", {
|
worldedit.register_command("save", {
|
||||||
params = "<file>",
|
params = "<file>",
|
||||||
description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
|
description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
|
||||||
@ -1378,7 +1361,6 @@ worldedit.register_command("save", {
|
|||||||
func = function(name, param)
|
func = function(name, param)
|
||||||
local result, count = worldedit.serialize(worldedit.pos1[name],
|
local result, count = worldedit.serialize(worldedit.pos1[name],
|
||||||
worldedit.pos2[name])
|
worldedit.pos2[name])
|
||||||
detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
|
|
||||||
|
|
||||||
local path = minetest.get_worldpath() .. "/schems"
|
local path = minetest.get_worldpath() .. "/schems"
|
||||||
-- Create directory if it does not already exist
|
-- Create directory if it does not already exist
|
||||||
|
@ -216,7 +216,7 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return minetest.log("error",
|
error(
|
||||||
"worldedit_gui requires a supported gui management mod to be installed.\n"..
|
"worldedit_gui requires a supported gui management mod to be installed.\n"..
|
||||||
"To use the it you need to either:\n"..
|
"To use the it you need to either:\n"..
|
||||||
"* use minetest_game or another sfinv-compatible subgame\n"..
|
"* use minetest_game or another sfinv-compatible subgame\n"..
|
||||||
|
Reference in New Issue
Block a user