clean up villages code, add biome farming support

This commit is contained in:
kno10 2024-09-03 17:16:36 +02:00
parent 0c4a120191
commit 27b6963d20
6 changed files with 313 additions and 582 deletions

View File

@ -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

View File

@ -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

View File

@ -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"'},

View File

@ -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

View File

@ -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

View File

@ -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