From eaaa2a4dd2f82cc1d3f09468a9cad499ac9bcd82 Mon Sep 17 00:00:00 2001 From: kno10 Date: Tue, 3 Sep 2024 17:16:36 +0200 Subject: [PATCH] clean up villages code, add biome farming support --- mods/MAPGEN/mcl_villages/api.lua | 141 +++----------- mods/MAPGEN/mcl_villages/buildings.lua | 248 +++++++++---------------- mods/MAPGEN/mcl_villages/const.lua | 30 ++- mods/MAPGEN/mcl_villages/init.lua | 188 +++++-------------- mods/MAPGEN/mcl_villages/paths.lua | 211 ++++++++++----------- mods/MAPGEN/mcl_villages/utils.lua | 77 +++----- 6 files changed, 313 insertions(+), 582 deletions(-) diff --git a/mods/MAPGEN/mcl_villages/api.lua b/mods/MAPGEN/mcl_villages/api.lua index 870d8910c..d8f248bf4 100644 --- a/mods/MAPGEN/mcl_villages/api.lua +++ b/mods/MAPGEN/mcl_villages/api.lua @@ -6,39 +6,28 @@ mcl_villages.schematic_wells = {} mcl_villages.on_village_placed = {} mcl_villages.on_villager_placed = {} mcl_villages.mandatory_buildings = {} -mcl_villages.forced_blocks = {} local S = minetest.get_translator(minetest.get_current_modname()) local function job_count(schem_lua) - -- Local copy so we don't trash the schema for other uses, because apparently - -- there isn't a non-destructive way to count occurrences of a string :( - local str = schem_lua local count = 0 - for _, n in pairs(mobs_mc.jobsites) do if string.find(n, "^group:") then if n == "group:cauldron" then - count = count + select(2, string.gsub(str, '"mcl_cauldrons:cauldron', "")) + count = count + select(2, string.gsub(schem_lua, '"mcl_cauldrons:cauldron', "")) else local name = string.sub(n, 6, -1) - local num = select(2, string.gsub(str, name, "")) + local num = select(2, string.gsub(schem_lua, name, "")) if num then - minetest.log( - "info", - string.format("[mcl_villages] Guessing how to handle %s counting it as %d job sites", name, num) - ) + minetest.log("info", string.format("[mcl_villages] Guessing how to handle %s counting it as %d job sites", name, num)) count = count + num else - minetest.log( - "warning", - string.format("[mcl_villages] Don't know how to handle group %s counting it as 1 job site", n) - ) + minetest.log("warning", string.format("[mcl_villages] Don't know how to handle group %s counting it as 1 job site", n)) count = count + 1 end end else - count = count + select(2, string.gsub(str, '{name="' .. n .. '"', "")) + count = count + select(2, string.gsub(schem_lua, '{name="' .. n .. '"', "")) end end @@ -62,18 +51,13 @@ local all_optional = { "yadjust", "no_ground_turnip", "no_clearance" } local function set_all_optional(record, data) for _, field in ipairs(all_optional) do - if record[field] then - data[field] = record[field] - end + if record[field] then data[field] = record[field] end end end local function set_mandatory(record, type) if record['is_mandatory'] then - if not mcl_villages.mandatory_buildings[type] then - mcl_villages.mandatory_buildings[type] = {} - end - + if not mcl_villages.mandatory_buildings[type] then mcl_villages.mandatory_buildings[type] = {} end table.insert(mcl_villages.mandatory_buildings[type], record["name"]) end end @@ -105,23 +89,15 @@ function mcl_villages.register_building(record) local data = load_schema(record["name"], record["mts"]) set_all_optional(record, data) - for _, field in ipairs(optional_fields) do - if record[field] then - data[field] = record[field] - end + if record[field] then data[field] = record[field] end end - -- Local copy so we don't trash the schema for other uses local str = data["schem_lua"] local num_beds = select(2, string.gsub(str, '"mcl_beds:bed_[^"]+_bottom"', "")) - - if num_beds > 0 then - data["num_beds"] = num_beds - end + if num_beds > 0 then data["num_beds"] = num_beds end local job_count = job_count(data["schem_lua"]) - if job_count > 0 then data["num_jobs"] = job_count table.insert(mcl_villages.schematic_jobs, data) @@ -132,102 +108,42 @@ function mcl_villages.register_building(record) end end -local supported_crop_types = { - "grain", - "root", - "gourd", - "bush", - "tree", - "flower", -} - local crop_list = {} - -function mcl_villages.default_crop() - return "mcl_farming:wheat_1" -end - -local weighted_crops = {} - -local function adjust_weights(biome, crop_type) - if weighted_crops[biome] == nil then - weighted_crops[biome] = {} +function mcl_villages.register_crop(crop_def) + local crops = crop_list[crop_def.type] or {} + for biome, weight in pairs(crop_def.biomes) do + if crops[biome] == nil then crops[biome] = {} end + crops[biome][crop_def.node] = weight end - - weighted_crops[biome][crop_type] = {} - - local factor = 100 / crop_list[biome][crop_type]["total_weight"] - local total = 0 - - for node, weight in pairs(crop_list[biome][crop_type]) do - if node ~= "total_weight" then - total = total + (math.round(weight * factor)) - table.insert(weighted_crops[biome][crop_type], { total = total, node = node }) - end - end - - table.sort(weighted_crops[biome][crop_type], function(a, b) - return a.total < b.total - end) + crop_list[crop_def.type] = crops end function mcl_villages.get_crop_types() - return table.copy(supported_crop_types) -end - -function mcl_villages.get_crops() - return table.copy(crop_list) + local ret = {} + for k, _ in pairs(crop_list) do + table.insert(ret, k) + end + return ret end function mcl_villages.get_weighted_crop(biome, crop_type, pr) - if weighted_crops[biome] == nil then - biome = "plains" - end + local crops = crop_list[crop_type] + if not crops then return end -- unknown crop + local crops = crops[biome] or crops["plains"] - if weighted_crops[biome][crop_type] == nil then - return - end - - local rand = pr:next(1, 99) - - for i, rec in ipairs(weighted_crops[biome][crop_type]) do - local weight = rec.total - local node = rec.node + local total = 0 + for _, weight in pairs(crops) do total = total + weight end + local rand = pr:next(0, 1e7) * 1e-7 * total + for node, weight in pairs(crops) do if rand <= weight then return node end + rand = rand - weight end - return end -function mcl_villages.register_crop(crop_def) - - local node = crop_def.node - local crop_type = crop_def.type - - if table.indexof(supported_crop_types, crop_type) == -1 then - minetest.log("warning", S("Crop type @1 is not supported", crop_type)) - return - end - - for biome, weight in pairs(crop_def.biomes) do - - if crop_list[biome] == nil then - crop_list[biome] = {} - end - - if crop_list[biome][crop_type] == nil then - crop_list[biome][crop_type] = { total_weight = 0 } - end - - crop_list[biome][crop_type][node] = weight - crop_list[biome][crop_type]["total_weight"] = crop_list[biome][crop_type]["total_weight"] + weight - adjust_weights(biome, crop_type) - end -end - function mcl_villages.register_on_village_placed(func) table.insert(mcl_villages.on_village_placed, func) end @@ -235,3 +151,4 @@ end function mcl_villages.register_on_villager_spawned(func) table.insert(mcl_villages.on_villager_placed, func) end + diff --git a/mods/MAPGEN/mcl_villages/buildings.lua b/mods/MAPGEN/mcl_villages/buildings.lua index 08acd7c79..c8f3b250f 100644 --- a/mods/MAPGEN/mcl_villages/buildings.lua +++ b/mods/MAPGEN/mcl_villages/buildings.lua @@ -2,59 +2,8 @@ local min_jobs = tonumber(minetest.settings:get("mcl_villages_min_jobs")) or 1 local max_jobs = tonumber(minetest.settings:get("mcl_villages_max_jobs")) or 12 local placement_priority = minetest.settings:get("mcl_villages_placement_priority") or "random" -local foundation_materials = {} -foundation_materials["mcl_core:sand"] = "mcl_core:sandstone" -foundation_materials["mcl_core:redsand"] = "mcl_core:redsandstone" - local S = minetest.get_translator(minetest.get_current_modname()) --- initialize settlement_info -function mcl_villages.initialize_settlement_info(pr) - local count_buildings = { - number_of_jobs = pr:next(min_jobs, max_jobs), - num_jobs = 0, - num_beds = 0, - } - return count_buildings -end - -------------------------------------------------------------------------------- --- evaluate settlement_info and place schematics -------------------------------------------------------------------------------- -local function spawn_cats(pos) - local sp=minetest.find_nodes_in_area_under_air(vector.offset(pos,-20,-20,-20),vector.offset(pos,20,20,20),{"group:opaque"}) - for _ = 1, math.random(3) do - local v = minetest.add_entity(vector.offset(sp[math.random(#sp)],0,1,0),"mobs_mc:cat") - if v and v:get_luaentity() then v:get_luaentity()._home = pos end - end -end - -local function init_nodes(p1, p2, pr) - vl_structures.construct_nodes(p1, p2, { - "mcl_itemframes:item_frame", - "mcl_itemframes:glow_item_frame", - "mcl_furnaces:furnace", - "mcl_anvils:anvil", - "mcl_books:bookshelf", - "mcl_armor_stand:armor_stand", - -- "mcl_smoker:smoker", - -- "mcl_barrels:barrel_closed", - -- "mcl_blast_furnace:blast_furnace", - -- "mcl_brewing:stand_000", - }) - - -- Support mods with custom job sites - local job_sites = minetest.find_nodes_in_area(p1, p2, mobs_mc.jobsites) - for _, v in pairs(job_sites) do - vl_structures.init_node_construct(v) - end - - local nodes = vl_structures.construct_nodes(p1, p2, {"mcl_chests:chest_small", "mcl_chests:chest" }) or {} - for p=1, #nodes do - mcl_villages.fill_chest(nodes[p], pr) - end -end - local function add_building(settlement, building, count_buildings) table.insert(settlement, building) count_buildings[building.name] = (count_buildings[building.name] or 0) + 1 @@ -138,7 +87,7 @@ function mcl_villages.create_site_plan(vm, minp, maxp, pr) local settlement = {} -- initialize all settlement_info table - local count_buildings = mcl_villages.initialize_settlement_info(pr) + local count_buildings = { num_jobs = 0, num_beds = 0, target_jobs = pr:next(min_jobs, max_jobs) } -- first building is townhall in the center local bindex = pr:next(1, #mcl_villages.schematic_bells) @@ -151,13 +100,13 @@ function mcl_villages.create_site_plan(vm, minp, maxp, pr) end end - while count_buildings.num_jobs < count_buildings.number_of_jobs do + while count_buildings.num_jobs < count_buildings.target_jobs do local rindex = pr:next(1, #mcl_villages.schematic_jobs) local building_info = mcl_villages.schematic_jobs[rindex] if - (building_info.min_jobs == nil or count_buildings.number_of_jobs >= building_info.min_jobs) - and (building_info.max_jobs == nil or count_buildings.number_of_jobs <= building_info.max_jobs) + (building_info.min_jobs == nil or count_buildings.target_jobs >= building_info.min_jobs) + and (building_info.max_jobs == nil or count_buildings.target_jobs <= building_info.max_jobs) and ( building_info.num_others == nil or (count_buildings[building_info.group or building_info.name] or 0) == 0 @@ -180,8 +129,8 @@ function mcl_villages.create_site_plan(vm, minp, maxp, pr) local building_info = mcl_villages.schematic_houses[rindex] if - (building_info.min_jobs == nil or count_buildings.number_of_jobs >= building_info.min_jobs) - and (building_info.max_jobs == nil or count_buildings.number_of_jobs <= building_info.max_jobs) + (building_info.min_jobs == nil or count_buildings.target_jobs >= building_info.min_jobs) + and (building_info.max_jobs == nil or count_buildings.target_jobs <= building_info.max_jobs) and ( building_info.num_others == nil or (count_buildings[building_info.group or building_info.name] or 0) == 0 @@ -212,12 +161,32 @@ function mcl_villages.create_site_plan(vm, minp, maxp, pr) return layout_town(vm, minp, maxp, pr, settlement) end +local function init_nodes(p1, p2, pr) + vl_structures.construct_nodes(p1, p2, { + "mcl_itemframes:item_frame", + "mcl_itemframes:glow_item_frame", + "mcl_furnaces:furnace", + "mcl_anvils:anvil", + "mcl_books:bookshelf", + "mcl_armor_stand:armor_stand", + -- jobsite: "mcl_smoker:smoker", + -- jobsite: "mcl_barrels:barrel_closed", + -- jobsite: "mcl_blast_furnace:blast_furnace", + -- jobsite: "mcl_brewing:stand_000", + }) + -- Support mods with custom job sites + local job_sites = minetest.find_nodes_in_area(p1, p2, mobs_mc.jobsites) + for _, v in pairs(job_sites) do vl_structures.init_node_construct(v) end + + local nodes = vl_structures.construct_nodes(p1, p2, {"mcl_chests:chest_small", "mcl_chests:chest" }) + for _, n in pairs(nodes) do mcl_villages.fill_chest(n, pr) end +end + +-- important: the vm will be written and then is outdated! function mcl_villages.place_schematics(vm, settlement, blockseed, pr) - -- local vm = VoxelManip() + -- first building is always the bell local bell_pos = vector.offset(settlement[1].minp, math.floor(settlement[1].size.x/2), 0, math.floor(settlement[1].size.z/2)) - local bell_center_pos - local bell_center_node_type for i, building in ipairs(settlement) do local minp, cpos, maxp, size, rotation = building.minp, building.pos, building.maxp, building.size, building.rotation @@ -232,87 +201,60 @@ function mcl_villages.place_schematics(vm, settlement, blockseed, pr) local schematic = loadstring(schem_lua)() -- the foundation and air space for the building was already built before - -- vm:read_from_map(vector.new(minp.x, minp.y, minp.z), vector.new(maxp.x, maxp.y, maxp.z)) - -- vm:get_data() - -- now added in placement code already, pos has the primary height if (building.yadjust or 0) ~= 0 then minp = vector.offset(minp, 0, building.yadjust, 0) end -- minetest.log("debug", "placing schematics for "..building.name.." at "..minetest.pos_to_string(minp).." on "..surface_material) - minetest.place_schematic_on_vmanip( - vm, - minp, - schematic, - rotation, - { ["mcl_core:dirt_with_grass"]=schematic.surface_mat or "mcl_core:dirt" }, - true, - { place_center_x = false, place_center_y = false, place_center_z = false } - ) - -- to help pathing, increase the height of no_path areas - local p = vector.zero() - for z = minp.z,maxp.z do - p.z = z - for x = minp.x,maxp.x do - p.x = x - for y = minp.y,maxp.y-1 do - p.y = y - local n = vm:get_node_at(p) - if n and n.name == "mcl_villages:no_paths" then - p.y = y+1 - n = vm:get_node_at(p) - if n and n.name == "air" then - vm:set_node_at(p, {name="mcl_villages:no_paths"}) - end - end - end - end - end + minetest.place_schematic_on_vmanip(vm, minp, schematic, rotation, nil, true, { place_center_x = false, place_center_y = false, place_center_z = false }) mcl_villages.store_path_ends(vm, minp, maxp, cpos, blockseed, bell_pos) - - if building.name == "belltower" then -- TODO: allow multiple types? - bell_center_pos = cpos - local center_node = vm:get_node_at(cpos) - bell_center_node_type = center_node.name - end + mcl_villages.increase_no_paths(vm, minp, maxp) -- help the path finder end vm:write_to_map(true) -- for path finder and light - local biome_data = minetest.get_biome_data(bell_pos) - local biome_name = minetest.get_biome_name(biome_data.biome) - mcl_villages.paths(blockseed, biome_name) - + -- Path planning and placement + mcl_villages.paths(blockseed, minetest.get_biome_name(minetest.get_biome_data(bell_pos).biome)) + -- Clean up paths and initialize nodes for i, building in ipairs(settlement) do + mcl_villages.clean_no_paths(building.minp, building.maxp) init_nodes(building.minp, building.maxp, pr) end - -- this will run delayed actions, such as spawning mobs - minetest.set_node(bell_center_pos, { name = "mcl_villages:village_block" }) - local meta = minetest.get_meta(bell_center_pos) + -- Replace center block with a temporary block, which will be used run delayed actions + local block_name = minetest.get_node(bell_pos).name -- to restore the node afterwards + minetest.swap_node(bell_pos, { name = "mcl_villages:village_block" }) + local meta = minetest.get_meta(bell_pos) + meta:set_string("node_type", block_name) meta:set_string("blockseed", blockseed) - meta:set_string("node_type", bell_center_node_type) - meta:set_string("infotext", S("The timer for this @1 has not run yet!", bell_center_node_type)) - minetest.get_node_timer(bell_center_pos):start(1.0) - - -- read back any changes (fixme: would be better if we would not need this often. - --local emin, emax = vm:get_emerged_area() - --vm:read_from_map(emin, emax) + meta:set_string("infotext", S("The timer for this village has not run yet!")) + minetest.get_node_timer(bell_pos):start(1.0) end +minetest.register_node("mcl_villages:village_block", { + drawtype = "glasslike", + groups = { not_in_creative_inventory = 1 }, + light_source = 14, -- This is a light source so that lamps don't get placed near it + -- Somethings don't work reliably when done in the map building + -- so we use a timer to run them later when they work more reliably + -- e.g. spawning mobs, running minetest.find_path + on_timer = function(pos, _) + local meta = minetest.get_meta(pos) + minetest.swap_node(pos, { name = meta:get_string("node_type") }) + mcl_villages.post_process_village(meta:get_string("blockseed")) + return false + end, +}) + function mcl_villages.post_process_village(blockseed) local village_info = mcl_villages.get_village(blockseed) - if not village_info then - return - end + if not village_info then return end -- minetest.log("Postprocessing village") local settlement_info = village_info.data - local jobs = {} - local beds = {} + local jobs, beds = {}, {} - local bell_pos = vector.copy(settlement_info[1]["pos"]) - local bell = vector.offset(bell_pos, 0, 2, 0) - local biome_data = minetest.get_biome_data(bell_pos) - local biome_name = minetest.get_biome_name(biome_data.biome) - --mcl_villages.paths(blockseed, biome_name) + local bell_pos = vector.copy(settlement_info[1].pos) + local bell = vector.offset(bell_pos, 0, 1, 0) + local biome_name = minetest.get_biome_name(minetest.get_biome_data(bell_pos).biome) + -- Spawn Golem local l = minetest.add_entity(bell, "mobs_mc:iron_golem"):get_luaentity() if l then l._home = bell @@ -320,45 +262,41 @@ function mcl_villages.post_process_village(blockseed) minetest.log("info", "Could not create a golem!") end - spawn_cats(bell) + -- Spawn cats + local sp = minetest.find_nodes_in_area_under_air(vector.offset(bell, -20, -10, -20),vector.offset(bell, 20, 10, 20), { "group:opaque" }) + for _ = 1, math.random(3) do + local v = minetest.add_entity(vector.offset(sp[math.random(#sp)], 0, 1, 0), "mobs_mc:cat") + if v and v:get_luaentity() then + v:get_luaentity()._home = bell_pos -- help them stay local + else + minetest.log("info", "Could not spawn a cat") + break + end + end + -- collect beds and job sites for _, building in pairs(settlement_info) do - local has_beds = building["num_beds"] and building["num_beds"] ~= nil - local has_jobs = building["num_jobs"] and building["num_jobs"] ~= nil - - local minp, maxp = building["minp"], building["maxp"] - - if has_jobs then + local minp, maxp = building.minp, building.maxp + if building.num_jobs then local jobsites = minetest.find_nodes_in_area(minp, maxp, mobs_mc.jobsites) - - for _, job_pos in pairs(jobsites) do - table.insert(jobs, job_pos) - end + for _, job_pos in pairs(jobsites) do table.insert(jobs, job_pos) end end - if has_beds then + if building.num_beds then local bld_beds = minetest.find_nodes_in_area(minp, maxp, { "group:bed" }) - for _, bed_pos in pairs(bld_beds) do - local bed_node = minetest.get_node(bed_pos) - local bed_group = minetest.get_item_group(bed_node.name, "bed") - - -- We only spawn at bed bottoms - -- 1 is bottom, 2 is top - if bed_group == 1 then - table.insert(beds, bed_pos) - end + local bed_group = minetest.get_item_group(minetest.get_node(bed_pos).name, "bed") + -- We only spawn at bed bottoms, 1 is bottom, 2 is top + if bed_group == 1 then table.insert(beds, bed_pos) end end end end + -- TODO: shuffle jobs? -- minetest.log("beds: "..#beds.." jobsites: "..#jobs) if beds then for _, bed_pos in pairs(beds) do - local res = minetest.forceload_block(bed_pos, true) - if res then - mcl_villages.forced_blocks[minetest.pos_to_string(bed_pos)] = minetest.get_us_time() - end + minetest.forceload_block(bed_pos, true) local m = minetest.get_meta(bed_pos) m:set_string("bell_pos", minetest.pos_to_string(bell_pos)) if m:get_string("villager") == "" then @@ -371,18 +309,13 @@ function mcl_villages.post_process_village(blockseed) m:set_string("infotext", S("A villager sleeps here")) local job_pos = table.remove(jobs, 1) - if job_pos then - villager_employ(l, job_pos) -- HACK: merge more MCLA villager code - end - - for _, callback in pairs(mcl_villages.on_villager_placed) do - callback(v, blockseed) - end + if job_pos then villager_employ(l, job_pos) end -- HACK: merge more MCLA villager job code? + for _, callback in pairs(mcl_villages.on_villager_placed) do callback(v, blockseed) end else minetest.log("info", "Could not create a villager!") end else - minetest.log("info", "bed already owned by " .. m:get_string("villager")) + minetest.log("info", "bed already owned by " .. m:get_string("villager")) -- should not happen unless villages overlap end end end @@ -395,7 +328,8 @@ function mcl_villages.terraform(vm, settlement, pr) for i, building in ipairs(settlement) do if not building.no_clearance then local pos, size = building.pos, building.size - pos = vector.offset(pos, -math.floor(size.x/2), 0, -math.floor(size.z/2)) + pos = vector.offset(pos, -math.floor((size.x-1)/2), 0, -math.floor((size.z-1)/2)) + -- TODO: allow different clearance for different buildings? vl_terraforming.clearance_vm(vm, pos.x-1, pos.y, pos.z-1, size.x+2, size.y, size.z+2, 2, building.surface_mat, building.dust_mat, pr) end end @@ -403,8 +337,8 @@ function mcl_villages.terraform(vm, settlement, pr) if not building.no_ground_turnip then local pos, size = building.pos, building.size local surface_mat = building.surface_mat - local platform_mat = building.platform_mat or { name = foundation_materials[surface_mat.name] or "mcl_core:dirt" } - local stone_mat = building.stone_mat or { name = "mcl_core:stone" } + local platform_mat = building.platform_mat or { name = mcl_villages.foundation_materials[surface_mat.name] or "mcl_core:dirt" } + local stone_mat = building.stone_mat or { name = mcl_villages.stone_materials[surface_mat.name] or "mcl_core:stone" } local dust_mat = building.dust_mat building.platform_mat = platform_mat -- remember for use in schematic placement building.stone_mat = stone_mat diff --git a/mods/MAPGEN/mcl_villages/const.lua b/mods/MAPGEN/mcl_villages/const.lua index 33ac64301..310582392 100644 --- a/mods/MAPGEN/mcl_villages/const.lua +++ b/mods/MAPGEN/mcl_villages/const.lua @@ -1,7 +1,8 @@ --- switch for debugging -function mcl_villages.debug(message) - minetest.log("verbose", "[mcl_villages] "..message) -end +-- maximum allowed difference in height for building a settlement +mcl_villages.max_height_difference = 56 + +-- legacy type in old schematics +minetest.register_alias("mcl_villages:stonebrickcarved", "mcl_core:stonebrickcarved") -- possible surfaces where buildings can be built mcl_villages.surface_mat = {} @@ -30,10 +31,15 @@ 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 --- maximum allowed difference in height for building a settlement -mcl_villages.max_height_difference = 56 +-- substitute foundation materials +mcl_villages.foundation_materials = {} +mcl_villages.foundation_materials["mcl_core:sand"] = "mcl_core:sandstone" +mcl_villages.foundation_materials["mcl_core:redsand"] = "mcl_core:redsandstone" -mcl_villages.half_map_chunk_size = 40 +-- substitute stone materials in foundation +mcl_villages.stone_materials = {} + +mcl_villages.default_crop = "mcl_farming:wheat_1" -- -- Biome based block substitutions @@ -119,16 +125,6 @@ mcl_villages.vl_to_mcla = { { '"mcl_bamboo:bamboo_door', '"mcl_doors:door_bamboo'}, } mcl_villages.mcla_to_vl = { - -- oneway - --{ '"mcl_villages:no_paths"', '"air"'}, -- TODO: support these - --{ '"mcl_villages:path_endpoint"', '"air"'}, -- TODO: support these - { '"mcl_villages:crop_root', '"mcl_farming:potato'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_grain', '"mcl_farming:wheat'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_gourd', '"mcl_farming:pumpkin'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_flower_0"', '"mcl_flowers:tulip_red"'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_flower_1"', '"mcl_flowers:tulip_orange"'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_flower_2"', '"mcl_flowers:tulip_pink"'}, -- TODO: support biome specific farming - { '"mcl_villages:crop_flower_3"', '"mcl_flowers:tulip_white"'}, -- TODO: support biome specific farming -- bidirectional { '"mcl_trees:tree_oak"', '"mcl_core:tree"'}, { '"mcl_trees:tree_dark_oak"', '"mcl_core:darktree"'}, diff --git a/mods/MAPGEN/mcl_villages/init.lua b/mods/MAPGEN/mcl_villages/init.lua index 3e940a290..f91cadc71 100644 --- a/mods/MAPGEN/mcl_villages/init.lua +++ b/mods/MAPGEN/mcl_villages/init.lua @@ -11,37 +11,23 @@ dofile(mcl_villages.modpath.."/api.lua") local S = minetest.get_translator(minetest.get_current_modname()) -minetest.register_alias("mcl_villages:stonebrickcarved", "mcl_core:stonebrickcarved") -minetest.register_node("mcl_villages:structblock", {drawtype="airlike",groups = {not_in_creative_inventory=1},}) --- we currently do not support/use these from MCLA: ---minetest.register_alias("mcl_villages:village_block", "air") ---minetest.register_alias("mcl_villages:building_block", "air") --- --- on map generation, try to build a settlement --- -local function build_a_settlement(minp, maxp, blockseed) - if mcl_villages.village_exists(blockseed) then return end - local pr = PcgRandom(blockseed) - local vm = VoxelManip(minp, maxp) - local settlement = mcl_villages.create_site_plan(vm, minp, maxp, pr) +local function ecb_village(blockpos, action, calls_remaining, param) + if calls_remaining >= 1 then return end + if mcl_villages.village_exists(param.blockseed) then return end + local pr = PcgRandom(param.blockseed) + local vm = VoxelManip(param.minp, param.maxp) + local settlement = mcl_villages.create_site_plan(vm, param.minp, param.maxp, pr) if not settlement then return false, false end -- all foundations first, then all buildings, to avoid damaging very close buildings mcl_villages.terraform(vm, settlement, pr) - mcl_villages.place_schematics(vm, settlement, blockseed, pr) - mcl_villages.add_village(blockseed, settlement) - --lvm:write_to_map(true) -- destory paths as of now - --mcl_villages.paths(blockseed) -- TODO: biome + mcl_villages.place_schematics(vm, settlement, param.blockseed, pr) + mcl_villages.add_village(param.blockseed, settlement) + --lvm:write_to_map(true) -- destorys paths as of now, as they are placed afterwards for _, on_village_placed_callback in pairs(mcl_villages.on_village_placed) do - on_village_placed_callback(settlement, blockseed) + on_village_placed_callback(settlement, param.blockseed) end end -local function ecb_village(blockpos, action, calls_remaining, param) - if calls_remaining >= 1 then return end - local minp, maxp, blockseed = param.minp, param.maxp, param.blockseed - build_a_settlement(minp, maxp, blockseed) -end - -- Disable natural generation in singlenode. local mg_name = minetest.get_mapgen_setting("mg_name") if mg_name ~= "singlenode" then @@ -50,87 +36,24 @@ if mg_name ~= "singlenode" then if village_boost == 0 then return end local pr = PcgRandom(blockseed) if pr:next(0,1e9) * 100e-9 >= village_boost then return end - local big_minp = vector.copy(minp) --vector.offset(minp, -16, -16, -16) - local big_maxp = vector.copy(maxp) --vector.offset(maxp, 16, 16, 16) - minetest.emerge_area(big_minp, big_maxp, ecb_village, - { minp = vector.copy(minp), maxp = vector.copy(maxp), blockseed = blockseed } - ) - end) - --[[ did not work, because later structure generation would make holes in our schematics - mcl_mapgen_core.register_generator("villages", function(lvm, data, data2, e1, e2, area, minp, maxp, blockseed) - if mcl_villages.village_exists(blockseed) then return false, false end - - lvm:set_data(data) -- FIXME: ugly hack, better directly manipulate the data array - lvm:set_param2_data(data2) - local pr = PcgRandom(blockseed) - if pr:next(0,1e9) * 100e-9 > village_boost then return end - local settlement = mcl_villages.create_site_plan(lvm, minp, maxp, pr) - if not settlement then return false, false end - - -- all foundations first, then all buildings, to avoid damaging very close buildings - mcl_villages.terraform(lvm, settlement, pr) - mcl_villages.place_schematics(lvm, settlement, pr) - -- TODO: replace with MCLA code: mcl_villages.paths(settlement) - mcl_villages.add_village(blockseed, settlement) - lvm:get_data(data) -- FIXME: ugly hack, better directly manipulate the data array - lvm:get_param2_data(data2) - return true, true - end, function(minp, maxp, blockseed) - for _, on_village_placed_callback in pairs(mcl_villages.on_village_placed) do - on_village_placed_callback(settlement, blockseed) + if village_boost < 25 then -- otherwise, this tends to transitively emerge too much + minp, maxp = vector.offset(minp, -16, 0, -16), vector.offset(maxp, 16, 0, 16) end - end, 15000) - ]]-- - --[[ causes issues when vertically close to the chunk boundary: - mcl_mapgen_core.register_generator("villages", nil, function(minp, maxp, blockseed) - if maxp.y < 0 or mcl_villages.village_exists(blockseed) then return end - local pr = PcgRandom(blockseed) - if pr:next(0,1e9) * 10ee-9 > village_boost then return end - --local lvm, emin, emax = minetest.get_mapgen_object("voxelmanip") -- did not get the lighting fixed? - local lvm = VoxelManip() - lvm:read_from_map(minp, maxp) - local settlement = mcl_villages.create_site_plan(lvm, minp, maxp, pr) - if not settlement then return false, false end - -- all foundations first, then all buildings, to avoid damaging very close buildings - mcl_villages.terraform(lvm, settlement, pr) - mcl_villages.place_schematics(lvm, settlement, blockseed, pr) - mcl_villages.add_village(blockseed, settlement) - --lvm:write_to_map(true) - --mcl_villages.paths(blockseed) -- TODO: biome - end, 15000) - ]]-- + minetest.emerge_area(minp, maxp, ecb_village, { minp = minp, maxp = maxp, blockseed = blockseed }) + end) end --- This is a light source so that lamps don't get placed near it -minetest.register_node("mcl_villages:village_block", { - drawtype = "airlike", - groups = { not_in_creative_inventory = 1 }, - light_source = 14, - - -- Somethings don't work reliably when done in the map building - -- so we use a timer to run them later when they work more reliably - -- e.g. spawning mobs, running minetest.find_path - on_timer = function(pos, _) - local meta = minetest.get_meta(pos) - local blockseed = meta:get_string("blockseed") - local node_type = meta:get_string("node_type") - minetest.set_node(pos, { name = node_type }) - mcl_villages.post_process_village(blockseed) - return false - end, -}) - --- struct to cause a village spawn when it can be fully emerged +-- Handle legacy structblocks that are not fully emerged yet. +minetest.register_node("mcl_villages:structblock", {drawtype="airlike",groups = {not_in_creative_inventory=1}}) minetest.register_lbm({ name = "mcl_villages:structblock", run_at_every_load = true, nodenames = {"mcl_villages:structblock"}, action = function(pos, node) minetest.set_node(pos, {name = "air"}) - local minp=vector.offset(pos, -40, -40, -40) - local maxp=vector.offset(pos, 40, 40, 40) + local minp, maxp = vector.offset(pos, -40, -40, -40), vector.offset(pos, 40, 40, 40) local blockseed = PcgRandom(minetest.hash_node_position(pos)):next() - minetest.emerge_area(minp, maxp, ecb_village, {minp=minp, maxp=maxp, blockseed=blockseed}) + minetest.emerge_area(minp, maxp, ecb_village, { minp = minp, maxp = maxp, blockseed = blockseed}) end }) @@ -146,24 +69,21 @@ if minetest.is_creative_enabled("") 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, mcl_villages.half_map_chunk_size) - local maxp = vector.add(pointed_thing.under, mcl_villages.half_map_chunk_size) - build_a_settlement(minp, maxp, math.random(0,32767)) + local pos = pointed_thing.under + local minp, maxp = vector.offset(pos, -40, -40, -40), vector.offset(pos, 40, 40, 40) + local blockseed = PcgRandom(minetest.hash_node_position(pos)):next() + minetest.emerge_area(minp, maxp, ecb_village, { minp = minp, maxp = maxp, blockseed = blockseed }) end }) mcl_wip.register_experimental_item("mcl_villages:tool") end -- This makes the temporary node invisble unless in creative mode -local drawtype = "airlike" -if minetest.is_creative_enabled("") then - drawtype = "glasslike" -end +local drawtype = minetest.is_creative_enabled("") and "glasslike" or "airlike" +-- Special node for schematics editing: no path on this place minetest.register_node("mcl_villages:no_paths", { - description = S( - "Prevent paths from being placed during villager generation. Replaced by air after village path generation" - ), + description = S("Prevent paths from being placed during villager generation. Replaced by air after village path generation"), paramtype = "light", drawtype = drawtype, inventory_image = "mcl_core_barrier.png", @@ -173,6 +93,7 @@ minetest.register_node("mcl_villages:no_paths", { groups = { creative_breakable = 1, not_solid = 1, not_in_creative_inventory = 1 }, }) +-- Special node for schematics editing: path endpoint minetest.register_node("mcl_villages:path_endpoint", { description = S("Mark the node as a good place for paths to connect to"), is_ground_content = false, @@ -184,12 +105,7 @@ minetest.register_node("mcl_villages:path_endpoint", { paramtype = "light", sunlight_propagates = true, drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { - { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 }, - }, - }, + node_box = { type = "fixed", fixed = { { -0.5, -0.5, -0.5, 0.5, -0.45, 0.5 } } }, _mcl_hardness = 0.1, _mcl_blast_resistance = 0.1, }) @@ -412,31 +328,6 @@ mcl_villages.register_building({ yadjust = 1, }) -for _, crop_type in pairs(mcl_villages.get_crop_types()) do - for count = 1, 8 do - local tile = crop_type .. "_" .. count .. ".png" - minetest.register_node("mcl_villages:crop_" .. crop_type .. "_" .. count, { - description = S("A place to plant @1 crops", crop_type), - is_ground_content = false, - tiles = { tile }, - wield_image = tile, - wield_scale = { x = 1, y = 1, z = 0.5 }, - groups = { handy = 1, supported_node = 1, not_in_creative_inventory = 1 }, - paramtype = "light", - sunlight_propagates = true, - drawtype = "nodebox", - node_box = { - type = "fixed", - fixed = { - { -8 / 16, -8 / 16, -8 / 16, 8 / 16, -7 / 16, 8 / 16 }, - }, - }, - _mcl_hardness = 0.1, - _mcl_blast_resistance = 0.1, - }) - end -end - mcl_villages.register_crop({ type = "grain", node = "mcl_farming:wheat_1", @@ -512,8 +403,9 @@ mcl_villages.register_crop({ }, }) +-- TODO: make flowers biome-specific for name, def in pairs(minetest.registered_nodes) do - if def.groups["flower"] and not def.groups["double_plant"] and name ~= "mcl_flowers:wither_rose" then + if def.groups.flower and not def.groups.double_plant and name ~= "mcl_flowers:wither_rose" then mcl_villages.register_crop({ type = "flower", node = name, @@ -528,3 +420,25 @@ for name, def in pairs(minetest.registered_nodes) do }) end end + +-- Crop placeholder nodes at different growth stages, for designing schematics +for _, crop_type in ipairs(mcl_villages.get_crop_types()) do + for count = 1, 8 do + local tile = crop_type .. "_" .. count .. ".png" + minetest.register_node("mcl_villages:crop_" .. crop_type .. "_" .. count, { + description = S("A place to plant @1 crops", crop_type), + is_ground_content = false, + tiles = { tile }, + wield_image = tile, + wield_scale = { x = 1, y = 1, z = 0.5 }, + groups = { handy = 1, supported_node = 1, not_in_creative_inventory = 1 }, + paramtype = "light", + sunlight_propagates = true, + drawtype = "nodebox", + node_box = { type = "fixed", fixed = { { -0.5, -0.5, -0.5, 0.5, -0.45, 0.5 } } }, + _mcl_hardness = 0.1, + _mcl_blast_resistance = 0.1, + }) + end +end + diff --git a/mods/MAPGEN/mcl_villages/paths.lua b/mods/MAPGEN/mcl_villages/paths.lua index 4aa79341a..c3b4be963 100644 --- a/mods/MAPGEN/mcl_villages/paths.lua +++ b/mods/MAPGEN/mcl_villages/paths.lua @@ -1,7 +1,6 @@ ------------------------------------------------------------------------------- -- generate paths between buildings ------------------------------------------------------------------------------- - local light_threshold = tonumber(minetest.settings:get("mcl_villages_light_threshold")) or 5 -- This ends up being a nested table. @@ -10,8 +9,40 @@ local light_threshold = tonumber(minetest.settings:get("mcl_villages_light_thres -- 3rd is the pos of the end points local path_ends = {} +-- note: not using LVM here, as this runs after the pathfinder +-- simple function to increase "no_paths" walls +function mcl_villages.clean_no_paths(minp, maxp) + local no_paths_nodes = minetest.find_nodes_in_area(minp, maxp, { "mcl_villages:no_paths" }) + if #no_paths_nodes > 0 then + minetest.bulk_set_node(no_paths_nodes, { name = "air" }) + end +end + +-- this can still run in LVM +-- simple function to increase "no_paths" walls +function mcl_villages.increase_no_paths(vm, minp, maxp) + local p = vector.zero() + for z = minp.z, maxp.z do + p.z = z + for x = minp.x, maxp.x do + p.x = x + for y = minp.y, maxp.y - 1 do + p.y = y + local n = vm:get_node_at(p) + if n and n.name == "mcl_villages:no_paths" then + p.y = y + 1 + n = vm:get_node_at(p) + if n and n.name == "air" then + vm:set_node_at(p, {name = "mcl_villages:no_paths" }) + end + end + end + end + end +end + -- Insert end points in to the nested tables -function mcl_villages.store_path_ends(lvm, minp, maxp, pos, blockseed, bell_pos) +function mcl_villages.store_path_ends(vm, minp, maxp, pos, blockseed, bell_pos) -- We store by distance because we create paths far away from the bell first local dist = vector.distance(bell_pos, pos) local id = "block_" .. blockseed -- cannot use integers as keys @@ -21,20 +52,18 @@ function mcl_villages.store_path_ends(lvm, minp, maxp, pos, blockseed, bell_pos) path_ends[id] = tab end if tab[dist] == nil then tab[dist] = {} end - -- TODO: improve, use LVM data instead of nodes + -- TODO: use LVM data instead of nodes? But we only process a subset of the area local v = vector.zero() - local i = 0 - for zi = minp.z-2, maxp.z+2 do + for zi = minp.z, maxp.z do v.z = zi - for yi = minp.y-2, maxp.y+2 do + for yi = minp.y, maxp.y do v.y = yi - for xi = minp.x-2, maxp.x+2 do + for xi = minp.x, maxp.x do v.x = xi - local n = lvm:get_node_at(v) + local n = vm:get_node_at(v) if n and n.name == "mcl_villages:path_endpoint" then - i = i + 1 table.insert(tab[dist], minetest.pos_to_string(v)) - lvm:set_node_at(v, { name = "air" }) + vm:set_node_at(v, { name = "air" }) end end end @@ -47,16 +76,14 @@ local function place_lamp(pos, pr) local schem_lua = mcl_villages.substitute_materials(pos, schema.schem_lua, pr) local schematic = loadstring(schem_lua)() - minetest.place_schematic( - vector.offset(pos, 0, schema.yadjust or 0, 0), - schematic, - "0", + minetest.place_schematic(vector.offset(pos, 0, schema.yadjust or 0, 0), schematic, "0", {["air"] = "ignore"}, -- avoid destroying stairs etc. true, { place_center_x = true, place_center_y = false, place_center_z = true } ) end +-- TODO: port this to lvm. local function smooth_path(path) -- Smooth out bumps in path or stairs can look naf for pass = 1, 3 do @@ -64,10 +91,10 @@ local function smooth_path(path) local prev_y = path[i - 1].y local y = path[i].y local next_y = path[i + 1].y - local bump_node = minetest.get_node(path[i]) + local bump = minetest.get_node(path[i]).name - -- TODO: replace bamboo underneath with dirt here? - if minetest.get_item_group(bump_node.name, "water") ~= 0 then + -- TODO: also replace bamboo underneath with dirt here? + if minetest.get_item_group(bump, "water") ~= 0 then -- ignore in this pass elseif y >= next_y + 2 and y <= prev_y then minetest.swap_node(vector.offset(path[i], 0, -1, 0), { name = "air" }) @@ -94,25 +121,25 @@ local function smooth_path(path) end end +-- TODO: port this to lvm. local function place_path(path, pr, stair, slab) -- Smooth out bumps in path or stairs can look naf for i = 2, #path - 1 do local prev_y = path[i - 1].y local y = path[i].y local next_y = path[i + 1].y - local bump_node = minetest.get_node(path[i]) + local bump = minetest.get_node(path[i]).name - if minetest.get_item_group(bump_node.name, "water") ~= 0 then + if minetest.get_item_group(bump, "water") ~= 0 then -- Find air - local found_surface = false - local up_pos = path[i] - while not found_surface do - up_pos = vector.offset(up_pos, 0, 1, 0) - local up_node = minetest.get_node(up_pos) - if up_node and minetest.get_item_group(up_node.name, "water") == 0 then - found_surface = true + local up_pos = vector.copy(path[i]) + while true do + up_pos.y = up_pos.y + 1 + local up_node = minetest.get_node(up_pos).name + if minetest.get_item_group(up_node, "water") == 0 then minetest.swap_node(up_pos, { name = "air" }) path[i] = up_pos + break end end elseif y < prev_y and y < next_y then @@ -129,36 +156,32 @@ local function place_path(path, pr, stair, slab) end for i, pos in ipairs(path) do - -- replace decorations, grass and flowers, with air - local n0 = minetest.get_node(pos) - if n0.name ~= "air" then - minetest.swap_node(pos, { name = "air" }) - end + local n0 = minetest.get_node(pos).name + if n0 ~= "air" then minetest.swap_node(pos, { name = "air" }) end local under_pos = vector.offset(pos, 0, -1, 0) - local n = minetest.get_node(under_pos) + local n = minetest.get_node(under_pos).name + local ndef = minetest.registered_nodes[n] + local groups = ndef and ndef.groups or {} local done = false - local is_stair = minetest.get_item_group(n.name, "stair") ~= 0 - if i > 1 and pos.y > path[i - 1].y then -- stairs up - if not is_stair then + if not groups.stair then done = true local param2 = minetest.dir_to_facedir(vector.subtract(pos, path[i - 1])) minetest.swap_node(under_pos, { name = stair, param2 = param2 }) end elseif i < #path-1 and pos.y > path[i + 1].y then -- stairs down - if not is_stair then + if not groups.stair then done = true local param2 = minetest.dir_to_facedir(vector.subtract(pos, path[i + 1])) minetest.swap_node(under_pos, { name = stair, param2 = param2 }) end - elseif not is_stair and i > 1 and pos.y < path[i - 1].y then + elseif not groups.stair and i > 1 and pos.y < path[i - 1].y then -- stairs down - local n2 = minetest.get_node(vector.offset(path[i - 1], 0, -1, 0)) - is_stair = minetest.get_item_group(n2.name, "stair") ~= 0 - if not is_stair then + local n2 = minetest.get_node(vector.offset(path[i - 1], 0, -1, 0)).name + if not minetest.get_item_group(n2, "stair") then done = true local param2 = minetest.dir_to_facedir(vector.subtract(path[i - 1], pos)) if i < #path - 1 then -- uglier, but easier to walk up? @@ -167,11 +190,10 @@ local function place_path(path, pr, stair, slab) minetest.add_node(pos, { name = stair, param2 = param2 }) pos.y = pos.y + 1 end - elseif not is_stair and i < #path-1 and pos.y < path[i + 1].y then + elseif not groups.stair and i < #path-1 and pos.y < path[i + 1].y then -- stairs up - local n2 = minetest.get_node(vector.offset(path[i + 1], 0, -1, 0)) - is_stair = minetest.get_item_group(n2.name, "stair") ~= 0 - if not is_stair then + local n2 = minetest.get_node(vector.offset(path[i + 1], 0, -1, 0)).name + if not minetest.get_item_group(n2, "stair") then done = true local param2 = minetest.dir_to_facedir(vector.subtract(path[i + 1], pos)) if i > 1 then -- uglier, but easier to walk up? @@ -184,22 +206,19 @@ local function place_path(path, pr, stair, slab) -- flat if not done then - if minetest.get_item_group(n.name, "water") ~= 0 then + if groups.water then minetest.add_node(under_pos, { name = slab }) - elseif n.name == "mcl_core:sand" or n.name == "mcl_core:redsand" then + elseif groups.sand then minetest.swap_node(under_pos, { name = "mcl_core:sandstonesmooth2" }) - elseif minetest.get_item_group(n.name, "soil") > 0 - and minetest.get_item_group(n.name, "dirtifies_below_solid") == 0 - then - minetest.swap_node(under_pos, { name = "mcl_core:grass_path" }) + elseif groups.soil and not groups.dirtifies_below_solid then + minetest.swap_node(under_pos, { name = "mcl_core:grass_path" }) end end -- Clear space for villagers to walk for j = 1, 2 do local over_pos = vector.offset(pos, 0, j, 0) - local m = minetest.get_node(over_pos) - if m.name ~= "air" then + if minetest.get_node(over_pos).name ~= "air" then minetest.swap_node(over_pos, { name = "air" }) end end @@ -208,17 +227,15 @@ local function place_path(path, pr, stair, slab) -- Do lamps afterwards so we don't put them where a path will be laid for _, pos in ipairs(path) do if minetest.get_node_light(pos, 0) < light_threshold then - local nn = minetest.find_nodes_in_area_under_air( - vector.offset(pos, -1, -1, -1), - vector.offset(pos, 1, 1, 1), + local nn = minetest.find_nodes_in_area_under_air(vector.offset(pos, -1, -1, -1), vector.offset(pos, 1, 1, 1), { "group:material_sand", "group:material_stone", "group:grass_block", "group:wood_slab" } ) + -- todo: shuffle nn? for _, npos in ipairs(nn) do - local node = minetest.get_node(npos) - if node.name ~= "mcl_core:grass_path" and minetest.get_item_group(node.name, "stair") == 0 then - if minetest.get_item_group(node.name, "wood_slab") ~= 0 then - local over_pos = vector.offset(npos, 0, 1, 0) - minetest.add_node(over_pos, { name = "mcl_torches:torch", param2 = 1 }) + local node = minetest.get_node(npos).name + if node ~= "mcl_core:grass_path" and minetest.get_item_group(node, "stair") == 0 then + if minetest.get_item_group(node, "wood_slab") ~= 0 then + minetest.add_node(vector.offset(npos, 0, 1, 0), { name = "mcl_torches:torch", param2 = 1 }) else place_lamp(npos, pr) end @@ -229,20 +246,11 @@ local function place_path(path, pr, stair, slab) end end --- Work out which end points should be connected --- works from the outside of the village in -function mcl_villages.paths(blockseed, biome_name) - local pr = PseudoRandom(blockseed) - local pathends = path_ends["block_" .. blockseed] - - if pathends == nil then - minetest.log("warning", string.format("[mcl_villages] Tried to set paths for block seed that doesn't exist %d", blockseed)) - return - end - +-- FIXME: ugly +function get_biome_stair_slab(biome_name) -- Use the same stair and slab throughout the entire village + -- The quotes are necessary to be matched as JSON strings local stair, slab = '"mcl_stairs:stair_oak"', '"mcl_stairs:slab_oak_top"' - -- Change stair and slab for biome 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 @@ -255,44 +263,45 @@ function mcl_villages.paths(blockseed, biome_name) stair = stair:gsub(sub[1], sub[2]) slab = slab:gsub(sub[1], sub[2]) end - -- The quotes are to match what is in schemas, but we don't want them now - stair = stair:gsub('"', "") - slab = slab:gsub('"', "") + -- The quotes are to match what is in JSON schemas, but we don't want them now + return stair:gsub('"', ""), slab:gsub('"', "") +end +-- Work out which end points should be connected +-- works from the outside of the village in +function mcl_villages.paths(blockseed, biome_name) + local pr = PcgRandom(blockseed) + local pathends = path_ends["block_" .. blockseed] + if pathends == nil then + minetest.log("warning", string.format("[mcl_villages] Tried to set paths for block seed that doesn't exist %d", blockseed)) + return + end + + -- Stair and slab style of the village + local stair, slab = get_biome_stair_slab(biome_name) -- Keep track of connections local connected = {} -- get a list of reverse sorted keys, which are distances local dist_keys = {} - for k in pairs(pathends) do - table.insert(dist_keys, k) - end - table.sort(dist_keys, function(a, b) - return a > b - end) + for k in pairs(pathends) do table.insert(dist_keys, k) end + table.sort(dist_keys, function(a, b) return a > b end) --minetest.log("Planning paths with "..#dist_keys.." nodes") for i, from in ipairs(dist_keys) do -- ep == end_point for _, from_ep in ipairs(pathends[from]) do local from_ep_pos = minetest.string_to_pos(from_ep) - local closest_pos - local closest_bld - local best = 10000000 + local closest_pos, closest_bld, best = nil, nil, 10000000 -- Most buildings only do other buildings that are closer to the bell -- for the bell do any end points that don't have paths near them - local lindex = i + 1 - if i == 0 then - lindex = 1 - end - - for j = lindex, #dist_keys do + local j = from == 0 and 1 or (i + 1) + for j = j, #dist_keys do local to = dist_keys[j] if from ~= to and connected[from .. "-" .. to] == nil and connected[to .. "-" .. from] == nil then for _, to_ep in ipairs(pathends[to]) do local to_ep_pos = minetest.string_to_pos(to_ep) - local dist = vector.distance(from_ep_pos, to_ep_pos) if dist < best then best = dist @@ -315,10 +324,9 @@ function mcl_villages.paths(blockseed, biome_name) place_path(path, pr, stair, slab) connected[from .. "-" .. closest_bld] = 1 else - minetest.log( - "warning", + minetest.log("warning", string.format( - "[mcl_villages] No path from %s to %s, distance %d", + "[mcl_villages] No good path from %s to %s, distance %d", minetest.pos_to_string(from_ep_pos), minetest.pos_to_string(closest_pos), vector.distance(from_ep_pos, closest_pos) @@ -329,20 +337,5 @@ function mcl_villages.paths(blockseed, biome_name) end end - -- Loop again to blow away no path nodes - for _, from in ipairs(dist_keys) do - for _, from_ep in ipairs(pathends[from]) do - local from_ep_pos = minetest.string_to_pos(from_ep) - local no_paths_nodes = minetest.find_nodes_in_area( - vector.offset(from_ep_pos, -32, -32, -32), - vector.offset(from_ep_pos, 32, 32, 32), - { "mcl_villages:no_paths" } - ) - if #no_paths_nodes > 0 then - minetest.bulk_set_node(no_paths_nodes, { name = "air" }) - end - end - end - path_ends["block_" .. blockseed] = nil end diff --git a/mods/MAPGEN/mcl_villages/utils.lua b/mods/MAPGEN/mcl_villages/utils.lua index 44e2a0537..0b9d507be 100644 --- a/mods/MAPGEN/mcl_villages/utils.lua +++ b/mods/MAPGEN/mcl_villages/utils.lua @@ -1,4 +1,5 @@ -- check the minimum distance of two squares, on axes +-- TODO: make local in village planning code only? function mcl_villages.check_distance(settlement, cpos, sizex, sizez, limit) for i, building in ipairs(settlement) do local opos, osizex, osizez = building.pos, building.size.x, building.size.z @@ -22,8 +23,7 @@ function mcl_villages.fill_chest(pos, pr) end -- fill chest local inv = minetest.get_inventory( {type="node", pos=pos} ) - - local function get_treasures(prand) + local function get_treasures(pr) local loottable = {{ stacks_min = 3, stacks_max = 8, @@ -47,46 +47,11 @@ function mcl_villages.fill_chest(pos, pr) { itemstring = "mcl_mobitems:diamond_horse_armor", weight = 1 }, } }} - local items = mcl_loot.get_multi_loot(loottable, prand) - return items + return mcl_loot.get_multi_loot(loottable, pr) end - - local items = get_treasures(pr) - mcl_loot.fill_inventory(inv, "main", items, pr) + mcl_loot.fill_inventory(inv, "main", get_treasures(pr), pr) end -------------------------------------------------------------------------------- --- initialize furnace -------------------------------------------------------------------------------- -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 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 ------------------------------------------------------------------------------- @@ -100,28 +65,41 @@ 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) + local biome_name = minetest.get_biome_name(minetest.get_biome_data(pos).biome) -- for now, map to MCLA, later back, so we can keep their rules unchanged for _, sub in pairs(mcl_villages.vl_to_mcla) do - modified_schem_lua = modified_schem_lua:gsub(sub[1], sub[2]) + schem_lua = schem_lua:gsub(sub[1], sub[2]) end 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]) + schem_lua = schem_lua:gsub(sub[1], sub[2]) end end -- MCLA node names back to VL for _, sub in pairs(mcl_villages.mcla_to_vl) do - modified_schem_lua = modified_schem_lua:gsub(sub[1], sub[2]) + schem_lua = schem_lua:gsub(sub[1], sub[2]) end - return modified_schem_lua + + -- Farming: place crops + if string.find(schem_lua, "mcl_villages:crop_") then + local map_name = mcl_villages.biome_map[biome_name] or "plains" + for _, crop in ipairs(mcl_villages.get_crop_types()) do + if string.find(schem_lua, "mcl_villages:crop_" .. crop) then + for count = 1, 8 do + local name = "mcl_villages:crop_" .. crop .. "_" .. count + local replacement = mcl_villages.get_weighted_crop(map_name, crop, pr) + schem_lua = schem_lua:gsub(name, replacement or mcl_villages.default_crop) + end + end + end + end + return schem_lua end +-- Persistent registry for villages local villages = {} local mod_storage = minetest.get_mod_storage() @@ -149,11 +127,10 @@ end function mcl_villages.add_village(name, data) lazy_load_village(name) if villages[name] then - minetest.log("info","Village already exists: " .. name ) + 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)) + mod_storage:set_string("mcl_villages." .. name, minetest.serialize({ name = name, data = data })) return true end +