diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1ee32fd --- /dev/null +++ b/.github/workflows/test.yml @@ -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 diff --git a/.luacheckrc b/.luacheckrc index 8b31058..3220320 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -14,5 +14,5 @@ read_globals = { "minetest", -- mods - "mapsync" + "mapsync", "mtt" } diff --git a/configure_tool.lua b/configure_tool.lua index c916e67..596a05d 100644 --- a/configure_tool.lua +++ b/configure_tool.lua @@ -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) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..73544f9 --- /dev/null +++ b/docker-compose.yml @@ -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: {} \ No newline at end of file diff --git a/encode.lua b/encode.lua new file mode 100644 index 0000000..5373137 --- /dev/null +++ b/encode.lua @@ -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 \ No newline at end of file diff --git a/encode.spec.lua b/encode.spec.lua new file mode 100644 index 0000000..a5c654f --- /dev/null +++ b/encode.spec.lua @@ -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) \ No newline at end of file diff --git a/init.lua b/init.lua index f0a0d60..aa10ceb 100644 --- a/init.lua +++ b/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 \ No newline at end of file diff --git a/mod.conf b/mod.conf index dbfbe93..81bdeea 100644 --- a/mod.conf +++ b/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 \ No newline at end of file diff --git a/place_tool.lua b/place_tool.lua index 8596c7c..2e0779c 100644 --- a/place_tool.lua +++ b/place_tool.lua @@ -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) \ No newline at end of file diff --git a/rotate.lua b/rotate.lua new file mode 100644 index 0000000..1aeb4ca --- /dev/null +++ b/rotate.lua @@ -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 diff --git a/schematic_flip.lua b/schematic_flip.lua new file mode 100644 index 0000000..ac5574c --- /dev/null +++ b/schematic_flip.lua @@ -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 diff --git a/schematic_orient.lua b/schematic_orient.lua new file mode 100644 index 0000000..9fcc483 --- /dev/null +++ b/schematic_orient.lua @@ -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 diff --git a/schematic_rotate.lua b/schematic_rotate.lua new file mode 100644 index 0000000..65d796e --- /dev/null +++ b/schematic_rotate.lua @@ -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 + diff --git a/schematic_rotate.spec.lua b/schematic_rotate.spec.lua new file mode 100644 index 0000000..894f1b2 --- /dev/null +++ b/schematic_rotate.spec.lua @@ -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) \ No newline at end of file diff --git a/schematic_transpose.lua b/schematic_transpose.lua new file mode 100644 index 0000000..86dddab --- /dev/null +++ b/schematic_transpose.lua @@ -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 diff --git a/serialize.lua b/serialize.lua index ac4cd34..9f2cc01 100644 --- a/serialize.lua +++ b/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() diff --git a/test/Dockerfile b/test/Dockerfile new file mode 100644 index 0000000..1ec599f --- /dev/null +++ b/test/Dockerfile @@ -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 \ No newline at end of file diff --git a/test/minetest.conf b/test/minetest.conf new file mode 100644 index 0000000..3a82daf --- /dev/null +++ b/test/minetest.conf @@ -0,0 +1,5 @@ +default_game = minetest_game +mg_name = v7 +mtt_enable = true +mtt_filter = pick_and_place +secure.trusted_mods = mtt diff --git a/test/world.mt b/test/world.mt new file mode 100644 index 0000000..22c55ea --- /dev/null +++ b/test/world.mt @@ -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