extracted the handling of schematics from mg_villages and created an extra mod for it

master
Sokomine 2015-05-01 18:04:03 +02:00
commit aedba53100
19 changed files with 3703 additions and 0 deletions

4
README.md Normal file
View File

@ -0,0 +1,4 @@
This mod is not finished yet.
Type "/giveme handle_schematics:build" to get a build chest.

273
analyze_mts_file.lua Normal file
View File

@ -0,0 +1,273 @@
--[[ 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 = io.open(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}; -- TODO: handle possible meta values contained in another file
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 = io.open(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 );
-- convert to .mts for later usage
if( res and store_as_mts ) then
handle_schematics.store_mts_file( store_as_mts, res );
end
end
return res;
end

83
analyze_we_file.lua Normal file
View File

@ -0,0 +1,83 @@
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 = io.open( scm..".we", "r")
if not f then
f, err = io.open( 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
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
if ent.meta == nil then
ent.meta = {fields={}, inventory={}}
end
scm[ent.y][ent.x][ent.z] = { nodenames_id[ ent.name ], ent.param2 }; --TODO ent.meta
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 };
end

824
build_chest.lua Normal file
View File

@ -0,0 +1,824 @@
-----------------------------------------------------------------------------------------------------------------
-- interface for manual placement of houses
-----------------------------------------------------------------------------------------------------------------
-- functions specific to the build_chest are now stored in this table
build_chest = {};
-- scaffolding that will be placed instead of other nodes in order to show
-- how large the building will be
build_chest.SUPPORT = 'build_chest:support';
-- contains information about all the buildings
build_chest.building = {};
-- returns the id under which the building is stored
build_chest.add_building = function( file_name, data )
if( not( file_name ) or not( data )) then
return;
end
build_chest.building[ file_name ] = data;
end
-- that many options can be shown simultaneously on one menu page
build_chest.MAX_OPTIONS = 24; -- 3 columns with 8 entries each
build_chest.menu = {};
build_chest.menu.main = {};
-- create a tree structure for the menu
build_chest.add_entry = function( path )
if( not( path ) or #path<1 ) then
return;
end
local sub_menu = build_chest.menu;
for i,v in ipairs( path ) do
if( not( sub_menu[ v ] )) then
sub_menu[ v ] = {};
end
sub_menu = sub_menu[ v ];
end
end
-- add a menu entry that will always be available
build_chest.add_entry( {'save a building'} );
-- needed for saving buildings
build_chest.end_pos_list = {};
--dofile( minetest.get_modpath( minetest.get_current_modname()).."/build_chest_handle_replacements.lua");
--dofile( minetest.get_modpath( minetest.get_current_modname()).."/build_chest_preview_image.lua");
--dofile( minetest.get_modpath( minetest.get_current_modname()).."/build_chest_add_schems.lua");
build_chest.read_building = function( building_name )
-- read data
local res = handle_schematics.analyze_file( building_name, nil, nil );
if( not( res )) then
return;
end
build_chest.building[ building_name ].size = res.size;
build_chest.building[ building_name ].nodenames = res.nodenames;
build_chest.building[ building_name ].rotated = res.rotated;
build_chest.building[ building_name ].burried = res.burried;
-- scm_data_cache is not stored as that would take up too much storage space
--build_chest.building[ building_name ].scm_data_cache = res.scm_data_cache;
-- create a statistic about how often each node occours
build_chest.building[ building_name ].statistic = handle_schematics.count_nodes( res );
build_chest.building[ building_name ].preview = build_chest.preview_image_create_views( res,
build_chest.building[ building_name ].orients );
return res;
end
build_chest.get_start_pos = function( pos )
-- rotate the building so that it faces the player
local node = minetest.get_node( pos );
local meta = minetest.get_meta( pos );
local building_name = meta:get_string( 'building_name' );
if( not( building_name )) then
return "No building_name provided.";
end
if( not( build_chest.building[ building_name ] )) then
return "No data found for this building.";
end
if( not( build_chest.building[ building_name ].size )) then
if( not( build_chest.read_building( building_name ))) then
return "Unable to read data file of this building.";
end
end
local selected_building = build_chest.building[ building_name ];
local mirror = 0; -- place_schematic does not support mirroring
local start_pos = {x=pos.x, y=pos.y, z=pos.z};
-- yoff(set) from mg_villages (manually given)
if( selected_building.yoff ) then
start_pos.y = start_pos.y + selected_building.yoff -1;
end
-- make sure the building always extends forward and to the right of the player
local param2_rotated = handle_schematics.translate_param2_to_rotation( node.param2, mirror, start_pos,
selected_building.size, selected_building.rotated, selected_building.burried, selected_building.orients,
selected_building.yoff );
-- save the data for later removal/improvement of the building in the chest
meta:set_string( 'start_pos', minetest.serialize( param2_rotated.start_pos ));
meta:set_string( 'end_pos', minetest.serialize( param2_rotated.end_pos ));
meta:set_string( 'rotate', tostring(param2_rotated.rotate ));
meta:set_int( 'mirror', mirror );
-- no replacements yet
meta:set_string( 'replacements', minetest.serialize( {} ));
return start_pos;
end
build_chest.update_formspec = function( pos, page, player, fields )
-- information about the village the build chest may belong to and about the owner
local meta = minetest.get_meta( pos );
local village_name = meta:get_string( 'village' );
local village_pos = minetest.deserialize( meta:get_string( 'village_pos' ));
local owner_name = meta:get_string( 'owner' );
local building_name = meta:get_string('building_name' );
-- distance from village center
local distance = math.floor( math.sqrt( (village_pos.x - pos.x ) * (village_pos.x - pos.x )
+ (village_pos.y - pos.y ) * (village_pos.x - pos.y )
+ (village_pos.z - pos.z ) * (village_pos.x - pos.z ) ));
if( page == 'please_remove' ) then
if( build_chest.stages_formspec_page_please_remove ) then
return build_chest.stages_formspec_page_please_remove( building_name, owner_name, village_name, village_pos, distance );
end
elseif( page == 'finished' ) then
if( build_chest.stages_formspec_page_finished ) then
return build_chest.stages_formspec_page_finished( building_name, owner_name, village_name, village_pos, distance );
end
elseif( page ~= 'main' ) then
-- if in doubt, return the old formspec
return meta:get_string('formspec');
end
-- create the header
local formspec = "size[13,10]"..
"label[3.3,0.0;Building box]"..
"label[0.3,0.4;Located at:]" .."label[3.3,0.4;"..(minetest.pos_to_string( pos ) or '?')..", which is "..tostring( distance ).." m away]"
.."label[7.3,0.4;from the village center]"..
"label[0.3,0.8;Part of village:]" .."label[3.3,0.8;"..(village_name or "?").."]"
.."label[7.3,0.8;located at "..(minetest.pos_to_string( village_pos ) or '?').."]"..
"label[0.3,1.2;Owned by:]" .."label[3.3,1.2;"..(owner_name or "?").."]"..
"label[3.3,1.6;Click on a menu entry to select it:]";
if( building_name and building_name ~= '' and build_chest.building[ building_name ] and build_chest.building[ building_name ].size) then
local size = build_chest.building[ building_name ].size;
formspec = formspec..
-- show which building has been selected
"label[0.3,9.5;Selected building:]"..
"label[2.3,9.5;"..minetest.formspec_escape(building_name).."]"..
-- size of the building
"label[0.3,9.8;Size ( wide x length x height ):]"..
"label[4.3,9.8;"..tostring( size.x )..' x '..tostring( size.z )..' x '..tostring( size.y ).."]";
end
local current_path = minetest.deserialize( meta:get_string( 'current_path' ) or 'return {}' );
if( #current_path > 0 ) then
formspec = formspec.."button[9.9,0.4;2,0.5;back;Back]";
end
-- offer a menu to set the positions for saving a building
if( #current_path > 0 and current_path[1]=='save a building' ) then
local saved_as_filename = meta:get_string('saved_as_filename');
if( saved_as_filename and saved_as_filename ~= "" ) then
local p1str = meta:get_string('p1');
local p2str = meta:get_string('p2');
return formspec..
"label[2.0,3;This area has been saved to the file]"..
"label[2.5,3.3;"..minetest.formspec_escape( saved_as_filename ).."]"..
"label[2.0,3.6;The area extends from]"..
"label[2.5,3.9;"..minetest.formspec_escape( p1str ).."]"..
"label[2.0,4.2;to the point]"..
"label[2.5,4.5;"..minetest.formspec_escape( p2str ).."]"..
"button[5,8.0;3,0.5;back;Back]";
end
local end_pos_mark = build_chest.end_pos_list[ player:get_player_name() ];
if( end_pos_mark
and end_pos_mark.x==pos.x
and end_pos_mark.y==pos.y
and end_pos_mark.z==pos.z ) then
return formspec..
"label[2,3.0;This chest marks the end position of your building. Please put another]"..
"label[2,3.3;build chest in front of your building and save it with that chest.]"..
"button[5,8.0;3,0.5;back;Back]";
end
if( end_pos_mark and end_pos_mark.start_pos ) then
if( end_pos_mark.start_pos.x == pos.x
and end_pos_mark.start_pos.y == pos.y
and end_pos_mark.start_pos.z == pos.z ) then
local p2 = {x=end_pos_mark.x, y=end_pos_mark.y, z=end_pos_mark.z};
local p1 = {x=end_pos_mark.start_pos.x, y=end_pos_mark.start_pos.y, z=end_pos_mark.start_pos.z};
local height = math.abs( p1.y - p2.y )+1;
local width = 0;
local length = 0;
if( end_pos_mark.param2==0 or end_pos_mark.param2==2 ) then
-- adjust p1 and p2 so that only the area we really care about is marked
if( p1.z > p2.z ) then
p1.z = p1.z-1;
p2.z = p2.z+1;
else
p1.z = p1.z+1;
p2.z = p2.z-1;
end
width = math.abs( p1.x - p2.x )+1;
length = math.abs( p1.z - p2.z )+1;
else
if( p1.x > p2.x ) then
p1.x = p1.x-1;
p2.x = p2.x+1;
else
p1.x = p1.x+1;
p2.x = p2.x-1;
end
length = math.abs( p1.x - p2.x )+1;
width = math.abs( p1.z - p2.z )+1;
end
return formspec..
-- p1 and p2 are passed on as inputs in order to avoid any unwanted future interferences
-- with any other build chests
"field[40,40;0.1,0.1;save_as_p1;;"..minetest.pos_to_string(p1).."]"..
"field[40,40;0.1,0.1;save_as_p2;;"..minetest.pos_to_string(p2).."]"..
"label[2,2.4;How high is your building? This does *not* include the height offset below. The]"..
"label[2,2.7;default value is calculated from the height difference between start and end position.]"..
"label[2,3.15;Total height of your building:]"..
"field[6,3.5;1,0.5;save_as_height;;"..tostring(height).."]"..
-- note: in mg_villages, yoff has to be 0 in order to include the ground floor as well;
-- "1" means the building without floor; here, "1" means a floating building
"label[2,3.8;The hight offset sets how deep your building will be burried in the ground. Examples:]"..
"label[2.5,4.1;A value of -4 will include a cellar which extends 4 nodes below this build chest.]"..
"label[2.5,4.4;A value of -1 will include the floor below the chest, but no cellar.]"..
"label[2.5,4.7;A positive value will make your building float in the air.]"..
"label[2,5.15;Add height offset:]"..
"field[6,5.5;1,0.5;save_as_yoff;;0]"..
"label[2,5.8;Without the changes entered in the input form above, your building will extend from]"..
"label[2.5,6.1;"..minetest.formspec_escape(
minetest.pos_to_string( p1 ).." to "..
minetest.pos_to_string( p2 ).." and span a volume of "..
-- x and z are swpapped here if rotated by 90 or 270 degree
tostring(width )..' (width) x '..
tostring(length)..' (depth) x '..
tostring(height)..' (height)').."]"..
"label[2,6.7;Please enter a descriptive filename. Allowed charcters: "..
minetest.formspec_escape("a-z, A-Z, 0-9, -, _, .").."]"..
"label[2,7.15;Save schematic as:]"..
"field[6,7.5;4,0.5;save_as_filename;;]"..
"button[2,8.0;3,0.5;abort_set_start_pos;Abort]"..
"button[6,8.0;3,0.5;save_as;Save building now]";
else
return formspec..
"label[3,3;You have selected another build chest as start position.]"..
"button[5,8.0;3,0.5;back;Back]"..
"button[5,5.0;3,0.5;abort_set_start_pos;Reset start position]";
end
end
if( fields.error_msg ) then
return formspec..
"label[4,4.5;Error while trying to set the start position:]"..
"textarea[4,5;6,2;error_msg;;"..
minetest.formspec_escape( fields.error_msg ).."]"..
"button[5,8.0;3,0.5;back;Back]";
end
return formspec..
"label[2.5,2.2;First, let us assume that you are facing the front of this build chest.]"..
"label[2,3.1;Are you looking at the BACKSIDE of your building, and does said backside stretch]"..
"label[2,3.4;to the right and in front of you? Then click on the button below:]"..
"button[4,4;5,0.5;set_end_pos;Set this position as new end position]"..
"label[2,5.2;Have you set the end position with another build chest using the method above]"..
"label[2,5.5;in the meantime? And are you now looking at the FRONT of your building, which]"..
"label[2,5.8;streches in front of you and to the right? Then click on Proceed:]"..
"button[5,6.4;3,0.5;set_start_pos;Proceed with saving]"..
"label[4,7.4;If this confuses you, you can also abort the process.]"..
"button[5,8.0;3,0.5;back;Abort]";
end
-- the building has been placed; offer to restore a backup
local backup_file = meta:get_string('backup');
if( backup_file and backup_file ~= "" ) then
return formspec.."button[3,3;3,0.5;restore_backup;Restore original landscape]";
end
-- offer diffrent replacement groups
if( fields.set_wood and fields.set_wood ~= "" ) then
return formspec..
"label[1,2.2;Select replacement for "..tostring( fields.set_wood )..".]"..
"label[1,2.5;Trees, saplings and other blocks will be replaced accordingly as well.]"..
-- invisible field that encodes the value given here
"field[-20,-20;0.1,0.1;set_wood;;"..minetest.formspec_escape( fields.set_wood ).."]"..
build_chest.replacements_get_group_list_formspec( pos, 'wood', 'wood_selection' );
end
if( fields.set_farming and fields.set_farming ~= "" ) then
return formspec..
"label[1,2.5;Select the fruit the farm is going to grow:]"..
-- invisible field that encodes the value given here
"field[-20,-20;0.1,0.1;set_farming;;"..minetest.formspec_escape( fields.set_farming ).."]"..
build_chest.replacements_get_group_list_formspec( pos, 'farming', 'farming_selection' );
end
if( fields.set_roof and fields.set_roof ~= "" ) then
return formspec..
"label[1,2.5;Select a roof type for the house:]"..
-- invisible field that encodes the value given here
"field[-20,-20;0.1,0.1;set_roof;;"..minetest.formspec_escape( fields.set_roof ).."]"..
build_chest.replacements_get_group_list_formspec( pos, 'roof', 'roof_selection' );
end
if( fields.preview and building_name ) then
return formspec..build_chest.preview_image_formspec( building_name,
minetest.deserialize( meta:get_string( 'replacements' )), fields.preview);
end
-- show list of all node names used
local start_pos = meta:get_string('start_pos');
if( building_name and building_name ~= '' and start_pos and start_pos ~= '' and meta:get_string('replacements')) then
return formspec..build_chest.replacements_get_list_formspec( pos );
end
-- find out where we currently are in the menu tree
local menu = build_chest.menu;
for i,v in ipairs( current_path ) do
if( menu and menu[ v ] ) then
menu = menu[ v ];
end
end
-- all submenu points at this menu position are options that need to be shown
local options = {};
for k,v in pairs( menu ) do
table.insert( options, k );
end
-- handle if there are multiple files under the same menu point
if( #options == 0 and build_chest.building[ current_path[#current_path]] ) then
options = {current_path[#current_path]};
end
-- we have found an end-node - a particular building
if( #options == 1 and options[1] and build_chest.building[ options[1]] ) then
-- a building has been selected
meta:set_string( 'building_name', options[1] );
local start_pos = build_chest.get_start_pos( pos );
if( type(start_pos)=='table' and start_pos and start_pos.x and build_chest.building[ options[1]].size) then
-- TODO: also show size and such
-- do replacements for realtest where necessary (this needs to be done only once)
local replacements = {};
replacements_group['realtest'].replace( replacements );
meta:set_string( 'replacements', minetest.serialize( replacements ));
return formspec..build_chest.replacements_get_list_formspec( pos );
elseif( type(start_pos)=='string' ) then
return formspec.."label[3,3;Error reading building data:]"..
"label[3.5,3.5;"..start_pos.."]";
else
return formspec.."label[3,3;Error reading building data.]";
end
end
table.sort( options );
local page_nr = meta:get_int( 'page_nr' );
-- if the options do not fit on a single page, split them up
if( #options > build_chest.MAX_OPTIONS ) then
if( not( page_nr )) then
page_nr = 0;
end
local new_options = {};
local new_index = build_chest.MAX_OPTIONS*page_nr;
for i=1,build_chest.MAX_OPTIONS do
if( options[ new_index+i ] ) then
new_options[ i ] = options[ new_index+i ];
end
end
-- we need to add prev/next buttons to the formspec
formspec = formspec.."label[7.5,1.5;"..minetest.formspec_escape(
"Showing "..tostring( new_index+1 )..
'-'..tostring( math.min( new_index+build_chest.MAX_OPTIONS, #options))..
'/'..tostring( #options )).."]";
if( page_nr > 0 ) then
formspec = formspec.."button[9.5,1.5;1,0.5;prev;prev]";
end
if( build_chest.MAX_OPTIONS*(page_nr+1) < #options ) then
formspec = formspec.."button[11,1.5;1,0.5;next;next]";
end
options = new_options;
end
-- found an end node of the menu graph
-- elseif( build_chest.stages_formspec_page_first_stage ) then
-- return build_chest.stages_formspec_page_first_stage( v.menu_path[( #current_path )], player, pos, meta, );
-- end
-- show the menu with the next options
local i = 0;
local x = 0;
local y = 0;
if( #options < 9 ) then
x = x + 4;
end
-- order alphabeticly
table.sort( options, function(a,b) return a < b end );
for index,k in ipairs( options ) do
i = i+1;
-- new column
if( y==8 ) then
x = x+4;
y = 0;
end
formspec = formspec .."button["..(x)..","..(y+2.5)..";4,0.5;selection;"..k.."]"
y = y+1;
--x = x+4;
end
return formspec;
end
build_chest.on_receive_fields = function(pos, formname, fields, player)
local meta = minetest.get_meta(pos);
local owner = meta:get_string('owner');
local pname = player:get_player_name();
-- make sure not everyone can mess up the build chest
if( owner and owner ~= '' and owner ~= pname
and minetest.is_protected( pos, pname )) then
minetest.chat_send_player( pname,
"Sorry. This build chest belongs to "..tostring( owner ).." and only "..
"accepts input from its owner or other players who can build here.");
return;
end
-- general menu handling
-- back button selected
if( fields.back ) then
local current_path = minetest.deserialize( meta:get_string( 'current_path' ) or 'return {}' );
table.remove( current_path ); -- revert latest selection
meta:set_string( 'current_path', minetest.serialize( current_path ));
meta:set_string( 'building_name', '');
meta:set_int( 'replace_row', 0 );
meta:set_int( 'page_nr', 0 );
meta:set_string( 'saved_as_filename', nil);
-- menu entry selected
elseif( fields.selection ) then
local current_path = minetest.deserialize( meta:get_string( 'current_path' ) or 'return {}' );
table.insert( current_path, fields.selection );
meta:set_string( 'current_path', minetest.serialize( current_path ));
-- if there are more menu items than can be shown on one page: show previous page
elseif( fields.prev ) then
local page_nr = meta:get_int( 'page_nr' );
if( not( page_nr )) then
page_nr = 0;
end
page_nr = math.max( page_nr - 1 );
meta:set_int( 'page_nr', page_nr );
-- if there are more menu items than can be shown on one page: show next page
elseif( fields.next ) then
local page_nr = meta:get_int( 'page_nr' );
if( not( page_nr )) then
page_nr = 0;
end
meta:set_int( 'page_nr', page_nr+1 );
-- specific to the build chest
-- the player has choosen a material from the list; ask for a replacement
elseif( fields.build_chest_replacements ) then
local event = minetest.explode_table_event( fields.build_chest_replacements );
local building_name = meta:get_string('building_name');
if( event and event.row and event.row > 0
and building_name
and build_chest.building[ building_name ] ) then
meta:set_int('replace_row', event.row );
end
-- the player has asked for a particular replacement
elseif( fields.store_replacement
and fields.replace_row_with and fields.replace_row_with ~= ""
and fields.replace_row_material and fields.replace_row_material ~= "") then
build_chest.replacements_apply( pos, meta, fields.replace_row_material, fields.replace_row_with );
elseif( fields.wood_selection ) then
build_chest.replacements_apply_for_group( pos, meta, 'wood', fields.wood_selection, fields.set_wood );
fields.set_wood = nil;
elseif( fields.farming_selection ) then
build_chest.replacements_apply_for_group( pos, meta, 'farming', fields.farming_selection, fields.set_farming );
fields.set_farming = nil;
elseif( fields.roof_selection ) then
build_chest.replacements_apply_for_group( pos, meta, 'roof', fields.roof_selection, fields.set_roof );
fields.set_roof = nil;
elseif( fields.proceed_with_scaffolding ) then
local building_name = meta:get_string('building_name');
local start_pos = minetest.deserialize( meta:get_string('start_pos'));
local end_pos = minetest.deserialize( meta:get_string('end_pos'));
local filename = meta:get_string('backup' );
if( not( filename ) or filename == "" ) then
-- <worldname>/backup_PLAYERNAME_x_y_z_burried_rotation.mts
filename = minetest.get_worldpath()..'/backup_'..
meta:get_string('owner')..'_'..
tostring( start_pos.x )..':'..tostring( start_pos.y )..':'..tostring( start_pos.z )..'_'..
'0_0.mts';
-- TODO: handle metadata
-- store a backup of the original landscape
minetest.create_schematic( start_pos, end_pos, nil, filename, nil);
meta:set_string('backup', filename );
minetest.chat_send_player( pname, 'CREATING backup schematic for this place in '..tostring( filename )..'.');
end
-- TODO: use scaffolding here (exchange some replacements)
local replacement_list = minetest.deserialize( meta:get_string( 'replacements' ));
local rotate = meta:get_string('rotate');
local mirror = meta:get_string('mirror');
local axis = build_chest.building[ building_name ].axis;
local no_plotmarker = 1;
-- actually place the building
--minetest.place_schematic( start_pos, building_name..'.mts', rotate, replacement_list, true );
mirror = nil;
fields.error_msg = handle_schematics.place_building_from_file( start_pos, end_pos, building_name, replacement_list, rotate, axis, mirror, no_plotmarker );
if( fields.error_msg ) then
fields.error_msg = 'Error: '..tostring( fields.error_msg );
end
-- restore the original landscape
elseif( fields.restore_backup ) then
local start_pos = minetest.deserialize( meta:get_string('start_pos'));
local end_pos = minetest.deserialize( meta:get_string('end_pos'));
local backup_file = meta:get_string( 'backup' );
if( start_pos and end_pos and start_pos.x and end_pos.x and backup_file and backup_file ~= "") then
minetest.place_schematic( start_pos, backup_file, "0", {}, true );
meta:set_string('backup', nil );
end
-- store a new end position
elseif( fields.set_end_pos ) then
local node = minetest.get_node( pos );
if( node and node.param2 ) then
build_chest.end_pos_list[ pname ] = {x=pos.x, y=pos.y, z=pos.z, param2=node.param2 };
end
elseif( fields.set_start_pos ) then
local error_msg = "";
local end_pos = build_chest.end_pos_list[ pname ];
if( not( end_pos )) then
error_msg = "Please mark the end position of your building first!";
else
local node = minetest.get_node( pos );
if( not( node ) or not( node.param2 )) then
error_msg = "A strange error happened.";
elseif( (node.param2 == 0 and end_pos.param2 ~= 2)
or (node.param2 == 1 and end_pos.param2 ~= 3)
or (node.param2 == 2 and end_pos.param2 ~= 0)
or (node.param2 == 3 and end_pos.param2 ~= 1)) then
error_msg = "One build chest needs to point to the front of your building, and "..
"the other one to the backside. This does not seem to be the case.";
elseif( (node.param2 == 2 and ( pos.x < end_pos.x or pos.z < end_pos.z )) -- x and z need to get larger
or (node.param2 == 3 and ( pos.x < end_pos.x or pos.z > end_pos.z )) -- x gets larger, z gets smaller
or (node.param2 == 0 and ( pos.x > end_pos.x or pos.z > end_pos.z )) -- x and z need to get smaller
or (node.param2 == 1 and ( pos.x > end_pos.x or pos.z < end_pos.z )) -- x gets smaller, z gets larger
) then
error_msg = "The end position does not fit to the orientation of this build chest.";
-- the chest takes up one node as well
elseif( math.abs(pos.x-end_pos.x)<1) then
error_msg = "Start- and end position share the same x value.";
elseif( math.abs(pos.z-end_pos.z)<1) then
error_msg = "Start- and end position share the same z value.";
-- all ok; we may proceed
else
error_msg = "";
build_chest.end_pos_list[ pname ].start_pos = {x=pos.x, y=pos.y, z=pos.z, param2=node.param2 };
end
fields.error_msg = error_msg;
end
-- in case the player selected the wrong chest for the save dialog
elseif( fields.abort_set_start_pos ) then
local end_pos = build_chest.end_pos_list[ pname ];
if( end_pos ) then
build_chest.end_pos_list[ pname ].start_pos = nil;
end
elseif( fields.save_as ) then
if( fields.save_as_p1 and fields.save_as_p2 and fields.save_as_filename ) then
-- restore p1 and p2, the positions of the area that is to be saved
local p1 = minetest.string_to_pos( fields.save_as_p1 );
local p2 = minetest.string_to_pos( fields.save_as_p2 );
-- take height changes into account
if( fields.save_as_height ) then
local new_height = tonumber( fields.save_as_height );
-- the new height is measured from the start position as well
if( new_height and new_height ~= (math.abs(p1.y-p2.y)+1)) then
p2.y = p1.y+new_height;
end
end
local burried = 0;
if( fields.save_as_yoff ) then
burried = tonumber( fields.save_as_yoff );
if( not( burried )) then
burried = 0;
end
-- the yoffset is applied to the start position
p1.y = p1.y + burried;
-- TODO: real negative values are not supported by analyze_mts_file
if( burried ~= 0 ) then
burried = -1*burried;
end
end
-- create an automatic filename if none is provided
local filename = fields.save_as_filename;
-- TODO: check the input if it contains only allowed chars (a-z, A-Z, 0-9, -, _, .)
if( not( filename )) then
filename = pname..'_'..tostring( p1 )..'_'..tostring(p2);
end
-- param2 needs to be translated init initial rotation as well
local node = minetest.get_node( pos );
if( node.param2 == 0 ) then
filename = filename..'_'..burried..'_90';
elseif( node.param2 == 3 ) then
filename = filename..'_'..burried..'_180';
elseif( node.param2 == 1 ) then
filename = filename..'_'..burried..'_0';
elseif( node.param2 == 2 ) then
filename = filename..'_'..burried..'_270';
end
-- TODO: what if there is no schems folder in that directory?
-- TODO: forbid overwriting existing files?
local worldpath = minetest.get_worldpath();
local filename_complete = worldpath..'/schems/'..filename..'.mts';
-- really save it with probability_list and slice_prob_list both as nil
minetest.create_schematic( p1, p2, nil, filename_complete, nil);
-- store that we have saved this area
meta:set_string('saved_as_filename', filename);
meta:set_string('p1', minetest.pos_to_string( p1 ));
meta:set_string('p2', minetest.pos_to_string( p2 ));
-- forget the end position
build_chest.end_pos_list[ pname ] = nil;
-- add this chest to the menu
local worldnameparts = string.split( worldpath, '/worlds/' );
if( not( worldnameparts ) or #worldnameparts < 1 ) then
worldnameparts = {'unkown world'};
end
build_chest.add_entry( {'main','worlds', worldnameparts[ #worldnameparts], 'schems', filename, worldpath..'/schems/'..filename});
build_chest.add_building( worldpath..'/schems/'..filename, {scm=filename, typ='nn'});
minetest.chat_send_player( pname,
'Created schematic \''..tostring( filename )..'\'. Saved area from '..
minetest.pos_to_string( p1 )..' to '..
minetest.pos_to_string( p2 ));
end
end
-- the final build stage may offer further replacements
if( build_chest.stages_on_receive_fields ) then
build_chest.stages_on_receive_fields(pos, formname, fields, player, meta);
end
meta:set_string( 'formspec', build_chest.update_formspec( pos, 'main', player, fields ));
end
minetest.register_node("handle_schematics:build", { --TODO
description = "Building-Spawner",
tiles = {"default_chest_side.png", "default_chest_top.png", "default_chest_side.png",
"default_chest_side.png", "default_chest_side.png", "default_chest_front.png"},
-- drawtype = 'signlike',
-- paramtype = "light",
-- paramtype2 = "wallmounted",
-- sunlight_propagates = true,
-- walkable = false,
-- selection_box = {
-- type = "wallmounted",
-- },
paramtype2 = "facedir",
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
legacy_facedir_simple = true,
after_place_node = function(pos, placer, itemstack)
-- TODO: check if placement is allowed
local meta = minetest.get_meta( pos );
meta:set_string( 'current_path', minetest.serialize( {} ));
meta:set_string( 'village', 'BEISPIELSTADT' ); --TODO
meta:set_string( 'village_pos', minetest.serialize( {x=1,y=2,z=3} )); -- TODO
meta:set_string( 'owner', placer:get_player_name());
meta:set_string('formspec', build_chest.update_formspec( pos, 'main', placer, {} ));
end,
on_receive_fields = function( pos, formname, fields, player )
return build_chest.on_receive_fields(pos, formname, fields, player);
end,
-- taken from towntest
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
if from_list=="needed" or to_list=="needed" then return 0 end
return count
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if listname=="needed" then return 0 end
return stack:get_count()
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
if listname=="needed" then return 0 end
-- if listname=="lumberjack" then return 0 end
return stack:get_count()
end,
can_dig = function(pos,player)
local meta = minetest.get_meta( pos );
local inv = meta:get_inventory();
local owner_name = meta:get_string( 'owner' );
local building_name = meta:get_string( 'building_name' );
local name = player:get_player_name();
if( not( meta ) or not( owner_name )) then
return true;
end
if( owner_name ~= name ) then
minetest.chat_send_player(name, "This building chest belongs to "..tostring( owner_name )..". You can't take it.");
return false;
end
if( building_name ~= nil and building_name ~= "" ) then
minetest.chat_send_player(name, "This building chest has been assigned to a building project. You can't take it away now.");
return false;
end
return true;
end,
-- have all materials been supplied and the remaining parts removed?
on_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta( pos );
local inv = meta:get_inventory();
local stage = meta:get_int( 'building_stage' );
if( inv:is_empty( 'needed' ) and inv:is_empty( 'main' )) then
if( stage==nil or stage < 6 ) then
build_chest.update_needed_list( pos, stage+1 ); -- request the material for the very first building step
else
meta:set_string( 'formspec', build_chest.update_formspec( pos, 'finished', player, {} ));
end
end
end,
on_metadata_inventory_put = function(pos, listname, index, stack, player)
return build_chest.on_metadata_inventory_put( pos, listname, index, stack, player );
end,
})

View File

@ -0,0 +1,55 @@
build_chest.add_files_to_menu = function( path, add_path )
local file,error = io.open( path, "rb")
if (file == nil) then
return;
end
local text = file:read("*a");
file:close();
for schem_file_name in string.gmatch(text, "([^\r\n]*)[\r\n]*") do
if( schem_file_name and schem_file_name ~= "" ) then
local help = string.split( schem_file_name, '/', true, -1, false);
local i = #help;
local found = 1;
-- search from the end of the file name for the first occourance of "mods" or "worlds"
-- as that will be the path where we will put it into the menu
while (i>1 and found==1) do
if( help[i]=='mods' or help[i]=='worlds' ) then
found = i;
end
i = i-1;
end
local name = help[#help];
local length1 = string.len( name );
local length2 = string.len( schem_file_name );
-- remove the file name extension
if( string.sub( name, -4 )=='.mts' ) then
name = string.sub( name, 1, length1-4 );
schem_file_name = string.sub( schem_file_name, 1, length2-4 );
elseif( string.sub( name, -3 )=='.we' ) then
name = string.sub( name, 1, length1-3 );
schem_file_name = string.sub( schem_file_name, 1, length2-3 );
end
help[#help] = name;
-- build the new menu path
local menu_path = {'main'};
for j=(i+1),#help do
table.insert( menu_path, help[j] );
end
schem_file_name = add_path..schem_file_name;
table.insert( menu_path, schem_file_name );
build_chest.add_entry( menu_path );
build_chest.add_building( schem_file_name, {scm=help[#help], typ='nn'});
end
end
end
build_chest.add_files_to_menu( minetest.get_modpath( minetest.get_current_modname()).."/list_of_schematics.txt", "");

View File

@ -0,0 +1,219 @@
-------------------------------------------------------------
--- contains the handling of replacements for the build chest
-------------------------------------------------------------
-- internal function
build_chest.replacements_get_extra_buttons = function( group, name, types_found_list, button_name, extra_buttons )
-- find out if there are any nodes that may need a group replacement
local found_type = "";
for k,w in ipairs( replacements_group[ group ].all ) do
-- we have found the full block of that group type
if( name == w ) then
found_type = w;
-- no primary node found; there may still be subordinate types
else
for nr,t in ipairs( replacements_group[ group ].data[ w ] ) do
if( name==t and not( types_found_list[ w ])) then
found_type = w;
end
end
end
end
if( found_type ~= "" and not( types_found_list[ found_type ])) then
extra_buttons.offset = extra_buttons.offset + 1;
extra_buttons.text = extra_buttons.text.."button[9.9,"..
tostring( (extra_buttons.offset*0.9)+2.8 )..";3.0,0.5;"..
tostring( button_name )..";"..
minetest.formspec_escape( found_type ).."]";
-- remember that we found and offered this type already; avoid duplicates
types_found_list[ found_type ] = 1;
end
return extra_buttons;
end
build_chest.replacements_get_list_formspec = function( pos, selected_row )
if( not( pos )) then
return "";
end
local meta = minetest.env:get_meta( pos );
local replacements = minetest.deserialize( meta:get_string( 'replacements' ));
local building_name = meta:get_string( 'building_name' );
if( not( building_name ) or not( build_chest.building[ building_name ])) then
return "";
end
local replace_row = meta:get_int('replace_row');
local formspec = "tableoptions[" ..
"color=#ff8000;" ..
"background=#0368;" ..
"border=true;" ..
--"highlight=#00008040;" ..
"highlight=#aaaaaaaa;" ..
"highlight_text=#7fffff]" ..
"tablecolumns[" ..
"color;" ..
"text,width=1,align=right;" ..
"color;" ..
"text,width=5;" ..
"color;" ..
"text,width=1;" ..
"color;" ..
"text,width=5]" ..
-- "tabheader["..
-- "1,1;columns;amount,original material,,target material;1;true;true]"..
"table["..
"0.5,2.7;9.4,6.8;build_chest_replacements;";
local j=1;
local may_proceed = true;
local replace_row_material = nil;
local replace_row_with = "";
-- make sure the statistic has been created
if( not( build_chest.building[ building_name ].statistic )) then
if( not( build_chest.read_building( building_name ))) then
return "label[2,2;Error: Unable to read building file.]";
end
end
-- used for setting wood type or plant(farming) type etc.
local extra_buttons = { text = "", offset = 0};
-- there may be wood types that only occour as stairs and/or slabs etc., without full blocks
local types_found_list_wood = {};
local types_found_list_farming = {};
local types_found_list_roof = {};
local not_the_first_entry = false;
for i,v in ipairs( build_chest.building[ building_name ].statistic ) do
local name = build_chest.building[ building_name ].nodenames[ v[1]];
-- nodes that are to be ignored do not need to be replaced
if( name ~= 'air' and name ~= 'ignore' and name ~= 'mg:ignore' and v[2] and v[2]>0) then
local anz = v[2];
-- find out if this node name gets replaced
local repl = name;
for j,r in ipairs( replacements ) do
if( r and r[1]==name ) then
repl = r[2];
end
end
-- avoid empty lines at the end
if( not_the_first_entry ) then
formspec = formspec..',';
end
formspec = formspec..'#fff,'..tostring( anz )..',';
if( name == repl and repl and minetest.registered_nodes[ repl ]) then
formspec = formspec.."#0ff,,#fff,,";
else
if( name and minetest.registered_nodes[ name ] ) then
formspec = formspec.."#0f0,"; -- green
else
formspec = formspec.."#ff0,"; -- yellow
end
formspec = formspec..name..',#fff,'..minetest.formspec_escape('-->')..',';
end
if( repl and (minetest.registered_nodes[ repl ] or repl=='air') ) then
formspec = formspec.."#0f0,"..repl; -- green
else
formspec = formspec.."#ff0,?"; -- yellow
may_proceed = false; -- we need a replacement for this material
end
if( j == replace_row ) then
replace_row_material = name;
if( repl ~= name ) then
replace_row_with = repl;
end
end
extra_buttons = build_chest.replacements_get_extra_buttons( 'wood', name, types_found_list_wood, 'set_wood', extra_buttons );
extra_buttons = build_chest.replacements_get_extra_buttons( 'farming', name, types_found_list_farming, 'set_farming', extra_buttons );
extra_buttons = build_chest.replacements_get_extra_buttons( 'roof', name, types_found_list_farming, 'set_roof', extra_buttons );
j=j+1;
not_the_first_entry = true;
end
end
formspec = formspec.."]";
-- add the proceed-button as soon as all unkown materials have been replaced
if( may_proceed ) then
formspec = formspec.."button[9.9,9.0;2.0,0.5;proceed_with_scaffolding;Proceed]";
end
formspec = formspec.."button[9.9,1.0;2.0,0.5;preview;Preview]";
if( extra_buttons.text and extra_buttons.text ~= "" ) then
formspec = formspec..extra_buttons.text..
"label[9.9,2.8;Replace by type:]";
end
if( replace_row_material ) then
formspec = formspec..
"label[0.5,2.1;Replace "..
minetest.formspec_escape( replace_row_material ).."]"..
"label[6.5,2.1;with:]"..
"field[7.5,2.4;4,0.5;replace_row_with;;"..
minetest.formspec_escape( replace_row_with ).."]"..
"field[-10,-10;0.1,0.1;replace_row_material;;"..
minetest.formspec_escape( replace_row_material ).."]"..
"button[11.1,2.1;1,0.5;store_replacement;Store]";
end
return formspec;
end
build_chest.replacements_apply = function( pos, meta, old_material, new_material )
-- a new value has been entered - we do not need to remember the row any longer
meta:set_int('replace_row', 0 );
local found = false;
-- only accept replacements which can actually be placed
if( new_material=='air' or minetest.registered_nodes[ new_material ] ) then
local replacements_orig = minetest.deserialize( meta:get_string( 'replacements' ));
for i,v in ipairs(replacements_orig) do
if( v and v[1]==old_material ) then
v[2] = new_material;
found = true;
end
end
if( not( found )) then
table.insert( replacements_orig, { old_material, new_material });
end
-- store the new set of replacements
meta:set_string( 'replacements', minetest.serialize( replacements_orig ));
end
end
build_chest.replacements_get_group_list_formspec = function( pos, group, button_name )
local formspec = "";
for i,v in ipairs( replacements_group[ group ].found ) do
formspec = formspec.."item_image_button["..tostring(((i-1)%8)+1)..","..
tostring(3+math.floor((i-1)/8))..";1,1;"..
tostring( v )..";"..tostring( button_name )..";"..tostring(i).."]";
end
return formspec;
end
build_chest.replacements_apply_for_group = function( pos, meta, group, selected, old_material )
local nr = tonumber( selected );
if( not(nr) or nr <= 0 or nr > #replacements_group[ group ].found ) then
return;
end
local new_material = replacements_group[ group ].found[ nr ];
if( old_material and old_material == new_material ) then
return;
end
local replacements = minetest.deserialize( meta:get_string( 'replacements' ));
if( not( replacements )) then
replacements = {};
end
replacements_group[ group ].replace_material( replacements, old_material, new_material );
-- store the new set of replacements
meta:set_string( 'replacements', minetest.serialize( replacements ));
end

View File

@ -0,0 +1,245 @@
build_chest.preview_image_draw_tile = function( content_id, image, x, z, dx, dz, tile_nr )
if( not( image )) then
local node_name = minetest.get_name_from_content_id( content_id );
if( not( node_name )) then
return '';
end
local node_def = minetest.registered_nodes[ node_name ];
if( not( node_def )) then
return '';
end
local tiles = node_def.tiles;
local tile = nil;
if( tiles ~= nil ) then
if( not(tile_nr) or tile_nr > #tiles or tile_nr < 1 ) then
tile_nr = 1;
end
tile = tiles[tile_nr];
end
if type(tile)=="table" then
tile=tile["name"]
end
image = tile;
if( not( image )) then
image = "unknown_object.png";
end
end
return "image["..tostring(x)..",".. tostring(z) ..";"..dx..','..dz..";" .. image .."]";
end
-- creates a 2d preview image (or rather, the data structure for it) of the building
-- internal function
build_chest.preview_image_create_one_view = function( data, side )
local params = {1, data.size.x, 1, 1, data.size.z, 1, 0, 0};
if( side==1 ) then
params = {1, data.size.x, 1, 1, data.size.z, 1, 0, 0};
elseif( side==2 ) then
params = {1, data.size.z, 1, 1, data.size.x, 1, 1, 1};
elseif( side==3 ) then
params = {1, data.size.x, 1, data.size.z, 0, -1, 0, 1};
elseif( side==4 ) then
params = {1, data.size.z, 1, data.size.x, 0, -1, 1, 0};
end
-- do not create preview images for buildings that are too big
if( params[2] * params[4] > 2500 ) then
return nil;
end
local preview = {};
for y = 1, data.size.y do
preview[ y ] = {};
for x = params[1], params[2], params[3] do
local found = nil;
local z = params[4];
local target_x = x;
if( params[8]==1 ) then
target_x = math.max( params[1],params[2] )- x;
end
while( not( found ) and z~= params[5]) do
local node = -1;
if( params[7]==0 ) then
node = data.scm_data_cache[y][x][z];
else
node = data.scm_data_cache[y][z][x];
end
if( node and node[1]
and data.nodenames[ node[1] ]
and data.nodenames[ node[1] ] ~= 'air'
and data.nodenames[ node[1] ] ~= 'ignore'
and data.nodenames[ node[1] ] ~= 'mg:ignore'
and data.nodenames[ node[1] ] ~= 'default:torch' ) then
-- a preview node is only set if there's no air there
preview[y][target_x] = node[1];
found = 1;
end
z = z+params[6];
end
if( not( found )) then
preview[y][target_x] = -1;
end
end
end
return preview;
end
-- internal function
build_chest.preview_image_create_view_from_top = function( data )
-- no view from top if the image is too big
if( data.size.z * data.size.y > 2500 ) then
return nil;
end
local preview = {};
for z = 1, data.size.z do
preview[ z ] = {};
for x = 1, data.size.x do
local found = nil;
local y = data.size.y;
while( not( found ) and y > 1) do
local node = data.scm_data_cache[y][x][z];
if( node and node[1]
and data.nodenames[ node[1] ]
and data.nodenames[ node[1] ] ~= 'air'
and data.nodenames[ node[1] ] ~= 'ignore'
and data.nodenames[ node[1] ] ~= 'mg:ignore'
and data.nodenames[ node[1] ] ~= 'default:torch' ) then
-- a preview node is only set if there's no air there
preview[z][x] = node[1];
found = 1;
end
y = y-1;
end
if( not( found )) then
preview[z][x] = -1;
end
end
end
return preview;
end
-- function called by the build chest to display one view
build_chest.preview_image_formspec = function( building_name, replacements, side_name )
if( not( building_name )
or not( build_chest.building[ building_name ] )
or not( build_chest.building[ building_name ].preview )) then
return "";
end
local side_names = {"front","right","back","left","top"};
local side = 1;
for i,v in ipairs( side_names ) do
if( side_name and side_name==v ) then
side = i;
end
end
local formspec = "";
for i=1,5 do
if( i ~= side ) then
formspec = formspec.."button["..tostring(3.3+1.2*(i-1))..
",2.2;1,0.5;preview;"..side_names[i].."]";
else
formspec = formspec.."label["..tostring(3.3+1.2*(i-1))..",2.2;"..side_names[i].."]";
end
end
local data = build_chest.building[ building_name ];
-- the draw_tile function is based on content_id
local content_ids = {};
for i,v in ipairs( data.nodenames ) do
local found = false;
for j,w in ipairs( replacements ) do
if( w and w[1] and w[1]==v) then
found = true;
if( minetest.registered_nodes[ w[2]] ) then
content_ids[ i ] = minetest.get_content_id( w[2] );
end
end
end
if( not( found )) then
if( minetest.registered_nodes[ v ]) then
content_ids[ i ] = minetest.get_content_id( v );
elseif( v ~= 'air' ) then
content_ids[ i ] = -1;
end
end
end
local scale = 0.5;
local tile_nr = 3; -- view from the side
if( side ~= 5 ) then
local scale_y = 6.0/data.size.y;
local scale_z = 10.0/data.size.z;
if( scale_y > scale_z) then
scale = scale_z;
else
scale = scale_y;
end
else
local scale_x = 10.0/data.size.x; -- only relevant for view from top
local scale_z = 6.0/data.size.z;
if( scale_x > scale_z) then
scale = scale_z;
else
scale = scale_x;
end
tile_nr = 1; -- view from top
end
if( not( side )) then
side = 1;
end
local preview = data.preview[ side ];
if( not( preview )) then
formspec = formspec.."label[3,3;Sorry, this schematic is too big for a preview image.]";
return formspec;
end
for y,y_values in ipairs( preview ) do
for l,v in ipairs( y_values ) do
-- air, ignore and mg:ignore are not stored
if( v and content_ids[ v ]==-1 ) then
formspec = formspec..build_chest.preview_image_draw_tile( nil, "unknown_node.png", (l*scale), 9-(y*scale), scale*1.3, scale*1.2, tile_nr);
elseif( v and v>0 and content_ids[v]) then
formspec = formspec..build_chest.preview_image_draw_tile( content_ids[ v ], nil, (l*scale), 9-(y*scale), scale*1.3, scale*1.2, tile_nr);
end
end
end
return formspec;
end
-- create all five preview images
build_chest.preview_image_create_views = function( res, orients )
-- create a 2d overview image (or rather, the data structure for it)
local preview = {
build_chest.preview_image_create_one_view( res, 2 ),
build_chest.preview_image_create_one_view( res, 1 ),
build_chest.preview_image_create_one_view( res, 4 ),
build_chest.preview_image_create_one_view( res, 3 )};
-- the building might be stored in rotated form
if( orients and #orients and orients[1] ) then
if( orients[1]==1 ) then
preview = {preview[2],preview[3],preview[4],preview[1]};
elseif( orients[1]==2 ) then
preview = {preview[3],preview[4],preview[1],preview[2]};
elseif( orients[1]==3 ) then
preview = {preview[4],preview[1],preview[2],preview[3]};
end
end
-- ...and add a preview image from top
preview[5] = build_chest.preview_image_create_view_from_top( res );
return preview;
end
-- this function makes sure that the building will always extend to the right and in front of the build chest

21
depends.txt Normal file
View File

@ -0,0 +1,21 @@
default?
doors?
farming?
wool?
stairs?
cottages?
moretrees?
trees?
forest?
dryplants?
cavestuff?
snow?
moresnow?
darkage?
ethereal?
deco?
metals?
grounds?
moreblocks?
bell?
mobf_trader?

107
handle_schematics_misc.lua Normal file
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

51
init.lua Normal file
View File

@ -0,0 +1,51 @@
handle_schematics.modpath = minetest.get_modpath( "handle_schematics");
-- adds worldedit_file.* namespace
-- deserialize worldedit savefiles
dofile(handle_schematics.modpath.."/worldedit_file.lua")
-- uses handle_schematics.* namespace
handle_schematics = {}
-- reads and analyzes .mts files (minetest schematics)
dofile(handle_schematics.modpath.."/analyze_mts_file.lua")
-- reads and analyzes worldedit files
dofile(handle_schematics.modpath.."/analyze_we_file.lua")
-- handles rotation and mirroring
dofile(handle_schematics.modpath.."/rotate.lua")
-- count nodes, take param2 into account for rotation etc.
dofile(handle_schematics.modpath.."/handle_schematics_misc.lua")
-- uses replacements_group.* namespace
-- these functions are responsible for the optional dependencies; they check
-- which nodes are available and may be offered as possible replacements
replacements_group = {};
dofile(handle_schematics.modpath.."/replacements_wood.lua")
dofile(handle_schematics.modpath.."/replacements_realtest.lua")
dofile(handle_schematics.modpath.."/replacements_farming.lua")
dofile(handle_schematics.modpath.."/replacements_roof.lua")
-- transforms the replacement list into a table;
-- also creates a replacement if needed and replaces default:torch
dofile(handle_schematics.modpath.."/replacements_get_table.lua")
-- uses build_chest.* namespace
-- a chest for spawning buildings manually
dofile(handle_schematics.modpath.."/build_chest.lua")
-- makes the replacements from replacements_group.* available to the build chest
dofile(handle_schematics.modpath.."/build_chest_handle_replacements.lua");
-- creates 2d previews of the schematic from left/right/back/front/top
dofile(handle_schematics.modpath.."/build_chest_preview_image.lua");
-- reads a file and adds the files listed there as menu entries
dofile(handle_schematics.modpath.."/build_chest_add_schems_from_file.lua");
-- chooses traders and spawn positions for buildings
dofile(handle_schematics.modpath.."/village_traders.lua")
-- the main functionality of the mod;
-- provides the function handle_schematics.place_building_from_file
-- (and also place_buildings for mg_villages)
dofile(handle_schematics.modpath.."/place_buildings.lua")
-- dofile(handle_schematics.modpath.."/fill_chest.lua")

810
place_buildings.lua Normal file
View File

@ -0,0 +1,810 @@
-- TODO: this function also occours in replacements.lua
handle_schematics.get_content_id_replaced = function( node_name, replacements )
if( not( node_name ) or not( replacements ) or not(replacements.table )) then
return minetest.get_content_id( 'ignore' );
end
if( replacements.table[ node_name ]) then
return minetest.get_content_id( replacements.table[ node_name ] );
else
return minetest.get_content_id( node_name );
end
end
-- either uses get_node_or_nil(..) or the data from voxelmanip
-- the function might as well be local (only used by *.mg_drop_moresnow)
handle_schematics.get_node_somehow = function( x, y, z, a, data, param2_data )
if( a and data and param2_data ) then
return { content = data[a:index(x, y, z)], param2 = param2_data[a:index(x, y, z)] };
end
-- no voxelmanip; get the node the normal way
local node = minetest.get_node_or_nil( {x=x, y=y, z=z} );
if( not( node ) ) then
return { content = moresnow.c_ignore, param2 = 0 };
end
return { content = minetest.get_content_id( node.name ), param2 = node.param2, name = node.name };
end
-- "drop" moresnow snow on diffrent shapes; works for voxelmanip and node-based setting
handle_schematics.mg_drop_moresnow = function( x, z, y_top, y_bottom, a, data, param2_data)
-- this only works if moresnow is installed
if( not( handle_schematics.moresnow_installed )) then
return;
end
local y = y_top;
local node_above = handle_schematics.get_node_somehow( x, y+1, z, a, data, param2_data );
local node_below = nil;
while( y >= y_bottom ) do
node_below = handle_schematics.get_node_somehow( x, y, z, a, data, param2_data );
if( node_above.content == moresnow.c_air
and node_below.content
and node_below.content ~= moresnow.c_ignore
and node_below.content ~= moresnow.c_air ) then
-- turn water into ice, but don't drop snow on it
if( node_below.content == minetest.get_content_id("default:water_source")
or node_below.content == minetest.get_content_id("default:river_water_source")) then
return { height = y, suggested = {new_id = minetest.get_content_id('default:ice'), param2 = 0 }};
end
-- if the node below drops snow when digged (i.e. is either snow or a moresnow node), we're finished
local get_drop = minetest.get_name_from_content_id( node_below.content );
if( get_drop ) then
get_drop = minetest.registered_nodes[ get_drop ];
if( get_drop and get_drop.drop and type( get_drop.drop )=='string' and get_drop.drop == 'default:snow') then
return;
end
end
if( not(node_below.content)
or node_below.content == moresnow.c_snow ) then
return;
end
local suggested = moresnow.suggest_snow_type( node_below.content, node_below.param2 );
-- c_snow_top and c_snow_fence can only exist when the node 2 below is a solid one
if( suggested.new_id == moresnow.c_snow_top
or suggested.new_id == moresnow.c_snow_fence) then
local node_below2 = handle_schematics.get_node_somehow( x, y-1, z, a, data, param2_data);
if( node_below2.content ~= moresnow.c_ignore
and node_below2.content ~= moresnow.c_air ) then
local suggested2 = moresnow.suggest_snow_type( node_below2.content, node_below2.param2 );
if( suggested2.new_id == moresnow.c_snow ) then
return { height = y+1, suggested = suggested };
end
end
-- it is possible that this is not the right shape; if so, the snow will continue to fall down
elseif( suggested.new_id ~= moresnow.c_ignore ) then
return { height = y+1, suggested = suggested };
end
-- TODO return; -- abort; there is no fitting moresnow shape for the node below
end
y = y-1;
node_above = node_below;
end
end
-- helper function for generate_building
-- places a marker that allows players to buy plots with houses on them (in order to modify the buildings)
local function generate_building_plotmarker( pos, minp, maxp, data, param2_data, a, cid, building_nr_in_bpos, village_id, filename)
-- position the plot marker so that players can later buy this plot + building in order to modify it
-- pos.o contains the original orientation (determined by the road and the side the building is
local p = {x=pos.x, y=pos.y+1, z=pos.z};
if( pos.o == 0 ) then
p.x = p.x - 1;
p.z = p.z + pos.bsizez - 1;
elseif( pos.o == 2 ) then
p.x = p.x + pos.bsizex;
elseif( pos.o == 1 ) then
p.z = p.z + pos.bsizez;
p.x = p.x + pos.bsizex - 1;
elseif( pos.o == 3 ) then
p.z = p.z - 1;
end
-- actually position the marker
if( p.x >= minp.x and p.x <= maxp.x and p.z >= minp.z and p.z <= maxp.z and p.y >= minp.y and p.y <= maxp.y) then
if( data[ a:index(p.x, p.y, p.z)] == cid.c_snow and p.y<maxp.y and moresnow and moresnow.c_snow_top and cid.c_snow_top ~= cid.c_ignore) then
data[ a:index(p.x, p.y+1, p.z)] = moresnow.c_snow_top;
end
data[ a:index(p.x, p.y, p.z)] = cid.c_plotmarker;
param2_data[a:index(p.x, p.y, p.z)] = pos.brotate;
-- store the necessary information in the marker so that it knows for which building it is responsible
local meta = minetest.get_meta( p );
meta:set_string('village_id', village_id );
meta:set_int( 'plot_nr', building_nr_in_bpos );
meta:set_string('infotext', 'Plot No. '..tostring( building_nr_in_bpos ).. ' with '..tostring( filename ));
end
end
-- we do have a list of all nodenames the building contains (the .mts file provided it);
-- we can thus apply all replacements to these nodenames;
-- this also checks param2 and sets some other variables to indicate that it's i.e. a tree or a chest
-- (which both need special handling later on)
local function generate_building_translate_nodenames( nodenames, replacements, cid, binfo_scm, mirror_x, mirror_z )
if( not( nodenames )) then
return;
end
local i;
local v;
local new_nodes = {};
for i,node_name in ipairs( nodenames ) do
new_nodes[ i ] = {}; -- array for collecting information about the new content id for nodes with number "i" in their .mts savefile
-- some nodes may be called differently when mirrored; needed for doors
local new_node_name = node_name;
if( new_node_name and ( mirror_x or mirror_z ) and handle_schematics.mirrored_node[ new_node_name ] ) then
new_node_name = handle_schematics.mirrored_node[ node_name ];
new_nodes[ i ].is_mirrored = 1; -- currently unused
end
-- apply the replacements
if( new_node_name and replacements.table[ new_node_name ] ) then
new_node_name = replacements.table[ new_node_name ];
new_nodes[ i ].is_replaced = 1; -- currently unused
end
-- only existing nodes can be placed
if( new_node_name and minetest.registered_nodes[ new_node_name ]) then
local regnode = minetest.registered_nodes[ new_node_name ];
new_nodes[ i ].new_node_name = new_node_name;
new_nodes[ i ].new_content = minetest.get_content_id( new_node_name );
if( regnode.on_construct ) then
new_nodes[ i ].on_construct = 1;
end
local new_content = new_nodes[ i ].new_content;
if( new_content == cid.c_dirt or new_content == cid.c_dirt_with_grass ) then
new_nodes[ i ].is_grass = 1;
elseif( new_content == cid.c_sapling
or new_content == cid.c_jsapling
or new_content == cid.c_psapling
or new_content == cid.c_savannasapling
or new_content == cid.c_pinesapling ) then
-- store that a tree is to be grown there
new_nodes[ i ].is_tree = 1;
elseif( new_content == cid.c_chest
or new_content == cid.c_chest_locked
or new_content == cid.c_chest_shelf
or new_content == cid.c_chest_ash
or new_content == cid.c_chest_aspen
or new_content == cid.c_chest_birch
or new_content == cid.c_chest_maple
or new_content == cid.c_chest_chestnut
or new_content == cid.c_chest_pine
or new_content == cid.c_chest_spruce) then
-- we're dealing with a chest that might need filling
new_nodes[ i ].is_chestlike = 1;
elseif( new_content == cid.c_chest_private
or new_content == cid.c_chest_work
or new_content == cid.c_chest_storage ) then
-- we're dealing with a chest that might need filling
new_nodes[ i ].is_chestlike = 1;
-- TODO: perhaps use a locked chest owned by the mob living there?
-- place a normal chest here
new_nodes[ i ].new_content = cid.c_chest;
elseif( new_content == cid.c_sign ) then
-- the sign may require some text to be written on it
new_nodes[ i ].is_sign = 1;
end
-- handle_schematics.get_param2_rotated( 'facedir', param2 ) needs to be called for nodes
-- which use either facedir or wallmounted;
-- realtest rotates some nodes diffrently and does not come with default:ladder
if( node_name == 'default:ladder' and not( minetest.registered_nodes[ node_name ])) then
new_nodes[ i ].change_param2 = {}; --{ 2->1, 5->2, 3->3, 4->0 }
new_nodes[ i ].change_param2[2] = 1;
new_nodes[ i ].change_param2[5] = 2;
new_nodes[ i ].change_param2[3] = 3;
new_nodes[ i ].change_param2[4] = 0;
new_nodes[ i ].paramtype2 = 'facedir';
-- ..except if they are stairs or ladders
elseif( string.sub( node_name, 1, 7 ) == 'stairs:' or string.sub( node_name, 1, 6 ) == 'doors:') then
new_nodes[ i ].paramtype2 = 'facedir';
-- normal nodes
elseif( regnode and regnode.paramtype2 and (regnode.paramtype2=='facedir' or regnode.paramtype2=='wallmounted')) then
new_nodes[ i ].paramtype2 = regnode.paramtype2;
end
-- we tried our best, but the replacement node is not defined
elseif( new_node_name ~= 'mg:ignore' ) then
local msg = 'ERROR: Did not find a suitable replacement for '..tostring( node_name )..' (suggested but inexistant: '..
tostring( new_node_name )..'). Building: '..tostring( binfo_scm )..'.';
if( mg_villages and mg_villages.print ) then
mg_villages.print( mg_villages.DEBUG_LEVEL_WARNING, msg );
else
print( msg );
end
msg = nil;
new_nodes[ i ].ignore = 1; -- keep the old content
else -- handle mg:ignore
new_nodes[ i ].ignore = 1;
end
end
return new_nodes;
end
local function generate_building(pos, minp, maxp, data, param2_data, a, extranodes, replacements, cid, extra_calls, building_nr_in_bpos, village_id, binfo_extra, road_node)
local binfo = binfo_extra;
if( not( binfo ) and mg_villages) then
binfo = mg_villages.BUILDINGS[pos.btype]
end
local scm
-- the building got removed from mg_villages.BUILDINGS in the meantime
if( not( binfo )) then
return;
end
-- schematics of .mts type are not handled here; they need to be placed using place_schematics
if( binfo.is_mts and binfo.is_mts == 1 ) then
return;
end
-- roads are very simple structures that are not stored as schematics
if( pos.btype == 'road' ) then
handle_schematics.place_road( minp, maxp, data, param2_data, a, road_node, pos, cid.c_air );
return;
end
if( not( pos.no_plotmarker )) then
generate_building_plotmarker( pos, minp, maxp, data, param2_data, a, cid, building_nr_in_bpos, village_id, binfo.scm );
end
-- skip building if it is not located at least partly in the area that is currently beeing generated
if( pos.x > maxp.x or pos.x + pos.bsizex < minp.x
or pos.z > maxp.z or pos.z + pos.bsizez < minp.z ) then
return;
end
if( pos.btype and
(( binfo.sizex ~= pos.bsizex and binfo.sizex ~= pos.bsizez )
or ( binfo.sizez ~= pos.bsizex and binfo.sizez ~= pos.bsizez )
or not( binfo.scm_data_cache ))) then
if( mg_villages and mg_villages.print ) then
mg_villages.print( mg_villages.DEBUG_LEVEL_WARNING,
'ERROR: This village was created using diffrent buildings than those known know. Cannot place unknown building.');
else
print( 'ERROR: Size information about this building differs. Cannot place building.');
end
return;
end
if( binfo.scm_data_cache )then
scm = binfo.scm_data_cache;
else
scm = binfo.scm
end
-- the fruit is set per building, not per village as the other replacements
if( binfo.farming_plus and binfo.farming_plus == 1 and pos.fruit and mg_villages) then
mg_villages.get_fruit_replacements( replacements, pos.fruit);
end
local traders = {};
if( handle_schematics.choose_traders ) then
local village_type = "";
if( village_id and mg_villages and mg_villages.all_villages and mg_villages.all_villages[ village_id ] ) then
village_type = mg_villages.all_villages[ village_id ].village_type;
end
local building_type = "";
if( binfo.typ ) then
building_type = binfo.typ;
end
if( not( pos.traders )) then
traders = handle_schematics.choose_traders( village_type, building_type, replacements )
end
end
local c_ignore = minetest.get_content_id("ignore")
local c_air = minetest.get_content_id("air")
local c_snow = minetest.get_content_id( "default:snow");
local c_dirt = minetest.get_content_id( "default:dirt" );
local c_dirt_with_grass = minetest.get_content_id( "default:dirt_with_grass" );
local c_dirt_with_snow = minetest.get_content_id( "default:dirt_with_snow" );
local scm_x = 0;
local scm_z = 0;
local step_x = 1;
local step_z = 1;
local scm_z_start = 0;
if( pos.brotate == 2 ) then
scm_x = pos.bsizex+1;
step_x = -1;
end
if( pos.brotate == 1 ) then
scm_z = pos.bsizez+1;
step_z = -1;
scm_z_start = scm_z;
end
local mirror_x = false;
local mirror_z = false;
if( pos.mirror ) then
if( binfo.axis and binfo.axis == 1 ) then
mirror_x = true;
mirror_z = false;
else
mirror_x = false;
mirror_z = true;
end
end
-- translate all nodenames and apply the replacements
local new_nodes = generate_building_translate_nodenames( binfo.nodenames, replacements, cid, binfo.scm, mirror_x, mirror_z );
for x = 0, pos.bsizex-1 do
scm_x = scm_x + step_x;
scm_z = scm_z_start;
for z = 0, pos.bsizez-1 do
scm_z = scm_z + step_z;
local xoff = scm_x;
local zoff = scm_z;
if( pos.brotate == 2 ) then
if( mirror_x ) then
xoff = pos.bsizex - scm_x + 1;
end
if( mirror_z ) then
zoff = scm_z;
else
zoff = pos.bsizez - scm_z + 1;
end
elseif( pos.brotate == 1 ) then
if( mirror_x ) then
xoff = pos.bsizez - scm_z + 1;
else
xoff = scm_z;
end
if( mirror_z ) then
zoff = pos.bsizex - scm_x + 1;
else
zoff = scm_x;
end
elseif( pos.brotate == 3 ) then
if( mirror_x ) then
xoff = pos.bsizez - scm_z + 1;
else
xoff = scm_z;
end
if( mirror_z ) then
zoff = scm_x;
else
zoff = pos.bsizex - scm_x + 1;
end
elseif( pos.brotate == 0 ) then
if( mirror_x ) then
xoff = pos.bsizex - scm_x + 1;
end
if( mirror_z ) then
zoff = pos.bsizez - scm_z + 1;
end
end
local has_snow = false;
local ground_type = c_dirt_with_grass;
for y = 0, binfo.ysize-1 do
local ax = pos.x+x;
local ay = pos.y+y+binfo.yoff;
local az = pos.z+z;
if (ax >= minp.x and ax <= maxp.x) and (ay >= minp.y and ay <= maxp.y) and (az >= minp.z and az <= maxp.z) then
local new_content = c_air;
local t = scm[y+1][xoff][zoff];
local node_content = data[a:index(ax, ay, az)];
if( binfo.yoff+y == 0 ) then
-- no snow on the gravel roads
if( node_content == c_dirt_with_snow or data[a:index(ax, ay+1, az)]==c_snow) then
has_snow = true;
end
ground_type = node_content;
end
if( not( t )) then
if( node_content ~= cid.c_plotmarker and (not(moresnow) or node_content ~= moresnow.c_snow_top )) then
data[ a:index(ax, ay, az)] = cid.c_air;
end
else
local n = new_nodes[ t[1] ]; -- t[1]: id of the old node
if( not( n.ignore )) then
new_content = n.new_content;
else
new_content = node_content;
end
-- replace all dirt and dirt with grass at that x,z coordinate with the stored ground grass node;
if( n.is_grass ) then
new_content = ground_type;
end
if( n.on_construct ) then
if( not( extra_calls.on_constr[ new_content ] )) then
extra_calls.on_constr[ new_content ] = { {x=ax, y=ay, z=az}};
else
table.insert( extra_calls.on_constr[ new_content ], {x=ax, y=ay, z=az});
end
end
-- do not overwrite plotmarkers
if( new_content ~= cid.c_air or node_content ~= cid.c_plotmarker ) then
data[ a:index(ax, ay, az)] = new_content;
end
-- store that a tree is to be grown there
if( n.is_tree ) then
table.insert( extra_calls.trees, {x=ax, y=ay, z=az, typ=new_content, snow=has_snow});
-- we're dealing with a chest that might need filling
elseif( n.is_chestlike ) then
table.insert( extra_calls.chests, {x=ax, y=ay, z=az, typ=new_content, bpos_i=building_nr_in_bpos});
-- the sign may require some text to be written on it
elseif( n.is_sign ) then
table.insert( extra_calls.signs, {x=ax, y=ay, z=az, typ=new_content, bpos_i=building_nr_in_bpos});
end
-- handle rotation
if( n.paramtype2 ) then
local param2 = t[2];
if( n.change_param2 and n.change_param2[ t[2] ]) then
param2 = n.change_param2[ param2 ];
end
local np2 = 0;
if( mirror_x ) then
np2 = handle_schematics.rotation_table[ n.paramtype2 ][ param2+1 ][ pos.brotate+1 ][ 2 ];
elseif( mirror_z ) then
np2 = handle_schematics.rotation_table[ n.paramtype2 ][ param2+1 ][ pos.brotate+1 ][ 3 ];
else
np2 = handle_schematics.rotation_table[ n.paramtype2 ][ param2+1 ][ pos.brotate+1 ][ 1 ];
end
--[[
local param2list = handle_schematics.get_param2_rotated( n.paramtype2, param2);
local np2 = param2list[ pos.brotate + 1];
-- mirror
if( mirror_x ) then
if( #param2list==5) then
np2 = handle_schematics.mirror_facedir[ ((pos.brotate+1)%2)+1 ][ np2+1 ];
elseif( #param2list<5
and ((pos.brotate%2==1 and (np2==4 or np2==5))
or (pos.brotate%2==0 and (np2==2 or np2==3)))) then
np2 = param2list[ (pos.brotate + 2)%4 +1];
end
elseif( mirror_z ) then
if( #param2list==5) then
np2 = handle_schematics.mirror_facedir[ (pos.brotate %2)+1 ][ np2+1 ];
elseif( #param2list<5
and ((pos.brotate%2==0 and (np2==4 or np2==5))
or (pos.brotate%2==1 and (np2==2 or np2==3)))) then
np2 = param2list[ (pos.brotate + 2)%4 +1];
end
end
--]]
param2_data[a:index(ax, ay, az)] = np2;
else
param2_data[a:index(ax, ay, az)] = t[2];
end
end
end
end
local ax = pos.x + x;
local az = pos.z + z;
local y_top = pos.y+binfo.yoff+binfo.ysize;
if( y_top+1 > maxp.y ) then
y_top = maxp.y-1;
end
local y_bottom = pos.y+binfo.yoff;
if( y_bottom < minp.y ) then
y_bottom = minp.y;
end
if( has_snow and ax >= minp.x and ax <= maxp.x and az >= minp.z and az <= maxp.z ) then
local res = handle_schematics.mg_drop_moresnow( ax, az, y_top, y_bottom-1, a, data, param2_data);
if( res and (data[ a:index(ax, res.height, az)]==cid.c_air
or data[ a:index(ax, res.height, az)]==cid.c_water )) then
data[ a:index(ax, res.height, az)] = res.suggested.new_id;
param2_data[a:index(ax, res.height, az)] = res.suggested.param2;
has_snow = false;
end
end
end
end
-- determine suitable positions for the traders
if( handle_schematics.choose_trader_pos and #traders>0) then
extra_calls.traders = handle_schematics.choose_trader_pos(pos, minp, maxp, data, param2_data, a, extranodes, replacements, cid, extra_calls, building_nr_in_bpos, village_id, binfo_extra, road_node, traders);
print('TRADERS CHOOSEN FOR '..tostring( binfo.scm )..': '..minetest.serialize( extra_calls.traders ));
end
end
-- actually place the buildings (at least those which came as .we files; .mts files are handled later on)
-- this code was also responsible for tree placement;
-- place_buildings is used by mg_villages exclusively. It calls the local function generate_building and
-- therefore resides in this file.
handle_schematics.place_buildings = function(village, minp, maxp, data, param2_data, a, cid, village_id)
-- this function is only relevant for mg_villages
if( not( mg_villages )) then
return;
end
local vx, vz, vs, vh = village.vx, village.vz, village.vs, village.vh
local village_type = village.village_type;
local bpos = village.to_add_data.bpos;
local replacements = mg_villages.get_replacement_table( village.village_type, nil, village.to_add_data.replacements );
cid.c_chest = handle_schematics.get_content_id_replaced( 'default:chest', replacements );
cid.c_chest_locked = handle_schematics.get_content_id_replaced( 'default:chest_locked', replacements );
cid.c_chest_private = handle_schematics.get_content_id_replaced( 'cottages:chest_private', replacements );
cid.c_chest_work = handle_schematics.get_content_id_replaced( 'cottages:chest_work', replacements );
cid.c_chest_storage = handle_schematics.get_content_id_replaced( 'cottages:chest_storage', replacements );
cid.c_chest_shelf = handle_schematics.get_content_id_replaced( 'cottages:shelf', replacements );
cid.c_chest_ash = handle_schematics.get_content_id_replaced( 'trees:chest_ash', replacements );
cid.c_chest_aspen = handle_schematics.get_content_id_replaced( 'trees:chest_aspen', replacements );
cid.c_chest_birch = handle_schematics.get_content_id_replaced( 'trees:chest_birch', replacements );
cid.c_chest_maple = handle_schematics.get_content_id_replaced( 'trees:chest_maple', replacements );
cid.c_chest_chestnut = handle_schematics.get_content_id_replaced( 'trees:chest_chestnut', replacements );
cid.c_chest_pine = handle_schematics.get_content_id_replaced( 'trees:chest_pine', replacements );
cid.c_chest_spruce = handle_schematics.get_content_id_replaced( 'trees:chest_spruce', replacements );
cid.c_sign = handle_schematics.get_content_id_replaced( 'default:sign_wall', replacements );
--print('REPLACEMENTS: '..minetest.serialize( replacements.table )..' CHEST: '..tostring( minetest.get_name_from_content_id( cid.c_chest ))); -- TODO
local extranodes = {}
local extra_calls = { on_constr = {}, trees = {}, chests = {}, signs = {}, traders = {} };
for i, pos in ipairs(bpos) do
-- roads are only placed if there are at least mg_villages.MINIMAL_BUILDUNGS_FOR_ROAD_PLACEMENT buildings in the village
if( not(pos.btype) or pos.btype ~= 'road' or village.anz_buildings > mg_villages.MINIMAL_BUILDUNGS_FOR_ROAD_PLACEMENT )then
-- replacements are in table format for mapgen-based building spawning
generate_building(pos, minp, maxp, data, param2_data, a, extranodes, replacements, cid, extra_calls, i, village_id, nil, mg_villages.road_node )
end
end
-- replacements are in list format for minetest.place_schematic(..) type spawning
return { extranodes = extranodes, bpos = bpos, replacements = replacements.list, dirt_roads = village.to_add_data.dirt_roads,
plantlist = village.to_add_data.plantlist, extra_calls = extra_calls };
end
-- place a schematic manually
--
-- pos needs to contain information about how to place the building:
-- pos.x, pos.y, pos.z where the building is to be placed
-- pos.btype determines which building will be placed; if not set, binfo_extra needs to be provided
-- pos.brotate contains a value of 0-3, which determines the rotation of the building
-- pos.bsizex size of the building in x direction
-- pos.bsizez size of the building in z direction
-- pos.mirror if set, the building will be mirrored
-- pos.no_plotmarker optional; needs to be set in order to avoid the generation of a plotmarker
-- building_nr optional; used for plotmarker
-- village_id optional; used for plotmarker
-- pos.fruit optional; determines the fruit a farm is going to grow (if binfo.farming_plus is set)
-- binfo contains general information about a building:
-- binfo.sizex size of the building in x direction
-- binfo.sizez
-- binfo.ysize
-- binfo.yoff how deep is the building burried?
-- binfo.nodenames list of the node names beeing used by the building
-- binfo.scm name of the file containing the schematic; only needed for an error message
-- binfo.scm_data_cache contains actual information about the nodes beeing used (the data)
-- binfo.is_mts optional; if set to 1, the function will abort
-- binfo.farming_plus optional; if set, pos.fruit needs to be set as well
-- binfo.axis optional; relevant for some mirroring operations
--
-- replacement_list contains replacements in the same list format as place_schematic uses
--
handle_schematics.place_building_using_voxelmanip = function( pos, binfo, replacement_list)
if( not( replacement_list ) or type( replacement_list ) ~= 'table' ) then
return;
end
-- if not defined, the building needs to start at pos.x,pos.y,pos.z - without offset
if( not( binfo.yoff )) then
binfo.yoff = 0;
end
-- TODO: calculate the end position from the given data
-- get a suitable voxelmanip object
-- (taken from minetest_game/mods/default/trees.lua)
local vm = minetest.get_voxel_manip()
local minp, maxp = vm:read_from_map(
{x = pos.x, y = pos.y, z = pos.z},
{x = pos.x+pos.bsizex, y = pos.y+binfo.ysize, z = pos.z+pos.bsizez} -- TODO
)
local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
local data = vm:get_data()
local param2_data = vm:get_param2_data();
-- translate the replacement_list into replacements.ids and replacements.table format
-- the first two parameters are nil because we do not want a new replacement list to be generated
local replacements = handle_schematics.get_replacement_table( nil, nil, replacement_list );
-- only very few nodes are actually used from the cid table (content ids)
local cid = {};
cid.c_air = minetest.get_content_id( 'air' );
cid.c_dirt = handle_schematics.get_content_id_replaced( 'default:dirt', replacements );
cid.c_dirt_with_grass = handle_schematics.get_content_id_replaced( 'default:dirt_with_grass',replacements );
cid.c_sapling = handle_schematics.get_content_id_replaced( 'default:sapling', replacements );
cid.c_jsapling = handle_schematics.get_content_id_replaced( 'default:junglesapling', replacements );
cid.c_psapling = handle_schematics.get_content_id_replaced( 'default:pine_sapling', replacements );
cid.c_savannasapling = handle_schematics.get_content_id_replaced( 'mg:savannasapling', replacements );
cid.c_pinesapling = handle_schematics.get_content_id_replaced( 'mg:pinesapling', replacements );
cid.c_plotmarker = handle_schematics.get_content_id_replaced( 'mg_villages:plotmarker', replacements );
cid.c_chest = handle_schematics.get_content_id_replaced( 'default:chest', replacements );
cid.c_chest_locked = handle_schematics.get_content_id_replaced( 'default:chest_locked', replacements );
cid.c_chest_private = handle_schematics.get_content_id_replaced( 'cottages:chest_private', replacements );
cid.c_chest_work = handle_schematics.get_content_id_replaced( 'cottages:chest_work', replacements );
cid.c_chest_storage = handle_schematics.get_content_id_replaced( 'cottages:chest_storage', replacements );
cid.c_chest_shelf = handle_schematics.get_content_id_replaced( 'cottages:shelf', replacements );
cid.c_chest_ash = handle_schematics.get_content_id_replaced( 'trees:chest_ash', replacements );
cid.c_chest_aspen = handle_schematics.get_content_id_replaced( 'trees:chest_aspen', replacements );
cid.c_chest_birch = handle_schematics.get_content_id_replaced( 'trees:chest_birch', replacements );
cid.c_chest_maple = handle_schematics.get_content_id_replaced( 'trees:chest_maple', replacements );
cid.c_chest_chestnut = handle_schematics.get_content_id_replaced( 'trees:chest_chestnut', replacements );
cid.c_chest_pine = handle_schematics.get_content_id_replaced( 'trees:chest_pine', replacements );
cid.c_chest_spruce = handle_schematics.get_content_id_replaced( 'trees:chest_spruce', replacements );
cid.c_sign = handle_schematics.get_content_id_replaced( 'default:sign_wall', replacements );
-- for roads
cid.c_sign = handle_schematics.get_content_id_replaced( 'default:gravel', replacements );
local extranodes = {}
local extra_calls = { on_constr = {}, trees = {}, chests = {}, signs = {}, traders = {} };
generate_building(pos, minp, maxp, data, param2_data, a, extranodes, replacements, cid, extra_calls, pos.building_nr, pos.village_id, binfo, cid.c_gravel);
-- store the changed map data
vm:set_data(data);
vm:set_param2_data(param2_data);
vm:write_to_map();
vm:update_liquids();
vm:update_map();
-- TODO: do the calls for the extranodes as well
-- replacements are in list format for minetest.place_schematic(..) type spawning
return { extranodes = extranodes, replacements = replacements.list, extra_calls = extra_calls };
end
-- places a building read from file "building_name" on the map between start_pos and end_pos using luavoxelmanip
-- returns error message on failure and nil on success
handle_schematics.place_building_from_file = function( start_pos, end_pos, building_name, replacement_list, rotate, axis, mirror, no_plotmarker )
if( not( building_name )) then
return "No file name given. Cannot find the schematic.";
end
local binfo = handle_schematics.analyze_file( building_name, nil, nil );
if( not( binfo )) then
return "Failed to import schematic. Only .mts and .we are supported!";
end
-- nodenames and scm_data_cache can be used directly;
-- the size dimensions need to be renamed
binfo.sizex = binfo.size.x;
binfo.sizez = binfo.size.z;
binfo.ysize = binfo.size.y;
-- this value has already been taken care of when determining start_pos
binfo.yoff = 0;
-- file name of the scm; only used for error messages
binfo.scm = building_name;
-- this is relevant for mirroring operations
binfo.axis = axis;
if( not( rotate ) or rotate=="0" ) then
start_pos.brotate = 0;
elseif( rotate=="90" ) then
start_pos.brotate = 1;
elseif( rotate=="180" ) then
start_pos.brotate = 2;
elseif( rotate=="270" ) then
start_pos.brotate = 3;
end
if( start_pos.brotate > 3 ) then
start_pos.brotate = start_pos.brotate % 4;
end
-- determine the size of the bulding from the place we assigned to it...
start_pos.bsizex = math.abs(end_pos.x - start_pos.x)+1;
start_pos.bsizez = math.abs(end_pos.z - start_pos.z)+1;
-- otpional; if set, the building will be mirrored
start_pos.mirror = mirror;
-- do not generate a plot marker as this is not part of a village;
-- otherwise, building_nr and village_id would have to be provided
start_pos.no_plotmarker = no_plotmarker;
-- all those calls to on_construct need to be done now
local res = handle_schematics.place_building_using_voxelmanip( start_pos, binfo, replacement_list);
if( not(res) or not( res.extra_calls )) then
return;
end
-- call on_construct where needed;
-- trees, chests and signs receive no special treatment here
for k, v in pairs( res.extra_calls.on_constr ) do
local node_name = minetest.get_name_from_content_id( k );
if( minetest.registered_nodes[ node_name ].on_construct ) then
for _, pos in ipairs(v) do
minetest.registered_nodes[ node_name ].on_construct( pos );
end
end
end
-- TODO: handle metadata (if any is provided)
end
-- add the dirt roads
handle_schematics.place_dirt_roads = function(village, minp, maxp, data, param2_data, a, c_road_node)
local c_air = minetest.get_content_id( 'air' );
for _, pos in ipairs(village.to_add_data.dirt_roads) do
handle_schematics.place_road( minp, maxp, data, param2_data, a, c_road_node, pos, c_air );
end
end
handle_schematics.place_road = function(minp, maxp, data, param2_data, a, c_road_node, pos, c_air )
local param2 = 0;
if( pos.bsizex > 2 ) then
param2 = 1;
end
if( not(pos.y >= minp.y and pos.y <= maxp.y-2)) then
return;
end
for x = math.max( pos.x, minp.x ), math.min( pos.x+pos.bsizex-1, maxp.x ) do
for z = math.max( pos.z, minp.z ), math.min( pos.z+pos.bsizez-1, maxp.z ) do
-- roads have a height of 1 block
data[ a:index( x, pos.y, z)] = c_road_node;
param2_data[ a:index( x, pos.y, z)] = param2;
-- ...with air above
data[ a:index( x, pos.y+1, z)] = c_air;
data[ a:index( x, pos.y+2, z)] = c_air;
end
end
end
if( minetest.get_modpath('moresnow' )) then
handle_schematics.moresnow_installed = true;
end

167
replacements_farming.lua Normal file
View File

@ -0,0 +1,167 @@
replacements_group['farming'] = {}
-- this contains a list of all found/available nodenames that may act as a replacement frming nodes
replacements_group['farming'].found = {};
-- contains a list of *all* known farming names - even of mods that may not be installed
replacements_group['farming'].all = {};
-- contains information about how a particular node is called if a particular farming mod is used;
replacements_group['farming'].data = {};
-- names of traders for the diffrent fruits
replacements_group['farming'].traders = {};
replacements_group['farming'].replace_material = function( replacements, old_material, new_material )
if( not( old_material ) or not( replacements_group['farming'].data[ old_material ])
or not( new_material ) or not( replacements_group['farming'].data[ new_material ])
or old_material == new_material ) then
return replacements;
end
local old_nodes = replacements_group['farming'].data[ old_material ];
local new_nodes = replacements_group['farming'].data[ new_material ];
for i=1,#old_nodes do
local old = old_nodes[i];
local new = old;
if( i<=#new_nodes and new_nodes[i] and minetest.registered_nodes[ new_nodes[i]] ) then
new = new_nodes[i];
local found = false;
for i,v in ipairs(replacements) do
if( v and v[1]==old ) then
v[2] = new;
found = true;
end
end
if( not( found )) then
table.insert( replacements, { old, new });
end
-- default to the last growth stage
elseif( i>#new_nodes and minetest.registered_nodes[ new_nodes[ #new_nodes ]]) then
table.insert( replacements, { old, new_nodes[ #new_nodes ] });
end
end
return replacements;
end
---------------------
-- internal functions
---------------------
replacements_group['farming'].add_material = function( fruit, fruit_item, prefix, seperator, postfix )
local is_loaded = false;
if( minetest.registered_items[ fruit_item ]
and minetest.registered_nodes[ prefix..fruit..seperator.."1"..postfix ] ) then
is_loaded = true;
table.insert( replacements_group['farming'].found, fruit_item );
end
table.insert( replacements_group['farming'].all, fruit_item );
local data = {};
-- handle seeds
if( minetest.registered_items[ prefix..fruit..'_seed' ]) then
data[1] = prefix..fruit..'_seed';
elseif( minetest.registered_items[ prefix..fruit..'seed' ]) then
data[1] = prefix..fruit..'seed';
else
data[1] = fruit_item;
end
for i=1,8 do
local node_name = prefix..fruit..seperator..tostring(i)..postfix;
if( is_loaded and minetest.registered_nodes[ node_name ]) then
table.insert( data, node_name );
-- if the mod is not loaded, we do not know how many growth stages it has;
-- in order to be on the safe side, store them all
elseif( not( is_loaded )) then
table.insert( data, node_name );
end
end
-- the last plant stage (the one that gives the fruit) usually has no number
local node_name = prefix..fruit;
if( is_loaded and minetest.registered_nodes[ node_name ]) then
table.insert( data, node_name );
elseif( not( is_loaded )) then
table.insert( data, node_name );
end
replacements_group['farming'].data[ fruit_item ] = data;
-- farming nodes do not count as ground (except for soil - which is not handled here)
if( mg_villages and mg_villages.node_is_ground ) then
for _,v in ipairs( data ) do
mg_villages.node_is_ground[ v ] = false;
end
end
if( is_loaded and mobf_trader and mobf_trader.add_trader ) then
-- TODO: use replacements for the payments where needed
local goods = {
{ fruit_item.." 1", "default:coal_lump 3", "default:wood 8"},
{ fruit_item.." 10", "default:steel_ingot 2", "default:chest_locked 1"}};
if( fruit_item ~= data[1] ) then
table.insert( goods, { data[1].." 1", "farming:scarecrow", "farming:scarecrow_light 1"});
table.insert( goods, { data[1].." 2", "default:dirt 20", "default:bucket_water", "default:steel_ingot 4", "default:leaves 99" });
end
table.insert( goods, {"farming:hoe_wood 1","default:wood 10", "default:cobble 10"});
mobf_trader.add_trader( mobf_trader.npc_trader_prototype,
"farmer growing "..fruit.."s", -- not always the right grammatical form
fruit.."_farmer_v",
goods,
{ "farmer" },
""
);
replacements_group['farming'].traders[ fruit_item ] = fruit..'_farmer_v';
end
end
-- create a list of all available fruit types
replacements_group['farming'].construct_farming_type_list = function()
-- farming from minetest_game
replacements_group['farming'].add_material( 'wheat', 'farming:wheat', 'farming:', '_', '' );
replacements_group['farming'].add_material( 'cotton', 'farming:cotton', 'farming:', '_', '' );
-- RealTest
replacements_group['farming'].add_material( 'flax', 'farming:string', 'farming:', '_', '' );
replacements_group['farming'].add_material( 'spelt', 'farming:wheat', 'farming:', '_', '' );
replacements_group['farming'].add_material( 'soy', 'farming:soy', 'farming:', '_', '' );
-- diffrent versions of farming_plus:
-- PilzAdam: https://forum.minetest.net/viewtopic.php?t=2787
-- TenPlus1: https://forum.minetest.net/viewtopic.php?t=9019
-- MTDad: https://forum.minetest.net/viewtopic.php?t=10187
local fruits = { 'strawberry', 'raspberry',
'carrot', 'rhubarb', 'cucumber',
'pumpkin', 'melon',
'orange', 'lemon', 'peach', 'walnut',
'potato','potatoe', -- diffrent mods spell them diffrently
'tomato', 'corn'
};
for i,fruit in ipairs( fruits ) do
if( minetest.registered_nodes[ 'farming_plus:'..fruit ]
and minetest.registered_nodes[ 'farming_plus:'..fruit..'_1' ]
and minetest.registered_items[ 'farming_plus:'..fruit..'_item' ] ) then
replacements_group['farming'].add_material( fruit, 'farming_plus:'..fruit..'_item', 'farming_plus:', '_', '' );
end
end
-- coffee beans from farming_plus/farming_plusplus
replacements_group['farming'].add_material( 'coffee', 'farming_plus:coffee_beans', 'farming_plus:', '_', '' );
-- Docfarming: https://forum.minetest.net/viewtopic.php?t=3948
fruits = {'carrot','corn','potato','raspberry'};
for i,fruit in ipairs( fruits ) do
replacements_group['farming'].add_material( fruit, 'docfarming:'..fruit, 'docfarming:', '', '' );
end
end
-- create the list of known farming fruits
replacements_group['farming'].construct_farming_type_list();

View File

@ -0,0 +1,21 @@
-- mapgen based replacements work best using a table, while minetest.place_schematic(..) based spawning needs a list
handle_schematics.get_replacement_table = function( housetype, pr, replacements )
local rtable = {};
local ids = {};
if( not( replacements ) and mg_villages and mg_villages.get_replacement_list) then
replacements = mg_villages.get_replacement_list( housetype, pr );
end
-- it is very problematic if the torches on houses melt snow and cause flooding; thus, we use a torch that is not hot
if( minetest.registered_nodes[ 'mg_villages:torch']) then
table.insert( replacements, {'default:torch', 'mg_villages:torch'});
end
for i,v in ipairs( replacements ) do
if( v and #v == 2 ) then
rtable[ v[1] ] = v[2];
ids[ minetest.get_content_id( v[1] )] = minetest.get_content_id( v[2] );
end
end
return { table = rtable, list = replacements, ids = ids };
end

82
replacements_realtest.lua Normal file
View File

@ -0,0 +1,82 @@
replacements_group['realtest'] = {}
-- parameter: replacements, name_in_default, name_in_realtest, to_realtest=true/false
replacements_group['realtest'].stairs = function( repl, def, rt, to_realtest)
if( to_realtest ) then
if( def ~= rt ) then
table.insert( repl, {'default:'..def, 'default:'..rt});
end
table.insert( repl, {'stairs:stair_'..def, 'default:'..rt..'_stair'});
table.insert( repl, {'stairs:slab_'..def, 'default:'..rt..'_slab'});
else
if( def ~= rt ) then
table.insert( repl, {'default:'..rt, 'default:'..def});
end
table.insert( repl, {'default:'..rt..'_stair', 'stairs:stair_'..def});
table.insert( repl, {'default:'..rt..'_stair_upside_down','stairs:stair_'..def});
-- upside-down-slab
table.insert( repl, {'default:'..rt..'_slab_r', 'stairs:slab_'..def});
table.insert( repl, {'default:'..rt..'_slab', 'stairs:slab_'..def});
end
return repl;
end
replacements_group['realtest'].replace = function( replacements )
local repl = {};
local to_realtest = false;
if( not( minetest.registered_nodes[ 'default:furnace' ])
and minetest.registered_nodes[ 'oven:oven' ]) then
to_realtest = true;
elseif( minetest.registered_nodes[ 'default:furnace' ]
and not( minetest.registered_nodes[ 'oven:oven' ])) then
to_realtest = false;
else
-- else no replacements required
return;
end
replacements_group['realtest'].stairs( repl, 'stone', 'stone', to_realtest );
replacements_group['realtest'].stairs( repl, 'cobble', 'stone_flat', to_realtest );
replacements_group['realtest'].stairs( repl, 'stonebrick', 'stone_bricks', to_realtest );
replacements_group['realtest'].stairs( repl, 'desert_stone', 'desert_stone', to_realtest );
replacements_group['realtest'].stairs( repl, 'desert_cobble', 'desert_stone_flat', to_realtest );
replacements_group['realtest'].stairs( repl, 'desert_stonebrick', 'desert_stone_bricks',to_realtest );
replacements_group['realtest'].stairs( repl, 'brick', 'brick', to_realtest );
if( to_realtest ) then
table.insert( repl, {'default:furnace', 'oven:oven'});
table.insert( repl, {'default:clay', 'grounds:clay'});
-- Realtest does not know about these nodes yet
table.insert( repl, {'farming:soil_wet', 'farming:soil'});
table.insert( repl, {'farming:desert_sand_soil', 'farming:soil'});
table.insert( repl, {'farming:desert_sand_soil_wet','farming:soil'});
for i=1,5 do
table.insert( repl, {'default:grass_'..i,'air' });
end
table.insert( repl, {'default:apple', 'air' });
table.insert( repl, {'default:obsidian_glass', 'default:glass' });
else
table.insert( repl, {'oven:oven', 'default:furnace'});
table.insert( repl, {'grounds:clay', 'default:clay'});
table.insert( repl, {'farming:soil', 'farming:soil_wet'});
end
for i,v in ipairs( repl ) do
if( v and v[2] and minetest.registered_nodes[ v[2]] ) then
local found = false;
for j,w in ipairs( replacements ) do
if( w and w[1] and w[1]==v[1] ) then
w[2] = v[2];
found = true;
end
end
if( not( found )) then
table.insert( replacements, {v[1],v[2]} );
end
end
end
return replacements;
end

110
replacements_roof.lua Normal file
View File

@ -0,0 +1,110 @@
replacements_group['roof'] = {}
-- this contains a list of all found/available nodenames that may act as a replacement frming nodes
replacements_group['roof'].found = {};
-- contains a list of *all* known roof names - even of mods that may not be installed
replacements_group['roof'].all = {};
-- contains information about how a particular node is called if a particular roof mod is used;
replacements_group['roof'].data = {};
replacements_group['roof'].replace_material = function( replacements, old_material, new_material )
if( not( old_material ) or not( replacements_group['roof'].data[ old_material ])
or not( new_material ) or not( replacements_group['roof'].data[ new_material ])
or old_material == new_material ) then
return replacements;
end
local old_nodes = replacements_group['roof'].data[ old_material ];
local new_nodes = replacements_group['roof'].data[ new_material ];
for i=1,#old_nodes do
local old = old_nodes[i];
local new = old;
if( i<=#new_nodes and new_nodes[i] and minetest.registered_nodes[ new_nodes[i]] ) then
new = new_nodes[i];
local found = false;
for i,v in ipairs(replacements) do
if( v and v[1]==old ) then
v[2] = new;
found = true;
end
end
if( not( found )) then
table.insert( replacements, { old, new });
end
end
end
return replacements;
end
---------------------
-- internal functions
---------------------
replacements_group['roof'].add_material = function( nodelist )
local is_loaded = false;
if( minetest.registered_items[ nodelist[1] ] ) then
is_loaded = true;
table.insert( replacements_group['roof'].found, nodelist[1] );
end
table.insert( replacements_group['roof'].all, nodelist[1]);
replacements_group['roof'].data[ nodelist[1] ] = nodelist;
end
-- create a list of all available fruit types
replacements_group['roof'].construct_roof_type_list = function()
-- roof from cottages
local roofs = {'straw', 'reet', 'wood', 'slate', 'red', 'brown', 'black'};
for i,v in ipairs( roofs ) do
replacements_group['roof'].add_material( {
'cottages:roof_connector_'..v,
'cottages:roof_flat_'..v,
'', -- no full block available
'cottages:roof_'..v
} );
end
-- from dryplants
roofs = {'reed', 'wetreed'};
for i,v in ipairs( roofs ) do
replacements_group['roof'].add_material( {
'dryplants:'..v..'_roof',
'dryplants:'..v..'_slab',
'dryplants:'..v,
'dryplants:'..v..'_roof',
'dryplants:'..v..'_roof_corner',
'dryplants:'..v..'_roof_corner_2'
} );
end
-- roof from homedecor
roofs = {'wood', 'terracotta', 'asphalt', 'glass'};
for i,v in ipairs( roofs ) do
replacements_group['roof'].add_material( {
'homedecor:shingle_side_'..v,
'homedecor:shingles_'..v,
'',
'homedecor:shingles_'..v,
'homedecor:shingle_inner_corner_'..v,
'homedecor:shingle_outer_corner_'..v,
} );
end
replacements_group['roof'].data[ 'homedecor:shingle_side_glass' ][2] = 'homedecor:skylight';
replacements_group['roof'].data[ 'homedecor:shingle_side_glass' ][4] = 'homedecor:skylight';
replacements_group['roof'].data[ 'homedecor:shingle_side_asphalt'][3] = 'streets:asphalt';
-- TODO: slopes from technic or other slopes mods?
end
-- create the list of known roof fruits
replacements_group['roof'].construct_roof_type_list();

233
replacements_wood.lua Normal file
View File

@ -0,0 +1,233 @@
replacements_group['wood'] = {}
-- this contains a list of all found/available nodenames that may act as a replacement for default:wood
replacements_group['wood'].found = {};
-- contains a list of *all* known wood names - even of mods that may not be installed
replacements_group['wood'].all = {};
-- contains information about how a particular node is called if a particular wood is used;
replacements_group['wood'].data = {};
-- names of traders for the diffrent wood types
replacements_group['wood'].traders = {};
------------------------------------------------------------------------------
-- external function; call it in order to replace old_wood with new_wood;
-- other nodes (trees, saplings, fences, doors, ...) are replaced accordingly,
-- depending on what new_wood has to offer
------------------------------------------------------------------------------
replacements_group['wood'].replace_material = function( replacements, old_wood, new_wood )
if( not( old_wood ) or not( replacements_group['wood'].data[ old_wood ])
or not( new_wood ) or not( replacements_group['wood'].data[ new_wood ])
or old_wood == new_wood ) then
return replacements;
end
local old_nodes = replacements_group['wood'].data[ old_wood ];
local new_nodes = replacements_group['wood'].data[ new_wood ];
for i=3,#old_nodes do
local old = old_nodes[i];
local new = old;
if( i<=#new_nodes and new_nodes[i] and minetest.registered_nodes[ new_nodes[i]] ) then
new = new_nodes[i];
local found = false;
for i,v in ipairs(replacements) do
if( v and v[1]==old ) then
v[2] = new;
found = true;
end
end
if( not( found )) then
table.insert( replacements, { old, new });
end
end
end
return replacements;
end
---------------------
-- internal functions
---------------------
-- wood (and its corresponding tree trunk) is a very good candidate for replacement in most houses
-- helper function for replacements_group['wood'].get_wood_type_list
replacements_group['wood'].add_material = function( candidate_list, mod_prefix, w_pre, w_post, t_pre, t_post, l_pre, l_post,
s_pre, s_post, stair_pre, stair_post, slab_pre, slab_post,
fence_pre, fence_post, gate_pre, gate_post )
if( not( candidate_list )) then
return;
end
for _,v in ipairs( candidate_list ) do
local is_loaded = false;
local wood_name = mod_prefix..w_pre..v..w_post;
-- create a complete list of all possible wood names
table.insert( replacements_group['wood'].all, wood_name );
-- create a list of all *installed* wood types
if( minetest.registered_nodes[ wood_name ]) then
table.insert( replacements_group['wood'].found, wood_name );
is_loaded = true;
end
-- there is no check if the node names created here actually exist
local data = { v, -- 1. base name of the node
mod_prefix, -- 2. mod name
wood_name, -- 3. replacement for default:wood
mod_prefix..t_pre..v..t_post, -- 4. " " for default:tree
mod_prefix..l_pre..v..l_post, -- 5. " " for default:leaves
mod_prefix..s_pre..v..s_post, -- 6. " " for default:sapling
stair_pre..v..stair_post, -- 7. " " for stairs:stair_wood
slab_pre..v..slab_post, -- 8. " " for stairs:slab_wood
fence_pre..v..fence_post, -- 9. " " for default:fence_wood
gate_pre..v..gate_post..'_open', -- 10. " " for cottages:gate_open
gate_pre..v..gate_post..'_closed',-- 11. " " for cottages:gate_closed
};
-- normal wood does have a number of nodes which might get replaced by more specialized wood types
if( mod_prefix=='default:' and v=='' ) then
local w = 'wood';
data[10] = 'cottages:gate_open';
data[11] = 'cottages:gate_closed';
data[12] = 'default:ladder';
data[13] = 'doors:door_'..w..'_t_1';
data[14] = 'doors:door_'..w..'_t_2';
data[15] = 'doors:door_'..w..'_b_1';
data[16] = 'doors:door_'..w..'_b_2';
data[17] = 'default:bookshelf';
data[18] = 'default:chest';
data[19] = 'default:chest_locked';
data[20] = 'stairs:stair_'..w..'upside_down';
data[21] = 'stairs:slab_'..w..'upside_down';
data[22] = 'doors:trapdoor_open';
data[23] = 'doors:trapdoor';
-- realtest has some further replacements
elseif( mod_prefix=='trees:' and w_post=='_planks' and t_post=='_log' ) then
data[12] = 'trees:'..v..'_ladder';
data[13] = 'doors:door_'..v..'_t_1';
data[14] = 'doors:door_'..v..'_t_2';
data[15] = 'doors:door_'..v..'_b_1';
data[16] = 'doors:door_'..v..'_b_2';
data[17] = 'decorations:bookshelf_'..v;
data[18] = 'trees:'..v..'_chest';
data[19] = 'trees:'..v..'_chest_locked';
data[20] = 'trees:'..v..'_planks_stair_upside_down';
data[21] = 'trees:'..v..'_planks_slab_upside_down';
data[22] = 'hatches:'..v..'_hatch_opened_top';
data[23] = 'hatches:'..v..'_hatch_opened_bottom';
end
replacements_group['wood'].data[ wood_name ] = data;
-- none of the wood nodes counts as ground
if( mg_villages and mg_villages.node_is_ground ) then
for _,v in ipairs( data ) do
mg_villages.node_is_ground[ v ] = false;
end
end
if( is_loaded and mobf_trader and mobf_trader.add_trader ) then
-- TODO: check if all offered payments exist
local goods = {
{ data[3].." 4", "default:dirt 24", "default:cobble 24"},
{ data[4].." 4", "default:apple 2", "default:coal_lump 4"},
{ data[4].." 8", "default:pick_stone 1", "default:axe_stone 1"},
{ data[4].." 12", "default:cobble 80", "default:steel_ingot 1"},
{ data[4].." 36", "bucket:bucket_empty 1", "bucket:bucket_water 1"},
{ data[4].." 42", "default:axe_steel 1", "default:mese_crystal 4"},
{ data[6].." 1", "default:mese 10", "default:steel_ingot 48"},
-- leaves are a cheaper way of getting saplings
{ data[5].." 10", "default:cobble 1", "default:dirt 2"}
};
mobf_trader.add_trader( mobf_trader.npc_trader_prototype,
"Trader of "..( v or "unknown" ).." wood",
v.."_wood_v",
goods,
{ "lumberjack" },
""
);
replacements_group['wood'].traders[ wood_name ] = v..'_wood_v';
end
end
end
-- TODO: there are also upside-down variants sometimes
-- TODO: moreblocks - those may be installed and offer further replacements
-- create a list of all available wood types
replacements_group['wood'].construct_wood_type_list = function()
-- https://github.com/minetest/minetest_game
-- default tree and jungletree; no gates available
replacements_group['wood'].add_material( {'', 'jungle' }, 'default:', '','wood','', 'tree', '','leaves', '','sapling',
'stairs:stair_', 'wood', 'stairs:slab_', 'wood', 'default:fence_','wood', 'NONE', '' );
-- default:pine_needles instead of leaves; no gates available
replacements_group['wood'].add_material( {'pine' }, 'default:', '','wood','', 'tree', '','_needles','','_sapling',
'stairs:stair_', 'wood', 'stairs:slab_', 'wood', 'default:fence_','wood', 'NONE','' );
-- https://github.com/Novatux/mg
-- trees from nores mapgen
replacements_group['wood'].add_material( {'savanna', 'pine' },'mg:', '','wood','', 'tree', '','leaves', '','sapling',
'stairs:stair_','wood', 'stairs:slab_','wood', 'NONE','', 'NONE','');
-- https://github.com/VanessaE/moretrees
-- minus the jungletree (already in default)
local moretrees_treelist = {"beech","apple_tree","oak","sequoia","birch","palm","spruce","pine","willow","acacia","rubber_tree","fir" };
replacements_group['wood'].add_material( moretrees_treelist, 'moretrees:', '', '_planks', '','_trunk', '','_leaves','','_sapling',
'moretrees:stair_','_planks', 'moretrees:slab_','_planks', 'NONE','', 'NONE','');
-- https://github.com/tenplus1/ethereal
-- ethereal does not have a common naming convention for leaves
replacements_group['wood'].add_material( {'acacia','redwood'},'ethereal:', '','_wood', '','_trunk', '','_leaves', '','_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_','', 'ethereal:','gate');
-- frost has another sapling type...
replacements_group['wood'].add_material( {'frost'}, 'ethereal:', '','_wood', '','_tree', '','_leaves', '','_tree_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_','wood', 'ethereal:','woodgate' );
-- those tree types do not use typ_leaves, but typleaves instead...
replacements_group['wood'].add_material( {'yellow'}, 'ethereal:', '','_wood', '','_trunk', '','leaves', '','_tree_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_','wood', 'ethereal:','gate' );
-- banana has a diffrent fence type....
replacements_group['wood'].add_material( {'banana'}, 'ethereal:', '','_wood', '','_trunk', '','leaves', '','_tree_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_', '', 'ethereal:','gate' );
-- palm has another name for the sapling again...
replacements_group['wood'].add_material( {'palm'}, 'ethereal:', '','_wood', '','_trunk', '','leaves', '','_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_', '', 'ethereal:','gate' );
-- the leaves are called willow_twig here...
replacements_group['wood'].add_material( {'willow'}, 'ethereal:', '','_wood', '','_trunk', '','_twig', '','_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'ethereal:fence_', '', 'ethereal:','gate' );
-- mushroom has its own name; it works quite well as a wood replacement; the red cap is used as leaves
-- the stairs are also called slightly diffrently (end in _trunk instead of _wood)
replacements_group['wood'].add_material( {'mushroom'}, 'ethereal:', '','_pore', '','_trunk', '','', '','_sapling',
'stairs:stair_','_trunk', 'stairs:slab_','_trunk', 'ethereal:fence_', '', 'ethereal:','gate' );
-- https://github.com/VanessaE/realtest_game
local realtest_trees = {'ash','aspen','birch','maple','chestnut','pine','spruce'};
replacements_group['wood'].add_material( realtest_trees, 'trees:', '','_planks', '','_log', '','_leaves', '','_sapling',
'trees:','_planks_stair', 'trees:','_planks_slab', 'fences:','_fence', 'NONE','' );
-- https://github.com/Gael-de-Sailly/Forest
local forest_trees = {'oak','birch','willow','fir','mirabelle','cherry','plum','beech','ginkgo','lavender'};
replacements_group['wood'].add_material( forest_trees, 'forest:', '', '_wood', '','_tree', '','_leaves', '','_sapling',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'NONE','', 'NONE','' );
-- https://github.com/bas080/trees
replacements_group['wood'].add_material( {'mangrove','palm','conifer'},'trees:', 'wood_','', 'tree_','', 'leaves_','', 'sapling_','',
'stairs:stair_','_wood', 'stairs:slab_','_wood', 'NONE','', 'NONE','' );
-- https://github.com/PilzAdam/farming_plus
-- TODO: this does not come with its own wood... banana and cocoa trees (only leaves, sapling and fruit)
-- TODO: farming_plus:TREETYP_sapling farming_plus:TREETYP_leaves farming_plus:TREETYP
-- TODO: in general: add fruits as replacements for apples
end
-- actually construct the data structure once
replacements_group['wood'].construct_wood_type_list();

114
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

146
village_traders.lua Normal file
View File

@ -0,0 +1,146 @@
handle_schematics.choose_traders = function( village_type, building_type, replacements )
if( not( building_type ) or not( village_type )) then
return;
end
-- some jobs are obvious
if( building_type == 'mill' ) then
return { 'miller' };
elseif( building_type == 'bakery' ) then
return { 'baker' };
elseif( building_type == 'school' ) then
return { 'teacher' };
elseif( building_type == 'forge' ) then
local traders = {'blacksmith', 'bronzesmith' };
return { traders[ math.random(#traders)] };
elseif( building_type == 'shop' ) then
local traders = {'seeds','flowers','misc','default','ore', 'fruit trader', 'wood'};
return { traders[ math.random(#traders)] };
-- there are no traders for these jobs - they'd require specialized mobs
elseif( building_type == 'tower'
or building_type == 'church'
or building_type == 'secular'
or building_type == 'tavern' ) then
return {};
end
if( village_type == 'charachoal' ) then
return { 'charachoal' };
elseif( village_type == 'claytrader' ) then
return { 'clay' };
end
local res = {};
if( building_type == 'shed'
or building_type == 'farm_tiny'
or building_type == 'house'
or building_type == 'house_large'
or building_type=='hut') then
local traders = { 'stonemason', 'stoneminer', 'carpenter', 'toolmaker',
'doormaker', 'furnituremaker', 'stairmaker', 'cooper', 'wheelwright',
'saddler', 'roofer', 'iceman', 'potterer', 'bricklayer', 'dyemaker',
'dyemakerl', 'glassmaker' }
-- sheds and farms both contain craftmen
res = { traders[ math.random( #traders )] };
if( building_type == 'shed'
or building_type == 'house'
or building_type == 'house_large'
or building_type == 'hut' ) then
return res;
end
end
if( building_type == 'field'
or building_type == 'farm_full'
or building_type == 'farm_tiny' ) then
local fruit = 'farming:cotton';
if( 'farm_full' ) then
-- RealTest
fruit = 'farming:wheat';
if( replacements_group['farming'].traders[ 'farming:soy']) then
fruit_item = 'farming:soy';
end
if( minetest.get_modpath("mobf") ) then
local animal_trader = {'animal_cow', 'animal_sheep', 'animal_chicken', 'animal_exotic'};
res[1] = animal_trader[ math.random( #animal_trader )];
end
return { res[1], replacements_group['farming'].traders[ fruit_item ]};
elseif( #replacements_group['farming'].found > 0 ) then
-- get a random fruit to grow
fruit = replacements_group['farming'].found[ math.random( #replacements_group['farming'].found) ];
return { res[1], replacements_group['farming'].traders[ fruit_item ]};
else
return res;
end
end
if( building_type == 'pasture' and minetest.get_modpath("mobf")) then
local animal_trader = {'animal_cow', 'animal_sheep', 'animal_chicken', 'animal_exotic'};
return { animal_trader[ math.random( #animal_trader )] };
end
-- TODO: banana,cocoa,rubber from farming_plus?
-- TODO: sawmill
if( building_type == 'lumberjack' or village_type == 'lumberjack' ) then
-- TODO: limit this to single houses
if( replacements.table and replacements.table[ 'default:wood' ] ) then
return { replacements_group['wood'].traders[ replacements.table[ 'default:wood' ]] };
elseif( #replacements_group['wood'].traders > 0 ) then
return { replacements_group['wood'].traders[ math.random( #replacements_group['wood'].traders) ]};
else
return { 'common_wood'};
end
end
-- tent, chateau: places for living at; no special jobs associated
-- nore,taoki,medieval,lumberjack,logcabin,canadian,grasshut,tent: further village types
return res;
end
handle_schematics.choose_trader_pos = function(pos, minp, maxp, data, param2_data, a, extranodes, replacements, cid, extra_calls, building_nr_in_bpos, village_id, binfo_extra, road_node, traders)
local trader_pos = {};
-- determine spawn positions for the mobs
for i,tr in ipairs( traders ) do
local tries = 0;
local found = false;
local pt = {x=pos.x, y=pos.y, z=pos.z};
while( tries < 10 and not(found)) do
-- get a random position for the trader
pt.x = pos.x+math.random(pos.bsizex);
pt.z = pos.z+math.random(pos.bsizez);
-- check if it is inside the area contained in data
if (pt.x >= minp.x and pt.x <= maxp.x) and (pt.y >= minp.y and pt.y <= maxp.y) and (pt.z >= minp.z and pt.z <= maxp.z) then
while( pt.y < maxp.y
and (data[ a:index( pt.x, pt.y, pt.z)]~=cid.c_air
or data[ a:index( pt.x, pt.y+1, pt.z)]~=cid.c_air )) do
pt.y = pt.y + 1;
end
-- TODO: check if this position is really suitable? traders standing on the roof are a bit odd
found = true;
end
tries = tries+1;
-- check if this position has already been assigned to another trader
for j,t in ipairs( trader_pos ) do
if( t.x==pt.x and t.y==pt.y and t.z==pt.z ) then
found = false;
end
end
end
if( found ) then
table.insert( trader_pos, {x=pt.x, y=pt.y, z=pt.z, typ=tr, bpos_i = building_nr_in_bpos} );
end
end
return trader_pos;
end

138
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