Add a grid based layout for villages
This commit is contained in:
parent
c23a3f2dd0
commit
14042b4688
@ -194,6 +194,7 @@ globals = {
|
||||
"mcl_lush_caves",
|
||||
"mcl_armor_trims",
|
||||
"mcl_villages",
|
||||
"AreaStore",
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
|
@ -1161,10 +1161,18 @@ function mcl_util.traverse_tower(pos, dir, callback)
|
||||
end
|
||||
|
||||
-- Voxel manip function to replace a node type with another in an area
|
||||
function mcl_util.replace_node_vm(pos1, pos2, mat_from, mat_to)
|
||||
local c_from = minetest.get_content_id(mat_from)
|
||||
function mcl_util.replace_node_vm(pos1, pos2, mat_from, mat_to, is_group)
|
||||
local c_to = minetest.get_content_id(mat_to)
|
||||
|
||||
local group_name
|
||||
local c_from
|
||||
|
||||
if is_group then
|
||||
group_name = string.match(mat_from, "group:(.+)")
|
||||
else
|
||||
c_from = minetest.get_content_id(mat_from)
|
||||
end
|
||||
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
||||
local a = VoxelArea:new({
|
||||
@ -1178,8 +1186,15 @@ function mcl_util.replace_node_vm(pos1, pos2, mat_from, mat_to)
|
||||
for y = pos1.y, pos2.y do
|
||||
for x = pos1.x, pos2.x do
|
||||
local vi = a:index(x, y, z)
|
||||
if data[vi] == c_from then
|
||||
data[vi] = c_to
|
||||
if is_group then
|
||||
local node_name = minetest.get_name_from_content_id(data[vi])
|
||||
if minetest.get_item_group(node_name, group_name) > 0 then
|
||||
data[vi] = c_to
|
||||
end
|
||||
else
|
||||
if data[vi] == c_from then
|
||||
data[vi] = c_to
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -276,3 +276,26 @@ If a crop cannot be found for a crop type in a biome, then a default will be
|
||||
used. This ensure all farming blocks are full, ven if it's al the same crop.
|
||||
|
||||
The default is wheat.
|
||||
|
||||
## Village Layout
|
||||
|
||||
There are two methods for layout out villages, circle layout is more likely to be
|
||||
used for small villages and grid for large villages.
|
||||
|
||||
The circle layout uses circles (surprise) to calculate if buildings overlap. It
|
||||
creates fairly widely spaced layouts.
|
||||
|
||||
The grid layout uses a predetermined grid layout to positions buildings and uses
|
||||
AreaStore to adjust building position if there are collisions.
|
||||
|
||||
The predetermined grid is below, position 0 is the bell, the other numbers are the order of placement.
|
||||
|
||||
||||||||
|
||||
| -- | -- | -- | -- | -- | -- | -- |
|
||||
|48|41|33|25|29|37|45|
|
||||
|40|17|13| 9|11|15|43|
|
||||
|32|19| 5| 1| 3|22|35|
|
||||
|28|23| 7| 0| 8|24|27|
|
||||
|36|21| 4| 2| 6|20|31|
|
||||
|44|16|12|10|14|18|39|
|
||||
|46|38|30|26|34|42|47|
|
||||
|
@ -92,49 +92,187 @@ local function init_nodes(p1, p2, size, rotation, pr)
|
||||
end
|
||||
end
|
||||
|
||||
local function layout_town(minp, maxp, pr, input_settlement_info)
|
||||
local settlement_info = {}
|
||||
local xdist = math.abs(minp.x - maxp.x)
|
||||
local zdist = math.abs(minp.z - maxp.z)
|
||||
-- TODO consided using mod_storage to persist this...
|
||||
local areas = AreaStore()
|
||||
areas:set_cache_params({ enabled = true, block_radius = 16, limit = 100 })
|
||||
|
||||
-- find center of village within interior of chunk
|
||||
local center = vector.new(
|
||||
minp.x + pr:next(math.floor(xdist * 0.2), math.floor(xdist * 0.8)),
|
||||
maxp.y,
|
||||
minp.z + pr:next(math.floor(zdist * 0.2), math.floor(zdist * 0.8))
|
||||
)
|
||||
local function record_building(record)
|
||||
local placement_pos = vector.copy(record.pos)
|
||||
|
||||
-- find center_surface of village
|
||||
local center_surface, surface_material = mcl_villages.find_surface(center, true)
|
||||
-- Allow adjusting y axis
|
||||
if record.yadjust then
|
||||
placement_pos = vector.offset(placement_pos, 0, record.yadjust, 0)
|
||||
end
|
||||
|
||||
local size = math.ceil(math.max(record.size.x, record.size.z) / 2)
|
||||
local minp = vector.offset(placement_pos, -size, -10, -size)
|
||||
local maxp = vector.offset(placement_pos, size, record.size.y + 10, size)
|
||||
|
||||
areas:insert_area(minp, maxp, record.name)
|
||||
end
|
||||
|
||||
-- TODO is there a good way to calculate this?
|
||||
local squares = {
|
||||
-- center
|
||||
{ x = 0, z = 0 },
|
||||
|
||||
--around center
|
||||
{ x = 1, z = 0 },
|
||||
{ x = -1, z = 0 },
|
||||
{ x = 1, z = 1 },
|
||||
{ x = -1, z = -1 },
|
||||
{ x = 1, z = -1 },
|
||||
{ x = -1, z = -1 },
|
||||
{ x = 0, z = 1 },
|
||||
{ x = 0, z = -1 },
|
||||
|
||||
--one out
|
||||
{ x = 2, z = 0 },
|
||||
{ x = -2, z = 0 },
|
||||
|
||||
{ x = 2, z = 1 },
|
||||
{ x = -2, z = -1 },
|
||||
{ x = 2, z = -1 },
|
||||
{ x = -2, z = 1 },
|
||||
|
||||
{ x = 2, z = 2 },
|
||||
{ x = -2, z = -2 },
|
||||
{ x = 2, z = -2 },
|
||||
{ x = -2, z = 2 },
|
||||
|
||||
{ x = 0, z = 2 },
|
||||
{ x = 0, z = -2 },
|
||||
{ x = 1, z = 2 },
|
||||
{ x = -1, z = -2 },
|
||||
{ x = 1, z = -2 },
|
||||
{ x = -1, z = 2 },
|
||||
|
||||
--two out
|
||||
{ x = 3, z = 0 }, --25
|
||||
{ x = -3, z = 0 },
|
||||
|
||||
{ x = 0, z = 3 },
|
||||
{ x = 0, z = -3 },
|
||||
{ x = 3, z = 1 },
|
||||
{ x = -3, z = -1 },
|
||||
|
||||
{ x = -1, z = 3 },
|
||||
{ x = 1, z = -3 },
|
||||
{ x = 3, z = -1 },
|
||||
{ x = -3, z = 1 },
|
||||
|
||||
{ x = 1, z = 3 },
|
||||
{ x = -1, z = -3 },
|
||||
{ x = 3, z = 2 },
|
||||
{ x = -3, z = -2 },
|
||||
{ x = 3, z = -2 },
|
||||
{ x = -3, z = 2 }, --40
|
||||
|
||||
{ x = 3, z = -2 },
|
||||
{ x = -3, z = 2 },
|
||||
{ x = 2, z = 3 },
|
||||
{ x = -2, z = -3 },
|
||||
{ x = 3, z = 3 },
|
||||
{ x = -3, z = -3 },
|
||||
{ x = -3, z = 3 },
|
||||
{ x = 3, z = -3 },
|
||||
}
|
||||
|
||||
local function layout_grid(pr, input_settlement_info, settlement_info, center)
|
||||
|
||||
local bell_schem = settlement_info[1]
|
||||
record_building(bell_schem)
|
||||
local center_surface = bell_schem.pos
|
||||
|
||||
for i = 2, #input_settlement_info do
|
||||
if i > #squares then
|
||||
minetest.log("warning", "Too many buldings for grid layout, remaining buildings will be missing.")
|
||||
return settlement_info
|
||||
end
|
||||
|
||||
local cur_schem = input_settlement_info[i]
|
||||
local placed = false
|
||||
local iter = 0
|
||||
|
||||
local x_step = squares[i].x
|
||||
local z_step = squares[i].z
|
||||
|
||||
local b_length = math.max(bell_schem.size.x, bell_schem.size.z)
|
||||
local c_length = math.max(cur_schem.size.x, cur_schem.size.z)
|
||||
|
||||
local dist = math.ceil(b_length + c_length / 2)
|
||||
local half_dist = math.ceil(dist / 2)
|
||||
|
||||
local next_x = bell_schem.pos.x + ((dist + 1) * x_step)
|
||||
local next_z = bell_schem.pos.z + ((dist + 1) * z_step)
|
||||
|
||||
while not placed do
|
||||
iter = iter + 1
|
||||
|
||||
local pos = vector.new(next_x, center_surface.y, next_z)
|
||||
local pos_surface, surface_material = mcl_villages.find_surface(pos)
|
||||
|
||||
if pos_surface then
|
||||
pos_surface = vector.round(pos_surface)
|
||||
|
||||
local minp = vector.offset(pos_surface, -half_dist, 0, -half_dist)
|
||||
local maxp = vector.offset(pos_surface, half_dist, cur_schem.size.y, half_dist)
|
||||
|
||||
local collisons = areas:get_areas_in_area(minp, maxp, true, true, true)
|
||||
|
||||
if table.count(collisons) == 0 then
|
||||
cur_schem.pos = vector.copy(pos_surface)
|
||||
cur_schem.surface_mat = surface_material
|
||||
record_building(cur_schem)
|
||||
table.insert(settlement_info, cur_schem)
|
||||
placed = true
|
||||
else
|
||||
local col
|
||||
-- sometimes this is a table with keys, and sometimes not ...
|
||||
for k, v in pairs(collisons) do
|
||||
col = v
|
||||
break
|
||||
end
|
||||
|
||||
if z_step < 0 then
|
||||
next_z = col.max.z + half_dist + 1
|
||||
elseif z_step > 0 then
|
||||
next_z = col.min.z - half_dist - 1
|
||||
elseif x_step < 0 then
|
||||
next_x = col.min.x - half_dist - 1
|
||||
elseif x_step > 0 then
|
||||
next_x = col.max.x + half_dist + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
next_x = next_x + (dist + 1 * x_step)
|
||||
next_z = next_z + (dist + 1 * z_step)
|
||||
end
|
||||
|
||||
if not placed and (iter % 5 == 0) then
|
||||
x_step = squares[i + (iter % 10)].x
|
||||
z_step = squares[i + (iter % 10)].z
|
||||
next_x = bell_schem.pos.x + ((dist + 1) * x_step)
|
||||
next_z = bell_schem.pos.z + ((dist + 1) * z_step)
|
||||
end
|
||||
|
||||
if not placed and iter >= 30 then
|
||||
minetest.log("warning", "Could not place " .. cur_schem.name)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function layout_circles(pr, input_settlement_info, settlement_info, center)
|
||||
local center_surface = settlement_info[1].pos
|
||||
local size = #input_settlement_info
|
||||
local max_dist = 20 + (size * 3)
|
||||
|
||||
-- Cache for chunk surfaces
|
||||
local chunks = {}
|
||||
chunks[mcl_vars.get_chunk_number(center)] = true
|
||||
|
||||
-- build settlement around center
|
||||
if not center_surface then
|
||||
minetest.log("info", string.format("[mcl_villages] Cannot build village at %s", minetest.pos_to_string(center)))
|
||||
return false
|
||||
else
|
||||
minetest.log(
|
||||
"info",
|
||||
string.format(
|
||||
"[mcl_villages] Will build a village at position %s with surface material %s",
|
||||
minetest.pos_to_string(center_surface),
|
||||
surface_material
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
local bell_info = table.copy(input_settlement_info[1])
|
||||
bell_info["pos"] = vector.copy(center_surface)
|
||||
bell_info["surface_mat"] = surface_material
|
||||
|
||||
table.insert(settlement_info, bell_info)
|
||||
|
||||
local size = #input_settlement_info
|
||||
local max_dist = 20 + (size * 3)
|
||||
|
||||
for i = 2, size do
|
||||
local cur_schem = input_settlement_info[i]
|
||||
|
||||
@ -200,6 +338,52 @@ local function layout_town(minp, maxp, pr, input_settlement_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function layout_town(minp, maxp, pr, input_settlement_info, grid)
|
||||
local settlement_info = {}
|
||||
local xdist = math.abs(minp.x - maxp.x)
|
||||
local zdist = math.abs(minp.z - maxp.z)
|
||||
|
||||
-- find center of village within interior of chunk
|
||||
local center = vector.new(
|
||||
minp.x + pr:next(math.floor(xdist * 0.2), math.floor(xdist * 0.8)),
|
||||
maxp.y,
|
||||
minp.z + pr:next(math.floor(zdist * 0.2), math.floor(zdist * 0.8))
|
||||
)
|
||||
|
||||
-- find center_surface of village
|
||||
local center_surface, surface_material = mcl_villages.find_surface(center, true)
|
||||
|
||||
-- build settlement around center
|
||||
if not center_surface then
|
||||
minetest.log(
|
||||
"info",
|
||||
string.format("[mcl_villages] Cannot build village at %s", minetest.pos_to_string(center))
|
||||
)
|
||||
return false
|
||||
else
|
||||
minetest.log(
|
||||
"info",
|
||||
string.format(
|
||||
"[mcl_villages] Will build a village at position %s with surface material %s",
|
||||
minetest.pos_to_string(center_surface),
|
||||
surface_material
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
local bell_info = table.copy(input_settlement_info[1])
|
||||
bell_info["pos"] = vector.copy(center_surface)
|
||||
bell_info["surface_mat"] = surface_material
|
||||
|
||||
table.insert(settlement_info, bell_info)
|
||||
|
||||
if grid then
|
||||
layout_grid(pr, input_settlement_info, settlement_info, center)
|
||||
else
|
||||
layout_circles(pr, input_settlement_info, settlement_info, center)
|
||||
end
|
||||
|
||||
return settlement_info
|
||||
end
|
||||
@ -305,7 +489,13 @@ function mcl_villages.create_site_plan_new(minp, maxp, pr)
|
||||
|
||||
table.insert(shuffled_settlement_info, 1, bell_info)
|
||||
|
||||
return layout_town(minp, maxp, pr, shuffled_settlement_info)
|
||||
-- More jobs increases chances of grid layout
|
||||
local grid = false
|
||||
if pr:next(1, count_buildings.num_jobs) > 4 then
|
||||
grid = true
|
||||
end
|
||||
|
||||
return layout_town(minp, maxp, pr, shuffled_settlement_info, grid), grid
|
||||
end
|
||||
|
||||
function mcl_villages.place_schematics_new(settlement_info, pr, blockseed)
|
||||
|
@ -17,7 +17,7 @@ function mcl_villages.grundstellungen()
|
||||
"mcl_core:sand",
|
||||
"mcl_core:redsand",
|
||||
--"mcl_core:silver_sand",
|
||||
"mcl_core:snow"
|
||||
--"mcl_core:snow"
|
||||
}
|
||||
|
||||
-- allow villages on more surfaces
|
||||
|
@ -1,10 +1,7 @@
|
||||
|
||||
local circles = minetest.settings:get_bool("mcl_villages_circles", true)
|
||||
local terrace = minetest.settings:get_bool("mcl_villages_terrace", true)
|
||||
local padding = tonumber(minetest.settings:get("mcl_villages_padding")) or 2
|
||||
local top_padding = tonumber(minetest.settings:get("mcl_villages_top_padding")) or 8
|
||||
local terrace_max_ext = tonumber(minetest.settings:get("mcl_villages_terrace_max_ext")) or 6
|
||||
|
||||
local padding = 2
|
||||
local top_padding = 20
|
||||
local terrace_max_ext = 6
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- function to fill empty space below baseplate when building on a hill
|
||||
@ -26,16 +23,16 @@ function mcl_villages.ground(pos, pr) -- role model: Wendelsteinkircherl, Branne
|
||||
end
|
||||
|
||||
-- Empty space above ground
|
||||
local function overground(pos, fwidth, fdepth, fheight)
|
||||
local function overground(pos, fwidth, fdepth, fheight, grid)
|
||||
|
||||
if circles then
|
||||
if not grid then
|
||||
local y = math.ceil(pos.y + 1)
|
||||
local radius_base = math.max(fwidth, fdepth)
|
||||
local radius = math.round((radius_base / 2) + padding)
|
||||
local dome = fheight + top_padding
|
||||
|
||||
for count2 = 1, fheight + top_padding do
|
||||
if terrace and radius_base > 3 then
|
||||
if radius_base > 3 then
|
||||
if count2 > dome then
|
||||
radius = radius - 1
|
||||
elseif count2 <= terrace_max_ext then
|
||||
@ -49,32 +46,30 @@ local function overground(pos, fwidth, fdepth, fheight)
|
||||
end
|
||||
else
|
||||
local count = 1
|
||||
if not terrace then
|
||||
count = count + 2
|
||||
end
|
||||
|
||||
if terrace then
|
||||
for y_adj = 1, pos.y + fheight + top_padding do
|
||||
local pos1 = vector.offset(pos, -count, y_adj, -count)
|
||||
local pos2 = vector.offset(pos, fwidth + count, y_adj, fdepth + count)
|
||||
mcl_util.bulk_set_node_vm(pos1, pos2, "air")
|
||||
-- Must treat as square because rotation is random
|
||||
local adjust = math.round(math.max(fwidth, fdepth) / 2)
|
||||
|
||||
if terrace and count <= terrace_max_ext then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
local x_adjust = fwidth / 2
|
||||
local z_adjust = fdepth / 2
|
||||
|
||||
local pos1 = vector.offset(pos, -x_adjust, 0, -z_adjust)
|
||||
local pos2 = vector.offset(pos, x_adjust, fheight, z_adjust)
|
||||
for y_adj = 1, fheight + top_padding do
|
||||
local pos1 = vector.offset(pos, -(adjust + count), y_adj, -(adjust + count))
|
||||
local pos2 = vector.offset(pos, adjust + count, y_adj, adjust + count)
|
||||
mcl_util.bulk_set_node_vm(pos1, pos2, "air")
|
||||
|
||||
-- Grid layout requires minimal terracing
|
||||
if count <= padding then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- clean out stumps and leaves
|
||||
local pos1 = vector.offset(pos, -(adjust + count), 1, -(adjust + count))
|
||||
local pos2 = vector.offset(pos, adjust + count, fheight + top_padding, adjust + count)
|
||||
mcl_util.replace_node_vm(pos1, pos2, "group:tree", "air", true)
|
||||
mcl_util.replace_node_vm(pos1, pos2, "group:leaves", "air", true)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_villages.terraform_new(settlement_info)
|
||||
function mcl_villages.terraform_new(settlement_info, grid)
|
||||
local fheight, fwidth, fdepth
|
||||
|
||||
-- Do ground first so that we can clear overhang for lower buildings
|
||||
@ -96,7 +91,7 @@ function mcl_villages.terraform_new(settlement_info)
|
||||
fheight = schematic_data["size"]["y"]
|
||||
|
||||
if not schematic_data["no_clearance"] then
|
||||
overground(pos, fwidth, fdepth, fheight)
|
||||
overground(pos, fwidth, fdepth, fheight, grid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -31,13 +31,14 @@ local function build_a_settlement(minp, maxp, blockseed)
|
||||
|
||||
local pr = PseudoRandom(blockseed)
|
||||
|
||||
local settlement_info = mcl_villages.create_site_plan_new(minp, maxp, pr)
|
||||
local settlement_info, grid = mcl_villages.create_site_plan_new(minp, maxp, pr)
|
||||
|
||||
if not settlement_info then
|
||||
minetest.log("No village for " .. blockseed)
|
||||
return
|
||||
end
|
||||
|
||||
mcl_villages.terraform_new(settlement_info, pr)
|
||||
mcl_villages.terraform_new(settlement_info, grid)
|
||||
mcl_villages.place_schematics_new(settlement_info, pr, blockseed)
|
||||
|
||||
-- TODO when run here minetest.find_path regularly fails :(
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
local terrace_max_ext = tonumber(minetest.settings:get("mcl_villages_terrace_max_ext")) or 6
|
||||
local terrace_max_ext = 6
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- function to copy tables
|
||||
@ -26,7 +26,7 @@ end
|
||||
function mcl_villages.find_surface(pos, wait, quick)
|
||||
local p6 = vector.new(pos)
|
||||
local cnt = 0
|
||||
local itter = 1 -- count up or down
|
||||
local itter = 1 -- look up
|
||||
local cnt_max = 200
|
||||
local wait_time = 10000000
|
||||
|
||||
@ -42,9 +42,18 @@ function mcl_villages.find_surface(pos, wait, quick)
|
||||
else
|
||||
surface_node = mcl_vars.get_node(p6)
|
||||
end
|
||||
if surface_node.name=="air" or surface_node.name=="ignore" then
|
||||
itter = -1
|
||||
|
||||
if
|
||||
surface_node.name == "air"
|
||||
or surface_node.name == "ignore"
|
||||
or surface_node.name == "mcl_core:snow"
|
||||
or (minetest.get_item_group(surface_node.name, "deco_block") > 0)
|
||||
or (minetest.get_item_group(surface_node.name, "plant") > 0)
|
||||
or (minetest.get_item_group(surface_node.name, "tree") > 0)
|
||||
then
|
||||
itter = -1 -- look down
|
||||
end
|
||||
|
||||
-- go through nodes an find surface
|
||||
while cnt < cnt_max do
|
||||
-- Check Surface_node and Node above
|
||||
@ -63,7 +72,7 @@ function mcl_villages.find_surface(pos, wait, quick)
|
||||
mcl_villages.debug("find_surface7: " ..surface_node.name.. " " .. surface_node_plus_1.name)
|
||||
return p6, surface_node.name
|
||||
else
|
||||
mcl_villages.debug("find_surface2: wrong surface+1")
|
||||
mcl_villages.debug("find_surface2: wrong layer above " .. surface_node_plus_1.name)
|
||||
end
|
||||
else
|
||||
mcl_villages.debug("find_surface3: wrong surface "..surface_node.name.." at pos "..minetest.pos_to_string(p6))
|
||||
|
Loading…
x
Reference in New Issue
Block a user