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",
|
||||
|
||||
-- mods
|
||||
"mapsync"
|
||||
"mapsync", "mtt"
|
||||
}
|
||||
|
@ -54,16 +54,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
end
|
||||
|
||||
if not fields.save and not fields.key_enter_field then
|
||||
return false
|
||||
return true
|
||||
end
|
||||
|
||||
local playername = player:get_player_name()
|
||||
if not pos1[playername] or not pos2[playername] then
|
||||
return false
|
||||
return true
|
||||
end
|
||||
|
||||
-- configure and unmark
|
||||
pick_and_place.configure(pos1[playername], pos2[playername], fields.name)
|
||||
pos1[playername] = nil
|
||||
pos2[playername] = nil
|
||||
|
||||
return true
|
||||
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")
|
||||
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 .. "/configure.lua")
|
||||
dofile(MP .. "/remove.lua")
|
||||
dofile(MP .. "/encode.lua")
|
||||
dofile(MP .. "/serialize.lua")
|
||||
dofile(MP .. "/entity.lua")
|
||||
dofile(MP .. "/handle_node.lua")
|
||||
@ -14,3 +20,8 @@ dofile(MP .. "/configure_tool.lua")
|
||||
dofile(MP .. "/pick_tool.lua")
|
||||
dofile(MP .. "/place_tool.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
|
||||
description = Pick and place utility
|
||||
depends = wield_events
|
||||
optional_depends = mapsync
|
||||
optional_depends = mapsync, mtt
|
||||
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 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 radius = math.ceil(distance / 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 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(player, size)
|
||||
|
||||
local pos1, pos2 = get_pos(meta, player)
|
||||
|
||||
if controls.aux1 then
|
||||
-- removal
|
||||
pick_and_place.remove_area(pos1, pos2)
|
||||
else
|
||||
-- placement
|
||||
local schematic = meta:get_string("schematic")
|
||||
local success, msg = pick_and_place.deserialize(pos1, schematic)
|
||||
if not success then
|
||||
minetest.chat_send_player(playername, "Placement error: " .. msg)
|
||||
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)
|
||||
local playername = player:get_player_name()
|
||||
local controls = player:get_player_control()
|
||||
|
||||
local meta = itemstack:get_meta()
|
||||
local size = minetest.string_to_pos(meta:get_string("size"))
|
||||
local pos1, pos2 = get_pos(player, size)
|
||||
local pos1, pos2 = get_pos(meta, player)
|
||||
|
||||
if controls.aux1 then
|
||||
-- removal preview
|
||||
@ -65,3 +79,49 @@ minetest.register_tool("pick_and_place:place", {
|
||||
pick_and_place.clear_preview(playername)
|
||||
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 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)
|
||||
local manip = minetest.get_voxel_manip()
|
||||
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 param2 = manip:get_param2_data()
|
||||
|
||||
local mapdata = {}
|
||||
local node_id_data = {}
|
||||
local param2_data = {}
|
||||
local metadata = {}
|
||||
|
||||
-- nodeid -> true
|
||||
local nodeids = {}
|
||||
|
||||
for z=pos1.z,pos2.z do
|
||||
for x=pos1.x,pos2.x do
|
||||
for y=pos1.y,pos2.y do
|
||||
for x=pos1.x,pos2.x do
|
||||
local i = area:index(x,y,z)
|
||||
nodeids[node_data[i]] = true
|
||||
table.insert(mapdata, encode_uint16(node_data[i]))
|
||||
table.insert(mapdata, char(param2[i]))
|
||||
table.insert(node_id_data, node_data[i])
|
||||
table.insert(param2_data, param2[i])
|
||||
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
|
||||
local nodes_with_meta = minetest.find_nodes_with_meta(pos1, pos2)
|
||||
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
|
||||
end
|
||||
|
||||
local data = {
|
||||
version = 1,
|
||||
mapdata = table.concat(mapdata),
|
||||
local schematic = {
|
||||
node_id_data = node_id_data,
|
||||
param2_data = param2_data,
|
||||
metadata = metadata,
|
||||
nodeid_mapping = nodeid_mapping,
|
||||
size = vector.add(vector.subtract(pos2, pos1), 1)
|
||||
}
|
||||
|
||||
local serialized_data = minetest.serialize(data)
|
||||
local compressed_data = minetest.compress(serialized_data, "deflate")
|
||||
local encoded_data = minetest.encode_base64(compressed_data)
|
||||
|
||||
return encoded_data
|
||||
return pick_and_place.encode_schematic(schematic)
|
||||
end
|
||||
|
||||
-- name -> nodeid
|
||||
local name_nodeid_mapping = {}
|
||||
|
||||
function pick_and_place.deserialize(pos1, 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 ~= 1 then
|
||||
return false, "invalid version: " .. (data.version or "nil")
|
||||
local schematic, err = pick_and_place.decode_schematic(encoded_data)
|
||||
if err then
|
||||
return false, "Decode error: " .. err
|
||||
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 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 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
|
||||
for z=pos1.z,pos2.z do
|
||||
for x=pos1.x,pos2.x do
|
||||
for y=pos1.y,pos2.y do
|
||||
for x=pos1.x,pos2.x do
|
||||
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
|
||||
local local_nodeid = localized_id_mapping[foreign_nodeid]
|
||||
|
||||
if local_nodeid ~= air_cid then
|
||||
node_data[i] = local_nodeid
|
||||
param2[i] = byte(data.mapdata, j+2)
|
||||
if nodeid ~= air_cid then
|
||||
node_data[i] = nodeid
|
||||
param2[i] = schematic.param2_data[j]
|
||||
end
|
||||
|
||||
j = j + 3
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- metadata
|
||||
for pos_str, meta_table in pairs(data.metadata) do
|
||||
-- set metadata
|
||||
for pos_str, meta_table in pairs(schematic.metadata) do
|
||||
local pos = minetest.string_to_pos(pos_str)
|
||||
local abs_pos = vector.add(pos1, pos)
|
||||
local meta = minetest.get_meta(abs_pos)
|
||||
meta:from_table(meta_table)
|
||||
end
|
||||
|
||||
-- set nodeid's and param2
|
||||
manip:set_data(node_data)
|
||||
manip:set_param2_data(param2)
|
||||
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