schematic rotation (#1)
* schematic rotation * fixes * remove auto-rotation code * schematic encoding v2 * testing * working encode/decode * rotate schematic * spec * rotate dialog * skip if rotation == 0 * wip * wip * wip * fix order * fix metadata rotation --------- Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
This commit is contained in:
parent
edbc905fe1
commit
9cd093046d
17
.github/workflows/test.yml
vendored
Normal file
17
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
ENGINE_VERSION: [5.5.0, 5.6.0, 5.7.0, latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: test
|
||||||
|
run: docker-compose up --exit-code-from sut
|
@ -14,5 +14,5 @@ read_globals = {
|
|||||||
"minetest",
|
"minetest",
|
||||||
|
|
||||||
-- mods
|
-- mods
|
||||||
"mapsync"
|
"mapsync", "mtt"
|
||||||
}
|
}
|
||||||
|
@ -54,16 +54,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not fields.save and not fields.key_enter_field then
|
if not fields.save and not fields.key_enter_field then
|
||||||
return false
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
if not pos1[playername] or not pos2[playername] then
|
if not pos1[playername] or not pos2[playername] then
|
||||||
return false
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- configure and unmark
|
-- configure and unmark
|
||||||
pick_and_place.configure(pos1[playername], pos2[playername], fields.name)
|
pick_and_place.configure(pos1[playername], pos2[playername], fields.name)
|
||||||
pos1[playername] = nil
|
pos1[playername] = nil
|
||||||
pos2[playername] = nil
|
pos2[playername] = nil
|
||||||
|
|
||||||
|
return true
|
||||||
end)
|
end)
|
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
version: "3.6"
|
||||||
|
|
||||||
|
services:
|
||||||
|
sut:
|
||||||
|
build:
|
||||||
|
context: ./test
|
||||||
|
args:
|
||||||
|
ENGINE_VERSION: ${ENGINE_VERSION:-5.7.0}
|
||||||
|
user: root
|
||||||
|
volumes:
|
||||||
|
- "./:/root/.minetest/worlds/world/worldmods/pick_and_place/"
|
||||||
|
- "world_data:/root/.minetest/worlds/world"
|
||||||
|
- "./test/world.mt:/root/.minetest/worlds/world/world.mt"
|
||||||
|
- "./test/minetest.conf:/minetest.conf"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
world_data: {}
|
101
encode.lua
Normal file
101
encode.lua
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
local char, byte = string.char, string.byte
|
||||||
|
|
||||||
|
local function encode_uint16(int)
|
||||||
|
local a, b = int % 0x100, int / 0x100
|
||||||
|
return char(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function decode_uint16(str, ofs)
|
||||||
|
ofs = ofs or 1
|
||||||
|
local a = byte(str, ofs)
|
||||||
|
local b = byte(str, ofs + 1)
|
||||||
|
return a + b * 0x100
|
||||||
|
end
|
||||||
|
|
||||||
|
-- nodeid -> name
|
||||||
|
local nodeid_name_mapping = {}
|
||||||
|
|
||||||
|
function pick_and_place.encode_schematic(schematic)
|
||||||
|
-- list of strings
|
||||||
|
local node_id_data = {}
|
||||||
|
local param2_data = {}
|
||||||
|
local nodeid_mapping = {}
|
||||||
|
|
||||||
|
-- nodeid -> true
|
||||||
|
local nodeids = {}
|
||||||
|
|
||||||
|
for i = 1, #schematic.node_id_data do
|
||||||
|
local node_id = schematic.node_id_data[i]
|
||||||
|
nodeids[node_id] = true
|
||||||
|
table.insert(node_id_data, encode_uint16(node_id))
|
||||||
|
table.insert(param2_data, char(schematic.param2_data[i]))
|
||||||
|
end
|
||||||
|
|
||||||
|
for nodeid in pairs(nodeids) do
|
||||||
|
local name = nodeid_name_mapping[nodeid]
|
||||||
|
if not name then
|
||||||
|
name = minetest.get_name_from_content_id(nodeid)
|
||||||
|
nodeid_name_mapping[nodeid] = name
|
||||||
|
end
|
||||||
|
|
||||||
|
nodeid_mapping[nodeid] = name
|
||||||
|
end
|
||||||
|
|
||||||
|
local serialized_data = minetest.serialize({
|
||||||
|
version = 2,
|
||||||
|
node_id_data = table.concat(node_id_data),
|
||||||
|
param2_data = table.concat(param2_data),
|
||||||
|
metadata = schematic.metadata,
|
||||||
|
nodeid_mapping = nodeid_mapping,
|
||||||
|
size = schematic.size
|
||||||
|
})
|
||||||
|
local compressed_data = minetest.compress(serialized_data, "deflate")
|
||||||
|
local encoded_data = minetest.encode_base64(compressed_data)
|
||||||
|
|
||||||
|
return encoded_data
|
||||||
|
end
|
||||||
|
|
||||||
|
-- name -> nodeid
|
||||||
|
local name_nodeid_mapping = {}
|
||||||
|
|
||||||
|
function pick_and_place.decode_schematic(encoded_data)
|
||||||
|
local compressed_data = minetest.decode_base64(encoded_data)
|
||||||
|
local serialized_data = minetest.decompress(compressed_data, "deflate")
|
||||||
|
local data = minetest.deserialize(serialized_data)
|
||||||
|
|
||||||
|
if data.version ~= 2 then
|
||||||
|
return false, "invalid version: " .. (data.version or "nil")
|
||||||
|
end
|
||||||
|
|
||||||
|
local schematic = {
|
||||||
|
node_id_data = {},
|
||||||
|
param2_data = {},
|
||||||
|
metadata = data.metadata,
|
||||||
|
size = data.size
|
||||||
|
}
|
||||||
|
|
||||||
|
-- foreign_nodeid -> local_nodeid
|
||||||
|
local localized_id_mapping = {}
|
||||||
|
|
||||||
|
for foreign_nodeid, name in pairs(data.nodeid_mapping) do
|
||||||
|
local local_nodeid = name_nodeid_mapping[name]
|
||||||
|
if not local_nodeid then
|
||||||
|
local_nodeid = minetest.get_content_id(name)
|
||||||
|
name_nodeid_mapping[name] = local_nodeid
|
||||||
|
end
|
||||||
|
|
||||||
|
localized_id_mapping[foreign_nodeid] = local_nodeid
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #data.param2_data do
|
||||||
|
-- localize nodeid mapping
|
||||||
|
local foreign_nodeid = decode_uint16(data.node_id_data, 1 + ((i-1) * 2))
|
||||||
|
local local_nodeid = localized_id_mapping[foreign_nodeid]
|
||||||
|
|
||||||
|
table.insert(schematic.node_id_data, local_nodeid)
|
||||||
|
table.insert(schematic.param2_data, byte(data.param2_data, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
return schematic
|
||||||
|
end
|
37
encode.spec.lua
Normal file
37
encode.spec.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
mtt.register("encode", function(callback)
|
||||||
|
local data = pick_and_place.encode_schematic({
|
||||||
|
node_id_data = {0, 0, 0, 0, 1, 2, 3, 4},
|
||||||
|
param2_data = {0, 0, 0, 0, 4, 3, 2, 1},
|
||||||
|
metadata = {
|
||||||
|
["(0,0,0)"] = {
|
||||||
|
meta = {
|
||||||
|
x = 1
|
||||||
|
},
|
||||||
|
inventory = {
|
||||||
|
main = {"default:stick 1"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size = {
|
||||||
|
x = 2,
|
||||||
|
y = 2,
|
||||||
|
z = 2
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert(data)
|
||||||
|
|
||||||
|
local schematic, err = pick_and_place.decode_schematic(data)
|
||||||
|
assert(not err)
|
||||||
|
assert(schematic)
|
||||||
|
|
||||||
|
assert(#schematic.node_id_data, 8)
|
||||||
|
assert(#schematic.param2_data, 8)
|
||||||
|
assert(schematic.metadata["(0,0,0)"])
|
||||||
|
assert(schematic.size.x == 2)
|
||||||
|
assert(schematic.size.y == 2)
|
||||||
|
assert(schematic.size.z == 2)
|
||||||
|
|
||||||
|
callback()
|
||||||
|
end)
|
11
init.lua
11
init.lua
@ -3,9 +3,15 @@ pick_and_place = {}
|
|||||||
|
|
||||||
local MP = minetest.get_modpath("pick_and_place")
|
local MP = minetest.get_modpath("pick_and_place")
|
||||||
dofile(MP .. "/common.lua")
|
dofile(MP .. "/common.lua")
|
||||||
|
dofile(MP .. "/rotate.lua")
|
||||||
|
dofile(MP .. "/schematic_rotate.lua")
|
||||||
|
dofile(MP .. "/schematic_flip.lua")
|
||||||
|
dofile(MP .. "/schematic_orient.lua")
|
||||||
|
dofile(MP .. "/schematic_transpose.lua")
|
||||||
dofile(MP .. "/pointed.lua")
|
dofile(MP .. "/pointed.lua")
|
||||||
dofile(MP .. "/configure.lua")
|
dofile(MP .. "/configure.lua")
|
||||||
dofile(MP .. "/remove.lua")
|
dofile(MP .. "/remove.lua")
|
||||||
|
dofile(MP .. "/encode.lua")
|
||||||
dofile(MP .. "/serialize.lua")
|
dofile(MP .. "/serialize.lua")
|
||||||
dofile(MP .. "/entity.lua")
|
dofile(MP .. "/entity.lua")
|
||||||
dofile(MP .. "/handle_node.lua")
|
dofile(MP .. "/handle_node.lua")
|
||||||
@ -14,3 +20,8 @@ dofile(MP .. "/configure_tool.lua")
|
|||||||
dofile(MP .. "/pick_tool.lua")
|
dofile(MP .. "/pick_tool.lua")
|
||||||
dofile(MP .. "/place_tool.lua")
|
dofile(MP .. "/place_tool.lua")
|
||||||
dofile(MP .. "/preview.lua")
|
dofile(MP .. "/preview.lua")
|
||||||
|
|
||||||
|
if minetest.get_modpath("mtt") and mtt.enabled then
|
||||||
|
dofile(MP .. "/encode.spec.lua")
|
||||||
|
dofile(MP .. "/schematic_rotate.spec.lua")
|
||||||
|
end
|
2
mod.conf
2
mod.conf
@ -1,5 +1,5 @@
|
|||||||
name = pick_and_place
|
name = pick_and_place
|
||||||
description = Pick and place utility
|
description = Pick and place utility
|
||||||
depends = wield_events
|
depends = wield_events
|
||||||
optional_depends = mapsync
|
optional_depends = mapsync, mtt
|
||||||
min_minetest_version = 5.5.0
|
min_minetest_version = 5.5.0
|
@ -1,6 +1,9 @@
|
|||||||
|
local FORMSPEC_NAME = "pick_and_place:place"
|
||||||
|
|
||||||
local has_mapsync = minetest.get_modpath("mapsync")
|
local has_mapsync = minetest.get_modpath("mapsync")
|
||||||
|
|
||||||
local function get_pos(player, size)
|
local function get_pos(meta, player)
|
||||||
|
local size = minetest.string_to_pos(meta:get_string("size"))
|
||||||
local distance = vector.distance(vector.new(), size)
|
local distance = vector.distance(vector.new(), size)
|
||||||
local radius = math.ceil(distance / 2)
|
local radius = math.ceil(distance / 2)
|
||||||
local offset = vector.round(vector.divide(size, 2))
|
local offset = vector.round(vector.divide(size, 2))
|
||||||
@ -25,28 +28,39 @@ minetest.register_tool("pick_and_place:place", {
|
|||||||
local controls = player:get_player_control()
|
local controls = player:get_player_control()
|
||||||
|
|
||||||
local meta = itemstack:get_meta()
|
local meta = itemstack:get_meta()
|
||||||
local schematic = meta:get_string("schematic")
|
|
||||||
local size = minetest.string_to_pos(meta:get_string("size"))
|
local pos1, pos2 = get_pos(meta, player)
|
||||||
local pos1, pos2 = get_pos(player, size)
|
|
||||||
|
|
||||||
if controls.aux1 then
|
if controls.aux1 then
|
||||||
-- removal
|
-- removal
|
||||||
pick_and_place.remove_area(pos1, pos2)
|
pick_and_place.remove_area(pos1, pos2)
|
||||||
else
|
else
|
||||||
-- placement
|
-- placement
|
||||||
|
local schematic = meta:get_string("schematic")
|
||||||
local success, msg = pick_and_place.deserialize(pos1, schematic)
|
local success, msg = pick_and_place.deserialize(pos1, schematic)
|
||||||
if not success then
|
if not success then
|
||||||
minetest.chat_send_player(playername, "Placement error: " .. msg)
|
minetest.chat_send_player(playername, "Placement error: " .. msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
on_secondary_use = function(_, player)
|
||||||
|
local playername = player:get_player_name()
|
||||||
|
|
||||||
|
-- show name input
|
||||||
|
minetest.show_formspec(playername, FORMSPEC_NAME, [[
|
||||||
|
size[9,1]
|
||||||
|
real_coordinates[true]
|
||||||
|
button_exit[0.1,0.1;2.8,0.8;deg90;90°]
|
||||||
|
button_exit[3.1,0.1;2.8,0.8;deg180;180°]
|
||||||
|
button_exit[6.1,0.1;2.8,0.8;deg270;270°]
|
||||||
|
]])
|
||||||
|
end,
|
||||||
on_step = function(itemstack, player)
|
on_step = function(itemstack, player)
|
||||||
local playername = player:get_player_name()
|
local playername = player:get_player_name()
|
||||||
local controls = player:get_player_control()
|
local controls = player:get_player_control()
|
||||||
|
|
||||||
local meta = itemstack:get_meta()
|
local meta = itemstack:get_meta()
|
||||||
local size = minetest.string_to_pos(meta:get_string("size"))
|
local pos1, pos2 = get_pos(meta, player)
|
||||||
local pos1, pos2 = get_pos(player, size)
|
|
||||||
|
|
||||||
if controls.aux1 then
|
if controls.aux1 then
|
||||||
-- removal preview
|
-- removal preview
|
||||||
@ -65,3 +79,49 @@ minetest.register_tool("pick_and_place:place", {
|
|||||||
pick_and_place.clear_preview(playername)
|
pick_and_place.clear_preview(playername)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||||
|
if formname ~= FORMSPEC_NAME then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local itemstack = player:get_wielded_item()
|
||||||
|
if itemstack:get_name() ~= "pick_and_place:place" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local rotation = 0
|
||||||
|
if fields.deg90 then
|
||||||
|
rotation = 90
|
||||||
|
elseif fields.deg180 then
|
||||||
|
rotation = 180
|
||||||
|
elseif fields.deg270 then
|
||||||
|
rotation = 270
|
||||||
|
end
|
||||||
|
|
||||||
|
if rotation == 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local meta = itemstack:get_meta()
|
||||||
|
local schematic_data = meta:get_string("schematic")
|
||||||
|
local schematic, err = pick_and_place.decode_schematic(schematic_data)
|
||||||
|
if err then
|
||||||
|
minetest.chat_send_player(player:get_player_name(), "Schematic decode error: " .. err)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- rotate schematic
|
||||||
|
pick_and_place.schematic_rotate(schematic, rotation)
|
||||||
|
meta:set_string("schematic", pick_and_place.encode_schematic(schematic))
|
||||||
|
|
||||||
|
-- rotate size
|
||||||
|
local size = minetest.string_to_pos(meta:get_string("size"))
|
||||||
|
size = pick_and_place.rotate_size(size, rotation)
|
||||||
|
meta:set_string("size", minetest.pos_to_string(size))
|
||||||
|
|
||||||
|
-- set tool
|
||||||
|
player:set_wielded_item(itemstack)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
32
rotate.lua
Normal file
32
rotate.lua
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
-- rotates the size for a given rotation
|
||||||
|
function pick_and_place.rotate_size(size, rotation)
|
||||||
|
if rotation == 90 or rotation == 270 then
|
||||||
|
-- invert x/z
|
||||||
|
return {
|
||||||
|
x = size.z,
|
||||||
|
y = size.y,
|
||||||
|
z = size.x
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- unchanged in 180 or 0 degrees
|
||||||
|
return size
|
||||||
|
end
|
||||||
|
|
||||||
|
-- look direction in 90-degree increments
|
||||||
|
function pick_and_place.get_player_rotation(player)
|
||||||
|
local yaw = player:get_look_horizontal()
|
||||||
|
local degrees = yaw / math.pi * 180
|
||||||
|
local rotation = 0
|
||||||
|
if degrees > 45 and degrees < (90+45) then
|
||||||
|
-- x-
|
||||||
|
rotation = 180
|
||||||
|
elseif degrees > (90+45) and degrees < (180+45) then
|
||||||
|
-- z-
|
||||||
|
rotation = 90
|
||||||
|
elseif degrees < 45 or degrees > (360-45) then
|
||||||
|
-- z+
|
||||||
|
rotation = 270
|
||||||
|
end
|
||||||
|
return rotation
|
||||||
|
end
|
41
schematic_flip.lua
Normal file
41
schematic_flip.lua
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
local function flip_data(data, size, indexFn, axis)
|
||||||
|
local pos = {x=0, y=0, z=0}
|
||||||
|
local max = { x=size.x, y=size.y, z=size.z }
|
||||||
|
local start = max[axis]
|
||||||
|
max[axis] = math.floor(max[axis] / 2)
|
||||||
|
|
||||||
|
while pos.x <= max.x do
|
||||||
|
pos.y = 0
|
||||||
|
while pos.y <= max.y do
|
||||||
|
pos.z = 0
|
||||||
|
while pos.z <= max.z do
|
||||||
|
local data_1 = data[indexFn(pos)]
|
||||||
|
local value = pos[axis] -- Save position
|
||||||
|
pos[axis] = start - value -- Shift position
|
||||||
|
local data_2 = data[indexFn(pos)]
|
||||||
|
data[indexFn(pos)] = data_1
|
||||||
|
pos[axis] = value -- Restore position
|
||||||
|
data[indexFn(pos)] = data_2
|
||||||
|
pos.z = pos.z + 1
|
||||||
|
end
|
||||||
|
pos.y = pos.y + 1
|
||||||
|
end
|
||||||
|
pos.x = pos.x + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pick_and_place.schematic_flip(node_ids, param2_data, metadata, max, axis)
|
||||||
|
local min = { x=0, y=0, z=0 }
|
||||||
|
local area = VoxelArea:new({MinEdge=min, MaxEdge=max})
|
||||||
|
|
||||||
|
local vmanipIndex = function(pos) return area:indexp(pos) end
|
||||||
|
local metaIndex = function(pos) return minetest.pos_to_string(pos) end
|
||||||
|
|
||||||
|
flip_data(node_ids, max, vmanipIndex, axis)
|
||||||
|
flip_data(param2_data, max, vmanipIndex, axis)
|
||||||
|
|
||||||
|
if metadata then
|
||||||
|
flip_data(metadata, max, metaIndex, axis)
|
||||||
|
end
|
||||||
|
end
|
88
schematic_orient.lua
Normal file
88
schematic_orient.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
local node_id_to_name_cache = {}
|
||||||
|
local node_ids_rotateable = {}
|
||||||
|
|
||||||
|
local wallmounted = {
|
||||||
|
[90] = {0, 1, 5, 4, 2, 3, 0, 0},
|
||||||
|
[180] = {0, 1, 3, 2, 5, 4, 0, 0},
|
||||||
|
[270] = {0, 1, 4, 5, 3, 2, 0, 0}
|
||||||
|
}
|
||||||
|
local facedir = {
|
||||||
|
[90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
|
||||||
|
9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
|
||||||
|
[180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
|
||||||
|
18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
|
||||||
|
[270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
|
||||||
|
7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
|
||||||
|
}
|
||||||
|
|
||||||
|
local function rotate_param2(node_name, param2, angle)
|
||||||
|
if not angle or angle == 0 then
|
||||||
|
return param2
|
||||||
|
end
|
||||||
|
|
||||||
|
local def = minetest.registered_nodes[node_name]
|
||||||
|
|
||||||
|
if def then
|
||||||
|
local wallmounted_substitution = wallmounted[angle]
|
||||||
|
local facedir_substitution = facedir[angle]
|
||||||
|
|
||||||
|
local paramtype2 = def.paramtype2
|
||||||
|
if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then
|
||||||
|
local orient = param2 % 8
|
||||||
|
return param2 - orient + wallmounted_substitution[orient + 1]
|
||||||
|
|
||||||
|
elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then
|
||||||
|
local orient = param2 % 32
|
||||||
|
return param2 - orient + facedir_substitution[orient + 1]
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local min = { x=0, y=0, z=0 }
|
||||||
|
|
||||||
|
local rotate_param2types = {
|
||||||
|
["wallmounted"] = true,
|
||||||
|
["colorwallmounted"] = true,
|
||||||
|
["facedir"] = true,
|
||||||
|
["colorfacedir"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function pick_and_place.schematic_orient(node_ids, param2_data, max, rotation)
|
||||||
|
-- https://github.com/Uberi/Minetest-WorldEdit/blob/master/worldedit/manipulations.lua#L555
|
||||||
|
local area = VoxelArea:new({MinEdge=min, MaxEdge=max})
|
||||||
|
|
||||||
|
local pos = {x=0, y=0, z=0}
|
||||||
|
while pos.x <= max.x do
|
||||||
|
pos.y = 0
|
||||||
|
while pos.y <= max.y do
|
||||||
|
pos.z = 0
|
||||||
|
while pos.z <= max.z do
|
||||||
|
local index = area:indexp(pos)
|
||||||
|
|
||||||
|
local param2 = param2_data[index]
|
||||||
|
local node_id = node_ids[index]
|
||||||
|
local node_name = node_id_to_name_cache[node_id]
|
||||||
|
if not node_name then
|
||||||
|
-- cache association
|
||||||
|
node_name = minetest.get_name_from_content_id(node_id)
|
||||||
|
node_id_to_name_cache[node_id] = node_name
|
||||||
|
-- check if param2 is facedir
|
||||||
|
local def = minetest.registered_nodes[node_name]
|
||||||
|
node_ids_rotateable[node_id] = rotate_param2types[def.paramtype2]
|
||||||
|
end
|
||||||
|
|
||||||
|
if node_ids_rotateable[node_id] then
|
||||||
|
-- rotate only the non-disabled and supported nodes
|
||||||
|
param2 = rotate_param2(node_name, param2, rotation)
|
||||||
|
param2_data[index] = param2
|
||||||
|
end
|
||||||
|
pos.z = pos.z + 1
|
||||||
|
end
|
||||||
|
pos.y = pos.y + 1
|
||||||
|
end
|
||||||
|
pos.x = pos.x + 1
|
||||||
|
end
|
||||||
|
end
|
120
schematic_rotate.lua
Normal file
120
schematic_rotate.lua
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
local zero = { x=0, y=0, z=0 }
|
||||||
|
|
||||||
|
-- creates a buffer for the rotation
|
||||||
|
local function create_buffer(data, size, max)
|
||||||
|
local area_src = VoxelArea:new({MinEdge=zero, MaxEdge=size})
|
||||||
|
local area_dst = VoxelArea:new({MinEdge=zero, MaxEdge=max})
|
||||||
|
|
||||||
|
local buf = {}
|
||||||
|
|
||||||
|
for z=0,size.z do
|
||||||
|
for x=0,size.x do
|
||||||
|
for y=0,size.y do
|
||||||
|
local i_src = area_src:index(x,y,z)
|
||||||
|
local i_dst = area_dst:index(x,y,z)
|
||||||
|
buf[i_dst] = data[i_src]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
|
||||||
|
-- extracts the rotated new size from the buffer
|
||||||
|
local function extract_from_buffer(buf, size, max, offset)
|
||||||
|
local area_src = VoxelArea:new({MinEdge=zero, MaxEdge=max})
|
||||||
|
local area_dst = VoxelArea:new({MinEdge=zero, MaxEdge=size})
|
||||||
|
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for z=0,size.z do
|
||||||
|
for x=0,size.x do
|
||||||
|
for y=0,size.y do
|
||||||
|
local i_src = area_src:index(x+offset.x,y+offset.y,z+offset.z)
|
||||||
|
local i_dst = area_dst:index(x,y,z)
|
||||||
|
data[i_dst] = buf[i_src]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_offset(metadata_map, offset)
|
||||||
|
local new_metadata_map = {}
|
||||||
|
for pos_str, metadata in pairs(metadata_map) do
|
||||||
|
local pos = minetest.string_to_pos(pos_str)
|
||||||
|
local new_pos = vector.subtract(pos, offset)
|
||||||
|
local new_pos_str = minetest.pos_to_string(new_pos)
|
||||||
|
|
||||||
|
new_metadata_map[new_pos_str] = metadata
|
||||||
|
end
|
||||||
|
return new_metadata_map
|
||||||
|
end
|
||||||
|
|
||||||
|
function pick_and_place.schematic_rotate(schematic, rotation)
|
||||||
|
if rotation <= 0 or rotation > 270 then
|
||||||
|
-- invalid or no rotation
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local other1, other2 = "x", "z"
|
||||||
|
local rotated_size = pick_and_place.rotate_size(schematic.size, rotation)
|
||||||
|
|
||||||
|
local metadata = schematic.metadata
|
||||||
|
|
||||||
|
local max_xz_axis = math.max(schematic.size.x, schematic.size.z)
|
||||||
|
local max = { x=max_xz_axis-1, y=schematic.size.y-1, z=max_xz_axis-1 }
|
||||||
|
|
||||||
|
-- create transform buffers
|
||||||
|
local node_id_buf = create_buffer(schematic.node_id_data, vector.subtract(schematic.size, 1), max)
|
||||||
|
local param2_buf = create_buffer(schematic.param2_data, vector.subtract(schematic.size, 1), max)
|
||||||
|
|
||||||
|
-- rotate
|
||||||
|
if rotation == 90 then
|
||||||
|
pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
|
||||||
|
pick_and_place.schematic_transpose(node_id_buf, param2_buf, metadata, max, other1, other2)
|
||||||
|
elseif rotation == 180 then
|
||||||
|
pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
|
||||||
|
pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other2)
|
||||||
|
elseif rotation == 270 then
|
||||||
|
pick_and_place.schematic_transpose(node_id_buf, param2_buf, metadata, max, other1, other2)
|
||||||
|
pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- extract from buffer with offset
|
||||||
|
local offset = {x=0, y=0, z=0}
|
||||||
|
local z_larger = schematic.size.z > schematic.size.x
|
||||||
|
local x_larger = schematic.size.z < schematic.size.x
|
||||||
|
local xz_diff = math.abs(schematic.size.x - schematic.size.z)
|
||||||
|
if rotation == 90 then
|
||||||
|
if z_larger then
|
||||||
|
offset.z = xz_diff
|
||||||
|
end
|
||||||
|
elseif rotation == 180 then
|
||||||
|
if x_larger then
|
||||||
|
offset.z = xz_diff
|
||||||
|
elseif z_larger then
|
||||||
|
offset.x = xz_diff
|
||||||
|
end
|
||||||
|
elseif rotation == 270 then
|
||||||
|
if x_larger then
|
||||||
|
offset.x = xz_diff
|
||||||
|
end
|
||||||
|
end
|
||||||
|
schematic.node_id_data = extract_from_buffer(node_id_buf, vector.subtract(rotated_size, 1), max, offset)
|
||||||
|
schematic.param2_data = extract_from_buffer(param2_buf, vector.subtract(rotated_size, 1), max, offset)
|
||||||
|
schematic.metadata = apply_offset(metadata, offset)
|
||||||
|
|
||||||
|
-- rotate size
|
||||||
|
schematic.size = rotated_size
|
||||||
|
|
||||||
|
-- orient rotated schematic
|
||||||
|
pick_and_place.schematic_orient(
|
||||||
|
schematic.node_id_data,
|
||||||
|
schematic.param2_data,
|
||||||
|
vector.subtract(rotated_size, 1),
|
||||||
|
rotation
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
30
schematic_rotate.spec.lua
Normal file
30
schematic_rotate.spec.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
mtt.register("rotate", function(callback)
|
||||||
|
|
||||||
|
local schematic = {
|
||||||
|
node_id_data = {0, 0, 0, 0, 1, 2, 3, 4},
|
||||||
|
param2_data = {0, 0, 0, 0, 4, 3, 2, 1},
|
||||||
|
metadata = {
|
||||||
|
["(0,0,0)"] = {
|
||||||
|
meta = {
|
||||||
|
x = 1
|
||||||
|
},
|
||||||
|
inventory = {
|
||||||
|
main = {"default:stick 1"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
size = {
|
||||||
|
x = 2,
|
||||||
|
y = 2,
|
||||||
|
z = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_and_place.schematic_rotate(schematic, 90)
|
||||||
|
|
||||||
|
assert(schematic.node_id_data[1] == 0)
|
||||||
|
assert(schematic.node_id_data[2] == 2)
|
||||||
|
assert(schematic.metadata["(0,0,1)"])
|
||||||
|
|
||||||
|
callback()
|
||||||
|
end)
|
44
schematic_transpose.lua
Normal file
44
schematic_transpose.lua
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
local function transpose_data(data, max, indexFn, axis1, axis2)
|
||||||
|
-- https://github.com/Uberi/Minetest-WorldEdit/blob/master/worldedit/manipulations.lua#L422
|
||||||
|
local pos = {x=0, y=0, z=0}
|
||||||
|
|
||||||
|
while pos.x <= max.x do
|
||||||
|
pos.y = 0
|
||||||
|
while pos.y <= max.y do
|
||||||
|
pos.z = 0
|
||||||
|
while pos.z <= max.z do
|
||||||
|
local extent1, extent2 = pos[axis1], pos[axis2]
|
||||||
|
if extent1 < extent2 then -- Transpose only if below the diagonal
|
||||||
|
local data_1 = data[indexFn(pos)]
|
||||||
|
local value1, value2 = pos[axis1], pos[axis2] -- Save position values
|
||||||
|
|
||||||
|
pos[axis1], pos[axis2] = extent2, extent1 -- Swap axis extents
|
||||||
|
local data_2 = data[indexFn(pos)]
|
||||||
|
data[indexFn(pos)] = data_1
|
||||||
|
|
||||||
|
pos[axis1], pos[axis2] = value1, value2 -- Restore position values
|
||||||
|
data[indexFn(pos)] = data_2
|
||||||
|
end
|
||||||
|
pos.z = pos.z + 1
|
||||||
|
end
|
||||||
|
pos.y = pos.y + 1
|
||||||
|
end
|
||||||
|
pos.x = pos.x + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pick_and_place.schematic_transpose(node_ids, param2_data, metadata, max, axis1, axis2)
|
||||||
|
local min = { x=0, y=0, z=0 }
|
||||||
|
local area = VoxelArea:new({MinEdge=min, MaxEdge=max})
|
||||||
|
|
||||||
|
local vmanipIndex = function(pos) return area:indexp(pos) end
|
||||||
|
local metaIndex = function(pos) return minetest.pos_to_string(pos) end
|
||||||
|
|
||||||
|
transpose_data(node_ids, max, vmanipIndex, axis1, axis2)
|
||||||
|
transpose_data(param2_data, max, vmanipIndex, axis1, axis2)
|
||||||
|
|
||||||
|
if metadata then
|
||||||
|
transpose_data(metadata, max, metaIndex, axis1, axis2)
|
||||||
|
end
|
||||||
|
end
|
104
serialize.lua
104
serialize.lua
@ -1,23 +1,5 @@
|
|||||||
|
|
||||||
local char, byte = string.char, string.byte
|
|
||||||
|
|
||||||
local air_cid = minetest.get_content_id("air")
|
local air_cid = minetest.get_content_id("air")
|
||||||
|
|
||||||
local function encode_uint16(int)
|
|
||||||
local a, b = int % 0x100, int / 0x100
|
|
||||||
return char(a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function decode_uint16(str, ofs)
|
|
||||||
ofs = ofs or 1
|
|
||||||
local a = byte(str, ofs)
|
|
||||||
local b = byte(str, ofs + 1)
|
|
||||||
return a + b * 0x100
|
|
||||||
end
|
|
||||||
|
|
||||||
-- nodeid -> name
|
|
||||||
local nodeid_name_mapping = {}
|
|
||||||
|
|
||||||
function pick_and_place.serialize(pos1, pos2)
|
function pick_and_place.serialize(pos1, pos2)
|
||||||
local manip = minetest.get_voxel_manip()
|
local manip = minetest.get_voxel_manip()
|
||||||
local e1, e2 = manip:read_from_map(pos1, pos2)
|
local e1, e2 = manip:read_from_map(pos1, pos2)
|
||||||
@ -26,36 +8,20 @@ function pick_and_place.serialize(pos1, pos2)
|
|||||||
local node_data = manip:get_data()
|
local node_data = manip:get_data()
|
||||||
local param2 = manip:get_param2_data()
|
local param2 = manip:get_param2_data()
|
||||||
|
|
||||||
local mapdata = {}
|
local node_id_data = {}
|
||||||
|
local param2_data = {}
|
||||||
local metadata = {}
|
local metadata = {}
|
||||||
|
|
||||||
-- nodeid -> true
|
|
||||||
local nodeids = {}
|
|
||||||
|
|
||||||
for z=pos1.z,pos2.z do
|
for z=pos1.z,pos2.z do
|
||||||
for x=pos1.x,pos2.x do
|
|
||||||
for y=pos1.y,pos2.y do
|
for y=pos1.y,pos2.y do
|
||||||
|
for x=pos1.x,pos2.x do
|
||||||
local i = area:index(x,y,z)
|
local i = area:index(x,y,z)
|
||||||
nodeids[node_data[i]] = true
|
table.insert(node_id_data, node_data[i])
|
||||||
table.insert(mapdata, encode_uint16(node_data[i]))
|
table.insert(param2_data, param2[i])
|
||||||
table.insert(mapdata, char(param2[i]))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- id -> name
|
|
||||||
local nodeid_mapping = {}
|
|
||||||
|
|
||||||
for nodeid in pairs(nodeids) do
|
|
||||||
local name = nodeid_name_mapping[nodeid]
|
|
||||||
if not name then
|
|
||||||
name = minetest.get_name_from_content_id(nodeid)
|
|
||||||
nodeid_name_mapping[nodeid] = name
|
|
||||||
end
|
|
||||||
|
|
||||||
nodeid_mapping[nodeid] = name
|
|
||||||
end
|
|
||||||
|
|
||||||
-- store metadata
|
-- store metadata
|
||||||
local nodes_with_meta = minetest.find_nodes_with_meta(pos1, pos2)
|
local nodes_with_meta = minetest.find_nodes_with_meta(pos1, pos2)
|
||||||
for _, pos in ipairs(nodes_with_meta) do
|
for _, pos in ipairs(nodes_with_meta) do
|
||||||
@ -76,34 +42,24 @@ function pick_and_place.serialize(pos1, pos2)
|
|||||||
metadata[minetest.pos_to_string(rel_pos)] = meta_table
|
metadata[minetest.pos_to_string(rel_pos)] = meta_table
|
||||||
end
|
end
|
||||||
|
|
||||||
local data = {
|
local schematic = {
|
||||||
version = 1,
|
node_id_data = node_id_data,
|
||||||
mapdata = table.concat(mapdata),
|
param2_data = param2_data,
|
||||||
metadata = metadata,
|
metadata = metadata,
|
||||||
nodeid_mapping = nodeid_mapping,
|
|
||||||
size = vector.add(vector.subtract(pos2, pos1), 1)
|
size = vector.add(vector.subtract(pos2, pos1), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
local serialized_data = minetest.serialize(data)
|
return pick_and_place.encode_schematic(schematic)
|
||||||
local compressed_data = minetest.compress(serialized_data, "deflate")
|
|
||||||
local encoded_data = minetest.encode_base64(compressed_data)
|
|
||||||
|
|
||||||
return encoded_data
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- name -> nodeid
|
|
||||||
local name_nodeid_mapping = {}
|
|
||||||
|
|
||||||
function pick_and_place.deserialize(pos1, encoded_data)
|
function pick_and_place.deserialize(pos1, encoded_data)
|
||||||
local compressed_data = minetest.decode_base64(encoded_data)
|
local schematic, err = pick_and_place.decode_schematic(encoded_data)
|
||||||
local serialized_data = minetest.decompress(compressed_data, "deflate")
|
if err then
|
||||||
local data = minetest.deserialize(serialized_data)
|
return false, "Decode error: " .. err
|
||||||
|
|
||||||
if data.version ~= 1 then
|
|
||||||
return false, "invalid version: " .. (data.version or "nil")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local pos2 = vector.add(pos1, vector.subtract(data.size, 1))
|
local pos2 = vector.add(pos1, vector.subtract(schematic.size, 1))
|
||||||
|
|
||||||
local manip = minetest.get_voxel_manip()
|
local manip = minetest.get_voxel_manip()
|
||||||
local e1, e2 = manip:read_from_map(pos1, pos2)
|
local e1, e2 = manip:read_from_map(pos1, pos2)
|
||||||
@ -112,47 +68,31 @@ function pick_and_place.deserialize(pos1, encoded_data)
|
|||||||
local node_data = manip:get_data()
|
local node_data = manip:get_data()
|
||||||
local param2 = manip:get_param2_data()
|
local param2 = manip:get_param2_data()
|
||||||
|
|
||||||
-- foreign_nodeid -> local_nodeid
|
|
||||||
local localized_id_mapping = {}
|
|
||||||
|
|
||||||
for foreign_nodeid, name in pairs(data.nodeid_mapping) do
|
|
||||||
local local_nodeid = name_nodeid_mapping[name]
|
|
||||||
if not local_nodeid then
|
|
||||||
local_nodeid = minetest.get_content_id(name)
|
|
||||||
name_nodeid_mapping[name] = local_nodeid
|
|
||||||
end
|
|
||||||
|
|
||||||
localized_id_mapping[foreign_nodeid] = local_nodeid
|
|
||||||
end
|
|
||||||
|
|
||||||
local j = 1
|
local j = 1
|
||||||
for z=pos1.z,pos2.z do
|
for z=pos1.z,pos2.z do
|
||||||
for x=pos1.x,pos2.x do
|
|
||||||
for y=pos1.y,pos2.y do
|
for y=pos1.y,pos2.y do
|
||||||
|
for x=pos1.x,pos2.x do
|
||||||
local i = area:index(x,y,z)
|
local i = area:index(x,y,z)
|
||||||
local foreign_nodeid = decode_uint16(data.mapdata, j)
|
local nodeid = schematic.node_id_data[j]
|
||||||
|
|
||||||
-- localize nodeid mapping
|
if nodeid ~= air_cid then
|
||||||
local local_nodeid = localized_id_mapping[foreign_nodeid]
|
node_data[i] = nodeid
|
||||||
|
param2[i] = schematic.param2_data[j]
|
||||||
if local_nodeid ~= air_cid then
|
|
||||||
node_data[i] = local_nodeid
|
|
||||||
param2[i] = byte(data.mapdata, j+2)
|
|
||||||
end
|
end
|
||||||
|
j = j + 1
|
||||||
j = j + 3
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- metadata
|
-- set metadata
|
||||||
for pos_str, meta_table in pairs(data.metadata) do
|
for pos_str, meta_table in pairs(schematic.metadata) do
|
||||||
local pos = minetest.string_to_pos(pos_str)
|
local pos = minetest.string_to_pos(pos_str)
|
||||||
local abs_pos = vector.add(pos1, pos)
|
local abs_pos = vector.add(pos1, pos)
|
||||||
local meta = minetest.get_meta(abs_pos)
|
local meta = minetest.get_meta(abs_pos)
|
||||||
meta:from_table(meta_table)
|
meta:from_table(meta_table)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- set nodeid's and param2
|
||||||
manip:set_data(node_data)
|
manip:set_data(node_data)
|
||||||
manip:set_param2_data(param2)
|
manip:set_param2_data(param2)
|
||||||
manip:write_to_map()
|
manip:write_to_map()
|
||||||
|
13
test/Dockerfile
Normal file
13
test/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ARG ENGINE_VERSION=5.7.0
|
||||||
|
FROM registry.gitlab.com/minetest/minetest/server:${ENGINE_VERSION}
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN apk add --no-cache lua-dev luarocks
|
||||||
|
|
||||||
|
RUN apk add git &&\
|
||||||
|
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
|
||||||
|
git clone https://github.com/BuckarooBanzay/mtt /root/.minetest/worlds/world/worldmods/mtt &&\
|
||||||
|
git clone https://github.com/mt-mods/wield_events /root/.minetest/worlds/world/worldmods/wield_events
|
||||||
|
|
||||||
|
ENTRYPOINT minetestserver --config /minetest.conf
|
5
test/minetest.conf
Normal file
5
test/minetest.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
default_game = minetest_game
|
||||||
|
mg_name = v7
|
||||||
|
mtt_enable = true
|
||||||
|
mtt_filter = pick_and_place
|
||||||
|
secure.trusted_mods = mtt
|
9
test/world.mt
Normal file
9
test/world.mt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
enable_damage = false
|
||||||
|
creative_mode = true
|
||||||
|
mod_storage_backend = sqlite3
|
||||||
|
auth_backend = sqlite3
|
||||||
|
player_backend = dummy
|
||||||
|
backend = dummy
|
||||||
|
gameid = minetest
|
||||||
|
world_name = world
|
||||||
|
server_announce = false
|
Loading…
x
Reference in New Issue
Block a user