-- works on the heightmap obtained in register.on_generated; -- returns all places where a flat area with dimensions lookfor_x_dim and -- lookfor_z_dim exists -- Returns: { places_x = {array}, places_z = {array}} -- with places_x: indices in heightmap where the previous -- lookfor_x_dim x lookfor_z_dim nodes are flat -- and places_z: indices in heightmap where the previous -- lookfor_z_dim x lookfor_x_dim nodes are flat -- minheight and maxheight determine weather places will be acceptable -- and returned; use it to i.e. get no places under water -- allow_floating ships and submarines do not really require the -- ground below them to be flat; shipwrecks may sink -- partly into the ground; other sunken structures -- may need this to be set to false handle_schematics.find_flat_land_get_candidates_fast = function( heightmap, minp, maxp, lookfor_x_dim, lookfor_z_dim, minheight, maxheight, allow_floating ) -- return empty result if search is invalid if( lookfor_x_dim < 1 or lookfor_z_dim < 1 -- we can't handle more than one mapchunk at a time this way or lookfor_x_dim > (maxp.x - minp.x - 2) or lookfor_z_dim > (maxp.z - minp.z - 2)) then return {}; end -- the return value; will contain the indices (of heightmap) where the -- searched for flat space exists local places_x = {}; -- lookfor_x_dim x lookfor_z_dim is flat local places_z = {}; -- lookfor_z_dim x lookfor_x_dim is flat -- the last zrun[ ax ] blocks in this column all had the same height local zrun = {}; local chunksize = maxp.x-minp.x+1; -- the last count blocks in this row had the same height local count=1; -- how many zrun[ ax ] values (columns) had the right height value -- up until now? local check_before_x = 0; local check_before_z = 0; -- last height in x direction local lastheight = -1; local i = 0; local ax = 0; local az = 0; -- identify and mark places that are flat areas of the required size for az=minp.z,maxp.z do for ax=minp.x,maxp.x do i = i+1; local height = heightmap[ i ]; -- fallback if no height is provided if( not(height)) then height = 0; end -- water just has to be deep enough if(( height==lastheight and ax>minp.x) or (allow_floating and height<0 and height<=maxheight and lastheight<=maxheight and ax>minp.x)) then count = count+1; else count = 1; -- new height - start new before-check check_before_x = 0; check_before_z = 0; end lastheight = height; -- count in z direction as well local height2 = heightmap[ i-chunksize ]; if( not(height2)) then height2 = 0; end -- it is enough to remember the last row in zrun if(( height==height2 and az>minp.z) or (allow_floating and height<0 and height<=maxheight and height2<=maxheight and az>minp.z)) then zrun[ ax ] = zrun[ ax ]+1; else zrun[ ax ] = 1; end -- the candidates before this one have to have enough space -- as well if( zrun[ ax ] >= lookfor_z_dim ) then check_before_z = check_before_z + 1; else check_before_z = 1; end if( zrun[ ax ] >= lookfor_x_dim ) then check_before_x = check_before_x + 1; else check_before_x = 1; end if( count >= lookfor_x_dim and check_before_z >= lookfor_x_dim and height >= minheight and height <= maxheight and height < maxp.y and height > minp.y) then table.insert( places_x, i ); end -- the place might fit if the building is rotated by 90 degree; -- a place be suitable for both rotations if( count >= lookfor_z_dim and check_before_x >= lookfor_z_dim and height >= minheight and height <= maxheight and height < maxp.y and height > minp.y) then table.insert( places_z, i ); end end end return {places_x=places_x, places_z=places_z}; end -- return just one place suitable for building something of size sizex x sizez -- at a height no less than minheight and no more than maxheight; -- find_flat_land_get_candidates_fast is used to find all places suitable for -- that in this mapchunk. This function here selects a random one of those -- places and returns a suitable rotation. -- margin_front, margin_back, margin_right and margin_left are added as it does -- not look well if there is i.e. just one free space in front of the front -- door handle_schematics.find_flat_land_for_building_with_border = function( heightmap, minp, maxp, sizex, sizez, minheight, maxheight, margin_front, margin_back, margin_right, margin_left, initial_rotation, allow_floating ) -- handle initial rotation (orients) if our building is rotated by 90 or 270 degree if( initial_rotation and initial_rotation%2==1 ) then local tmp = sizex; sizex = sizez; sizez = tmp; tmp = nil; end local sizex_full = sizex + margin_front + margin_back - 1; local sizez_full = sizez + margin_right + margin_left - 1; -- find candidates local res = handle_schematics.find_flat_land_get_candidates_fast( heightmap, minp, maxp, sizex_full, sizez_full, minheight, maxheight, allow_floating ); --print("Found normal: "..tostring( #res.places_x ).." and rotated: "..tostring( #res.places_z )); -- nothing suitable found? then abort if( (#res.places_x + #res.places_z )< 1 ) then return; end -- select a random place - either sizex x sizez or sizez x sizex local c = math.random( 1, #res.places_x + #res.places_z ); local i = 1; local rotation = 0; -- if the place is part of res.places_z... if( c > #res.places_x ) then i = res.places_z[ c-#res.places_x ]; -- swap x and z due to rotation of 90 or 270 degree local tmp = sizex; sizex = sizez; sizez = tmp; -- swap sizex_full and sizez_full as well (those include the margins) tmp = sizex_full; sizex_full = sizez_full; sizez_full = tmp; tmp = nil; if( math.random(1,2)==1 ) then rotation = "270"; else rotation = "90"; end -- or part of res.places_x... else i = res.places_x[ c ]; if( math.random(1,2)==1 ) then rotation = "0"; else rotation = "180"; end end local chunksize = maxp.x - minp.x + 1; -- translate index back into coordinates local p = {x=minp.x+(i%chunksize)-1, y=heightmap[ i ], z=minp.z+math.floor(i/chunksize)}; -- the index in the heightmap p.i = i; -- p.plot_start and p.plot_end mark the total area of the entire plot, -- including building and borders; perhaps this will be of intrest -- later on (i.e. for clearing the place in margin_front from trees -- etc); p.plot.p1 marks the begining of the plot p.plot_end = { x = p.x, y = p.y, z = p.z }; p.plot_start = { x = p.x - sizex_full, y = p.y, z = p.z - sizez_full }; p.plot_rotation = rotation; -- the actual start and end coordinates for the building; -- y is always on the same plane; x and z need to be calculated p.build_end = { y = p.y }; p.build_start = { y = p.y }; -- the building may have been stored in a rotated way in the schematic -- file; we need to adjust the rotation when placing it p.build_rotation = tostring((p.plot_rotation + (initial_rotation*90))%360); -- determine x and z if( p.plot_rotation=="270") then p.build_start.z = p.plot_start.z + margin_front; p.build_start.x = p.plot_start.x + margin_right; elseif( p.plot_rotation== "90") then p.build_start.z = p.plot_start.z + margin_back; p.build_start.x = p.plot_start.x + margin_right; elseif( p.plot_rotation== "0") then p.build_start.x = p.plot_start.x + margin_front; p.build_start.z = p.plot_start.z + margin_right; elseif( p.plot_rotation=="180") then p.build_start.x = p.plot_start.x + margin_back; p.build_start.z = p.plot_start.z + margin_left; else -- error: wrong rotation return; end p.build_end.z = p.build_start.z + sizez - 1; p.build_end.x = p.build_start.x + sizex - 1; return p; end -- Places a schematic read from filename on flat land found somewhere in the -- current mapchunk (determined by minp, maxp). Rotation and replacements -- will be random. -- -- Note: There is no guarantee that a place can be found. If no sufficiently -- large flat space is found, the schematic will NOT be placed. -- The larger the schematic (including margins) is, the less likely it -- will be that such a place can be found (unless you use flat mapgen). -- -- Parameter: -- * heghtmap heightmap of current mapchunk; can be obtained through -- minetest.get_mapgen_object('heightmap') -- * minp, maxp minimal and maximal coordinates of current mapchunk; -- can be obtained in register_on_generated -- * sizex, sizez length of the schematic in x (resp. z) direction -- * minheight schematic will only be placed on flat land with at least a -- height of minheigt; negative minheigt means: under water -- * maxheight schematic will not be placed at higher locations than this -- * margin_front added in front of the schematic so that there will be room -- to enter and exit the house; otherwise buildings may look -- strange (few people want a mountain or deep drop right in -- of their main door) -- * margin_back, margin_right, margin_left further margins used to let the -- house stand out more; -- * filename full path to the schematic including .mts extension -- * replacements will be filled randomly, but can also be provided -- * yoffset some schematics may have basements -- * initial_rotation some schematics need to be rotated first so that their -- front shows up at the front. Allowed values: 0,1,2 or 3 -- Returns information about where and how the building was placed. -- TODO: pass yoffset on to the search-for-place-function as well? -- TODO: read a larger voxelmanip area for tree modifications? handle_schematics.place_schematic_on_flat_land = function( heightmap, minp, maxp, sizex, sizez, minheight, maxheight, margin_front, margin_back, margin_right, margin_left, filename, replacements, yoffset, initial_rotation, binfo, allow_floating ) -- find a flat area of the required size local p = handle_schematics.find_flat_land_for_building_with_border( heightmap, minp, maxp, sizex, sizez, minheight, maxheight, margin_front, margin_back, margin_right, margin_left, initial_rotation, allow_floating ); if( not( p )) then return; end if( not( binfo.yoff )) then binfo.yoff = 0; end -- ships tend to swim on water if( binfo.is_ship ) then -- TODO: get water level p.y = 1; -- the ship starts below the water surface p.build_start.y = 1 + binfo.yoff; p.build_end.y = 1; p.plot_start.y = 1 + binfo.yoff; p.plot_end.y = 1; end -- shipwrecks need to rest on the ground (more or less); we can also ignore -- the border around them if( binfo.is_submerged ) then -- first step: search for all nodes at the bottom of the schematic -- where the shipwreck connects to the ground - and see how deep -- we have to go so that each of these nodes rests on a ground node local chunksize = maxp.x - minp.x + 1; local target_height = 1; for x=1, binfo.sizex do for z=1, binfo.sizez do if( binfo.scm_data_cache[1][x][z] and binfo.scm_data_cache[1][x][z][1]~=c_air) then local nx, nz; if( p.build_rotation=="0" ) then nx = p.build_start.x+x-1; nz = p.build_start.z+z-1; elseif( p.build_rotation=="90") then nx = p.build_start.x+z-1; nz = p.build_end.z-x+1; elseif( p.build_rotation=="180") then nx = p.build_end.x-x+1; nz = p.build_end.z-z+1; else nx = p.build_end.x-z+1; nz = p.build_start.z+x-1; end local h = heightmap[ (nz-minp.z)*chunksize + (nx-minp.x)]; if( h and h>minp.y and h 0 ) then local tree_node = minetest.get_node( tree_trunks[1]); if( tree_node and tree_node.name ) then for k, wood_data in pairs( replacements_group['wood'].data) do if( wood_data and wood_data[4]==tree_node.name ) then -- take this wood type materials["wood"] = k; end end end end -- choose random replacements (apart from wood if that is already set) replacements = handle_schematics.replace_randomized( replacements, materials ); local replacements_table = handle_schematics.get_replacement_table( nil, nil, replacements ); local cid = handle_schematics.get_cid_table( replacements_table ); -- clear area above ground for buildigns placed there; do not do this -- for shipwrecks and the like if( not( binfo.is_submerged )) then -- remove trees, leaves, snow etc. for ax=p.plot_start.x, p.plot_end.x do for az=p.plot_start.z, p.plot_end.z do for ay=p.y+2, maxp.y do data[ a:index( ax, ay, az )] = cid.c_air; end end end end --[[ -- place 3 mese lamps so that buildings can be found easier while debugging local cid_meselamp = minetest.get_content_id( "default:meselamp" ); for dy = p.y + 6, p.y + 26 do data[ a:index( p.x,dy,p.z )] = cid_meselamp; end local nodename="wool:white;" if( p.plot_rotation=="270") then nodename = "wool:cyan"; elseif( p.plot_rotation== "0") then nodename = "wool:yellow"; elseif( p.plot_rotation== "90") then nodename = "wool:orange"; elseif( p.plot_rotation=="180") then nodename = "wool:blue"; end local cid_wool = minetest.get_content_id( nodename ); -- mark the area for dx = p.plot_start.x, p.plot_end.x do data[ a:index( dx,p.y,p.plot_start.z )] = cid_wool; data[ a:index( dx,p.y,p.plot_end.z )] = cid_wool; end for dz = p.plot_start.z, p.plot_end.z do data[ a:index( p.plot_start.x,p.y,dz )] = cid_wool; data[ a:index( p.plot_end.x, p.y,dz )] = cid_wool; end --]] local extranodes = {} local extra_calls = { on_constr = {}, trees = {}, chests = {}, signs = {}, traders = {}, door_a = {}, door_b = {}, scaffolding = {}, clear_meta = {}, beds = {}, workplaces = {} }; local start_pos = {x=p.build_start.x, y=p.y, z=p.build_start.z, scm = filename, brotate = p.build_rotation/90, no_plotmarker = true; }; start_pos.bsizex = math.abs(p.build_end.x - start_pos.x)+1; start_pos.bsizez = math.abs(p.build_end.z - start_pos.z)+1; p.start_pos = start_pos; -- last parameter false -> place dirt nodes instead of trying to keep the ground nodes local missing_nodes = handle_schematics.generate_building(start_pos, minp2, maxp2, data, param2_data, a, extranodes, replacements_table, cid, extra_calls, start_pos.building_nr, start_pos.village_id, binfo, cid.c_gravel, keep_ground, scaffolding_only); -- 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(); -- do the necessary on_construct calls that mostly affect metadata handle_schematics.call_on_construct( extra_calls.on_constr ); -- set up doors properly (to whatever minetest_game currently demands) handle_schematics.call_door_setup( extra_calls.door_b ); -- players expect chests to be filled with something for them if( not( binfo.typ )) then binfo.typ = "unkown"; end handle_schematics.fill_chests( extra_calls.chests, nil, binfo.typ); return p; end -- mark the place where a structure has been spawned as unusable (occupied) -- by setting it to a height of maxp.y+1 handle_schematics.mark_flat_land_as_used = function(heightmap, minp, maxp, i, sizex, sizez) local offset = maxp.x - minp.x - sizex + 1; for dz = 1, sizez do for dx = 1, sizex do heightmap[ i ] = maxp.y+1; i = i-1; end i = i - offset; end return heightmap; end