minetest-game-adventuretest/mods/handle_schematics/analyze_mts_file.lua

280 lines
8.7 KiB
Lua

--[[ 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
minetest.log('error','[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