diff --git a/mods/MAPGEN/mcl_villages/buildings.lua b/mods/MAPGEN/mcl_villages/buildings.lua index 1b8ce3cba..e2e9a5057 100644 --- a/mods/MAPGEN/mcl_villages/buildings.lua +++ b/mods/MAPGEN/mcl_villages/buildings.lua @@ -2,7 +2,7 @@ ------------------------------------------------------------------------------- -- build schematic, replace material, rotation ------------------------------------------------------------------------------- -function settlements.build_schematic(vm, data, va, pos, building, replace_wall, name) +function mcl_villages.build_schematic(vm, data, va, pos, building, replace_wall, name) -- get building node material for better integration to surrounding local platform_material = mcl_vars.get_node(pos) if not platform_material or (platform_material.name == "air" or platform_material.name == "ignore") then @@ -36,7 +36,7 @@ function settlements.build_schematic(vm, data, va, pos, building, replace_wall, local height = schematic["size"]["y"] local possible_rotations = {"0", "90", "180", "270"} local rotation = possible_rotations[ math.random( #possible_rotations ) ] - settlements.foundation( + mcl_villages.foundation( pos, width, depth, @@ -57,123 +57,125 @@ end]] ------------------------------------------------------------------------------- -- initialize settlement_info ------------------------------------------------------------------------------- -function settlements.initialize_settlement_info(pr) +function mcl_villages.initialize_settlement_info(pr) local count_buildings = {} -- count_buildings table reset - for k,v in pairs(settlements.schematic_table) do + for k,v in pairs(mcl_villages.schematic_table) do count_buildings[v["name"]] = 0 end -- randomize number of buildings local number_of_buildings = pr:next(10, 25) - local number_built = 1 - settlements.debug("Village ".. number_of_buildings) + local number_built = 0 + mcl_villages.debug("Village ".. number_of_buildings) return count_buildings, number_of_buildings, number_built end + +------------------------------------------------------------------------------- +-- check ground for a single building +------------------------------------------------------------------------------- +local function try_place_building(pos_surface, building_all_info, rotation, settlement_info, pr) + local fwidth, fdepth = building_all_info["hwidth"], building_all_info["hdepth"] + if rotation == "90" or rotation == "270" then fwidth, fdepth = fdepth, fwidth end + local fheight = building_all_info["hheight"] + -- use building centers for better placement + pos_surface.x = pos_surface.x - math.ceil(fwidth / 2) + pos_surface.z = pos_surface.z - math.ceil(fdepth / 2) + -- to find the y position, also check the corners + local ys = {pos_surface.y} + local pos_c + pos_c = mcl_villages.find_surface_down(vector.new(pos_surface.x-1, pos_surface.y+fheight, pos_surface.z-1)) + if pos_c then table.insert(ys, pos_c.y) end + pos_c = mcl_villages.find_surface_down(vector.new(pos_surface.x+fwidth+2, pos_surface.y+fheight, pos_surface.z-1)) + if pos_c then table.insert(ys, pos_c.y) end + pos_c = mcl_villages.find_surface_down(vector.new(pos_surface.x-1, pos_surface.y+fheight, pos_surface.z+fdepth+2)) + if pos_c then table.insert(ys, pos_c.y) end + pos_c = mcl_villages.find_surface_down(vector.new(pos_surface.x+fwidth+2, pos_surface.y+fheight, pos_surface.z+fdepth+2)) + if pos_c then table.insert(ys, pos_c.y) end + table.sort(ys) + -- well supported base, not too uneven? + if #ys < 5 or ys[#ys]-ys[1] > fheight + 3 then return nil end + pos_surface.y = ys[math.ceil(#ys/2)] + -- check distance to other buildings + if mcl_villages.check_distance(settlement_info, pos_surface, building_all_info["hsize"]) then + return pos_surface + end + return nil +end ------------------------------------------------------------------------------- -- fill settlement_info -------------------------------------------------------------------------------- -function settlements.create_site_plan(maxp, minp, pr) - local settlement_info = {} - local building_all_info +function mcl_villages.create_site_plan(minp, maxp, pr) + local center = vector.new(math.floor((minp.x+maxp.x)/2),maxp.y,math.floor((minp.z+maxp.z)/2)) + minetest.log("action", "sudo make me a village at: " .. minetest.pos_to_string(center)) local possible_rotations = {"0", "90", "180", "270"} + local center_surface - -- find center of chunk - local center = { - x=math.floor((minp.x+maxp.x)/2), - y=maxp.y, - z=math.floor((minp.z+maxp.z)/2) - } - - -- find center_surface of chunk - local center_surface , surface_material = settlements.find_surface(center, true) - local chunks = {} - chunks[mcl_vars.get_chunk_number(center)] = true - - -- go build settlement around center - if not center_surface then - minetest.log("action", "Cannot build village at: " .. minetest.pos_to_string(center)) - return false - else - minetest.log("action", "Village built.") - --minetest.log("action", "Build village at: " .. minetest.pos_to_string(center) .. " with surface material: " .. surface_material) - end - - -- initialize all settlement_info table - local count_buildings, number_of_buildings, number_built = settlements.initialize_settlement_info(pr) - -- first building is townhall in the center - building_all_info = settlements.schematic_table[1] - local rotation = possible_rotations[ pr:next(1, #possible_rotations ) ] - -- add to settlement info table - local index = 1 - settlement_info[index] = { - pos = center_surface, - name = building_all_info["name"], - hsize = building_all_info["hsize"], - rotat = rotation, - surface_mat = surface_material - } - --increase index for following buildings - index = index + 1 + local count_buildings, number_of_buildings, number_built = mcl_villages.initialize_settlement_info(pr) + local settlement_info = {} -- now some buildings around in a circle, radius = size of town center - local x, z, r = center_surface.x, center_surface.z, building_all_info["hsize"] + local x, y, z, r = center.x, maxp.y, center.z, 0 -- draw j circles around center and increase radius by math.random(2,5) - for j = 1,20 do - -- set position on imaginary circle - for j = 0, 360, 15 do - local angle = j * math.pi / 180 - local ptx, ptz = x + r * math.cos( angle ), z + r * math.sin( angle ) - ptx = settlements.round(ptx, 0) - ptz = settlements.round(ptz, 0) - local pos1 = { x=ptx, y=center_surface.y+50, z=ptz} - local chunk_number = mcl_vars.get_chunk_number(pos1) - local pos_surface, surface_material - if chunks[chunk_number] then - pos_surface, surface_material = settlements.find_surface(pos1) - else - chunks[chunk_number] = true - pos_surface, surface_material = settlements.find_surface(pos1, true) - end - if not pos_surface then break end + for j = 1,10 do + for angle = 0, math.pi*2, 0.262 do -- 24 attempts on a circle + local pos1 = vector.new(math.floor(x + r * math.cos(angle) + 0.5), y, math.floor(z - r * math.sin(angle) + 0.5)) + local pos_surface, surface_material = mcl_villages.find_surface(pos1, false) + if pos_surface then + local randomized_schematic_table = mcl_villages.shuffle(mcl_villages.schematic_table, pr) + if #settlement_info == 0 then randomized_schematic_table = { mcl_villages.schematic_table[1] } end -- place town bell first + -- pick schematic + local size = #randomized_schematic_table + for i = 1, #randomized_schematic_table do + local building_all_info = randomized_schematic_table[i] + -- already enough buildings of that type? + if count_buildings[building_all_info["name"]] < building_all_info["max_num"]*number_of_buildings then + local rotation = possible_rotations[pr:next(1, #possible_rotations)] + local pos = try_place_building(pos_surface, building_all_info, rotation, settlement_info, pr) + if pos then + if #settlement_info == 0 then -- town bell + center_surface, y = pos, pos.y + max_height_difference + end + -- limit height differences to town center + if math.abs(pos.y - center_surface.y) > max_height_difference * 0.7 then + break -- other buildings likely will not fit either + end + count_buildings[building_all_info["name"]] = count_buildings[building_all_info["name"]] +1 + number_built = number_built + 1 - local randomized_schematic_table = shuffle(settlements.schematic_table, pr) - -- pick schematic - local size = #randomized_schematic_table - for i = size, 1, -1 do - -- already enough buildings of that type? - if count_buildings[randomized_schematic_table[i]["name"]] < randomized_schematic_table[i]["max_num"]*number_of_buildings then - building_all_info = randomized_schematic_table[i] - -- check distance to other buildings - local distance_to_other_buildings_ok = settlements.check_distance(settlement_info, pos_surface, building_all_info["hsize"]) - if distance_to_other_buildings_ok then - -- count built houses - count_buildings[building_all_info["name"]] = count_buildings[building_all_info["name"]] +1 - rotation = possible_rotations[ pr:next(1, #possible_rotations ) ] - number_built = number_built + 1 - settlement_info[index] = { - pos = pos_surface, - name = building_all_info["name"], - hsize = building_all_info["hsize"], - rotat = rotation, - surface_mat = surface_material - } - index = index + 1 - break + pos.y = pos.y + (building_all_info["yadjust"] or 0) + table.insert(settlement_info, { + pos = pos, + name = building_all_info["name"], + hsize = building_all_info["hsize"], + rotat = rotation, + surface_mat = surface_material + }) + -- minetest.log("action", "Placing "..building_all_info["name"].." at "..minetest.pos_to_string(pos)) + break + end end end + if number_of_buildings == number_built then + break + end end - if number_of_buildings == number_built then - break - end + if r == 0 then break end -- no angles in the very center end if number_built >= number_of_buildings then break end r = r + pr:next(2,5) + if r > 35 then break end -- avoid touching neighboring blocks end - settlements.debug("really ".. number_built) + mcl_villages.debug("really ".. number_built) + if number_built <= 8 then + minetest.log("action", "Bad village location, could only place "..number_built.." buildings.") + return + end + minetest.log("action", "Village completed at " .. minetest.pos_to_string(center)) + minetest.log("Village completed at " .. minetest.pos_to_string(center)) -- for debugging only return settlement_info end ------------------------------------------------------------------------------- @@ -201,6 +203,7 @@ local function spawn_iron_golem(pos) --minetest.log("action", "Attempt to spawn iron golem.") local p = minetest.find_node_near(pos,50,"mcl_core:grass_path") if p then + p.y = p.y + 1 local l=minetest.add_entity(p,"mobs_mc:iron_golem"):get_luaentity() if l then l._home = p @@ -245,100 +248,63 @@ local function init_nodes(p1, p2, size, rotation, pr) if nodes and #nodes > 0 then for p=1, #nodes do local pos = nodes[p] - settlements.fill_chest(pos, pr) + mcl_villages.fill_chest(pos, pr) end end end -function settlements.place_schematics(settlement_info, pr) +function mcl_villages.place_schematics(settlement_info, pr) local building_all_info + local lvm = VoxelManip() for i, built_house in ipairs(settlement_info) do local is_last = i == #settlement_info - for j, schem in ipairs(settlements.schematic_table) do + for j, schem in ipairs(mcl_villages.schematic_table) do if settlement_info[i]["name"] == schem["name"] then building_all_info = schem break end end - - - local pos = settlement_info[i]["pos"] local rotation = settlement_info[i]["rotat"] -- get building node material for better integration to surrounding - local platform_material = settlement_info[i]["surface_mat"] - --platform_material_name = minetest.get_name_from_content_id(platform_material) - -- pick random material - --local material = wallmaterial[pr:next(1,#wallmaterial)] - -- - local building = building_all_info["mts"] - local replace_wall = building_all_info["rplc"] - -- schematic conversion to lua - local schem_lua = minetest.serialize_schematic(building, - "lua", - {lua_use_comments = false, lua_num_indent_spaces = 0}).." return schematic" - schem_lua = schem_lua:gsub("mcl_core:stonebrickcarved", "mcl_villages:stonebrickcarved") - -- replace material - if replace_wall then - --Note, block substitution isn't matching node names exactly; so nodes that are to be substituted that have the same prefixes cause bugs. - -- Example: Attempting to swap out 'mcl_core:stonebrick'; which has multiple, additional sub-variants: (carved, cracked, mossy). Will currently cause issues, so leaving disabled. - if platform_material == "mcl_core:snow" or platform_material == "mcl_core:dirt_with_grass_snow" or platform_material == "mcl_core:podzol" then - schem_lua = schem_lua:gsub("mcl_core:tree", "mcl_core:sprucetree") - schem_lua = schem_lua:gsub("mcl_core:wood", "mcl_core:sprucewood") - --schem_lua = schem_lua:gsub("mcl_fences:fence", "mcl_fences:spruce_fence") - --schem_lua = schem_lua:gsub("mcl_stairs:slab_wood_top", "mcl_stairs:slab_sprucewood_top") - --schem_lua = schem_lua:gsub("mcl_stairs:stair_wood", "mcl_stairs:stair_sprucewood") - --schem_lua = schem_lua:gsub("mesecons_pressureplates:pressure_plate_wood_off", "mesecons_pressureplates:pressure_plate_sprucewood_off") - elseif platform_material == "mcl_core:sand" or platform_material == "mcl_core:redsand" then - schem_lua = schem_lua:gsub("mcl_core:tree", "mcl_core:sandstonecarved") - schem_lua = schem_lua:gsub("mcl_core:cobble", "mcl_core:sandstone") - schem_lua = schem_lua:gsub("mcl_core:wood", "mcl_core:sandstonesmooth") - --schem_lua = schem_lua:gsub("mcl_fences:fence", "mcl_fences:birch_fence") - --schem_lua = schem_lua:gsub("mcl_stairs:slab_wood_top", "mcl_stairs:slab_birchwood_top") - --schem_lua = schem_lua:gsub("mcl_stairs:stair_wood", "mcl_stairs:stair_birchwood") - --schem_lua = schem_lua:gsub("mesecons_pressureplates:pressure_plate_wood_off", "mesecons_pressureplates:pressure_plate_birchwood_off") - --schem_lua = schem_lua:gsub("mcl_stairs:stair_stonebrick", "mcl_stairs:stair_redsandstone") - --schem_lua = schem_lua:gsub("mcl_core:stonebrick", "mcl_core:redsandstonesmooth") - schem_lua = schem_lua:gsub("mcl_core:brick_block", "mcl_core:redsandstone") - end + local surface_material = settlement_info[i]["surface_mat"] or "mcl_core:stone" + local platform_material = surface_material + local schem_lua = building_all_info["schem_lua"] + if not schem_lua then + schem_lua = minetest.serialize_schematic(building_all_info["mts"], "lua", { lua_use_comments = false, lua_num_indent_spaces = 0 }) .. " return schematic" + building_all_info["schem_lua"] = schem_lua end - schem_lua = schem_lua:gsub("mcl_core:dirt_with_grass", platform_material) - - --[[ Disable special junglewood for now. - -- special material for spawning npcs - schem_lua = schem_lua:gsub("mcl_core:junglewood", "settlements:junglewood") - --]] - - schem_lua = schem_lua:gsub("mcl_stairs:stair_wood_outer", "mcl_stairs:slab_wood") - schem_lua = schem_lua:gsub("mcl_stairs:stair_stone_rough_outer", "air") - - -- format schematic string - local schematic = loadstring(schem_lua)() + schem_lua = schem_lua:gsub('"mcl_core:dirt"', '"'..platform_material..'"') + schem_lua = schem_lua:gsub('"mcl_core:dirt_with_grass"', '"'..surface_material..'"') + local schematic = loadstring(mcl_villages.substitute_materials(pos, schem_lua, pr))() local is_belltower = building_all_info["name"] == "belltower" - -- build foundation for the building an make room above - - mcl_structures.place_schematic( + -- already built the foundation for the building and made room above + local sx, sy, sz = schematic.size.x, schematic.size.y, schematic.size.z + local p2 = vector.new(pos.x+sx-1,pos.y+sy-1,pos.z+sz-1) + lvm:read_from_map(pos, p2) + minetest.place_schematic_on_vmanip( + lvm, pos, schematic, rotation, nil, true, - nil, - function(p1, p2, size, rotation, pr) - if is_belltower then - spawn_iron_golem(p1) - else - init_nodes(p1, p2, size, rotation, pr) - spawn_villagers(p1,p2) - fix_village_water(p1,p2) - end - end, - pr + { place_center_x = false, place_center_y = false, place_center_z = false } ) + lvm:write_to_map(true) -- FIXME: postpone + if rotation == "90" or rotation == "270" then sx, sz = sz, sx end + init_nodes(pos, p2, schematic.size, rotation, pr) + + if is_belltower then + spawn_iron_golem(pos) + else + spawn_villagers(pos,p2) + fix_village_water(pos,p2) + end end end diff --git a/mods/MAPGEN/mcl_villages/const.lua b/mods/MAPGEN/mcl_villages/const.lua index feff6a1d2..30ee9508f 100644 --- a/mods/MAPGEN/mcl_villages/const.lua +++ b/mods/MAPGEN/mcl_villages/const.lua @@ -1,5 +1,5 @@ -- switch for debugging -function settlements.debug(message) +function mcl_villages.debug(message) -- minetest.chat_send_all(message) -- minetest.log("warning", "[mcl_villages] "..message) minetest.log("verbose", "[mcl_villages] "..message) @@ -20,44 +20,51 @@ local wallmaterial = { "mcl_core:sandstonesmooth2" } --]] -settlements.surface_mat = {} -------------------------------------------------------------------------------- --- Set array to list --- https://stackoverflow.com/questions/656199/search-for-an-item-in-a-lua-list -------------------------------------------------------------------------------- -function settlements.grundstellungen() - settlements.surface_mat = settlements.Set { - "mcl_core:dirt_with_grass", - --"mcl_core:dry_dirt_with_grass", - "mcl_core:dirt_with_grass_snow", - --"mcl_core:dirt_with_dry_grass", - "mcl_core:podzol", - "mcl_core:sand", - "mcl_core:redsand", - --"mcl_core:silver_sand", - "mcl_core:snow" - } -end -- -- possible surfaces where buildings can be built -- +mcl_villages.surface_mat = {} +mcl_villages.surface_mat["mcl_core:andesite"] = true +mcl_villages.surface_mat["mcl_core:diorite"] = true +mcl_villages.surface_mat["mcl_core:dirt"] = true +mcl_villages.surface_mat["mcl_core:dirt_with_grass"] = true +--mcl_villages.surface_mat["mcl_core:dirt_with_dry_grass"] = true +mcl_villages.surface_mat["mcl_core:dirt_with_grass_snow"] = true +--mcl_villages.surface_mat["mcl_core:dry_dirt_with_grass"] = true +mcl_villages.surface_mat["mcl_core:grass_path"] = true +mcl_villages.surface_mat["mcl_core:granite"] = true +mcl_villages.surface_mat["mcl_core:podzol"] = true +mcl_villages.surface_mat["mcl_core:redsand"] = true +mcl_villages.surface_mat["mcl_core:sand"] = true +mcl_villages.surface_mat["mcl_core:sandstone"] = true +mcl_villages.surface_mat["mcl_core:sandstonesmooth"] = true +mcl_villages.surface_mat["mcl_core:sandstonesmooth2"] = true +--mcl_villages.surface_mat["mcl_core:silver_sand"] = true +mcl_villages.surface_mat["mcl_core:snow"] = true +mcl_villages.surface_mat["mcl_core:stone"] = true +mcl_villages.surface_mat["mcl_core:stone_with_coal"] = true +mcl_villages.surface_mat["mcl_core:stone_with_iron"] = true +mcl_villages.surface_mat["mcl_colorblocks:hardened_clay"] = true +mcl_villages.surface_mat["mcl_colorblocks:hardened_clay_orange"] = true +mcl_villages.surface_mat["mcl_colorblocks:hardened_clay_red"] = true +mcl_villages.surface_mat["mcl_colorblocks:hardened_clay_white"] = true -- -- path to schematics -- -schem_path = settlements.modpath.."/schematics/" +schem_path = mcl_villages.modpath.."/schematics/" -- -- list of schematics -- local basic_pseudobiome_villages = minetest.settings:get_bool("basic_pseudobiome_villages", true) -settlements.schematic_table = { - {name = "belltower", mts = schem_path.."belltower.mts", hwidth = 5, hdepth = 5, hheight = 9, hsize = 14, max_num = 0 , rplc = basic_pseudobiome_villages }, +mcl_villages.schematic_table = { + {name = "belltower", mts = schem_path.."belltower.mts", hwidth = 5, hdepth = 5, hheight = 9, hsize = 14, max_num = 0.0001 , rplc = basic_pseudobiome_villages, yadjust = 1 }, {name = "large_house", mts = schem_path.."large_house.mts", hwidth = 12, hdepth = 12, hheight = 9, hsize = 14, max_num = 0.08 , rplc = basic_pseudobiome_villages }, - {name = "blacksmith", mts = schem_path.."blacksmith.mts", hwidth = 8, hdepth = 11, hheight = 13, hsize = 13, max_num = 0.055, rplc = basic_pseudobiome_villages }, + {name = "blacksmith", mts = schem_path.."blacksmith.mts", hwidth = 8, hdepth = 11, hheight = 13, hsize = 13, max_num = 0.055 , rplc = basic_pseudobiome_villages }, {name = "butcher", mts = schem_path.."butcher.mts", hwidth = 12, hdepth = 8, hheight = 10, hsize = 14, max_num = 0.03 , rplc = basic_pseudobiome_villages }, {name = "church", mts = schem_path.."church.mts", hwidth = 13, hdepth = 13, hheight = 14, hsize = 15, max_num = 0.04 , rplc = basic_pseudobiome_villages }, - {name = "farm", mts = schem_path.."farm.mts", hwidth = 9, hdepth = 7, hheight = 13, hsize = 13, max_num = 0.1 , rplc = basic_pseudobiome_villages }, + {name = "farm", mts = schem_path.."farm.mts", hwidth = 9, hdepth = 7, hheight = 13, hsize = 13, max_num = 0.1 , rplc = basic_pseudobiome_villages, yadjust = 1 }, {name = "lamp", mts = schem_path.."lamp.mts", hwidth = 3, hdepth = 4, hheight = 13, hsize = 10, max_num = 0.1 , rplc = false }, {name = "library", mts = schem_path.."library.mts", hwidth = 12, hdepth = 12, hheight = 8, hsize = 13, max_num = 0.04 , rplc = basic_pseudobiome_villages }, {name = "medium_house", mts = schem_path.."medium_house.mts", hwidth = 9, hdepth = 12, hheight = 8, hsize = 14, max_num = 0.08 , rplc = basic_pseudobiome_villages }, @@ -67,7 +74,7 @@ settlements.schematic_table = { } -- --- maximum allowed difference in height for building a sttlement +-- maximum allowed difference in height for building a settlement -- max_height_difference = 56 -- @@ -75,3 +82,185 @@ max_height_difference = 56 -- half_map_chunk_size = 40 --quarter_map_chunk_size = 20 + +-- +-- Biome based block substitutions +-- +-- TODO maybe this should be in the biomes? +mcl_villages.biome_map = { + BambooJungle = "bamboo", + BambooJungleEdge = "bamboo", + BambooJungleEdgeM = "bamboo", + BambooJungleM = "bamboo", + + Jungle = "jungle", + JungleEdge = "jungle", + JungleEdgeM = "jungle", + JungleM = "jungle", + + Desert = "desert", + + Savanna = "acacia", + SavannaM = "acacia", + + Mesa = "hardened_clay", + MesaBryce = "hardened_clay ", + MesaPlateauF = "hardened_clay", + MesaPlateauFM = "hardened_clay", + + MangroveSwamp = "mangrove", + + RoofedForest = "dark_oak", + + BirchForest = "birch", + BirchForestM = "birch", + + ColdTaiga = "spruce", + ExtremeHills = "spruce", + ExtremeHillsM = "spruce", + IcePlains = "spruce", + IcePlainsSpikes = "spruce", + MegaSpruceTaiga = "spruce", + MegaTaiga = "spruce", + Taiga = "spruce", + ["ExtremeHills+"] = "spruce", + + CherryGrove = "cherry", + + -- no change + --FlowerForest = "oak", + --Forest = "oak", + --MushroomIsland = "", + --Plains = "oak", + --StoneBeach = "", + --SunflowerPlains = "oak", + --Swampland = "oak", +} + +mcl_villages.material_substitions = { + desert = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_sandstonesmooth%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_birchwood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:birch_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:birch_door%1"' }, + + { "mcl_core:cobble", "mcl_core:sandstone" }, + { '"mcl_stairs:stair_cobble([^"]*)"', '"mcl_stairs:stair_sandstone%1"' }, + { '"mcl_walls:cobble([^"]*)"', '"mcl_walls:sandstone%1"' }, + { '"mcl_stairs:slab_cobble([^"]*)"', '"mcl_stairs:slab_sandstone%1"' }, + + { '"mcl_core:stonebrick"', '"mcl_core:redsandstone"' }, + { '"mcl_core:stonebrick_([^"]+)"', '"mcl_core:redsandstone_%1"' }, + { '"mcl_walls:stonebrick([^"]*)"', '"mcl_walls:redsandstone%1"' }, + { '"mcl_stairs:stair_stonebrick"', '"mcl_stairs:stair_redsandstone"' }, + { '"mcl_stairs:stair_stonebrick_([^"]+)"', '"mcl_stairs:stair_redsandstone_%1"' }, + + { '"mcl_stairs:slab_brick_block([^"]*)"', '"mcl_core:redsandstonesmooth2%1"' }, + { '"mcl_core:brick_block"', '"mcl_core:redsandstonesmooth2"' }, + + { "mcl_trees:tree_oak", "mcl_core:redsandstonecarved" }, + { "mcl_trees:wood_oak", "mcl_core:redsandstonesmooth" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:birch_fence%1"' }, + { '"mcl_stairs:stair_oak_bark([^"]*)"', '"mcl_stairs:stair_sandstonesmooth2%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_sandstonesmooth%1"' }, + }, + spruce = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_sprucewood%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_sprucewood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:spruce_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:spruce_door%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_spruce" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_spruce" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:spruce_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_spruce%1"' }, + }, + birch = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_birchwood%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_birchwood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:birch_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:birch_door%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_birch" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_birch" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:birch_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_birch%1"' }, + }, + acacia = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_acaciawood%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_acaciawood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:acacia_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:acacia_door%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_acacia" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_acacia" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:acacia_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_acacia%1"' }, + }, + dark_oak = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_darkwood%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_darkwood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:dark_oak_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:dark_oak_door%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_dark_oak" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_dark_oak" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:dark_oak_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_dark_oak%1"' }, + }, + jungle = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_junglewood%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_junglewood_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:jungle_trapdoor%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:jungle_door%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_jungle" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_jungle" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:jungle_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_jungle%1"' }, + }, + bamboo = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_bamboo_block%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_bamboo_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:trapdoor_bamboo%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:door_bamboo%1"' }, + + { "mcl_core:cobble", "mcl_core:andesite" }, + { '"mcl_stairs:stair_cobble([^"]*)"', '"mcl_stairs:stair_andesite%1"' }, + { '"mcl_walls:cobble([^"]*)"', '"mcl_walls:andesite%1"' }, + { '"mcl_stairs:slab_cobble([^"]*)"', '"mcl_stairs:slab_andesite%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_bamboo" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_bamboo" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:bamboo_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_bamboo%1"' }, + }, + cherry = { + { '"mcl_stairs:slab_oak([^"]*)"', '"mcl_stairs:slab_cherry_blossom%1"' }, + { + '"mesecons_pressureplates:pressure_plate_oak_([^"]+)"', + '"mesecons_pressureplates:pressure_plate_cherry_blossom_%1"', + }, + { '"mcl_doors:trapdoor_oak([^"]*)"', '"mcl_doors:trapdoor_cherry_blossom%1"' }, + { '"mcl_doors:door_oak([^"]*)"', '"mcl_doors:door_cherry_blossom%1"' }, + { "mcl_trees:tree_oak", "mcl_trees:tree_cherry_blossom" }, + { "mcl_trees:wood_oak", "mcl_trees:wood_cherry_blossom" }, + { '"mcl_fences:oak_fence([^"]*)"', '"mcl_fences:cherry_blossom_fence%1"' }, + { '"mcl_stairs:stair_oak([^"]*)"', '"mcl_stairs:stair_cherry_blossom%1"' }, + }, +} diff --git a/mods/MAPGEN/mcl_villages/foundation.lua b/mods/MAPGEN/mcl_villages/foundation.lua index e53a4a3ce..cf9f9f403 100644 --- a/mods/MAPGEN/mcl_villages/foundation.lua +++ b/mods/MAPGEN/mcl_villages/foundation.lua @@ -1,48 +1,71 @@ -local function mcl_log (message) - mcl_util.mcl_log (message, "[Village - Foundation]") -end - local foundation_materials = {} - foundation_materials["mcl_core:sand"] = "mcl_core:sandstone" --"mcl_core:sandstonecarved" -------------------------------------------------------------------------------- --- function to fill empty space below baseplate when building on a hill -------------------------------------------------------------------------------- -function settlements.ground(pos, pr, platform_material) -- role model: Wendelsteinkircherl, Brannenburg - local p2 = vector.new(pos) - local cnt = 0 - - local mat = "mcl_core:dirt" - if not platform_material then - mat = "mcl_core:dirt" - else - mat = platform_material - end - - p2.y = p2.y-1 - while true do - cnt = cnt+1 - if cnt > 20 then break end - if cnt>pr:next(2,4) then - if not platform_material then - mat = "mcl_core:stone" - end +local function is_air(node) + return not node or node.name == "air" or node.name == "ignore" +end +local function is_solid(node) + if not node or node.name == "air" or node.name == "ignore" then return false end + --if string.find(node.name,"leaf") then return false end + --if string.find(node.name,"tree") then return false end + local ndef = minetest.registered_nodes[node.name] + return ndef and ndef.walkable +end +local function excavate(lvm,xi,yi,zi,pr) + local pos, n, c = vector.new(xi,yi,zi), nil, 0 + local node = lvm:get_node_at(pos) + if is_air(node) then return false end -- already empty, nothing to do + pos.y = pos.y-1 + if not is_air(lvm:get_node_at(pos)) then return false end -- below is solid, do not clear above anymore + -- count empty nodes below otherwise + for x = xi-1,xi+1 do + for z = zi-1,zi+1 do + pos.x, pos.z = x, z + if is_air(lvm:get_node_at(pos)) then c = c + 1 end end - minetest.swap_node(p2, {name=mat}) - p2.y = p2.y-1 end + -- try to completely remove trees overhead + if not string.find(node.name, "leaf") and not string.find(node.name, "tree") then + -- stop randomly depending on fill, to narrow down the caves + if pr:next(0,905) > c * 100 then return false end + end + lvm:set_node_at(vector.new(xi, yi, zi),{name="air"}) + return true -- modified +end +local function grow_foundation(lvm,xi,yi,zi,pr,surface_mat,platform_mat) + local pos, n, c = vector.new(xi,yi,zi), nil, 0 + if is_solid(lvm:get_node_at(pos)) then return false end -- already solid, nothing to do + pos.y = pos.y+1 + local cur = lvm:get_node_at(pos) + if not is_solid(cur) then return false end -- above is empty, do not fill below + if cur and cur.name and cur.name ~= surface_mat then platform_mat = cur.name end + if pr:next(1,5) == 5 then -- randomly switch to stone sometimes + platform_mat = "mcl_core:stone" + end + -- count solid nodes above otherwise + for x = xi-1,xi+1 do + for z = zi-1,zi+1 do + pos.x, pos.z = x, z + if is_solid(lvm:get_node_at(pos)) then c = c + 1 end + end + end + -- stop randomly depending on fill, to narrow down the foundation + if pr:next(0,905) > c * 100 then return false end + lvm:set_node_at(vector.new(xi, yi, zi),{name=platform_mat}) + return true -- modified end ------------------------------------------------------------------------------- -- function clear space above baseplate ------------------------------------------------------------------------------- -function settlements.terraform(settlement_info, pr) +function mcl_villages.terraform(settlement_info, pr) local fheight, fwidth, fdepth, schematic_data + --local lvm, emin, emax = minetest.get_mapgen_object("voxelmanip") + local lvm = VoxelManip() for i, built_house in ipairs(settlement_info) do -- pick right schematic_info to current built_house - for j, schem in ipairs(settlements.schematic_table) do + for j, schem in ipairs(mcl_villages.schematic_table) do if settlement_info[i]["name"] == schem["name"] then schematic_data = schem break @@ -50,41 +73,187 @@ function settlements.terraform(settlement_info, pr) end local pos = settlement_info[i]["pos"] if settlement_info[i]["rotat"] == "0" or settlement_info[i]["rotat"] == "180" then - fwidth = schematic_data["hwidth"] - fdepth = schematic_data["hdepth"] + fwidth, fdepth = schematic_data["hwidth"], schematic_data["hdepth"] else - fwidth = schematic_data["hdepth"] - fdepth = schematic_data["hwidth"] + fwidth, fdepth = schematic_data["hdepth"], schematic_data["hwidth"] end - --fheight = schematic_data["hheight"] * 3 -- remove trees and leaves above fheight = schematic_data["hheight"] -- remove trees and leaves above + -- use biome-specific materials local surface_mat = settlement_info[i]["surface_mat"] - mcl_log("Surface material: " .. tostring(surface_mat)) - local platform_mat = foundation_materials[surface_mat] - mcl_log("Foundation material: " .. tostring(platform_mat)) + mcl_villages.debug("Surface material: " .. tostring(surface_mat)) + local platform_mat = foundation_materials[surface_mat] or "mcl_core:dirt" + mcl_villages.debug("Foundation material: " .. tostring(platform_mat)) - -- - -- now that every info is available -> create platform and clear space above - -- - for xi = 0,fwidth-1 do - for zi = 0,fdepth-1 do - for yi = 0,fheight *3 do - if yi == 0 then - local p = {x=pos.x+xi, y=pos.y, z=pos.z+zi} - -- Pass in biome info and make foundations of same material (seed: apple for desert) - settlements.ground(p, pr, platform_mat) - else - -- write ground --- local p = {x=pos.x+xi, y=pos.y+yi, z=pos.z+zi} --- local node = mcl_vars.get_node(p) --- if node and node.name ~= "air" then --- minetest.swap_node(p,{name="air"}) --- end - minetest.swap_node({x=pos.x+xi, y=pos.y+yi, z=pos.z+zi},{name="air"}) + lvm:read_from_map(vector.new(pos.x-2, pos.y-20, pos.z-2), vector.new(pos.x+fwidth+2, pos.y+fheight+20, pos.z+fdepth+2)) + -- TODO: further optimize by using raw data arrays instead of set_node_at. But OK for a first draft. + lvm:get_data() + -- excavate the needed volume, some headroom, and add a baseplate + local p2 = vector.new(pos) + for xi = pos.x,pos.x+fwidth-1 do + for zi = pos.z,pos.z+fdepth-1 do + lvm:set_node_at(vector.new(xi, pos.y+1, zi),{name="air"}) + -- pos.y+2 to pos.y+5 are filled larger below! + for yi = pos.y+6,pos.y+fheight do + lvm:set_node_at(vector.new(xi, yi, zi),{name="air"}) + end + local cp = vector.new(xi, pos.y, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + local cp = vector.new(xi, pos.y - 1, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) then + lvm:set_node_at(cp, {name=platform_mat}) + end + end + end + -- slightly widen the cave, to make easier to enter for mobs + for xi = pos.x-1,pos.x+fwidth do + for zi = pos.z-1,pos.z+fdepth do + for yi = pos.y+2,pos.y+5 do + lvm:set_node_at(vector.new(xi, yi, zi),{name="air"}) + end + end + end + -- some extra gaps + for xi = pos.x-2,pos.x+fwidth+1 do + for zi = pos.z-2,pos.z+fdepth+1 do + if pr:next(1,4) == 1 then + for yi = pos.y+3,pos.y+5 do + lvm:set_node_at(vector.new(xi, yi, zi),{name="air"}) end end end end + -- slightly widen the baseplate, to make easier to enter for mobs + for xi = pos.x,pos.x+fwidth-1 do + local cp = vector.new(xi, pos.y-1, pos.z) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) then + lvm:set_node_at(cp, {name=platform_mat}) + end + local cp = vector.new(xi, pos.y-1, pos.z-1) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + local cp = vector.new(xi, pos.y-1, pos.z+fdepth-1) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) then + lvm:set_node_at(cp, {name=platform_mat}) + end + local cp = vector.new(xi, pos.y-1, pos.z+fdepth) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + for zi = pos.z,pos.z+fdepth-1 do + local cp = vector.new(pos.x, pos.y-1, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) then + lvm:set_node_at(cp, {name=platform_mat}) + end + local cp = vector.new(pos.x-1, pos.y-1, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + local cp = vector.new(pos.x+fwidth-1, pos.y-1, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) then + lvm:set_node_at(cp, {name=platform_mat}) + end + local cp = vector.new(pos.x+fwidth, pos.y-1, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + -- make some additional steps, along both x sides + for xi = pos.x,pos.x+fwidth-1 do + local cp = vector.new(xi, pos.y-3, pos.z-1) + if is_solid(lvm:get_node_at(cp)) then + cp = vector.new(xi, pos.y-2, pos.z-1) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + cp.z = pos.z-2 + cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + local cp = vector.new(xi, pos.y-3, pos.z+fdepth) + if is_solid(lvm:get_node_at(cp)) then + cp = vector.new(xi, pos.y-2, pos.z+fdepth) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + cp.z = pos.z + fdepth + 1 + cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + end + -- make some additional steps, along both z sides + for zi = pos.z,pos.z+fdepth-1 do + local cp = vector.new(pos.x-1, pos.y-3, zi) + if is_solid(lvm:get_node_at(cp)) then + cp = vector.new(pos.x-1, pos.y-2, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + cp.x = pos.x-2 + cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + local cp = vector.new(pos.x+fwidth, pos.y-3, zi) + if is_solid(lvm:get_node_at(cp)) then + cp = vector.new(pos.x+fwidth, pos.y-2, zi) + local cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + cp.x = pos.x+fwidth+1 + cur = lvm:get_node_at(cp) + if not is_solid(cur) or cur.name == platform_mat then + lvm:set_node_at(cp, {name=surface_mat}) + end + end + end + -- cave some additional area overhead, try to make it interesting though + for yi = pos.y+3,pos.y+fheight*3 do + local active = false + for xi = pos.x-2,pos.x+fwidth+1 do + for zi = pos.z-2,pos.z+fdepth+1 do + if excavate(lvm,xi,yi,zi,pr) then + active = true + end + end + end + if not active and yi > pos.y+fheight+5 then break end + end + -- construct additional baseplate below, also try to make it interesting + for yi = pos.y-2,pos.y-20,-1 do + local active = false + for xi = pos.x-1,pos.x+fwidth do + for zi = pos.z-1,pos.z+fdepth do + if grow_foundation(lvm,xi,yi,zi,pr,surface_mat,platform_mat) then + active = true + end + end + end + if not active and yi < pos.y-5 then break end + end + lvm:write_to_map(false) end end diff --git a/mods/MAPGEN/mcl_villages/init.lua b/mods/MAPGEN/mcl_villages/init.lua index 6662f6bd1..56d92df64 100644 --- a/mods/MAPGEN/mcl_villages/init.lua +++ b/mods/MAPGEN/mcl_villages/init.lua @@ -1,20 +1,16 @@ -settlements = {} -settlements.modpath = minetest.get_modpath(minetest.get_current_modname()) +mcl_villages = {} +mcl_villages.modpath = minetest.get_modpath(minetest.get_current_modname()) -dofile(settlements.modpath.."/const.lua") -dofile(settlements.modpath.."/utils.lua") -dofile(settlements.modpath.."/foundation.lua") -dofile(settlements.modpath.."/buildings.lua") -dofile(settlements.modpath.."/paths.lua") ---dofile(settlements.modpath.."/convert_lua_mts.lua") --- --- load settlements on server --- -settlements.grundstellungen() +local village_chance = tonumber(minetest.settings:get("mcl_villages_village_chance")) or 5 + +dofile(mcl_villages.modpath.."/const.lua") +dofile(mcl_villages.modpath.."/utils.lua") +dofile(mcl_villages.modpath.."/foundation.lua") +dofile(mcl_villages.modpath.."/buildings.lua") +dofile(mcl_villages.modpath.."/paths.lua") local S = minetest.get_translator(minetest.get_current_modname()) -local villagegen={} -- -- register block for npc spawn -- @@ -32,42 +28,20 @@ minetest.register_node("mcl_villages:stonebrickcarved", { minetest.register_node("mcl_villages:structblock", {drawtype="airlike",groups = {not_in_creative_inventory=1},}) - - ---[[ Enable for testing, but use MineClone2's own spawn code if/when merging. --- --- register inhabitants --- -if minetest.get_modpath("mobs_mc") then - mcl_mobs:register_spawn("mobs_mc:villager", --name - {"mcl_core:stonebrickcarved"}, --nodes - 15, --max_light - 0, --min_light - 20, --chance - 7, --active_object_count - 31000, --max_height - nil) --day_toggle -end ---]] - -- -- on map generation, try to build a settlement -- local function build_a_settlement(minp, maxp, blockseed) - local pr = PseudoRandom(blockseed) + if mcl_villages.village_exists(blockseed) then return end - -- fill settlement_info with buildings and their data - local settlement_info = settlements.create_site_plan(maxp, minp, pr) + local pr = PseudoRandom(blockseed) + local settlement_info = mcl_villages.create_site_plan(minp, maxp, pr) if not settlement_info then return end - -- evaluate settlement_info and prepair terrain - settlements.terraform(settlement_info, pr) - - -- evaluate settlement_info and build paths between buildings - settlements.paths(settlement_info) - - -- evaluate settlement_info and place schematics - settlements.place_schematics(settlement_info, pr) + mcl_villages.terraform(settlement_info, pr) + mcl_villages.place_schematics(settlement_info, pr) + mcl_villages.paths(settlement_info) + mcl_villages.add_village(blockseed, settlement_info) end local function ecb_village(blockpos, action, calls_remaining, param) @@ -76,33 +50,24 @@ local function ecb_village(blockpos, action, calls_remaining, param) build_a_settlement(minp, maxp, blockseed) end +local villagegen={} -- Disable natural generation in singlenode. local mg_name = minetest.get_mapgen_setting("mg_name") if mg_name ~= "singlenode" then mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed) - if maxp.y < 0 then return end - - -- randomly try to build settlements - if blockseed % 77 ~= 17 then return end - --minetest.log("Rng good. Generate attempt") - - -- needed for manual and automated settlement building - -- don't build settlements on (too) uneven terrain + if maxp.y < 0 or village_chance == 0 then return end + local pr = PseudoRandom(blockseed) + if pr:next(0, 100) > village_chance then return end local n=minetest.get_node_or_nil(minp) - if n and n.name == "mcl_villages:structblock" then return end - --minetest.log("No existing village attempt here") - - if villagegen[minetest.pos_to_string(minp)] ~= nil then return end - - --minetest.log("Not in village gen. Put down placeholder: " .. minetest.pos_to_string(minp) .. " || " .. minetest.pos_to_string(maxp)) + --if n and n.name == "mcl_villages:structblock" then return end + if n and n.name ~= "air" then return end minetest.set_node(minp,{name="mcl_villages:structblock"}) - local height_difference = settlements.evaluate_heightmap() - if not height_difference or height_difference > max_height_difference then - minetest.log("action", "Do not spawn village here as heightmap not good") - return - end - --minetest.log("Build me a village: " .. minetest.pos_to_string(minp) .. " || " .. minetest.pos_to_string(maxp)) + --[[minetest.emerge_area( + minp, maxp, --vector.offset(minp, -16, -16, -16), vector.offset(maxp, 16, 16, 16), + ecb_village, + { minp = vector.copy(minp), maxp = vector.copy(maxp), blockseed = blockseed } + )]]-- villagegen[minetest.pos_to_string(minp)]={minp=vector.new(minp), maxp=vector.new(maxp), blockseed=blockseed} end) end @@ -125,15 +90,15 @@ if minetest.is_creative_enabled("") then minetest.register_craftitem("mcl_villages:tool", { description = S("mcl_villages build tool"), inventory_image = "default_tool_woodshovel.png", - -- build ssettlement + -- build settlement on_place = function(itemstack, placer, pointed_thing) if not pointed_thing.under then return end if not minetest.check_player_privs(placer, "server") then minetest.chat_send_player(placer:get_player_name(), S("Placement denied. You need the “server” privilege to place villages.")) return end - local minp = vector.subtract( pointed_thing.under, half_map_chunk_size) - local maxp = vector.add( pointed_thing.under, half_map_chunk_size) + local minp = vector.subtract(pointed_thing.under, half_map_chunk_size) + local maxp = vector.add(pointed_thing.under, half_map_chunk_size) build_a_settlement(minp, maxp, math.random(0,32767)) end }) diff --git a/mods/MAPGEN/mcl_villages/mod.conf b/mods/MAPGEN/mcl_villages/mod.conf index d8e2aa7d4..9f626d53b 100644 --- a/mods/MAPGEN/mcl_villages/mod.conf +++ b/mods/MAPGEN/mcl_villages/mod.conf @@ -1,5 +1,5 @@ name = mcl_villages author = Rochambeau description = This mod adds settlements on world generation. -depends = mcl_util, mcl_mapgen_core, mcl_structures, mcl_core, mcl_loot -optional_depends = mcl_farming, mobs_mc +depends = mcl_util, mcl_mapgen_core, mcl_structures, mcl_core, mcl_loot, mobs_mc +optional_depends = mcl_farming diff --git a/mods/MAPGEN/mcl_villages/paths.lua b/mods/MAPGEN/mcl_villages/paths.lua index 63f2ba146..8f3ef3104 100644 --- a/mods/MAPGEN/mcl_villages/paths.lua +++ b/mods/MAPGEN/mcl_villages/paths.lua @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------- -- generate paths between buildings ------------------------------------------------------------------------------- -function settlements.paths(settlement_info) +function mcl_villages.paths(settlement_info) local starting_point local end_point local distance @@ -67,7 +67,7 @@ function settlements.paths(settlement_info) distance = dist_east_p_to_end end -- find surface of new starting point - local surface_point, surface_mat = settlements.find_surface(starting_point) + local surface_point, surface_mat = mcl_villages.find_surface(starting_point) -- replace surface node with mcl_core:grass_path if surface_point then diff --git a/mods/MAPGEN/mcl_villages/utils.lua b/mods/MAPGEN/mcl_villages/utils.lua index 5ed3cbbe0..061354827 100644 --- a/mods/MAPGEN/mcl_villages/utils.lua +++ b/mods/MAPGEN/mcl_villages/utils.lua @@ -1,106 +1,105 @@ -local get_node = mcl_vars.get_node - ------------------------------------------------------------------------------- -- function to copy tables ------------------------------------------------------------------------------- -function settlements.shallowCopy(original) +function mcl_villages.shallowCopy(original) local copy = {} for key, value in pairs(original) do copy[key] = value end return copy end --- --- --- -function settlements.round(num, numDecimalPlaces) - local mult = 10^(numDecimalPlaces or 0) - return math.floor(num * mult + 0.5) / mult -end +local function is_above_surface(name) + return name == "air" or + string.find(name,"grass") or + string.find(name,"tree") or + string.find(name,"leaves") or + string.find(name,"snow") or + string.find(name,"fern") or + string.find(name,"flower") or + string.find(name,"bush") +end +local get_node = mcl_vars.get_node +function mcl_villages.find_surface_down(pos, surface_node) + local p6 = vector.new(pos) + surface_node = surface_node or get_node(p6) --, true, 1000000) + if not surface_node then return end + for y = p6.y - 1, math.max(0,p6.y - 120), -1 do + p6.y = y + local top_node = surface_node + surface_node = get_node(p6) + if not surface_node then return nil end + if is_above_surface(top_node.name) then + if mcl_villages.surface_mat[surface_node.name] then + -- minetest.log("verbose", "Found "..surface_node.name.." below "..top_node.name) + return p6, surface_node.name + end + else + local ndef = minetest.registered_nodes[surface_node.name] + if ndef and ndef.walkable then + return nil + end + end + end +end +function mcl_villages.find_surface_up(pos, surface_node) + local p6 = vector.new(pos) + surface_node = surface_node or get_node(p6) --, true, 1000000) + if not surface_node then return end + for y = p6.y + 1, p6.y + 50 do + p6.y = y + local top_node = get_node(p6) + if not top_node then return nil end + if is_above_surface(top_node.name) then + if mcl_villages.surface_mat[surface_node.name] then + -- minetest.log("verbose","Found "..surface_node.name.." below "..top_node.name) + p6.y = p6.y - 1 + return p6, surface_node.name + end + else + local ndef = minetest.registered_nodes[surface_node.name] + if ndef and ndef.walkable then + return nil + end + end + surface_node = top_node + end +end ------------------------------------------------------------------------------- -- function to find surface block y coordinate -- returns surface postion ------------------------------------------------------------------------------- -function settlements.find_surface(pos, wait) +function mcl_villages.find_surface(pos, wait) local p6 = vector.new(pos) - local cnt = 0 - local itter = 1 -- count up or down - local cnt_max = 200 - -- check, in which direction to look for surface + if p6.y < 0 then p6.y = 0 end -- start at water level local surface_node if wait then surface_node = get_node(p6, true, 10000000) else surface_node = get_node(p6) end - if surface_node.name=="air" or surface_node.name=="ignore" then - itter = -1 + -- downward, if starting position is empty + if is_above_surface(surface_node.name) then + return mcl_villages.find_surface_down(p6, surface_node) + else + return mcl_villages.find_surface_up(p6, surface_node) end - -- go through nodes an find surface - while cnt < cnt_max do - -- Check Surface_node and Node above - if surface_node and settlements.surface_mat[surface_node.name] then - local surface_node_plus_1 = get_node({ x=p6.x, y=p6.y+1, z=p6.z}) - - if surface_node_plus_1 then - - local surface_node_minus_1 = get_node({ x=p6.x, y=p6.y-1, z=p6.z}) - local is_leaf_below = minetest.get_item_group(surface_node_minus_1, "leaves") ~= 0 or - string.find(surface_node_minus_1.name,"leaves") - - if not is_leaf_below and ((string.find(surface_node_plus_1.name,"air") or - string.find(surface_node_plus_1.name,"fern") or - string.find(surface_node_plus_1.name,"flower") or - string.find(surface_node_plus_1.name,"bush") or - string.find(surface_node_plus_1.name,"tree") or - string.find(surface_node_plus_1.name,"grass")) or - string.find(surface_node_plus_1.name,"snow")) - then - settlements.debug("find_surface success: " ..surface_node.name.. " " .. surface_node_plus_1.name) - settlements.debug("node below: " .. tostring(surface_node_minus_1.name)) - settlements.debug("node below leaves group: " .. tostring(minetest.get_item_group(surface_node_minus_1, "leaves"))) - return p6, surface_node.name - else - settlements.debug("find_surface2: wrong surface+1") - end - else - settlements.debug("find_surface8: missing node or plus_1") - end - else - settlements.debug("find_surface3: wrong surface "..surface_node.name.." at pos "..minetest.pos_to_string(p6)) - end - - p6.y = p6.y + itter - if p6.y < 0 then - settlements.debug("find_surface4: y<0") - return nil - end - cnt = cnt+1 - surface_node = get_node(p6) - end - settlements.debug("find_surface5: cnt_max overflow") - return nil end ------------------------------------------------------------------------------- -- check distance for new building ------------------------------------------------------------------------------- -function settlements.check_distance(settlement_info, building_pos, building_size) - local distance +function mcl_villages.check_distance(settlement_info, building_pos, building_size) for i, built_house in ipairs(settlement_info) do - distance = math.sqrt( - ((building_pos.x - built_house["pos"].x)*(building_pos.x - built_house["pos"].x))+ - ((building_pos.z - built_house["pos"].z)*(building_pos.z - built_house["pos"].z))) - if distance < building_size or distance < built_house["hsize"] then - return false - end + local dx, dz = building_pos.x - built_house["pos"].x, building_pos.z - built_house["pos"].z + local dsq = dx*dx+dz*dz + if dsq < building_size^2 or dsq < built_house["hsize"]^2 then return false end end return true end ------------------------------------------------------------------------------- -- fill chests ------------------------------------------------------------------------------- -function settlements.fill_chest(pos, pr) +function mcl_villages.fill_chest(pos, pr) -- initialize chest (mts chests don't have meta) local meta = minetest.get_meta(pos) if meta:get_string("infotext") ~= "Chest" then @@ -148,123 +147,93 @@ end ------------------------------------------------------------------------------- -- initialize furnace ------------------------------------------------------------------------------- -function settlements.initialize_furnace(pos) - -- find chests within radius - local furnacepos = minetest.find_node_near(pos, - 7, --radius - {"mcl_furnaces:furnace"}) - -- initialize furnacepos (mts furnacepos don't have meta) - if furnacepos - then - local meta = minetest.get_meta(furnacepos) - if meta:get_string("infotext") ~= "furnace" - then - minetest.registered_nodes["mcl_furnaces:furnace"].on_construct(furnacepos) - end - end +function mcl_villages.initialize_furnace(pos) + -- find chests within radius + local furnacepos = minetest.find_node_near(pos, + 7, --radius + {"mcl_furnaces:furnace"}) + -- initialize furnacepos (mts furnacepos don't have meta) + if furnacepos then + local meta = minetest.get_meta(furnacepos) + if meta:get_string("infotext") ~= "furnace" then + minetest.registered_nodes["mcl_furnaces:furnace"].on_construct(furnacepos) + end + end end ------------------------------------------------------------------------------- -- initialize anvil ------------------------------------------------------------------------------- -function settlements.initialize_anvil(pos) - -- find chests within radius - local anvilpos = minetest.find_node_near(pos, - 7, --radius - {"mcl_anvils:anvil"}) - -- initialize anvilpos (mts anvilpos don't have meta) - if anvilpos - then - local meta = minetest.get_meta(anvilpos) - if meta:get_string("infotext") ~= "anvil" - then - minetest.registered_nodes["mcl_anvils:anvil"].on_construct(anvilpos) - end - end +function mcl_villages.initialize_anvil(pos) + -- find chests within radius + local anvilpos = minetest.find_node_near(pos, + 7, --radius + {"mcl_anvils:anvil"}) + -- initialize anvilpos (mts anvilpos don't have meta) + if anvilpos then + local meta = minetest.get_meta(anvilpos) + if meta:get_string("infotext") ~= "anvil" then + minetest.registered_nodes["mcl_anvils:anvil"].on_construct(anvilpos) + end + end end ------------------------------------------------------------------------------- -- randomize table ------------------------------------------------------------------------------- -function shuffle(tbl, pr) - local table = settlements.shallowCopy(tbl) - local size = #table - for i = size, 1, -1 do - local rand = pr:next(1, size) - table[i], table[rand] = table[rand], table[i] +function mcl_villages.shuffle(tbl, pr) + local copy = {} + for key, value in ipairs(tbl) do + table.insert(copy, pr:next(1, #copy + 1), value) end - return table + return copy end -------------------------------------------------------------------------------- --- evaluate heightmap -------------------------------------------------------------------------------- -function settlements.evaluate_heightmap() - local heightmap = minetest.get_mapgen_object("heightmap") - if not heightmap then - minetest.log("action", "No heightmap. That should not happen") - return max_height_difference + 1 - end +-- Load a schema and replace nodes in it based on biome +function mcl_villages.substitute_materials(pos, schem_lua, pr) + local modified_schem_lua = schem_lua + local biome_data = minetest.get_biome_data(pos) + local biome_name = minetest.get_biome_name(biome_data.biome) - --minetest.log("action", "heightmap size: " .. tostring(#heightmap)) - - -- max height and min height, initialize with impossible values for easier first time setting - local max_y = -50000 - local min_y = 50000 - -- only evaluate the center square of heightmap 40 x 40 - local square_start = 1621 - local square_end = 1661 - for j = 1 , 40, 1 do - if square_start >= #heightmap then - --minetest.log("action", "Heightmap size reached. Go no further outside") - break + if mcl_villages.biome_map[biome_name] and mcl_villages.material_substitions[mcl_villages.biome_map[biome_name]] then + for _, sub in pairs(mcl_villages.material_substitions[mcl_villages.biome_map[biome_name]]) do + modified_schem_lua = modified_schem_lua:gsub(sub[1], sub[2]) end - for i = square_start, square_end, 1 do - --minetest.log("action", "current hm index: " .. tostring(i) .. "current hm entry: " .. tostring(heightmap[i])) + end - if i >= #heightmap then - --minetest.log("action", "Heightmap size reached. Go no further") - break - end - local current_hm_entry = heightmap[i] - if current_hm_entry then - -- skip buggy heightmaps, return high value. Converted mcl5 maps can be -31007 - if current_hm_entry == -31000 or heightmap[i] == 31000 then - --minetest.log("action", "incorrect heighmap values. abandon") - return max_height_difference + 1 - end - if current_hm_entry < min_y then - min_y = current_hm_entry - end - if current_hm_entry > max_y then - max_y = current_hm_entry - end - else - --minetest.log("action", "Failed to get hm index: " .. tostring(i) .. "and ... " .. tostring(#heightmap)) - end + return modified_schem_lua +end + +local villages = {} +local mod_storage = minetest.get_mod_storage() + +local function lazy_load_village(name) + if not villages[name] then + local data = mod_storage:get("mcl_villages." .. name) + if data then + villages[name] = minetest.deserialize(data) end - -- set next line - square_start = square_start + 80 - square_end = square_end + 80 end - -- return the difference between highest and lowest pos in chunk - local height_diff = max_y - min_y +end - --minetest.log("action", "height_diff = " .. tostring(height_diff)) - - -- filter buggy heightmaps - if height_diff <= 1 then - return max_height_difference + 1 +function mcl_villages.get_village(name) + lazy_load_village(name) + if villages[name] then + return table.copy(villages[name]) end - --minetest.log("action", "return heigh diff = " .. tostring(height_diff)) - -- debug info - settlements.debug("heightdiff ".. height_diff) - return height_diff end -------------------------------------------------------------------------------- --- Set array to list --- https://stackoverflow.com/questions/656199/search-for-an-item-in-a-lua-list -------------------------------------------------------------------------------- -function settlements.Set (list) - local set = {} - for _, l in ipairs(list) do set[l] = true end - return set + +function mcl_villages.village_exists(name) + lazy_load_village(name) + return villages[name] ~= nil +end + +function mcl_villages.add_village(name, data) + lazy_load_village(name) + if villages[name] then + minetest.log("info","Village already exists: " .. name ) + return false + end + + local new_village = {name = name, data = data} + mod_storage:set_string("mcl_villages." .. name, minetest.serialize(new_village)) + return true end