199 lines
6.7 KiB
Lua
199 lines
6.7 KiB
Lua
|
|
|||
|
-- 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 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 = handle_schematics.findMC2MTConversion(
|
|||
|
-- (Y×length + Z)×width + X.
|
|||
|
mc_schematic_data.Blocks[ ((y-1)*size.z + (z-1) )*size.x + (x-1) +1],
|
|||
|
mc_schematic_data.Data[ ((y-1)*size.z + (z-1) )*size.x + (x-1) +1] );
|
|||
|
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
|
|||
|
scm[y][x][z] = { nodenames_id[ new_node[1]], new_node[2]};
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
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 = {}};
|
|||
|
end
|
|||
|
|