mcl_villages = {} local chunk_offset_top = 16 local chunk_offset_bottom = 3 local max_height_difference = 12 local minp_min = -64 local chance_per_chunk = 1 local noise_multiplier = 1 local random_offset = 1 local random_multiply = 19 local struct_threshold = chance_per_chunk - 1 local modname = minetest.get_current_modname() local modpath = minetest.get_modpath(modname) local S = minetest.get_translator(modname) local basic_pseudobiome_villages = minetest.settings:get_bool("basic_pseudobiome_villages", true) local schem_path = modpath .. "/schematics/" local schematic_table = { {name = "large_house", mts = schem_path.."large_house.mts", hwidth = 11, hdepth = 12, hheight = 9, hsize = 14, max_num = 0.08 , rplc = basic_pseudobiome_villages }, {name = "blacksmith", mts = schem_path.."blacksmith.mts", hwidth = 7, hdepth = 7, hheight = 13, hsize = 13, max_num = 0.055, rplc = basic_pseudobiome_villages }, {name = "butcher", mts = schem_path.."butcher.mts", hwidth = 11, 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 = 7, hdepth = 7, hheight = 13, hsize = 13, max_num = 0.1 , rplc = basic_pseudobiome_villages }, {name = "lamp", mts = schem_path.."lamp.mts", hwidth = 3, hdepth = 3, 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 = 8, hdepth = 12, hheight = 8, hsize = 14, max_num = 0.08 , rplc = basic_pseudobiome_villages }, {name = "small_house", mts = schem_path.."small_house.mts", hwidth = 9, hdepth = 7, hheight = 8, hsize = 13, max_num = 0.7 , rplc = basic_pseudobiome_villages }, {name = "tavern", mts = schem_path.."tavern.mts", hwidth = 11, hdepth = 10, hheight = 10, hsize = 13, max_num = 0.050, rplc = basic_pseudobiome_villages }, {name = "well", mts = schem_path.."well.mts", hwidth = 6, hdepth = 8, hheight = 6, hsize = 10, max_num = 0.045, rplc = basic_pseudobiome_villages }, } local surface_mat = { ["mcl_core:dirt_with_dry_grass"] = true, ["mcl_core:dirt_with_grass"] = true, ["mcl_core:dirt_with_grass_snow"] = true, ["mcl_core:podzol"] = true, ["mcl_core:redsand"] = true, ["mcl_core:sand"] = true, ["mcl_core:snow"] = true, } local storage = minetest.get_mod_storage() local villages = minetest.deserialize(storage:get_string("villages") or "return {}") or {} local minetest_get_spawn_level = minetest.get_spawn_level local minetest_get_node = minetest.get_node local minetest_find_nodes_in_area = minetest.find_nodes_in_area local mcl_structures_get_perlin_noise_level = mcl_structures.get_perlin_noise_level local math_pi = math.pi local math_cos = math.cos local math_sin = math.sin local math_min = math.min local math_max = math.max local math_floor = math.floor local math_ceil = math.ceil local minetest_swap_node = minetest.swap_node local minetest_registered_nodes = minetest.registered_nodes local air_offset = chunk_offset_top - 1 local ground_offset = chunk_offset_bottom + 1 local surface_search_list = {} for k, _ in pairs(surface_mat) do table.insert(surface_search_list, k) end local function math_round(x) return (x < 0) and math_ceil(x - 0.5) or math_floor(x + 0.5) end local function find_surface(pos, minp, maxp) local x, z = pos.x, pos.z local y_top = maxp.y local y_max = y_top - air_offset if #minetest_find_nodes_in_area({x=x, y=y_max, z=z}, {x=x, y=y_top, z=z}, "air") < chunk_offset_top then return end y_max = y_max - 1 local y_bottom = minp.y local y_min = y_bottom + chunk_offset_bottom local nodes = minetest_find_nodes_in_area({x=x, y=y_min, z=z}, {x=x, y=y_max, z=z}, surface_search_list) for _, surface_pos in pairs(nodes) do local node_name_from_above = minetest_get_node({x=surface_pos.x, y=surface_pos.y+1, z=surface_pos.z}).name if string.find(node_name_from_above, "air" ) or string.find(node_name_from_above, "snow" ) or string.find(node_name_from_above, "fern" ) or string.find(node_name_from_above, "flower") or string.find(node_name_from_above, "bush" ) or string.find(node_name_from_above, "tree" ) or string.find(node_name_from_above, "grass" ) then return surface_pos, minetest_get_node(surface_pos).name end end end local function get_treasures(pr) local loottable = {{ stacks_min = 3, stacks_max = 8, items = { { itemstring = "mcl_core:diamond" , weight = 3, amount_min = 1, amount_max = 3 }, { itemstring = "mcl_core:iron_ingot" , weight = 10, amount_min = 1, amount_max = 5 }, { itemstring = "mcl_core:gold_ingot" , weight = 5, amount_min = 1, amount_max = 3 }, { itemstring = "mcl_farming:bread" , weight = 15, amount_min = 1, amount_max = 3 }, { itemstring = "mcl_core:apple" , weight = 15, amount_min = 1, amount_max = 3 }, { itemstring = "mcl_tools:pick_iron" , weight = 5, }, { itemstring = "mcl_tools:sword_iron" , weight = 5, }, { itemstring = "mcl_armor:chestplate_iron" , weight = 5, }, { itemstring = "mcl_armor:helmet_iron" , weight = 5, }, { itemstring = "mcl_armor:leggings_iron" , weight = 5, }, { itemstring = "mcl_armor:boots_iron" , weight = 5, }, { itemstring = "mcl_core:obsidian" , weight = 5, amount_min = 3, amount_max = 7 }, { itemstring = "mcl_core:sapling" , weight = 5, amount_min = 3, amount_max = 7 }, { itemstring = "mcl_mobitems:saddle" , weight = 3, }, { itemstring = "mobs_mc:iron_horse_armor" , weight = 1, }, { itemstring = "mobs_mc:gold_horse_armor" , weight = 1, }, { itemstring = "mobs_mc:diamond_horse_armor", weight = 1, }, } }} local items = mcl_loot.get_multi_loot(loottable, pr) return items end local function fill_chest(pos, pr) local meta = minetest.get_meta(pos) minetest.registered_nodes["mcl_chests:chest_small"].on_construct(pos) local inv = minetest.get_inventory( {type="node", pos=pos} ) local items = get_treasures(pr) mcl_loot.fill_inventory(inv, "main", items, pr) end local possible_rotations = {"0", "90", "180", "270"} local function get_random_rotation(pr) return possible_rotations[pr:next(1, #possible_rotations)] end local function create_site_plan(minp, maxp, pr) local plan = {} local building_all_info local center = vector.add(minp, mcl_mapgen.HALF_CS_NODES) local center_surface, surface_material = find_surface(center, minp, maxp) if not center_surface then return end local number_of_buildings = pr:next(10, 25) local shuffle = {} local count_buildings = {} for i = 1, #schematic_table do shuffle[i] = i count_buildings[i] = 0 end for i = #shuffle, 2, -1 do local j = pr:next(1, i) shuffle[i], shuffle[j] = shuffle[j], shuffle[i] end local number_built = 1 local shuffle_index = pr:next(1, #schematic_table) -- first building is townhall in the center plan[#plan + 1] = { pos = center_surface, building = schematic_table[shuffle_index], rotation = get_random_rotation(pr), surface_mat = surface_material, } count_buildings[1] = count_buildings[1] + 1 -- now some buildings around in a circle, radius = size of town center local x, z, r = center_surface.x, center_surface.z, schematic_table[1].hsize -- draw j circles around center and increase radius by random(2, 5) for k = 1, 20 do -- set position on imaginary circle for j = 0, 360, 15 do local angle = j * math_pi / 180 local pos_surface, surface_material = find_surface( { x = math_round(x + r * math_cos(angle)), z = math_round(z + r * math_sin(angle)) }, minp, maxp ) if pos_surface then shuffle_index = (shuffle_index % (#schematic_table)) + 1 local schematic_index = shuffle[shuffle_index] local schematic = schematic_table[schematic_index] if count_buildings[schematic_index] < schematic.max_num * number_of_buildings then local hsize2 = schematic.hsize^2 local is_distance_ok = true for _, built_house in pairs(plan) do local pos = built_house.pos local building = built_house.building local distance2 = (pos_surface.x - pos.x)^2 + (pos_surface.z - pos.z)^2 if distance2 < building.hsize^2 or distance2 < hsize2 then is_distance_ok = false break end end if is_distance_ok then plan[#plan + 1] = { pos = pos_surface, building = schematic, rotation = get_random_rotation(pr), surface_mat = surface_material, } count_buildings[schematic_index] = count_buildings[schematic_index] + 1 number_built = number_built + 1 break end end end if number_built >= number_of_buildings then break end end if number_built >= number_of_buildings then break end r = r + pr:next(2, 5) end return plan end local function ground(pos, minp, maxp, pr) local p2 = vector.new(pos) local cnt = 0 local mat = "mcl_core:dirt" p2.y = p2.y - 1 local min_y = math_max(minp.y, p2.y - pr:next(17,27)) local stone_level = p2.y - pr:next(2, 4) while p2.y >= min_y do if p2.y == stone_level then mat = "mcl_core:stone" end minetest.swap_node(p2, {name=mat}) p2.y = p2.y - 1 end end local function terraform(plan, minp, maxp, pr) local fheight, fwidth, fdepth, schematic_data, pos, rotation for _, built_house in pairs(plan) do schematic_data = built_house.building pos = built_house.pos rotation = built_house.rotation if rotation == "0" or rotation == "180" then fwidth = schematic_data.hwidth fdepth = schematic_data.hdepth else fwidth = schematic_data.hdepth fdepth = schematic_data.hwidth end fheight = schematic_data.hheight for xi = pos.x, pos.x + fwidth - 1 do for zi = pos.z, pos.z + fdepth - 1 do for yi = pos.y, math_min(pos.y + fheight * 3, maxp.y) do local p = {x = xi, y = yi, z = zi} if yi == pos.y then ground(p, minp, maxp, pr) else minetest_swap_node(p, {name = "air"}) end end end end end end local function paths(plan, minp, maxp) local starting_point = find_surface({x = plan[1].pos.x + 2, z = plan[1].pos.z + 2}, minp, maxp) if not starting_point then return end starting_point.y = starting_point.y + 1 for i = 2, #plan do local p = plan[i] local end_point = p.pos end_point.y = end_point.y + 1 local path = minetest.find_path(starting_point, end_point, mcl_mapgen.CS_NODES, 2, 2, "A*_noprefetch") if path then for _, pos in pairs(path) do pos.y = pos.y - 1 local surface_mat = minetest.get_node(pos).name if surface_mat == "mcl_core:sand" or surface_mat == "mcl_core:redsand" then minetest.swap_node(pos, {name = "mcl_core:sandstonesmooth2"}) else minetest.swap_node(pos, {name = "mcl_core:grass_path"}) end end end end end local function init_nodes(p1, rotation, pr, size) local p2 = vector.subtract(vector.add(p1, size), 1) local nodes = minetest.find_nodes_in_area(p1, p2, {"mcl_itemframes:item_frame", "mcl_furnaces:furnace", "mcl_anvils:anvil"}) for _, pos in pairs(nodes) do local name = minetest_get_node(pos).name local def = minetest_registered_nodes[minetest_get_node(pos).name] def.on_construct(pos) if name == "mcl_chests:chest" then fill_chest(pos, pr) end end end local function place_schematics(plan, pr) for _, built_house in pairs(plan) do local pos = built_house.pos local rotation = built_house.rotation local platform_material = built_house.surface_mat local building = built_house.building.mts local replace_wall = built_house.building.rplc 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") 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") 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_core:brick_block", "mcl_core:redsandstone") end end schem_lua = schem_lua:gsub("mcl_core:dirt_with_grass", platform_material) 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") local schematic = loadstring(schem_lua)() -- build foundation for the building an make room above -- place schematic mcl_structures.place_schematic({ pos = pos, schematic = schematic, rotation = rotation, on_placed = init_nodes, pr = pr, }) end end -- -- register block for npc spawn -- minetest.register_node("mcl_villages:stonebrickcarved", { description = S("Chiseled Stone Village Bricks"), _doc_items_longdesc = doc.sub.items.temp.build, tiles = {"mcl_core_stonebrick_carved.png"}, stack_max = 64, drop = "mcl_core:stonebrickcarved", groups = {pickaxey=1, stone=1, stonebrick=1, building_block=1, material_stone=1}, sounds = mcl_sounds.node_sound_stone_defaults(), is_ground_content = false, _mcl_blast_resistance = 6, _mcl_hardness = 1.5, }) -- -- on map generation, try to build a settlement -- local function build_a_settlement(minp, maxp, pr) minetest.log("action","[mcl_villages] Building village at mapchunk " .. minetest.pos_to_string(minp) .. "..." .. minetest.pos_to_string(maxp)) local pr = pr or PseudoRandom(mcl_mapgen.get_block_seed3(minp)) local plan = create_site_plan(minp, maxp, pr) if not plan then return end paths(plan, minp, maxp) terraform(plan, minp, maxp, pr) place_schematics(plan, pr) table.insert(villages, minp) storage:set_string("villages", minetest.serialize(villages)) end -- Disable natural generation in singlenode. if mg_name ~= "singlenode" then local mg_name = minetest.get_mapgen_setting("mg_name") local scan_last_node = mcl_mapgen.LAST_BLOCK * mcl_mapgen.BS - 1 local scan_offset = mcl_mapgen.BS mcl_mapgen.register_mapgen(function(minp, maxp, chunkseed) if minp.y < minp_min then return end local pr = PseudoRandom(chunkseed * random_multiply + random_offset) local random_number = pr:next(1, chance_per_chunk) local noise = mcl_structures_get_perlin_noise_level(minp) * noise_multiplier if (random_number + noise) < struct_threshold then return end local min, max = 9999999, -9999999 for i = 1, pr:next(5,10) do local surface_point = find_surface( vector.add( vector.new( pr:next(scan_offset, scan_last_node), 0, pr:next(scan_offset, scan_last_node) ), minp ), minp, maxp ) if not surface_point then return end local y = surface_point.y min = math_min(y, min) max = math_max(y, max) end local height_difference = max - min if height_difference > max_height_difference then return end build_a_settlement(minp, maxp, chunkkseed) end, mcl_mapgen.order.VILLAGES) end function mcl_villages.get_villages() return villages end