initial import of handle_schematics to "hsl" lib

master
Alexander Weber 2017-01-05 14:13:15 +01:00
parent c3b188efc7
commit 7cec1697ee
10 changed files with 1641 additions and 0 deletions

View File

@ -0,0 +1,215 @@
-- This code is used to read Minecraft schematic files.
--
-- The .schematic file format is described here:
-- http://minecraft.gamepedia.com/Schematic_file_format?cookieSetup=true
-- It is based on the NBT format, which is described here:
-- http://minecraft.gamepedia.com/NBT_Format?cookieSetup=true
-- position in the decompressed string data_stream
local curr_index = 1;
-- helper arry so that the values do not have to be calculated anew each time
local pot256 = { 1 };
for i=1,8 do
pot256[ #pot256+1 ] = pot256[ #pot256 ] * 256;
end
-- read length bytes from data_stream and turn it into an integer value
local read_signed = function( data_stream, length)
local res = 0;
for i=length,1,-1 do
res = res + (string.byte( data_stream, curr_index )* pot256[ i ]);
-- move one further
curr_index = curr_index+1;
end
return res;
end
-- this table will collect the few tags we're actually intrested in
local mc_schematic_data = {};
-- this will be a recursive function
local read_tag;
-- needs to be defined now because it will contain a recursive function
local read_one_tag;
-- read payload of one tag (= a data element in a NBT data structure)
read_one_tag = function( data_stream, tag, title_tag )
if( tag<= 0 or not(data_stream)) then
return;
elseif( tag==1 ) then -- TAG_BYTE: 1 byte
return read_signed( data_stream, 1 );
elseif( tag==2 ) then -- TAG_SHORT: 2 bytes
return read_signed( data_stream, 2 );
elseif( tag==3 ) then -- TAG_INT: 4 bytes
return read_signed( data_stream, 4 );
elseif( tag==4 ) then -- TAG_LONG: 8 bytes
return read_signed( data_stream, 8 );
elseif( tag==5 ) then -- TAG_FLOAT: 4 bytes
return read_signed( data_stream, 4 ); -- the float values are unused here
elseif( tag==6 ) then -- TAG_DOUBLE: 8 bytes
return read_signed( data_stream, 8 ); -- the float values are unused here
elseif( tag==7 ) then -- TAG_Byte_Array
local size = read_signed( data_stream, 4 ); -- TAG_INT
local res = {};
for i=1,size do
-- a Byte_Array does not contain any sub-tags
res[i] = read_one_tag( data_stream, 1, nil ); -- read TAG_BYTE
end
return res;
elseif( tag==8 ) then -- TAG_String
local size = read_signed( data_stream, 2);
local res = string.sub( data_stream, curr_index, curr_index+size-1 );
-- move on in the data stream
curr_index = curr_index + size;
return res;
elseif( tag==9 ) then -- TAG_List
-- these exact values are not particulary intresting
local tagtyp = read_signed( data_stream, 1 ); -- TAG_BYTE
local size = read_signed( data_stream, 4 ); -- TAG_INT
local res = {};
for i=1,size do
-- we need to pass title_tag on to the "child"
res[i] = read_one_tag( data_stream, tagtyp, title_tag );
end
return res;
elseif( tag==10 ) then -- TAG_Compound
return read_tag( data_stream, title_tag );
elseif( tag==11 ) then -- TAG_Int_Array
local size = read_signed( data_stream, 4 ); -- TAG_INT
local res = {};
for i=1,size do
-- a Int_Array does not contain any sub-tags
res[i] = read_one_tag( data_stream, 3, nil ); -- TAG_INT
end
return res;
end
end
-- read tag type, tag name and call read_one_tag to get the payload;
read_tag = function( data_stream, title_tag )
local schematic_data = {};
while( data_stream ) do
local tag = string.byte( data_stream, curr_index);
-- move on in the data stream
curr_index = curr_index + 1;
if( not( tag ) or tag <= 0 ) then
return;
end
local tag_name_length = string.byte( data_stream, curr_index ) * 256 + string.byte(data_stream, curr_index + 1);
-- move 2 further
curr_index = curr_index + 2;
local tag_name = string.sub( data_stream, curr_index, curr_index+tag_name_length-1 );
-- move on...
curr_index = curr_index + tag_name_length;
--print('[analyze_mc_schematic_file] Found: Tag '..tostring( tag )..' <'..tostring( tag_name )..'>');
local res = read_one_tag( data_stream, tag, tag_name );
-- Entities and TileEntities are ignored
if( title_tag == 'Schematic'
and( tag_name == 'Width'
or tag_name == 'Height'
or tag_name == 'Length'
or tag_name == 'Materials' -- "Classic" or "Alpha" (=Survival)
or tag_name == 'Blocks'
or tag_name == 'Data'
)) then
mc_schematic_data[ tag_name ] = res;
end
end
return;
end
handle_schematics.analyze_mc_schematic_file = function( path )
-- these files are usually compressed; there is no point to start if the
-- decompress function is missing
if( minetest.decompress == nil) then
return nil;
end
local file, err = save_restore.file_access(path..'.schematic', "rb")
if (file == nil) then
-- print('[analyze_mc_schematic_file] ERROR: NO such file: '..tostring( path..'.schematic'));
return nil
end
local compressed_data = file:read( "*all" );
--local data_string = minetest.decompress(compressed_data, "deflate" );
local data_string = compressed_data; -- TODO
print('FILE SIZE: '..tostring( string.len( data_string ))); -- TODO
file.close(file)
-- we use this (to this file) global variable to store gathered information;
-- doing so inside the recursive functions proved problematic
mc_schematic_data = {};
-- this index will iterate through the schematic data
curr_index = 1;
-- actually analyze the data
read_tag( data_string, nil );
if( not( mc_schematic_data.Width )
or not( mc_schematic_data.Height )
or not( mc_schematic_data.Length )
or not( mc_schematic_data.Blocks )
or not( mc_schematic_data.Data )) then
print('[analyze_mc_schematic_file] ERROR: Failed to analyze '..tostring( path..'.schematic'));
return nil;
end
local translation_function = handle_schematics.findMC2MTConversion;
if( minetest.get_modpath('mccompat')) then
translation_function = mccompat.findMC2MTConversion;
end
local max_msg = 40; -- just for error handling
local size = {x=mc_schematic_data.Width, y=mc_schematic_data.Height, z=mc_schematic_data.Length};
local scm = {};
local nodenames = {};
local nodenames_id = {};
for y=1,size.y do
scm[y] = {};
for x=1,size.x do
scm[y][x] = {};
for z =1,size.z do
local new_node = translation_function(
-- (Y×length + Z)×width + X.
mc_schematic_data.Blocks[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
mc_schematic_data.Data[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1] );
-- some MC nodes store the information about a node in TWO block and data fields (doors, large flowers, ...)
if( new_node[3] and new_node[3]~=0 ) then
new_node = translation_function(
-- (Y×length + Z)×width + X.
mc_schematic_data.Blocks[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
mc_schematic_data.Data[ ((y-1)*size.z + (z-1) )*size.x + (size.x-x) +1],
mc_schematic_data.Blocks[ ((y-1+new_node[3])*size.z + (z-1) )*size.x + (size.x-x) +1],
mc_schematic_data.Data[ ((y-1+new_node[3])*size.z + (z-1) )*size.x + (size.x-x) +1] );
end
if( not( nodenames_id[ new_node[1]] )) then
nodenames_id[ new_node[1] ] = #nodenames + 1;
nodenames[ nodenames_id[ new_node[1] ]] = new_node[1];
end
-- print a few warning messages in case something goes wrong - but do not exaggerate
if( not( new_node[2] and max_msg>0)) then
-- print('[handle_schematics:schematic] MISSING param2: '..minetest.serialize( new_node ));
new_node[2]=0;
max_msg=max_msg-1;
end
-- save some space by not saving air
if( new_node[1] ~= 'air' ) then
scm[y][x][z] = { nodenames_id[ new_node[1]], new_node[2]};
end
end
end
end
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = {}, after_place_node = {}, rotated=90, burried=0, scm_data_cache = scm, metadata = {}};
end

View File

@ -0,0 +1,279 @@
--[[ taken from src/mg_schematic.cpp:
Minetest Schematic File Format
All values are stored in big-endian byte order.
[u32] signature: 'MTSM'
[u16] version: 3
[u16] size X
[u16] size Y
[u16] size Z
For each Y:
[u8] slice probability value
[Name-ID table] Name ID Mapping Table
[u16] name-id count
For each name-id mapping:
[u16] name length
[u8[] ] name
ZLib deflated {
For each node in schematic: (for z, y, x)
[u16] content
For each node in schematic:
[u8] probability of occurance (param1)
For each node in schematic:
[u8] param2
}
Version changes:
1 - Initial version
2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always
3 - Added y-slice probabilities; this allows for variable height structures
--]]
--handle_schematics = {}
-- taken from https://github.com/MirceaKitsune/minetest_mods_structures/blob/master/structures_io.lua (Taokis Sructures I/O mod)
-- gets the size of a structure file
-- nodenames: contains all the node names that are used in the schematic
-- on_constr: lists all the node names for which on_construct has to be called after placement of the schematic
handle_schematics.analyze_mts_file = function( path )
local size = { x = 0, y = 0, z = 0, version = 0 }
local version = 0;
local file, err = save_restore.file_access(path..'.mts', "rb")
if (file == nil) then
return nil
end
--print('[handle_schematics] Analyzing .mts file '..tostring( path..'.mts' ));
--if( not( string.byte )) then
-- print( '[handle_schematics] Error: string.byte undefined.');
-- return nil;
--end
-- thanks to sfan5 for this advanced code that reads the size from schematic files
local read_s16 = function(fi)
return string.byte(fi:read(1)) * 256 + string.byte(fi:read(1))
end
local function get_schematic_size(f)
-- make sure those are the first 4 characters, otherwise this might be a corrupt file
if f:read(4) ~= "MTSM" then
return nil
end
-- advance 2 more characters
local version = read_s16(f); --f:read(2)
-- the next characters here are our size, read them
return read_s16(f), read_s16(f), read_s16(f), version
end
size.x, size.y, size.z, size.version = get_schematic_size(file)
-- read the slice probability for each y value that was introduced in version 3
if( size.version >= 3 ) then
-- the probability is not very intresting for buildings so we just skip it
file:read( size.y );
end
-- this list is not yet used for anything
local nodenames = {};
-- this list is needed for calling on_construct after place_schematic
local on_constr = {};
-- nodes that require after_place_node to be called
local after_place_node = {};
-- after that: read_s16 (2 bytes) to find out how many diffrent nodenames (node_name_count) are present in the file
local node_name_count = read_s16( file );
for i = 1, node_name_count do
-- the length of the next name
local name_length = read_s16( file );
-- the text of the next name
local name_text = file:read( name_length );
table.insert( nodenames, name_text );
-- in order to get this information, the node has to be defined and loaded
if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].on_construct) then
table.insert( on_constr, name_text );
end
-- some nodes need after_place_node to be called for initialization
if( minetest.registered_nodes[ name_text ] and minetest.registered_nodes[ name_text ].after_place_node) then
table.insert( after_place_node, name_text );
end
end
local rotated = 0;
local burried = 0;
local parts = path:split('_');
if( parts and #parts > 2 ) then
if( parts[#parts]=="0" or parts[#parts]=="90" or parts[#parts]=="180" or parts[#parts]=="270" ) then
rotated = tonumber( parts[#parts] );
burried = tonumber( parts[ #parts-1 ] );
if( not( burried ) or burried>20 or burried<0) then
burried = 0;
end
end
end
-- decompression was recently added; if it is not yet present, we need to use normal place_schematic
if( minetest.decompress == nil) then
file.close(file);
return nil; -- normal place_schematic is no longer supported as minetest.decompress is now part of the release version of minetest
-- return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = on_constr, after_place_node = after_place_node, rotated=rotated, burried=burried, scm_data_cache = nil };
end
local compressed_data = file:read( "*all" );
local data_string = minetest.decompress(compressed_data, "deflate" );
file.close(file)
local ids = {};
local needs_on_constr = {};
local is_air = 0;
-- translate nodenames to ids
for i,v in ipairs( nodenames ) do
ids[ i ] = minetest.get_content_id( v );
needs_on_constr[ i ] = false;
if( minetest.registered_nodes[ v ] and minetest.registered_nodes[ v ].on_construct ) then
needs_on_constr[ i ] = true;
end
if( v == 'air' ) then
is_air = i;
end
end
local p2offset = (size.x*size.y*size.z)*3;
local i = 1;
local scm = {};
for z = 1, size.z do
for y = 1, size.y do
for x = 1, size.x do
if( not( scm[y] )) then
scm[y] = {};
end
if( not( scm[y][x] )) then
scm[y][x] = {};
end
local id = string.byte( data_string, i ) * 256 + string.byte( data_string, i+1 );
i = i + 2;
local p2 = string.byte( data_string, p2offset + math.floor(i/2));
id = id+1;
if( id ~= is_air ) then
scm[y][x][z] = {id, p2};
end
end
end
end
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = on_constr, after_place_node = after_place_node, rotated=rotated, burried=burried, scm_data_cache = scm };
end
handle_schematics.store_mts_file = function( path, data )
data.nodenames[ #data.nodenames+1 ] = 'air';
local file, err = save_restore.file_access(path..'.mts', "wb")
if (file == nil) then
return nil
end
local write_s16 = function( fi, a )
fi:write( string.char( math.floor( a/256) ));
fi:write( string.char( a%256 ));
end
data.size.version = 3; -- we only support version 3 of the .mts file format
file:write( "MTSM" );
write_s16( file, data.size.version );
write_s16( file, data.size.x );
write_s16( file, data.size.y );
write_s16( file, data.size.z );
-- set the slice probability for each y value that was introduced in version 3
if( data.size.version >= 3 ) then
-- the probability is not very intresting for buildings so we just skip it
for i=1,data.size.y do
file:write( string.char(255) );
end
end
-- set how many diffrent nodenames (node_name_count) are present in the file
write_s16( file, #data.nodenames );
for i = 1, #data.nodenames do
-- the length of the next name
write_s16( file, string.len( data.nodenames[ i ] ));
file:write( data.nodenames[ i ] );
end
-- this string will later be compressed
local node_data = "";
-- actual node data
for z = 1, data.size.z do
for y = 1, data.size.y do
for x = 1, data.size.x do
local a = data.scm_data_cache[y][x][z];
if( a and type( a ) == 'table') then
node_data = node_data..string.char( math.floor( a[1]/256) )..string.char( a[1]%256-1);
else
node_data = node_data..string.char( 0 )..string.char( #data.nodenames-1 );
end
end
end
end
-- probability of occurance
for z = 1, data.size.z do
for y = 1, data.size.y do
for x = 1, data.size.x do
node_data = node_data..string.char( 255 );
end
end
end
-- param2
for z = 1, data.size.z do
for y = 1, data.size.y do
for x = 1, data.size.x do
local a = data.scm_data_cache[y][x][z];
if( a and type( a) == 'table' ) then
node_data = node_data..string.char( a[2] );
else
node_data = node_data..string.char( 0 );
end
end
end
end
local compressed_data = minetest.compress( node_data, "deflate" );
file:write( compressed_data );
file.close(file);
print('SAVING '..path..'.mts (converted from .we).');
end
-- read .mts and .we files
handle_schematics.analyze_file = function( file_name, origin_offset, store_as_mts )
local res = handle_schematics.analyze_mts_file( file_name );
-- alternatively, read the mts file
if( not( res )) then
res = handle_schematics.analyze_we_file( file_name, origin_offset );
if( not( res )) then
res = handle_schematics.analyze_mc_schematic_file( file_name );
end
-- print error message only if all import methods failed
if( not( res )) then
print('[handle_schematics] ERROR: Failed to import file \"'..tostring( file_name )..'\"[.mts|.we|.wem|.schematic]');
-- convert to .mts for later usage
elseif( store_as_mts ) then
handle_schematics.store_mts_file( store_as_mts, res );
end
end
return res;
end

View File

@ -0,0 +1,100 @@
handle_schematics.analyze_we_file = function(scm, we_origin)
local c_ignore = minetest.get_content_id("ignore")
-- this table will contain the nodes read
local nodes = {}
-- check if it is a worldedit file
-- (no idea why reading that is done in such a complicated way; a simple deserialize and iteration over all nodes ought to do as well)
local f, err = save_restore.file_access( scm..".we", "r")
if not f then
f, err = save_restore.file_access( scm..".wem", "r")
if not f then
-- error("Could not open schematic '" .. scm .. ".we': " .. err)
return nil;
end
end
local value = f:read("*a")
f:close()
local nodes = worldedit_file.load_schematic(value, we_origin)
-- create a list of nodenames
local nodenames = {};
local nodenames_id = {};
for i,ent in ipairs( nodes ) do
if( ent and ent.name and not( nodenames_id[ ent.name ])) then
nodenames_id[ ent.name ] = #nodenames + 1;
nodenames[ nodenames_id[ ent.name ] ] = ent.name;
end
end
scm = {}
local maxx, maxy, maxz = -1, -1, -1
local all_meta = {};
for i = 1, #nodes do
local ent = nodes[i]
ent.x = ent.x + 1
ent.y = ent.y + 1
ent.z = ent.z + 1
if ent.x > maxx then
maxx = ent.x
end
if ent.y > maxy then
maxy = ent.y
end
if ent.z > maxz then
maxz = ent.z
end
if scm[ent.y] == nil then
scm[ent.y] = {}
end
if scm[ent.y][ent.x] == nil then
scm[ent.y][ent.x] = {}
end
if ent.param2 == nil then
ent.param2 = 0
end
-- metadata is only of intrest if it is not empty
if( ent.meta and (ent.meta.fields or ent.meta.inventory)) then
local has_meta = false;
for _,v in pairs( ent.meta.fields ) do
has_meta = true;
end
for _,v in pairs(ent.meta.inventory) do
has_meta = true;
end
if( has_meta == true ) then
all_meta[ #all_meta+1 ] = {
x=ent.x,
y=ent.y,
z=ent.z,
fields = ent.meta.fields,
inventory = ent.meta.inventory};
end
end
scm[ent.y][ent.x][ent.z] = { nodenames_id[ ent.name ], ent.param2 };
end
for y = 1, maxy do
if scm[y] == nil then
scm[y] = {}
end
for x = 1, maxx do
if scm[y][x] == nil then
scm[y][x] = {}
end
end
end
local size = {};
size.y = math.max(maxy,0);
size.x = math.max(maxx,0);
size.z = math.max(maxz,0);
return { size = { x=size.x, y=size.y, z=size.z}, nodenames = nodenames, on_constr = {}, after_place_node = {}, rotated=0, burried=0, scm_data_cache = scm, metadata = all_meta };
end

View File

@ -0,0 +1,162 @@
handle_schematics.sort_pos_get_size = function( p1, p2 )
local res = {x=p1.x, y=p1.y, z=p1.z,
sizex = math.abs( p1.x - p2.x )+1,
sizey = math.abs( p1.y - p2.y )+1,
sizez = math.abs( p1.z - p2.z )+1};
if( p2.x < p1.x ) then
res.x = p2.x;
end
if( p2.y < p1.y ) then
res.y = p2.y;
end
if( p2.z < p1.z ) then
res.z = p2.z;
end
return res;
end
local handle_schematics_get_meta_table = function( pos, all_meta, start_pos )
local m = minetest.get_meta( pos ):to_table();
local empty_meta = true;
-- the inventory part contains functions and cannot be fed to minetest.serialize directly
local invlist = {};
local count_inv = 0;
local inv_is_empty = true;
for name, list in pairs( m.inventory ) do
invlist[ name ] = {};
count_inv = count_inv + 1;
for i, stack in ipairs(list) do
if( not( stack:is_empty())) then
invlist[ name ][ i ] = stack:to_string();
empty_meta = false;
inv_is_empty = false;
end
end
end
-- the fields part at least is unproblematic
local count_fields = 0;
if( empty_meta and m.fields ) then
for k,v in pairs( m.fields ) do
empty_meta = false;
count_fields = count_fields + 1;
end
end
-- ignore default:sign_wall without text on it
if( count_inv==0
and count_fields<=3 and m.fields.formspec and m.fields.infotext
and m.fields.formspec == "field[text;;${text}]"
and m.fields.infotext == "\"\"") then
-- also consider signs empty if their text has been set once and deleted afterwards
if( not( m.fields.text ) or m.fields.text == "" ) then
print('SKIPPING empty sign AT '..minetest.pos_to_string( pos)..' while saving metadata.');
empty_meta = true;
end
elseif( count_inv > 0 and inv_is_empty
and count_fields>0 and m.fields.formspec ) then
local n = minetest.get_node( pos );
if( n and n.name
and (n.name=='default:chest' or n.name=='default:chest_locked' or n.name=='default:bookshelf'
or n.name=='default:furnace' or n.name=='default:furnace_active'
or n.name=='cottages:shelf' or n.name=='cottages:anvil' or n.name=='cottages:threshing_floor' )) then
print('SKIPPING empty '..tostring(n.name)..' AT '..minetest.pos_to_string( pos )..' while saving metadata.');
empty_meta = true;
end
end
-- only save if there is something to be saved
if( not( empty_meta )) then
-- positions are stored as relative positions
all_meta[ #all_meta+1 ] = {
x=pos.x-start_pos.x,
y=pos.y-start_pos.y,
z=pos.z-start_pos.z,
fields = m.fields,
inventory = invlist};
end
end
-- reads metadata values from start_pos to end_pos and stores them in a file
handle_schematics.save_meta = function( start_pos, end_pos, filename )
local all_meta = {};
local p = handle_schematics.sort_pos_get_size( start_pos, end_pos );
if( minetest.find_nodes_with_meta ) then
for _,pos in ipairs( minetest.find_nodes_with_meta( start_pos, end_pos )) do
handle_schematics_get_meta_table( pos, all_meta, p );
end
else
for x=p.x, p.x+p.sizex do
for y=p.y, p.y+p.sizey do
for z=p.z, p.z+p.sizez do
handle_schematics_get_meta_table( {x=x-p.x, y=y-p.y, z=z-p.z}, all_meta, p );
end
end
end
end
if( #all_meta > 0 ) then
save_restore.save_data( 'schems/'..filename..'.meta', all_meta );
end
end
-- all metadata values will be deleted when this function is called,
-- making the area ready for new voxelmanip/schematic data
handle_schematics.clear_meta = function( start_pos, end_pos )
local empty_meta = { inventory = {}, fields = {} };
if( minetest.find_nodes_with_meta ) then
for _,pos in ipairs( minetest.find_nodes_with_meta( start_pos, end_pos )) do
local meta = minetest.get_meta( pos );
meta:from_table( empty_meta );
end
end
end
-- restore metadata from file
-- TODO: use relative instead of absolute positions (already done for .we files)
-- TODO: handle mirror
handle_schematics.restore_meta = function( filename, all_meta, start_pos, end_pos, rotate, mirror )
if( not( all_meta ) and filename ) then
all_meta = save_restore.restore_data( filename..'.meta' );
end
for _,pos in ipairs( all_meta ) do
local p = {};
if( rotate == 0 ) then
p = {x=start_pos.x+pos.x-1, y=start_pos.y+pos.y-1, z=start_pos.z+pos.z-1};
elseif( rotate == 1 ) then
p = {x=start_pos.x+pos.z-1, y=start_pos.y+pos.y-1, z=end_pos.z -pos.x+1};
elseif( rotate == 2 ) then
p = {x=end_pos.x -pos.x+1, y=start_pos.y+pos.y-1, z=end_pos.z -pos.z+1};
elseif( rotate == 3 ) then
p = {x=end_pos.x -pos.z+1, y=start_pos.y+pos.y-1, z=start_pos.z+pos.x-1};
end
local meta = minetest.get_meta( p );
meta:from_table( {inventory = pos.inventory, fields = pos.fields });
end
end
-- return true on success; will overwrite existing files
handle_schematics.create_schematic_with_meta = function( p1, p2, base_filename )
-- create directory for the schematics (same path as WorldEdit uses)
save_restore.create_schems_directory();
local complete_filename = minetest.get_worldpath()..'/schems/'..base_filename..'.mts';
-- actually create the schematic
minetest.create_schematic( p1, p2, nil, complete_filename, nil);
-- save metadata; the file will only be created if there is any metadata that is to be saved
handle_schematics.save_meta( p1, p2, base_filename );
return save_restore.file_exists( complete_filename );
end

View File

@ -0,0 +1,107 @@
-- helper function; sorts by the second element of the table
local function handle_schematics_comp(a,b)
if (a[2] > b[2]) then
return true;
end
end
-- create a statistic about how frequent each node name occoured
handle_schematics.count_nodes = function( data )
local statistic = {};
-- make sure all node names are counted (air may sometimes be included without occouring)
for id=1, #data.nodenames do
statistic[ id ] = { id, 0};
end
for z = 1, data.size.z do
for y = 1, data.size.y do
for x = 1, data.size.x do
local a = data.scm_data_cache[y][x][z];
if( a ) then
local id = 0;
if( type( a )=='table' ) then
id = a[1];
else
id = a;
end
if( statistic[ id ] and statistic[ id ][ 2 ] ) then
statistic[ id ] = { id, statistic[ id ][ 2 ]+1 };
end
end
end
end
end
table.sort( statistic, handle_schematics_comp );
return statistic;
end
-- this function makes sure that the building will always extend to the right and in front of the build chest
handle_schematics.translate_param2_to_rotation = function( param2, mirror, start_pos, orig_max, rotated, burried, orients, yoff )
-- mg_villages stores available rotations of buildings in orients={0,1,2,3] format
if( orients and #orients and orients[1]~=0) then
-- reset rotated - else we'd apply it twice
rotated = 0;
if( orients[1]==1 ) then
rotated = rotated + 90;
elseif( orients[1]==2 ) then
rotated = rotated + 180;
elseif( orients[1]==3 ) then
rotated = rotated + 270;
end
if( rotated >= 360 ) then
rotated = rotated % 360;
end
end
local max = {x=orig_max.x, y=orig_max.y, z=orig_max.z};
-- if the schematic has been saved in a rotated way, swapping x and z may be necessary
if( rotated==90 or rotated==270) then
max.x = orig_max.z;
max.z = orig_max.x;
end
-- the building may have a cellar or something alike
if( burried and burried ~= 0 and yoff == nil ) then
start_pos.y = start_pos.y - burried;
end
-- make sure the building always extends forward and to the right of the player
local rotate = 0;
if( param2 == 0 ) then rotate = 270; if( mirror==1 ) then start_pos.x = start_pos.x - max.x + max.z; end -- z gets larger
elseif( param2 == 1 ) then rotate = 0; start_pos.z = start_pos.z - max.z; -- x gets larger
elseif( param2 == 2 ) then rotate = 90; start_pos.z = start_pos.z - max.x;
if( mirror==0 ) then start_pos.x = start_pos.x - max.z; -- z gets smaller
else start_pos.x = start_pos.x - max.x; end
elseif( param2 == 3 ) then rotate = 180; start_pos.x = start_pos.x - max.x; -- x gets smaller
end
if( param2 == 1 or param2 == 0) then
start_pos.z = start_pos.z + 1;
elseif( param2 == 1 or param2 == 2 ) then
start_pos.x = start_pos.x + 1;
end
if( param2 == 1 ) then
start_pos.x = start_pos.x + 1;
end
rotate = rotate + rotated;
-- make sure the rotation does not reach or exceed 360 degree
if( rotate >= 360 ) then
rotate = rotate - 360;
end
-- rotate dimensions when needed
if( param2==0 or param2==2) then
local tmp = max.x;
max.x = max.z;
max.z = tmp;
end
return { rotate=rotate, start_pos = {x=start_pos.x, y=start_pos.y, z=start_pos.z},
end_pos = {x=(start_pos.x+max.x-1), y=(start_pos.y+max.y-1), z=(start_pos.z+max.z-1) },
max = {x=max.x, y=max.y, z=max.z}};
end

36
libs/hsl/init.lua Normal file
View File

@ -0,0 +1,36 @@
--[[ handle_schematics library
extracted from https://github.com/Sokomine/handle_schematics
see https://github.com/Sokomine/handle_schematics/issues/7
]]
local hsl = {}
-- temporary path assignment till the hsl is own mod
local modpath = minetest.get_modpath(minetest.get_current_modname())
modpath = modpath..'/libs/hsl/'
-- adds worldedit_file.* namespace
-- deserialize worldedit savefiles
dofile(modpath.."worldedit_file.lua")
-- uses handle_schematics.* namespace
-- reads and analyzes .mts files (minetest schematics)
dofile(modpath.."/analyze_mts_file.lua")
-- reads and analyzes worldedit files
dofile(modpath.."/analyze_we_file.lua")
-- reads and analyzes Minecraft schematic files
dofile(modpath.."/translate_nodenames_for_mc_schematic.lua")
dofile(modpath.."/analyze_mc_schematic_file.lua")
-- handles rotation and mirroring
dofile(modpath.."/rotate.lua")
-- count nodes, take param2 into account for rotation etc.
dofile(modpath.."/handle_schematics_misc.lua")
-- store and restore metadata
dofile(modpath.."/save_restore.lua");
dofile(modpath.."/handle_schematics_meta.lua");

114
libs/hsl/rotate.lua Normal file
View File

@ -0,0 +1,114 @@
local rotate_facedir = function(facedir)
return ({1, 2, 3, 0,
13, 14, 15, 12,
17, 18, 19, 16,
9, 10, 11, 8,
5, 6, 7, 4,
21, 22, 23, 20})[facedir+1]
end
-- accessd through handle_schematics.mirror_facedir[ (rotation%2)+1 ][ facedir+1 ]
handle_schematics.mirror_facedir =
{{ 2, 1, 0, 3, -- 0, 1, 2, 3
8, 9, 10, 11, -- 4, 5, 6, 7
4, 5, 6, 7, -- 8, 9,10,11
12, 13, 14, 15, --12,13,14,15
16, 17, 18, 19, --16,17,18,19
22, 21, 20, 23 --20,21,22,23
},
{ 0, 3, 2, 1, -- 0, 1, 2, 3
4, 7, 6, 5, -- 4, 5, 6, 7
8, 9, 10, 11, -- 8, 9,10,11
16, 17, 18, 19, --12,13,14,15
12, 15, 14, 13, --16,17,18,19
20, 23, 22, 21 --20,21,22,23
}};
local rotate_wallmounted = function(wallmounted)
return ({0, 1, 5, 4, 2, 3})[wallmounted+1]
end
handle_schematics.get_param2_rotated = function( paramtype2, p2 )
local p2r = {};
p2r[ 1 ] = p2;
if( paramtype2 == 'wallmounted' ) then
for i = 2,4 do
p2r[ i ] = rotate_wallmounted( p2r[ i-1 ]);
end
elseif( paramtype2 == 'facedir' ) then
for i = 2,4 do
p2r[ i ] = rotate_facedir( p2r[ i-1 ]);
end
p2r[5]=1; -- indicate that it is wallmounted
else
return { p2, p2, p2, p2 };
end
return p2r;
end
handle_schematics.mirrored_node = {};
handle_schematics.add_mirrored_node_type = function( name, mirrored_name )
handle_schematics.mirrored_node[ name ] = mirrored_name;
local id = minetest.get_content_id( name );
local id_mi = minetest.get_content_id( mirrored_name );
local c_ignore = minetest.get_content_id( 'ignore' );
if( id and id_mi and id ~= c_ignore and id_mi ~= c_ignore ) then
handle_schematics.mirrored_node[ id ] = id_mi;
end
end
local door_materials = {'wood','steel','glass','obsidian_glass'};
for _,material in ipairs( door_materials ) do
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_b_1', 'doors:door_'..material..'_b_2' );
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_t_1', 'doors:door_'..material..'_t_2' );
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_b_2', 'doors:door_'..material..'_b_1' );
handle_schematics.add_mirrored_node_type( 'doors:door_'..material..'_t_2', 'doors:door_'..material..'_t_1' );
end
handle_schematics.rotation_table = {};
handle_schematics.rotation_table[ 'facedir' ] = {};
handle_schematics.rotation_table[ 'wallmounted' ] = {};
for paramtype2,v in pairs( handle_schematics.rotation_table ) do
for param2 = 0,23 do
if( param2 < 6 or paramtype2 == 'facedir' ) then
local param2list = handle_schematics.get_param2_rotated( paramtype2, param2);
handle_schematics.rotation_table[ paramtype2 ][ param2+1 ] = {};
for rotation = 0,3 do
local np2 = param2list[ rotation + 1];
local mirror_x = np2;
local mirror_z = np2;
-- mirror_x
if( #param2list==5) then
mirror_x = handle_schematics.mirror_facedir[ (( rotation +1)%2)+1 ][ np2+1 ];
elseif( #param2list<5
and (( rotation%2==1 and (np2==4 or np2==5))
or ( rotation%2==0 and (np2==2 or np2==3)))) then
mirror_x = param2list[ ( rotation + 2)%4 +1];
end
-- mirror_z
if( #param2list==5) then
mirror_z = handle_schematics.mirror_facedir[ (rotation %2)+1 ][ np2+1 ];
elseif( #param2list<5
and (( rotation%2==0 and (np2==4 or np2==5))
or ( rotation%2==1 and (np2==2 or np2==3)))) then
mirror_z = param2list[ ( rotation + 2)%4 +1];
end
handle_schematics.rotation_table[ paramtype2 ][ param2+1 ][ rotation+1 ] = { np2, mirror_x, mirror_z };
end
end
end
end

88
libs/hsl/save_restore.lua Normal file
View File

@ -0,0 +1,88 @@
-- reserve the namespace
save_restore = {}
-- TODO: if this gets more versatile, add sanity checks for filename
-- TODO: apart from allowing filenames, schems/<filename> also needs to be allowed
-- TODO: save and restore ought to be library functions and not implemented in each individual mod!
save_restore.save_data = function( filename, data )
local path = minetest.get_worldpath()..'/'..filename;
local file = io.open( path, 'w' );
if( file ) then
file:write( minetest.serialize( data ));
file:close();
else
print("[save_restore] Error: Savefile '"..tostring( path ).."' could not be written.");
end
end
save_restore.restore_data = function( filename )
local file = io.open( filename, 'r' );
if( file ) then
local data = file:read("*all");
file:close();
return minetest.deserialize( data );
else
print("[save_restore] Error: Savefile '"..tostring( filename ).."' not found.");
return {}; -- return empty table
end
end
save_restore.file_exists = function( filename )
local file = save_restore.file_access( filename, 'r' );
if( file ) then
file:close();
return true;
end
return;
end
save_restore.create_schems_directory = function()
local directory = minetest.get_worldpath()..'/schems';
if( not( save_restore.file_exists( directory ))) then
if( minetest.mkdir ) then
minetest.mkdir( directory );
else
os.execute("mkdir \""..directory.. "\"");
end
end
end
-- we only need the function io.open in a version that can read schematic files from diffrent places,
-- even if a secure environment is enforced; this does require an exception for the mod
local ie_io_open = io.open;
if( minetest.request_insecure_environment ) then
local ie, req_ie = _G, minetest.request_insecure_environment
if req_ie then ie = req_ie() end
if ie then
ie_io_open = ie.io.open;
end
end
-- only a certain type of files can be read and written
save_restore.file_access = function( path, params )
if( (params=='r' or params=='rb')
and ( string.find( path, '.mts', -4 )
or string.find( path, '.schematic', -11 )
or string.find( path, '.we', -3 )
or string.find( path, '.wem', -4 ) )) then
return ie_io_open( path, params );
elseif( (params=='w' or params=='wb')
and ( string.find( path, '.mts', -4 )
or string.find( path, '.schematic', -11 )
or string.find( path, '.we', -3 )
or string.find( path, '.wem', -4 ) )) then
return ie_io_open( path, params );
end
end

View File

@ -0,0 +1,402 @@
-- based on:
-- # Minecraft to Minetest WE schematic MCEdit filter
-- # by sfan5
-- #blockdata -1 means ignore
local P2_IGNORE = -1;
-- #blockdata -2 means copy without change
local P2_COPY = -2;
-- #blockdata -3 means copy and convert the mc facedir value to mt facedir
local P2_CONVERT= -3;
-- #blockdata -4 is for stairs to support upside down ones
local P2_STAIR = -4;
-- #blockdata selects one of the listed subtypes
local P2_SELECT = -5;
-- #Reference MC: http://media-mcw.cursecdn.com/8/8c/DataValuesBeta.png
-- #Reference MT:
-- # https://github.com/minetest/common/blob/master/mods/default/init.lua
-- # https://github.com/minetest/common/blob/master/mods/wool/init.lua
-- # https://github.com/minetest/common/blob/master/mods/stairs/init.lua
local conversionTable = {
-- #blockid blockdata minetest-nodename
-- [0] = {P2_IGNORE, "air"},
[1] = {P2_IGNORE, "default:stone"},
-- 0: stone; 1: granite; 2: polished granite;
-- 3: diorite; 4: polished diorite; 5: andesite;
-- 6: polished andesite
[2] = {P2_IGNORE, "default:dirt_with_grass"},
[3] = {P2_IGNORE, "default:dirt"},
-- 0: dirt; 1: coarse dirt; 2: podzol
[4] = {P2_IGNORE, "default:cobble"},
[5] = {P2_SELECT, { [0]="default:wood",
[1]="moretrees:spruce_planks",
[2]="moretrees:birch_planks",
[3]="default:junglewood",
[4]="moretrees:acacia_planks",
[5]="moretrees:oak_planks"}},
[6] = {P2_SELECT, { [0]="default:wood",
[1]="moretrees:spruce_sapling",
[2]="moretrees:birch_sapling",
[3]="default:junglesapling",
[4]="moretrees:acacia_sapling",
[5]="moretrees:oak_sapling"}},
[7] = {P2_IGNORE, "minecraft:bedrock"}, --# FIXME Bedrock
[8] = {P2_IGNORE, "default:water_flowing"},
[9] = {P2_IGNORE, "default:water_source"},
[10] = {P2_IGNORE, "default:lava_flowing"},
[11] = {P2_IGNORE, "default:lava_source"},
[12] = {P2_SELECT, { [0]="default:sand",
[1]="default:desert_sand"}},
[13] = {P2_IGNORE, "default:gravel"},
[14] = {P2_IGNORE, "default:stone_with_gold"},
[15] = {P2_IGNORE, "default:stone_with_iron"},
[16] = {P2_IGNORE, "default:stone_with_coal"},
-- TODO: the trees have facedir
[17] = {P2_SELECT, { [0]="default:tree",
[1]="moretrees:spruce_trunk",
[2]="moretrees:birch_trunk",
[3]="default:jungletree",
[4]="moretrees:acacia_trunk",
[5]="moretrees:oak_trunk"}},
[18] = {P2_SELECT, { [0]="default:leaves",
[1]="moretrees:spruce_leaves",
[2]="moretrees:birch_leaves",
[3]="default:jungleleaves",
[4]="default:leaves",
[5]="moretrees:spruce_leaves",
[6]="moretrees:birch_leaves",
[7]="default:jungleleaves",
[8]="default:leaves",
[9]="moretrees:spruce_leaves",
[10]="moretrees:birch_leaves",
[11]="default:jungleleaves",
[12]="default:leaves",
[13]="moretrees:spruce_leaves",
[14]="moretrees:birch_leaves",
[15]="default:jungleleaves"}},
[19] = {P2_CONVERT,"minecraft:sponge"},
[20] = {P2_IGNORE, "default:glass"},
[21] = {P2_IGNORE, "default:stone_with_copper"}, -- Lapis Lazuli Ore
[22] = {P2_IGNORE, "default:copperblock"}, -- Lapis Lazuli Block
[23] = {P2_CONVERT,"minecraft:dispenser"},
[24] = {P2_SELECT, { [0]="default:sandstone",
[1]="default:sandstonebrick",
[2]="default:sandstone"}},
[25] = {P2_CONVERT,"minecraft:nodeblock"},
[26] = {P2_CONVERT,"beds:bed"}, -- TODO: might require special handling?
[27] = {P2_CONVERT,"minecraft:golden_rail"},
[28] = {P2_CONVERT,"minecraft:detector_rail"},
-- 29: sticky piston
[30] = {P2_CONVERT,"minecraft:web"},
[31] = {P2_SELECT, { [0]="default:dry_shrub",
[1]="default:grass_4",
[2]="ferns:fern_02"}},
[32] = {P2_IGNORE, "default:dry_shrub"},
-- 34: piston head
[35] = {P2_SELECT, { [0]="wool:white",
[1]="wool:orange",
[2]="wool:magenta",
[3]="wool:light_blue",
[4]="wool:yellow",
[5]="wool:green",
[6]="wool:pink",
[7]="wool:dark_grey",
[8]="wool:grey",
[9]="wool:cyan",
[10]="wool:violet",
[11]="wool:blue",
[12]="wool:brown",
[13]="wool:dark_green",
[14]="wool:red",
[15]="wool:black"}},
-- 36: piston extension
[37] = {P2_IGNORE, "flowers:dandelion_yellow"},
[38] = {P2_SELECT, { [0]="flowers:rose",
[1]="flowers:geranium",
[2]="flowers:viola",
[3]="flowers:dandelion_white",
[4]="tulips:red",
[5]="flowers:tulip",
[6]="tulips:white",
[7]="tulips:pink",
[8]="tulips:black"}},
[41] = {P2_IGNORE, "default:goldblock"},
[42] = {P2_IGNORE, "default:steelblock"},
-- double stone slabs...full blocks?
[43] = {P2_SELECT, { [0]="default:stone",
[1]="default:sandstonebrick",
[2]="default:wood",
[3]="default:cobble",
[4]="default:brick",
[5]="default:stonebrick",
[6]="nether:brick",
[7]="quartz:quartz",
[8]="moreblocks:split_stone_tile",
[9]="default:sandstone"}},
[44] = {P2_SELECT, { [0]="stairs:slab_stone",
[1]="stairs:slab_sandstone",
[2]="stairs:slab_wood",
[3]="stairs:slab_cobble",
[4]="stairs:slab_brick",
[5]="stairs:slab_stonebrick",
[6]="stairs:slab_nether",
[7]="stairs:slab_quartz",
[8]="stairs:slab_stoneupside_down",
[9]="stairs:slab_sandstoneupside_down",
[10]="stairs:slab_woodupside_down",
[11]="stairs:slab_cobbleupside_down",
[12]="stairs:slab_brickupside_down",
[13]="stairs:slab_stonebrickupside_down",
[14]="stairs:slab_netzerupside_down",
[15]="stairs:slab_quartzupside_down"}},
[45] = {P2_IGNORE, "default:brick"},
[46] = {P2_CONVERT,"tnt:tnt"},
[47] = {P2_IGNORE, "default:bookshelf"},
[48] = {P2_IGNORE, "default:mossycobble"},
[49] = {P2_IGNORE, "default:obsidian"},
[50] = {P2_CONVERT,"default:torch"},
[51] = {P2_IGNORE, "fire:basic_flame"},
[52] = {P2_CONVERT,"minecraft:mob_spawner"},
[53] = {P2_STAIR, "stairs:stair_wood"},
[54] = {P2_IGNORE, "default:chest"},
[56] = {P2_IGNORE, "default:stone_with_diamond"},
[57] = {P2_IGNORE, "default:diamondblock"},
[58] = {P2_CONVERT,"minecraft:crafting_table"},
[59] = {P2_IGNORE, "farming:wheat_8"},
[60] = {P2_IGNORE, "farming:soil_wet"},
[61] = {P2_IGNORE, "default:furnace"},
[62] = {P2_IGNORE, "default:furnace_active"},
[63] = {P2_IGNORE, "default:sign_wall"},
[64] = {P2_IGNORE, "doors:door_wood_t_1"},
[65] = {P2_IGNORE, "default:ladder"},
[66] = {P2_IGNORE, "default:rail"},
[67] = {P2_STAIR, "stairs:stair_cobble"},
[68] = {P2_CONVERT,"default:sign_wall"},
[71] = {P2_IGNORE, "doors:door_steel_t_1"},
[78] = {P2_IGNORE, "default:snow"},
[79] = {P2_IGNORE, "default:ice"},
[80] = {P2_IGNORE, "default:snowblock"},
[81] = {P2_IGNORE, "default:cactus"},
[82] = {P2_IGNORE, "default:clay"},
[83] = {P2_IGNORE, "default:papyrus"},
[84] = {P2_CONVERT,"minecraft:jukebox"},
[85] = {P2_IGNORE, "default:fence_wood"},
[86] = {P2_CONVERT,"farming:pumpkin"},
[91] = {P2_CONVERT,"farming:pumpkin_face_light"},
[92] = {P2_CONVERT,"minecraft:cake"},
[95] = {P2_IGNORE, "minecraft:stained_glass"}, -- TODO
[96] = {P2_CONVERT,"doors:trapdoor"},
[97] = {P2_IGNORE, "minecraft:monster_egg"},
[98] = {P2_IGNORE, "default:stonebrick"},
[108]= {P2_STAIR, "stairs:stair_brick"},
[109]= {P2_CONVERT,"stairs:stair_stonebrick"},
-- TODO: double ... wood slab...
[125]= {P2_SELECT, { [0]="default:wood",
[1]="moretrees:spruce_planks",
[2]="moretrees:birch_planks",
[3]="default:junglewood",
[4]="moretrees:acacia_planks",
[5]="moretrees:oak_planks"}},
[125]= {P2_IGNORE, "default:wood"},
[126]= {P2_SELECT, { [0]="stairs:slab_wood",
[1]="stairs:slab_spruce_planks",
[2]="stairs:slab_birch_planks",
[3]="stairs:slab_junglewood",
[4]="stairs:slab_acacia_planks",
[5]="stairs:slab_oak_planks",
[8]="stairs:slab_woodupside_down",
[9]="stairs:slab_spruce_planksupside_down",
[10]="stairs:slab_birch_planksupside_down",
[11]="stairs:slab_junglewoodupside_down",
[12]="stairs:slab_acacia_planksupside_down",
[13]="stairs:slab_oak_planksupside_down"}},
[126]= {P2_IGNORE, "stairs:slab_wood"},
[128]= {P2_STAIR, "stairs:stair_sandstone"},
[129]= {P2_IGNORE, "default:stone_with_mese"},
[133]= {P2_IGNORE, "default:mese"},
[134]= {P2_STAIR, "stairs:stair_wood"},
[135]= {P2_STAIR, "stairs:stair_wood"},
[136]= {P2_STAIR, "stairs:stair_junglewood"},
-- #Mesecons section
-- # Reference: https://github.com/Jeija/minetest-mod-mesecons/blob/master/mesecons_alias/init.lua
[25] = {P2_IGNORE, "mesecons_noteblock:noteblock"},
[29] = {P2_CONVERT,"mesecons_pistons:piston_sticky_off"},
[33] = {P2_CONVERT,"mesecons_pistons:piston_normal_off"},
[55] = {P2_IGNORE, "mesecons:wire_00000000_off"},
[69] = {P2_CONVERT,"mesecons_walllever:wall_lever_off"},
[70] = {P2_IGNORE, "mesecons_pressureplates:pressure_plate_stone_off"},
[72] = {P2_IGNORE, "mesecons_pressureplates:pressure_plate_wood_off"},
[73] = {P2_IGNORE, "default:stone_with_mese"},
[74] = {P2_IGNORE, "default:stone_with_mese"},
[75] = {P2_CONVERT,"mesecons_torch:torch_off"},
[76] = {P2_CONVERT,"mesecons_torch:torch_on"},
[77] = {P2_CONVERT,"mesecons_button:button_off"},
[93] = {P2_CONVERT,"mesecons_delayer:delayer_off_1"},
[94] = {P2_CONVERT,"mesecons_delayer:delayer_on_1"},
-- see mod https://github.com/doyousketch2/stained_glass
[95] = {P2_SELECT, { [0]="default:glass", -- TODO
[1]="stained_glass:orange__",
[2]="stained_glass:magenta__",
[3]="stained_glass:skyblue__",
[4]="stained_glass:yellow__",
[5]="stained_glass:lime__",
[6]="stained_glass:redviolet__",
[7]="stained_glass:dark_grey__", -- TODO
[8]="stained_glass:grey__", -- TODO
[9]="stained_glass:cyan__",
[10]="stained_glass:violet__",
[11]="stained_glass:blue__",
[12]="stained_glass:orange_dark_",
[13]="stained_glass:green__",
[14]="stained_glass:red__",
[15]="stained_glass:black__"}}, -- TODO
[101]= {P2_CONVERT,"xpanes:bar"},
[102]= {P2_CONVERT,"xpanes:pane"},
[103]= {P2_IGNORE, "farming:melon"},
[104]= {P2_IGNORE, "minecraft:pumpkin_stem"},
[105]= {P2_IGNORE, "minecraft:melon_stem"},
[106]= {P2_CONVERT,"vines:vine"},
[107]= {P2_CONVERT,"minecraft:fence_gate"},
[108]= {P2_STAIR, "stairs:stair_brick"},
[109]= {P2_STAIR, "stairs:stair_stonebrick"},
[110]= {P2_CONVERT,"minecraft:mycelium"},
[111]= {P2_CONVERT,"flowers:waterlily"},
[112]= {P2_CONVERT,"minecraft:nether_brick"},
[113]= {P2_CONVERT,"minecraft:nether_brick_fence"},
[114]= {P2_CONVERT,"minecraft:nether_brick_stairs"},
[115]= {P2_CONVERT,"minecraft:nether_wart"},
[116]= {P2_CONVERT,"minecraft:enchanting_table"},
[117]= {P2_CONVERT,"minecraft:brewing_stand"},
[118]= {P2_CONVERT,"minecraft:cauldron"},
[119]= {P2_CONVERT,"minecraft:end_portal"},
[120]= {P2_CONVERT,"minecraft:end_portal_frame"},
[121]= {P2_CONVERT,"minecraft:end_stone"},
[122]= {P2_CONVERT,"minecraft:dragon_egg"},
[123]= {P2_IGNORE, "mesecons_lightstone_red_off"},
[124]= {P2_IGNORE, "mesecons_lightstone_red_on"},
[125]= {P2_CONVERT,"minecraft:double_wooden_slab"},
[126]= {P2_CONVERT,"stairs:slab_wood"},
[127]= {P2_CONVERT,"farming_plus:cocoa"},
[137]= {P2_IGNORE, "mesecons_commandblock:commandblock_off"},
[151]= {P2_IGNORE, "mesecons_solarpanel:solar_panel_off"},
[152]= {P2_IGNORE, "default:mese"},
-- see mod https://github.com/tenplus1/bakedclay
[159] = {P2_SELECT, { [0]="bakedclay:white",
[1]="bakedclay:orange",
[2]="bakedclay:magenta",
[3]="bakedclay:light_blue", -- TODO
[4]="bakedclay:yellow",
[5]="bakedclay:green",
[6]="bakedclay:pink",
[7]="bakedclay:dark_grey",
[8]="bakedclay:grey",
[9]="bakedclay:cyan",
[10]="bakedclay:violet",
[11]="bakedclay:blue",
[12]="bakedclay:brown",
[13]="bakedclay:dark_green",
[14]="bakedclay:red",
[15]="bakedclay:black"}},
-- see mod mccarpet https://forum.minetest.net/viewtopic.php?t=7419
[171] = {P2_SELECT, { [0]="mccarpet:white",
[1]="mccarpet:orange",
[2]="mccarpet:magenta",
[3]="mccarpet:light_blue", -- TODO
[4]="mccarpet:yellow",
[5]="mccarpet:green",
[6]="mccarpet:pink",
[7]="mccarpet:dark_grey",
[8]="mccarpet:grey",
[9]="mccarpet:cyan",
[10]="mccarpet:violet",
[11]="mccarpet:blue",
[12]="mccarpet:brown",
[13]="mccarpet:dark_green",
[14]="mccarpet:red",
[15]="mccarpet:black"}},
[181] = {P2_SELECT, { [0]="default:desert_stonebrick",
[1]="default:desertstone"}},
-- #Nether section
-- # Reference: https://github.com/PilzAdam/nether/blob/master/init.lua
[43] = {P2_IGNORE, "nether:brick"},
[87] = {P2_IGNORE, "nether:rack"},
[88] = {P2_IGNORE, "nether:sand"},
[89] = {P2_IGNORE, "nether:glowstone"},
[90] = {P2_CONVERT,"nether:portal"},
-- #Riesenpilz Section
-- # Reference: https://github.com/HybridDog/riesenpilz/blob/master/init.lua
[39] = {P2_IGNORE, "riesenpilz:brown"},
[40] = {P2_IGNORE, "riesenpilz:red"},
[99] = {P2_CONVERT,"riesenpilz:head_brown"},
[100]= {P2_CONVERT,"riesenpilz:head_brown"},
}
local mc2mtFacedir = function(blockdata)
-- #Minetest
-- # x+ = 2
-- # x- = 3
-- # z+ = 1
-- # z- = 0
-- #Minecraft
-- # x+ = 3
-- # x- = 1
-- # z+ = 0
-- # z- = 2
local tbl = {
[3]= 2,
[1]= 1,
[0]= 3,
[2]= 0,
}
if( tbl[ blockdata ] ) then
return tbl[ blockdata ];
-- this happens with i.e. wallmounted torches...
else
return blockdata;
end
end
local mc2mtstairs = function( name, blockdata)
if blockdata >= 4 then
return {name.. "upside_down", mc2mtFacedir(blockdata - 4)}
else
return {name, mc2mtFacedir(blockdata)}
end
end
-- returns {translated_node_name, translated_param2}
handle_schematics.findMC2MTConversion = function(blockid, blockdata)
if (blockid == 0 ) then
return {"air",0};
-- fallback
elseif( not( conversionTable[ blockid ])) then
return { "minecraft:"..tostring( blockid )..'_'..tostring( blockdata ), 0};
end
local conv = conversionTable[ blockid ];
if( conv[1] == P2_IGNORE ) then
return { conv[2], 0};
elseif( conv[1] == P2_COPY ) then
return { conv[2], blockdata};
elseif( conv[1] == P2_CONVERT) then
return { conv[2], mc2mtFacedir(blockdata)};
elseif( conv[1] == P2_STAIR ) then
return mc2mtstairs(conv[2], blockdata);
elseif( conv[1] == P2_SELECT
and conv[2][ blockdata ] ) then
return { conv[2][ blockdata ], 0};
elseif( conv[1] == P2_SELECT
and not(conv[2][ blockdata ] )) then
return { conv[2][0], 0};
else
return { conv[2], 0 };
end
return {air, 0};
end

138
libs/hsl/worldedit_file.lua Normal file
View File

@ -0,0 +1,138 @@
------------------------------------------------------------------------------------------
-- This is the file
-- https://github.com/Uberi/Minetest-WorldEdit/blob/master/worldedit/serialization.lua
-- Changes:
-- * worldedit namespace renamed to worldeit_file
-- * eliminiated functions that are not needed
-- * made function load_schematic non-local
-- * originx, originy and originz are now passed as parameters to worldedit_file.load_schematic;
-- they are required for an old file format
------------------------------------------------------------------------------------------
worldedit_file = {} -- add the namespace
--- Schematic serialization and deserialiation.
-- @module worldedit.serialization
worldedit_file.LATEST_SERIALIZATION_VERSION = 5
local LATEST_SERIALIZATION_HEADER = worldedit_file.LATEST_SERIALIZATION_VERSION .. ":"
--[[
Serialization version history:
1: Original format. Serialized Lua table with a weird linked format...
2: Position and node seperated into sub-tables in fields `1` and `2`.
3: List of nodes, one per line, with fields seperated by spaces.
Format: <X> <Y> <Z> <Name> <Param1> <Param2>
4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`,
`name`, `param1`, `param2`, and `meta` fields.
5: Added header and made `param1`, `param2`, and `meta` fields optional.
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
--]]
--- Reads the header of serialized data.
-- @param value Serialized WorldEdit data.
-- @return The version as a positive natural number, or 0 for unknown versions.
-- @return Extra header fields as a list of strings, or nil if not supported.
-- @return Content (data after header).
function worldedit_file.read_header(value)
if value:find("^[0-9]+[%-:]") then
local header_end = value:find(":", 1, true)
local header = value:sub(1, header_end - 1):split(",")
local version = tonumber(header[1])
table.remove(header, 1)
local content = value:sub(header_end + 1)
return version, header, content
end
-- Old versions that didn't include a header with a version number
if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format
return 3, nil, value
elseif value:find("^[^\"']+%{%d+%}") then
if value:find("%[\"meta\"%]") then -- Meta flat table format
return 2, nil, value
end
return 1, nil, value -- Flat table format
elseif value:find("%{") then -- Raw nested table format
return 4, nil, value
end
return nil
end
--- Loads the schematic in `value` into a node list in the latest format.
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
-- by ChillCode, available under the MIT license.
-- @return A node list in the latest format, or nil on failure.
function worldedit_file.load_schematic(value, we_origin)
local version, header, content = worldedit_file.read_header(value)
local nodes = {}
if version == 1 or version == 2 then -- Original flat table format
local tables = minetest.deserialize(content)
if not tables then return nil end
-- Transform the node table into an array of nodes
for i = 1, #tables do
for j, v in pairs(tables[i]) do
if type(v) == "table" then
tables[i][j] = tables[v[1]]
end
end
end
nodes = tables[1]
if version == 1 then --original flat table format
for i, entry in ipairs(nodes) do
local pos = entry[1]
entry.x, entry.y, entry.z = pos.x, pos.y, pos.z
entry[1] = nil
local node = entry[2]
entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2
entry[2] = nil
end
end
elseif version == 3 or version=="3" then -- List format
if( not( we_origin ) or #we_origin <3) then
we_origin = { 0, 0, 0 };
end
for x, y, z, name, param1, param2 in content:gmatch(
"([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" ..
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
param1, param2 = tonumber(param1), tonumber(param2)
table.insert(nodes, {
x = we_origin[1] + tonumber(x),
y = we_origin[2] + tonumber(y),
z = we_origin[3] + tonumber(z),
name = name,
param1 = param1 ~= 0 and param1 or nil,
param2 = param2 ~= 0 and param2 or nil,
})
end
elseif version == 4 or version == 5 then -- Nested table format
if not jit then
-- This is broken for larger tables in the current version of LuaJIT
nodes = minetest.deserialize(content)
else
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit
nodes = {}
content = content:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
local startpos, startpos1, endpos = 1, 1
while true do -- go through each individual node entry (except the last)
startpos, endpos = escaped:find("},%s*{", startpos)
if not startpos then
break
end
local current = content:sub(startpos1, startpos)
local entry = minetest.deserialize("return " .. current)
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
local entry = minetest.deserialize("return " .. content:sub(startpos1)) -- process the last entry
table.insert(nodes, entry)
end
else
return nil
end
return nodes
end